@resira/sdk 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -0
- package/README.md +6 -5
- package/dist/index.cjs +43 -137
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -6
- package/dist/index.d.ts +9 -6
- package/dist/index.js +43 -138
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable SDK and public API tracking updates are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.3.1
|
|
6
|
+
|
|
7
|
+
- Removed client-side 50/50 origin selection, sticky routing, and browser storage writes.
|
|
8
|
+
- Defaulted production routing to a single deterministic API origin for server-side balancing setups.
|
|
9
|
+
- Kept `baseUrl` and ordered `baseUrls` support for explicit override/failover control.
|
|
10
|
+
- Added `getBaseUrl()` on the SDK client for easier inspection/debugging.
|
|
11
|
+
|
|
5
12
|
## 0.3.0
|
|
6
13
|
|
|
7
14
|
- Added `Dish`, `DishModel`, `DishResponse`, and `DishListResponse` types for 3D model / AR dish data.
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# @resira/sdk v0.3.
|
|
1
|
+
# @resira/sdk v0.3.1
|
|
2
2
|
|
|
3
3
|
TypeScript SDK for the Resira public reservation API.
|
|
4
4
|
|
|
@@ -7,7 +7,7 @@ Version history lives in [CHANGELOG.md](./CHANGELOG.md).
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npm install @resira/sdk@0.3.
|
|
10
|
+
npm install @resira/sdk@0.3.1
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
## Quick start
|
|
@@ -34,17 +34,18 @@ const resira = new Resira({
|
|
|
34
34
|
| --- | --- | --- | --- |
|
|
35
35
|
| `apiKey` | `string` | required | Public Resira API key |
|
|
36
36
|
| `baseUrl` | `string` | unset | Pin all SDK requests to one origin |
|
|
37
|
-
| `baseUrls` | `Array<string \| { url: string; weight?: number }>` | unset |
|
|
37
|
+
| `baseUrls` | `Array<string \| { url: string; weight?: number }>` | unset | Ordered fallback origin list; first valid origin is used |
|
|
38
38
|
| `maxRetries` | `number` | `3` | Retry attempts for 429 and 5xx responses |
|
|
39
39
|
| `retryBaseDelay` | `number` | `500` | Base exponential backoff delay in ms |
|
|
40
40
|
| `fetch` | `typeof fetch` | `globalThis.fetch` | Custom fetch implementation |
|
|
41
41
|
|
|
42
42
|
## Routing behavior
|
|
43
43
|
|
|
44
|
-
- The SDK
|
|
45
|
-
- Ambient host-app env vars such as `NEXT_PUBLIC_API_URL` are ignored.
|
|
44
|
+
- The SDK no longer performs client-side 50/50 routing or writes routing state into browser storage.
|
|
46
45
|
- Only explicit SDK config via `baseUrl` or `baseUrls` overrides the default routing behavior.
|
|
46
|
+
- `baseUrls` is treated as an ordered fallback list; the first valid origin is used.
|
|
47
47
|
- In development, the SDK defaults to `http://localhost:3001`.
|
|
48
|
+
- In production, the SDK defaults to `https://api.resira.app`.
|
|
48
49
|
|
|
49
50
|
## Core methods
|
|
50
51
|
|
package/dist/index.cjs
CHANGED
|
@@ -45,130 +45,12 @@ var DEFAULT_LOCAL_BASE_URLS = [
|
|
|
45
45
|
{ url: "http://localhost:3001", weight: 100 }
|
|
46
46
|
];
|
|
47
47
|
var DEFAULT_PRODUCTION_BASE_URLS = [
|
|
48
|
-
{ url: "https://
|
|
49
|
-
{ url: "https://api.resira.app", weight: 50 }
|
|
48
|
+
{ url: "https://api.resira.app", weight: 100 }
|
|
50
49
|
];
|
|
51
|
-
var STICKY_SELECTION_KEY = "resira_sdk_api_origin_sticky_v1";
|
|
52
|
-
var ROUTING_STATS_KEY = "resira_sdk_api_routing_stats_v1";
|
|
53
|
-
var stickySelectionCache = /* @__PURE__ */ new Map();
|
|
54
50
|
function readEnv(name) {
|
|
55
51
|
if (typeof process === "undefined") return void 0;
|
|
56
52
|
return process.env?.[name];
|
|
57
53
|
}
|
|
58
|
-
function isBrowser() {
|
|
59
|
-
return typeof window !== "undefined";
|
|
60
|
-
}
|
|
61
|
-
function getStorage(type) {
|
|
62
|
-
if (!isBrowser()) return null;
|
|
63
|
-
try {
|
|
64
|
-
return type === "session" ? window.sessionStorage : window.localStorage;
|
|
65
|
-
} catch {
|
|
66
|
-
return null;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
function getBaseUrlSignature(baseUrls) {
|
|
70
|
-
return baseUrls.map((entry) => `${entry.url}|${entry.weight}`).join(",");
|
|
71
|
-
}
|
|
72
|
-
function pickWeightedBaseUrl(baseUrls) {
|
|
73
|
-
const totalWeight = baseUrls.reduce((sum, entry) => sum + entry.weight, 0);
|
|
74
|
-
if (totalWeight <= 0) {
|
|
75
|
-
return DEFAULT_PRODUCTION_BASE_URLS[0];
|
|
76
|
-
}
|
|
77
|
-
let cursor = Math.random() * totalWeight;
|
|
78
|
-
for (const entry of baseUrls) {
|
|
79
|
-
cursor -= entry.weight;
|
|
80
|
-
if (cursor < 0) {
|
|
81
|
-
return entry;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
return baseUrls[baseUrls.length - 1] ?? DEFAULT_PRODUCTION_BASE_URLS[0];
|
|
85
|
-
}
|
|
86
|
-
function ensureAnalyticsQueue() {
|
|
87
|
-
if (!isBrowser()) return null;
|
|
88
|
-
const analyticsWindow = window;
|
|
89
|
-
if (!analyticsWindow.va) {
|
|
90
|
-
analyticsWindow.va = (...params) => {
|
|
91
|
-
analyticsWindow.vaq = analyticsWindow.vaq || [];
|
|
92
|
-
analyticsWindow.vaq.push(params);
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
return analyticsWindow;
|
|
96
|
-
}
|
|
97
|
-
function readStickySelection(signature) {
|
|
98
|
-
const cached = stickySelectionCache.get(signature);
|
|
99
|
-
if (cached) return cached;
|
|
100
|
-
const storage = getStorage("session");
|
|
101
|
-
const raw = storage?.getItem(STICKY_SELECTION_KEY);
|
|
102
|
-
if (!raw) return null;
|
|
103
|
-
try {
|
|
104
|
-
const parsed = JSON.parse(raw);
|
|
105
|
-
if (!parsed?.signature || !parsed?.url || parsed.signature !== signature) {
|
|
106
|
-
return null;
|
|
107
|
-
}
|
|
108
|
-
stickySelectionCache.set(signature, parsed);
|
|
109
|
-
return parsed;
|
|
110
|
-
} catch {
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
function persistStickySelection(signature, url) {
|
|
115
|
-
const selection = {
|
|
116
|
-
signature,
|
|
117
|
-
url,
|
|
118
|
-
assignedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
119
|
-
};
|
|
120
|
-
stickySelectionCache.set(signature, selection);
|
|
121
|
-
getStorage("session")?.setItem(STICKY_SELECTION_KEY, JSON.stringify(selection));
|
|
122
|
-
}
|
|
123
|
-
function recordRoutingStats(signature, entry) {
|
|
124
|
-
const storage = getStorage("local");
|
|
125
|
-
if (!storage) return;
|
|
126
|
-
let stats = {};
|
|
127
|
-
try {
|
|
128
|
-
stats = JSON.parse(storage.getItem(ROUTING_STATS_KEY) ?? "{}");
|
|
129
|
-
} catch {
|
|
130
|
-
stats = {};
|
|
131
|
-
}
|
|
132
|
-
const current = stats[signature] ?? {
|
|
133
|
-
lastAssignedAt: "",
|
|
134
|
-
lastOrigin: "",
|
|
135
|
-
selections: 0,
|
|
136
|
-
origins: {}
|
|
137
|
-
};
|
|
138
|
-
current.lastAssignedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
139
|
-
current.lastOrigin = entry.url;
|
|
140
|
-
current.selections += 1;
|
|
141
|
-
current.origins[entry.url] = (current.origins[entry.url] ?? 0) + 1;
|
|
142
|
-
stats[signature] = current;
|
|
143
|
-
storage.setItem(ROUTING_STATS_KEY, JSON.stringify(stats));
|
|
144
|
-
}
|
|
145
|
-
function emitRoutingSelection(entry, signature, totalWeight) {
|
|
146
|
-
if (!isBrowser()) return;
|
|
147
|
-
const detail = {
|
|
148
|
-
source: "sdk",
|
|
149
|
-
strategy: "session-sticky",
|
|
150
|
-
originHost: new URL(entry.url).host,
|
|
151
|
-
originUrl: entry.url,
|
|
152
|
-
originWeight: entry.weight,
|
|
153
|
-
totalWeight,
|
|
154
|
-
signature
|
|
155
|
-
};
|
|
156
|
-
ensureAnalyticsQueue()?.va?.("event", {
|
|
157
|
-
name: "api_origin_selected",
|
|
158
|
-
data: detail
|
|
159
|
-
});
|
|
160
|
-
window.dispatchEvent(
|
|
161
|
-
new CustomEvent("resira:api-origin-selected", {
|
|
162
|
-
detail
|
|
163
|
-
})
|
|
164
|
-
);
|
|
165
|
-
const debugEnabled = /^(1|true)$/i.test(
|
|
166
|
-
readEnv("NEXT_PUBLIC_RESIRA_API_ROUTING_DEBUG") ?? readEnv("NEXT_PUBLIC_API_ROUTING_DEBUG") ?? ""
|
|
167
|
-
);
|
|
168
|
-
if (debugEnabled) {
|
|
169
|
-
console.info("[resira-sdk] sticky origin selected", detail);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
54
|
function normalizeUrl(url) {
|
|
173
55
|
return url.trim().replace(/\/+$/, "");
|
|
174
56
|
}
|
|
@@ -187,6 +69,35 @@ function toResolvedBaseUrl(value) {
|
|
|
187
69
|
weight: isPositiveNumber(value.weight) ? value.weight : 1
|
|
188
70
|
};
|
|
189
71
|
}
|
|
72
|
+
function parseJsonBaseUrls(value) {
|
|
73
|
+
const parsed = JSON.parse(value);
|
|
74
|
+
if (!Array.isArray(parsed)) return [];
|
|
75
|
+
return parsed.flatMap((entry) => {
|
|
76
|
+
const resolved = typeof entry === "string" ? toResolvedBaseUrl(entry) : entry && typeof entry === "object" ? toResolvedBaseUrl(entry) : null;
|
|
77
|
+
return resolved ? [resolved] : [];
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
function parseDelimitedBaseUrls(value) {
|
|
81
|
+
return value.split(",").flatMap((entry) => {
|
|
82
|
+
const [rawUrl, rawWeight] = entry.split("|");
|
|
83
|
+
const url = normalizeUrl(rawUrl ?? "");
|
|
84
|
+
if (!url) return [];
|
|
85
|
+
const parsedWeight = rawWeight ? Number(rawWeight.trim()) : 1;
|
|
86
|
+
return [{
|
|
87
|
+
url,
|
|
88
|
+
weight: isPositiveNumber(parsedWeight) ? parsedWeight : 1
|
|
89
|
+
}];
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
function parseEnvBaseUrls(value) {
|
|
93
|
+
if (!value) return [];
|
|
94
|
+
try {
|
|
95
|
+
const parsed = parseJsonBaseUrls(value);
|
|
96
|
+
if (parsed.length > 0) return parsed;
|
|
97
|
+
} catch {
|
|
98
|
+
}
|
|
99
|
+
return parseDelimitedBaseUrls(value);
|
|
100
|
+
}
|
|
190
101
|
function resolveBaseUrls(config) {
|
|
191
102
|
if (config.baseUrl) {
|
|
192
103
|
return [{ url: normalizeUrl(config.baseUrl), weight: 100 }];
|
|
@@ -198,27 +109,20 @@ function resolveBaseUrls(config) {
|
|
|
198
109
|
});
|
|
199
110
|
if (explicit.length > 0) return explicit;
|
|
200
111
|
}
|
|
112
|
+
const envBaseUrls = parseEnvBaseUrls(readEnv("RESIRA_API_BASE_URLS"));
|
|
113
|
+
if (envBaseUrls.length > 0) {
|
|
114
|
+
return envBaseUrls;
|
|
115
|
+
}
|
|
201
116
|
if (readEnv("NODE_ENV") === "development") {
|
|
202
117
|
return DEFAULT_LOCAL_BASE_URLS;
|
|
203
118
|
}
|
|
204
119
|
return DEFAULT_PRODUCTION_BASE_URLS;
|
|
205
120
|
}
|
|
206
121
|
function pickBaseUrl(baseUrls) {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
return stickyEntry.url;
|
|
212
|
-
}
|
|
213
|
-
const selectedEntry = pickWeightedBaseUrl(baseUrls);
|
|
214
|
-
persistStickySelection(signature, selectedEntry.url);
|
|
215
|
-
recordRoutingStats(signature, selectedEntry);
|
|
216
|
-
emitRoutingSelection(
|
|
217
|
-
selectedEntry,
|
|
218
|
-
signature,
|
|
219
|
-
baseUrls.reduce((sum, entry) => sum + entry.weight, 0)
|
|
220
|
-
);
|
|
221
|
-
return selectedEntry.url;
|
|
122
|
+
return baseUrls[0]?.url ?? DEFAULT_PRODUCTION_BASE_URLS[0].url;
|
|
123
|
+
}
|
|
124
|
+
function getRoutingStats() {
|
|
125
|
+
return {};
|
|
222
126
|
}
|
|
223
127
|
|
|
224
128
|
// src/client.ts
|
|
@@ -281,6 +185,10 @@ var Resira = class {
|
|
|
281
185
|
this.retryBaseDelay = config.retryBaseDelay ?? DEFAULT_RETRY_BASE_DELAY;
|
|
282
186
|
this._fetch = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
283
187
|
}
|
|
188
|
+
/** The resolved API origin used by this client instance. */
|
|
189
|
+
getBaseUrl() {
|
|
190
|
+
return pickBaseUrl(this.baseUrls);
|
|
191
|
+
}
|
|
284
192
|
// ─────────────────────────────────────────────────────────────
|
|
285
193
|
// Public API methods
|
|
286
194
|
// ─────────────────────────────────────────────────────────────
|
|
@@ -755,9 +663,6 @@ var Resira = class {
|
|
|
755
663
|
}
|
|
756
664
|
return jittered;
|
|
757
665
|
}
|
|
758
|
-
getBaseUrl() {
|
|
759
|
-
return pickBaseUrl(this.baseUrls);
|
|
760
|
-
}
|
|
761
666
|
};
|
|
762
667
|
|
|
763
668
|
exports.Resira = Resira;
|
|
@@ -765,5 +670,6 @@ exports.ResiraApiError = ResiraApiError;
|
|
|
765
670
|
exports.ResiraError = ResiraError;
|
|
766
671
|
exports.ResiraNetworkError = ResiraNetworkError;
|
|
767
672
|
exports.ResiraRateLimitError = ResiraRateLimitError;
|
|
673
|
+
exports.getRoutingStats = getRoutingStats;
|
|
768
674
|
//# sourceMappingURL=index.cjs.map
|
|
769
675
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/routing.ts","../src/client.ts"],"names":["url"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { ApiErrorBody } from \"./types.js\";\n\n// ═══════════════════════════════════════════════════════════════\n// Base error\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Base error class for all SDK errors.\n *\n * Use `instanceof` checks to distinguish error types:\n *\n * ```ts\n * try { … } catch (e) {\n * if (e instanceof ResiraRateLimitError) { … }\n * if (e instanceof ResiraApiError) { … }\n * if (e instanceof ResiraNetworkError) { … }\n * }\n * ```\n */\nexport class ResiraError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ResiraError\";\n // Fix prototype chain for instanceof to work after transpilation\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n// ═══════════════════════════════════════════════════════════════\n// API error (4xx / 5xx with a parsed body)\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Thrown when the API returns a non-2xx response with a JSON body.\n *\n * ```ts\n * err.status // 400, 404, 409, 422, 500, …\n * err.code // HTTP status text (e.g. \"Not Found\")\n * err.body // Parsed `{ error: \"…\" }` from the server\n * ```\n */\nexport class ResiraApiError extends ResiraError {\n /** HTTP status code. */\n readonly status: number;\n /** HTTP status text (e.g. `\"Bad Request\"`). */\n readonly code: string;\n /** Parsed error body from the API. */\n readonly body: ApiErrorBody;\n /** The full `Response` object (headers are still accessible). */\n readonly response: Response;\n\n constructor(status: number, code: string, body: ApiErrorBody, response: Response) {\n super(body.error || `API error ${status}`);\n this.name = \"ResiraApiError\";\n this.status = status;\n this.code = code;\n this.body = body;\n this.response = response;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n /** Whether this error is retryable (5xx or 429). */\n get retryable(): boolean {\n return this.status === 429 || this.status >= 500;\n }\n}\n\n// ═══════════════════════════════════════════════════════════════\n// Rate limit error (429)\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Thrown when the API returns 429 Too Many Requests.\n *\n * ```ts\n * err.retryAfter // Seconds until the client should retry\n * ```\n */\nexport class ResiraRateLimitError extends ResiraApiError {\n /** Seconds to wait before retrying (from `Retry-After` header). */\n readonly retryAfter: number;\n\n constructor(body: ApiErrorBody, response: Response) {\n super(429, \"Too Many Requests\", body, response);\n this.name = \"ResiraRateLimitError\";\n this.retryAfter =\n body.retryAfter ??\n (Number.parseInt(response.headers.get(\"retry-after\") ?? \"60\", 10) ||\n 60);\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n// ═══════════════════════════════════════════════════════════════\n// Network error (fetch failed, DNS, timeout, etc.)\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Thrown when the request never reached the server.\n *\n * This wraps the underlying `TypeError` or `DOMException` from\n * `fetch()` failures (DNS resolution, TLS, network offline, etc.).\n */\nexport class ResiraNetworkError extends ResiraError {\n /** The original error thrown by `fetch`. */\n readonly cause: unknown;\n\n constructor(message: string, cause: unknown) {\n super(message);\n this.name = \"ResiraNetworkError\";\n this.cause = cause;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","import type { ResiraConfig, WeightedBaseUrl } from \"./types.js\";\n\ninterface ResolvedBaseUrl {\n url: string;\n weight: number;\n}\n\ninterface StickySelection {\n signature: string;\n url: string;\n assignedAt: string;\n}\n\ninterface RoutingStatsEntry {\n lastAssignedAt: string;\n lastOrigin: string;\n selections: number;\n origins: Record<string, number>;\n}\n\ntype AnalyticsWindow = Window & {\n va?: (...args: unknown[]) => void;\n vaq?: unknown[][];\n};\n\nconst DEFAULT_LOCAL_BASE_URLS: ResolvedBaseUrl[] = [\n { url: \"http://localhost:3001\", weight: 100 },\n];\n\nconst DEFAULT_PRODUCTION_BASE_URLS: ResolvedBaseUrl[] = [\n { url: \"https://resira-api-sips-production.up.railway.app\", weight: 50 },\n { url: \"https://api.resira.app\", weight: 50 },\n];\n\nconst STICKY_SELECTION_KEY = \"resira_sdk_api_origin_sticky_v1\";\nconst ROUTING_STATS_KEY = \"resira_sdk_api_routing_stats_v1\";\n\nconst stickySelectionCache = new Map<string, StickySelection>();\n\nfunction readEnv(name: string): string | undefined {\n if (typeof process === \"undefined\") return undefined;\n return process.env?.[name];\n}\n\nfunction isBrowser(): boolean {\n return typeof window !== \"undefined\";\n}\n\nfunction getStorage(type: \"session\" | \"local\"): Storage | null {\n if (!isBrowser()) return null;\n\n try {\n return type === \"session\" ? window.sessionStorage : window.localStorage;\n } catch {\n return null;\n }\n}\n\nfunction getBaseUrlSignature(baseUrls: ResolvedBaseUrl[]): string {\n return baseUrls.map((entry) => `${entry.url}|${entry.weight}`).join(\",\");\n}\n\nfunction pickWeightedBaseUrl(baseUrls: ResolvedBaseUrl[]): ResolvedBaseUrl {\n const totalWeight = baseUrls.reduce((sum, entry) => sum + entry.weight, 0);\n if (totalWeight <= 0) {\n return DEFAULT_PRODUCTION_BASE_URLS[0];\n }\n\n let cursor = Math.random() * totalWeight;\n for (const entry of baseUrls) {\n cursor -= entry.weight;\n if (cursor < 0) {\n return entry;\n }\n }\n\n return baseUrls[baseUrls.length - 1] ?? DEFAULT_PRODUCTION_BASE_URLS[0];\n}\n\nfunction ensureAnalyticsQueue(): AnalyticsWindow | null {\n if (!isBrowser()) return null;\n\n const analyticsWindow = window as AnalyticsWindow;\n if (!analyticsWindow.va) {\n analyticsWindow.va = (...params: unknown[]) => {\n analyticsWindow.vaq = analyticsWindow.vaq || [];\n analyticsWindow.vaq.push(params);\n };\n }\n\n return analyticsWindow;\n}\n\nfunction readStickySelection(signature: string): StickySelection | null {\n const cached = stickySelectionCache.get(signature);\n if (cached) return cached;\n\n const storage = getStorage(\"session\");\n const raw = storage?.getItem(STICKY_SELECTION_KEY);\n if (!raw) return null;\n\n try {\n const parsed = JSON.parse(raw) as StickySelection;\n if (!parsed?.signature || !parsed?.url || parsed.signature !== signature) {\n return null;\n }\n stickySelectionCache.set(signature, parsed);\n return parsed;\n } catch {\n return null;\n }\n}\n\nfunction persistStickySelection(signature: string, url: string): void {\n const selection: StickySelection = {\n signature,\n url,\n assignedAt: new Date().toISOString(),\n };\n\n stickySelectionCache.set(signature, selection);\n getStorage(\"session\")?.setItem(STICKY_SELECTION_KEY, JSON.stringify(selection));\n}\n\nfunction recordRoutingStats(signature: string, entry: ResolvedBaseUrl): void {\n const storage = getStorage(\"local\");\n if (!storage) return;\n\n let stats: Record<string, RoutingStatsEntry> = {};\n try {\n stats = JSON.parse(storage.getItem(ROUTING_STATS_KEY) ?? \"{}\") as Record<string, RoutingStatsEntry>;\n } catch {\n stats = {};\n }\n\n const current = stats[signature] ?? {\n lastAssignedAt: \"\",\n lastOrigin: \"\",\n selections: 0,\n origins: {},\n };\n\n current.lastAssignedAt = new Date().toISOString();\n current.lastOrigin = entry.url;\n current.selections += 1;\n current.origins[entry.url] = (current.origins[entry.url] ?? 0) + 1;\n\n stats[signature] = current;\n storage.setItem(ROUTING_STATS_KEY, JSON.stringify(stats));\n}\n\nfunction emitRoutingSelection(entry: ResolvedBaseUrl, signature: string, totalWeight: number): void {\n if (!isBrowser()) return;\n\n const detail = {\n source: \"sdk\",\n strategy: \"session-sticky\",\n originHost: new URL(entry.url).host,\n originUrl: entry.url,\n originWeight: entry.weight,\n totalWeight,\n signature,\n };\n\n ensureAnalyticsQueue()?.va?.(\"event\", {\n name: \"api_origin_selected\",\n data: detail,\n });\n\n window.dispatchEvent(\n new CustomEvent(\"resira:api-origin-selected\", {\n detail,\n }),\n );\n\n const debugEnabled = /^(1|true)$/i.test(\n readEnv(\"NEXT_PUBLIC_RESIRA_API_ROUTING_DEBUG\") ?? readEnv(\"NEXT_PUBLIC_API_ROUTING_DEBUG\") ?? \"\",\n );\n if (debugEnabled) {\n console.info(\"[resira-sdk] sticky origin selected\", detail);\n }\n}\n\nfunction normalizeUrl(url: string): string {\n return url.trim().replace(/\\/+$/, \"\");\n}\n\nfunction isPositiveNumber(value: unknown): value is number {\n return typeof value === \"number\" && Number.isFinite(value) && value > 0;\n}\n\nfunction toResolvedBaseUrl(value: string | WeightedBaseUrl): ResolvedBaseUrl | null {\n if (typeof value === \"string\") {\n const url = normalizeUrl(value);\n return url ? { url, weight: 1 } : null;\n }\n\n const url = normalizeUrl(value.url);\n if (!url) return null;\n\n return {\n url,\n weight: isPositiveNumber(value.weight) ? value.weight : 1,\n };\n}\n\nfunction parseJsonBaseUrls(value: string): ResolvedBaseUrl[] {\n const parsed = JSON.parse(value) as unknown;\n if (!Array.isArray(parsed)) return [];\n\n return parsed.flatMap((entry) => {\n const resolved =\n typeof entry === \"string\"\n ? toResolvedBaseUrl(entry)\n : entry && typeof entry === \"object\"\n ? toResolvedBaseUrl(entry as WeightedBaseUrl)\n : null;\n\n return resolved ? [resolved] : [];\n });\n}\n\nfunction parseDelimitedBaseUrls(value: string): ResolvedBaseUrl[] {\n return value\n .split(\",\")\n .flatMap((entry) => {\n const [rawUrl, rawWeight] = entry.split(\"|\");\n const url = normalizeUrl(rawUrl ?? \"\");\n if (!url) return [];\n\n const parsedWeight = rawWeight ? Number(rawWeight.trim()) : 1;\n return [{\n url,\n weight: isPositiveNumber(parsedWeight) ? parsedWeight : 1,\n }];\n });\n}\n\nfunction parseEnvBaseUrls(value: string | undefined): ResolvedBaseUrl[] {\n if (!value) return [];\n\n try {\n const parsed = parseJsonBaseUrls(value);\n if (parsed.length > 0) return parsed;\n } catch {\n // Fall back to delimiter parsing.\n }\n\n return parseDelimitedBaseUrls(value);\n}\n\nexport function resolveBaseUrls(config: ResiraConfig): ResolvedBaseUrl[] {\n if (config.baseUrl) {\n return [{ url: normalizeUrl(config.baseUrl), weight: 100 }];\n }\n\n if (config.baseUrls?.length) {\n const explicit = config.baseUrls.flatMap((entry) => {\n const resolved = toResolvedBaseUrl(entry);\n return resolved ? [resolved] : [];\n });\n\n if (explicit.length > 0) return explicit;\n }\n\n // Intentionally ignore ambient host-app env vars here.\n // Widgets using the SDK should not inherit a parent app's\n // NEXT_PUBLIC_API_URL / endpoint overrides by accident.\n // Only explicit SDK config (baseUrl/baseUrls) may override\n // the built-in routing defaults.\n\n if (readEnv(\"NODE_ENV\") === \"development\") {\n return DEFAULT_LOCAL_BASE_URLS;\n }\n\n return DEFAULT_PRODUCTION_BASE_URLS;\n}\n\nexport function pickBaseUrl(baseUrls: ResolvedBaseUrl[]): string {\n const signature = getBaseUrlSignature(baseUrls);\n const stickySelection = readStickySelection(signature);\n const stickyEntry = stickySelection\n ? baseUrls.find((entry) => entry.url === stickySelection.url)\n : null;\n if (stickyEntry) {\n return stickyEntry.url;\n }\n\n const selectedEntry = pickWeightedBaseUrl(baseUrls);\n persistStickySelection(signature, selectedEntry.url);\n recordRoutingStats(signature, selectedEntry);\n emitRoutingSelection(\n selectedEntry,\n signature,\n baseUrls.reduce((sum, entry) => sum + entry.weight, 0),\n );\n\n return selectedEntry.url;\n}\n\nexport function getRoutingStats(): Record<string, RoutingStatsEntry> {\n const storage = getStorage(\"local\");\n if (!storage) return {};\n\n try {\n return JSON.parse(storage.getItem(ROUTING_STATS_KEY) ?? \"{}\") as Record<string, RoutingStatsEntry>;\n } catch {\n return {};\n }\n}","import { ResiraApiError, ResiraNetworkError, ResiraRateLimitError } from \"./errors.js\";\nimport { pickBaseUrl, resolveBaseUrls } from \"./routing.js\";\nimport type {\n ApiErrorBody,\n Availability,\n AvailabilityParams,\n ConfirmPaymentRequest,\n ConfirmPaymentResponse,\n CreatePaymentIntentRequest,\n CreateReservationRequest,\n Dish,\n DishListResponse,\n DishResponse,\n ListReservationsParams,\n PaginatedReservations,\n PaymentIntentResponse,\n ProductListResponse,\n PropertyConfig,\n Reservation,\n ReservationResponse,\n ResiraConfig,\n ResourceListResponse,\n ValidatePromoCodeResponse,\n} from \"./types.js\";\n\n// ═══════════════════════════════════════════════════════════════\n// Constants\n// ═══════════════════════════════════════════════════════════════\n\nconst DEFAULT_MAX_RETRIES = 3;\nconst DEFAULT_RETRY_BASE_DELAY = 500; // ms\nconst API_PREFIX = \"/v1/public\";\n\n// ═══════════════════════════════════════════════════════════════\n// Helpers\n// ═══════════════════════════════════════════════════════════════\n\n/** Generate a random UUID v4 for idempotency keys. */\nfunction uuid(): string {\n // Use crypto.randomUUID when available (Node 19+, all modern browsers)\n if (typeof globalThis.crypto?.randomUUID === \"function\") {\n return globalThis.crypto.randomUUID();\n }\n // Fallback: manual v4 UUID\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n return (c === \"x\" ? r : (r & 0x3) | 0x8).toString(16);\n });\n}\n\n/** Build a query string from a flat params object. Skips `undefined` values. */\nfunction toQueryString(params: Record<string, string | number | boolean | undefined>): string {\n const parts: string[] = [];\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined && value !== null) {\n parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);\n }\n }\n return parts.length > 0 ? `?${parts.join(\"&\")}` : \"\";\n}\n\n/** Sleep for `ms` milliseconds. */\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Convert camelCase keys to snake_case for query parameters.\n * The API expects snake_case query params (`start_date`, `party_size`).\n */\nfunction camelToSnake(str: string): string {\n return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);\n}\n\n/** Convert an object's keys from camelCase to snake_case. */\nfunction keysToSnake(\n obj: Record<string, string | number | boolean | undefined>,\n): Record<string, string | number | boolean | undefined> {\n const result: Record<string, string | number | boolean | undefined> = {};\n for (const [key, value] of Object.entries(obj)) {\n result[camelToSnake(key)] = value;\n }\n return result;\n}\n\n/** Convert snake_case string to camelCase. */\nfunction snakeToCamel(str: string): string {\n return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());\n}\n\n/** Deep-convert all keys in an object/array from camelCase to snake_case. */\nfunction deepKeysToSnake(obj: unknown): unknown {\n if (Array.isArray(obj)) return obj.map(deepKeysToSnake);\n if (obj !== null && typeof obj === \"object\") {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n result[camelToSnake(key)] = deepKeysToSnake(value);\n }\n return result;\n }\n return obj;\n}\n\n/** Deep-convert all keys in an object/array from snake_case to camelCase. */\nfunction deepKeysToCamel(obj: unknown): unknown {\n if (Array.isArray(obj)) return obj.map(deepKeysToCamel);\n if (obj !== null && typeof obj === \"object\") {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n result[snakeToCamel(key)] = deepKeysToCamel(value);\n }\n return result;\n }\n return obj;\n}\n\n// ═══════════════════════════════════════════════════════════════\n// Client\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Resira SDK client.\n *\n * ```ts\n * const resira = new Resira({\n * apiKey: \"resira_live_abc123…\",\n * baseUrl: \"https://api.example.com\", // optional\n * });\n *\n * const availability = await resira.getAvailability(\"prop-1\", {\n * startDate: \"2026-07-01\",\n * endDate: \"2026-07-07\",\n * });\n * ```\n */\nexport class Resira {\n private readonly apiKey: string;\n private readonly baseUrls: ReturnType<typeof resolveBaseUrls>;\n private readonly maxRetries: number;\n private readonly retryBaseDelay: number;\n private readonly _fetch: typeof globalThis.fetch;\n\n constructor(config: ResiraConfig) {\n if (!config.apiKey) {\n throw new Error(\"Resira: apiKey is required\");\n }\n this.apiKey = config.apiKey;\n this.baseUrls = resolveBaseUrls(config);\n this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;\n this.retryBaseDelay = config.retryBaseDelay ?? DEFAULT_RETRY_BASE_DELAY;\n this._fetch = config.fetch ?? globalThis.fetch.bind(globalThis);\n }\n\n // ─────────────────────────────────────────────────────────────\n // Public API methods\n // ─────────────────────────────────────────────────────────────\n\n /**\n * Fetch the public property configuration.\n *\n * Returns non-sensitive settings such as the Stripe publishable\n * key, deposit percentage, currency, and branding info.\n *\n * ```ts\n * const config = await resira.getConfig();\n * console.log(config.stripePublishableKey); // \"pk_test_…\"\n * ```\n */\n async getConfig(): Promise<PropertyConfig> {\n return this.request<PropertyConfig>(\"GET\", \"/config\");\n }\n\n /**\n * Validate a promo/discount code.\n *\n * Returns discount details if valid, or an error message if not.\n *\n * ```ts\n * const result = await resira.validatePromoCode(\"SUMMER20\");\n * if (result.valid) {\n * console.log(result.discountType, result.discountValue);\n * }\n * ```\n */\n async validatePromoCode(code: string): Promise<ValidatePromoCodeResponse> {\n try {\n const raw = await this.request<Record<string, unknown>>(\n \"POST\",\n \"/promo-codes/validate\",\n { code },\n );\n const data = deepKeysToCamel(raw) as Record<string, unknown>;\n return {\n valid: true,\n discountType: data.discountType as \"percent\" | \"fixed\" | undefined,\n discountValue: data.discountValue as number | undefined,\n currency: data.currency as string | undefined,\n minOrderCents: data.minOrderCents as number | undefined,\n };\n } catch (err: unknown) {\n // 4xx errors mean invalid code\n if (\n err &&\n typeof err === \"object\" &&\n \"status\" in err &&\n (err as { status: number }).status < 500\n ) {\n const apiErr = err as { body?: { error?: string } };\n return {\n valid: false,\n error: apiErr.body?.error ?? \"Invalid promo code\",\n };\n }\n throw err;\n }\n }\n\n /**\n * List all active resources for the organization.\n *\n * Returns resource cards with name, description, price, duration,\n * image, and type — suitable for building a resource picker.\n *\n * ```ts\n * const { resources } = await resira.listResources();\n * ```\n */\n async listResources(): Promise<ResourceListResponse> {\n return this.request<ResourceListResponse>(\"GET\", \"/resources\");\n }\n\n /**\n * Query availability for a resource.\n *\n * **Properties** (date-based):\n * ```ts\n * await resira.getAvailability(\"prop-1\", {\n * startDate: \"2026-07-01\",\n * endDate: \"2026-07-07\",\n * });\n * ```\n *\n * **Restaurants** (time-slot):\n * ```ts\n * await resira.getAvailability(\"table-5\", {\n * date: \"2026-07-01\",\n * partySize: 4,\n * });\n * ```\n *\n * Omit params to get all blocked dates (for calendar rendering).\n */\n async getAvailability(\n resourceId: string,\n params: AvailabilityParams = {},\n ): Promise<Availability> {\n if (!resourceId) {\n throw new Error(\"resourceId is required for getAvailability\");\n }\n const qs = toQueryString(keysToSnake(params as Record<string, string | number | undefined>));\n return this.request<Availability>(\n \"GET\",\n `/resources/${encodeURIComponent(resourceId)}/availability${qs}`,\n );\n }\n\n /**\n * Query availability for a product/service.\n *\n * Aggregates capacity across all linked equipment. If a product\n * has 2 jet skis (each capacity=1), slots show capacity: 2.\n * Both pending and confirmed reservations count as occupied.\n *\n * ```ts\n * await resira.getProductAvailability(\"prod-123\", {\n * date: \"2026-07-01\",\n * durationMinutes: 30,\n * });\n * ```\n */\n async getProductAvailability(\n productId: string,\n params: AvailabilityParams = {},\n ): Promise<Availability> {\n if (!productId) {\n throw new Error(\"productId is required for getProductAvailability\");\n }\n const qs = toQueryString(keysToSnake(params as Record<string, string | number | undefined>));\n return this.request<Availability>(\n \"GET\",\n `/products/${encodeURIComponent(productId)}/availability${qs}`,\n );\n }\n\n /**\n * Create a reservation.\n *\n * Uses `POST /v2/api/reservations` — the `resourceId` is sent in\n * the request body, **not** in the URL path.\n *\n * An `Idempotency-Key` header is automatically attached so that\n * retries (including those from exponential backoff) are safe.\n *\n * ```ts\n * const { reservation } = await resira.createReservation({\n * resourceId: \"prop-1\",\n * guestName: \"Jane Doe\",\n * guestEmail: \"jane@example.com\",\n * startDate: \"2026-07-01\",\n * endDate: \"2026-07-07\",\n * partySize: 3,\n * });\n * ```\n */\n async createReservation(\n payload: CreateReservationRequest,\n options?: { idempotencyKey?: string },\n ): Promise<ReservationResponse> {\n if (!payload.resourceId) {\n throw new Error(\"resourceId is required for createReservation\");\n }\n const idempotencyKey = options?.idempotencyKey ?? uuid();\n\n // Note: uses /v2/api prefix, not /v1/public\n const url = `${this.getBaseUrl()}/v2/api/reservations`;\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.apiKey}`,\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n \"Idempotency-Key\": idempotencyKey,\n };\n\n const init: RequestInit = {\n method: \"POST\",\n headers,\n body: JSON.stringify(payload),\n };\n\n let lastError: ResiraApiError | ResiraNetworkError | ResiraRateLimitError | undefined;\n\n for (let attempt = 0; attempt <= this.maxRetries; attempt++) {\n if (attempt > 0) {\n const backoff = this.calculateBackoff(attempt, lastError);\n await sleep(backoff);\n }\n\n let response: Response;\n try {\n response = await this._fetch(url, init);\n } catch (err) {\n lastError = new ResiraNetworkError(\n `Network request failed: ${err instanceof Error ? err.message : String(err)}`,\n err,\n );\n continue;\n }\n\n if (response.ok) {\n const raw = (await response.json()) as Record<string, unknown>;\n // The API may nest under \"reservation\" or \"booking\" depending on endpoint\n const resData = (raw.reservation ?? raw.booking ?? {}) as Record<string, unknown>;\n\n // Augment with request data: API response doesn't include\n // startTime/endTime/startDate/endDate/resourceId for v2 reservations\n const reservation: Reservation = {\n id: (resData.id as string) ?? \"\",\n resourceId: (resData.resourceId as string) ?? payload.resourceId,\n status: (resData.status as string) ?? \"pending\",\n startDate: (resData.startDate as string) ?? payload.startDate,\n endDate: (resData.endDate as string) ?? payload.endDate,\n startTime: (resData.startTime as string) ?? payload.startTime,\n endTime: (resData.endTime as string) ?? payload.endTime,\n startDatetime: (resData.startDatetime as string) ?? payload.startTime,\n endDatetime: (resData.endDatetime as string) ?? payload.endTime,\n guestName: (resData.guestName as string) ?? payload.guestName,\n partySize: (resData.partySize as number) ?? (resData.guests as number) ?? payload.partySize ?? 2,\n totalPrice: (resData.totalPrice as number) ?? 0,\n currency: (resData.currency as string) ?? \"EUR\",\n createdAt: (resData.createdAt as string) ?? new Date().toISOString(),\n };\n\n return { reservation };\n }\n\n let errorBody: ApiErrorBody;\n try {\n errorBody = (await response.json()) as ApiErrorBody;\n } catch {\n errorBody = { error: response.statusText || `HTTP ${response.status}` };\n }\n\n if (response.status === 429) {\n lastError = new ResiraRateLimitError(errorBody, response);\n continue;\n }\n\n if (response.status >= 500) {\n lastError = new ResiraApiError(response.status, response.statusText, errorBody, response);\n continue;\n }\n\n throw new ResiraApiError(response.status, response.statusText, errorBody, response);\n }\n\n throw lastError!;\n }\n\n /**\n * List reservations for a resource (paginated).\n *\n * ```ts\n * const page = await resira.listReservations(\"prop-1\", {\n * status: \"confirmed\",\n * page: 1,\n * limit: 50,\n * });\n * console.log(page.data); // Reservation[]\n * console.log(page.totalPages); // number\n * ```\n */\n async listReservations(\n resourceId: string,\n params: ListReservationsParams = {},\n ): Promise<PaginatedReservations> {\n const qs = toQueryString(params as Record<string, string | number | undefined>);\n return this.request<PaginatedReservations>(\n \"GET\",\n `/resources/${encodeURIComponent(resourceId)}/reservations${qs}`,\n );\n }\n\n /**\n * Get a single reservation by ID.\n *\n * ```ts\n * const { reservation } = await resira.getReservation(\"prop-1\", \"res-uuid\");\n * ```\n */\n async getReservation(\n resourceId: string,\n reservationId: string,\n ): Promise<ReservationResponse> {\n return this.request<ReservationResponse>(\n \"GET\",\n `/resources/${encodeURIComponent(resourceId)}/reservations/${encodeURIComponent(reservationId)}`,\n );\n }\n\n /**\n * List all active products/services for the organisation.\n *\n * Calls `GET /v1/public/products` which returns products with\n * pricing, duration, and linked equipment.\n *\n * Falls back to mapping resources → products if the endpoint\n * returns 404 (backend hasn't been updated yet).\n *\n * ```ts\n * const { products } = await resira.listProducts();\n * ```\n */\n async listProducts(): Promise<ProductListResponse> {\n try {\n return await this.request<ProductListResponse>(\"GET\", \"/products\");\n } catch (err: unknown) {\n // Fallback: if /products endpoint doesn't exist yet (404),\n // map resources → products shape\n if (\n err &&\n typeof err === \"object\" &&\n \"status\" in err &&\n (err as { status: number }).status === 404\n ) {\n const resourceResponse = await this.listResources();\n const products = resourceResponse.resources.map((r) => ({\n id: r.id,\n name: r.name,\n description: r.description,\n durationMinutes: r.durationMinutes ?? 60,\n priceCents: r.pricePerSession ?? 0,\n currency: r.currency ?? \"EUR\",\n imageUrl: r.imageUrl,\n active: r.active,\n sortOrder: 0,\n pricingModel: \"per_session\" as const,\n equipmentIds: [r.id],\n equipmentNames: [r.name],\n }));\n return { products, count: products.length };\n }\n throw err;\n }\n }\n\n /**\n * List all dishes with 3D model data for the organisation.\n *\n * Returns dishes with name, description, price, image, and\n * 3D model URLs for AR/preview rendering.\n *\n * ```ts\n * const { dishes } = await resira.listDishes();\n * ```\n */\n async listDishes(): Promise<DishListResponse> {\n return this.request<DishListResponse>(\"GET\", \"/dishes\");\n }\n\n /**\n * Get a single dish by ID.\n *\n * ```ts\n * const { dish } = await resira.getDish(\"dish-uuid\");\n * console.log(dish.name, dish.model?.glbUrl);\n * ```\n */\n async getDish(dishId: string): Promise<DishResponse> {\n if (!dishId) {\n throw new Error(\"dishId is required for getDish\");\n }\n return this.request<DishResponse>(\n \"GET\",\n `/dishes/${encodeURIComponent(dishId)}`,\n );\n }\n\n /**\n * Create a Stripe payment intent for a booking.\n *\n * The server creates the payment intent, calculates the amount,\n * and returns a `clientSecret` to confirm payment on the frontend\n * using Stripe Elements.\n *\n * ```ts\n * const { clientSecret, amountNow, amountAtVenue } =\n * await resira.createPaymentIntent({\n * productId: \"prod-123\",\n * resourceId: \"res-456\",\n * partySize: 2,\n * startDate: \"2026-07-01\",\n * startTime: \"2026-07-01T10:00:00Z\",\n * endTime: \"2026-07-01T11:00:00Z\",\n * guestName: \"Jane Doe\",\n * guestEmail: \"jane@example.com\",\n * });\n * ```\n */\n async createPaymentIntent(\n payload: CreatePaymentIntentRequest,\n options?: { idempotencyKey?: string },\n ): Promise<PaymentIntentResponse> {\n const idempotencyKey = options?.idempotencyKey ?? uuid();\n const raw = await this.request<Record<string, unknown>>(\n \"POST\",\n \"/payments/create-intent\",\n payload,\n { \"Idempotency-Key\": idempotencyKey },\n );\n // Normalize response keys to camelCase\n const data = deepKeysToCamel(raw) as Record<string, unknown>;\n // Map backend field names to SDK field names:\n // backend: amountDue, totalPrice\n // SDK: amountNow, totalAmount, amountAtVenue\n const totalAmount = (data.totalAmount as number) ?? (data.totalPrice as number) ?? 0;\n const amountNow = (data.amountNow as number) ?? (data.amountDue as number) ?? totalAmount;\n const amountAtVenue = (data.amountAtVenue as number) ?? (totalAmount - amountNow);\n return {\n clientSecret: data.clientSecret as string,\n paymentIntentId: data.paymentIntentId as string,\n amountNow,\n amountAtVenue,\n totalAmount,\n currency: (data.currency as string) ?? \"EUR\",\n paymentOption: data.paymentOption as string | undefined,\n reservationId: data.reservationId as string | undefined,\n discountAmount: (data.discountAmount as number) ?? undefined,\n originalPrice: (data.originalPrice as number) ?? undefined,\n promoCodeApplied: (data.promoCodeApplied as string) ?? undefined,\n };\n }\n\n /**\n * Confirm that a payment was successful after the guest\n * completes the Stripe payment flow.\n *\n * This transitions the reservation from `pending` to `confirmed`.\n *\n * ```ts\n * const { reservation, paymentStatus } =\n * await resira.confirmPayment({\n * paymentIntentId: \"pi_xxx\",\n * reservationId: \"res-uuid\",\n * });\n * ```\n */\n async confirmPayment(\n payload: ConfirmPaymentRequest,\n ): Promise<ConfirmPaymentResponse> {\n const raw = await this.request<Record<string, unknown>>(\n \"POST\",\n \"/payments/confirm\",\n payload,\n );\n // Normalize response keys to camelCase in case backend returns snake_case\n return deepKeysToCamel(raw) as ConfirmPaymentResponse;\n }\n\n // ─────────────────────────────────────────────────────────────\n // Internal HTTP layer\n // ─────────────────────────────────────────────────────────────\n\n /**\n * Execute an HTTP request with retry logic and error normalization.\n *\n * - Attaches `Authorization: Bearer <apiKey>` on every request.\n * - Retries on 429 and 5xx with exponential backoff + jitter.\n * - Parses error bodies and throws typed error classes.\n */\n private async request<T>(\n method: string,\n path: string,\n body?: unknown,\n extraHeaders?: Record<string, string>,\n ): Promise<T> {\n const url = `${this.getBaseUrl()}${API_PREFIX}${path}`;\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.apiKey}`,\n Accept: \"application/json\",\n ...extraHeaders,\n };\n\n if (body !== undefined) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n\n const init: RequestInit = {\n method,\n headers,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n };\n\n let lastError: ResiraApiError | ResiraNetworkError | undefined;\n\n for (let attempt = 0; attempt <= this.maxRetries; attempt++) {\n // ── Wait before retry ────────────────────────────────\n if (attempt > 0) {\n const backoff = this.calculateBackoff(attempt, lastError);\n await sleep(backoff);\n }\n\n // ── Execute the request ──────────────────────────────\n let response: Response;\n try {\n response = await this._fetch(url, init);\n } catch (err) {\n lastError = new ResiraNetworkError(\n `Network request failed: ${err instanceof Error ? err.message : String(err)}`,\n err,\n );\n // Network errors are always retryable\n continue;\n }\n\n // ── Success ──────────────────────────────────────────\n if (response.ok) {\n return (await response.json()) as T;\n }\n\n // ── Parse error body ─────────────────────────────────\n let errorBody: ApiErrorBody;\n try {\n errorBody = (await response.json()) as ApiErrorBody;\n } catch {\n errorBody = { error: response.statusText || `HTTP ${response.status}` };\n }\n\n // ── Rate limit ───────────────────────────────────────\n if (response.status === 429) {\n lastError = new ResiraRateLimitError(errorBody, response);\n // Always retry 429s (up to maxRetries)\n continue;\n }\n\n // ── Server error (5xx) — retryable ───────────────────\n if (response.status >= 500) {\n lastError = new ResiraApiError(\n response.status,\n response.statusText,\n errorBody,\n response,\n );\n continue;\n }\n\n // ── Client error (4xx) — NOT retryable ───────────────\n throw new ResiraApiError(\n response.status,\n response.statusText,\n errorBody,\n response,\n );\n }\n\n // All retries exhausted — throw the last error\n throw lastError!;\n }\n\n /**\n * Calculate backoff delay in milliseconds for a given retry attempt.\n *\n * Uses exponential backoff with full jitter:\n * delay = random(0, base * 2^attempt)\n *\n * For 429 responses, respects the `Retry-After` header as a minimum.\n */\n private calculateBackoff(\n attempt: number,\n lastError?: ResiraApiError | ResiraNetworkError,\n ): number {\n const exponential = this.retryBaseDelay * 2 ** (attempt - 1);\n const jittered = Math.random() * exponential;\n\n // If the server told us to wait, use that as the floor\n if (lastError instanceof ResiraRateLimitError) {\n const serverDelay = lastError.retryAfter * 1000;\n return Math.max(serverDelay, jittered);\n }\n\n return jittered;\n }\n\n private getBaseUrl(): string {\n return pickBaseUrl(this.baseUrls);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/routing.ts","../src/client.ts"],"names":["url"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { ApiErrorBody } from \"./types.js\";\n\n// ═══════════════════════════════════════════════════════════════\n// Base error\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Base error class for all SDK errors.\n *\n * Use `instanceof` checks to distinguish error types:\n *\n * ```ts\n * try { … } catch (e) {\n * if (e instanceof ResiraRateLimitError) { … }\n * if (e instanceof ResiraApiError) { … }\n * if (e instanceof ResiraNetworkError) { … }\n * }\n * ```\n */\nexport class ResiraError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ResiraError\";\n // Fix prototype chain for instanceof to work after transpilation\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n// ═══════════════════════════════════════════════════════════════\n// API error (4xx / 5xx with a parsed body)\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Thrown when the API returns a non-2xx response with a JSON body.\n *\n * ```ts\n * err.status // 400, 404, 409, 422, 500, …\n * err.code // HTTP status text (e.g. \"Not Found\")\n * err.body // Parsed `{ error: \"…\" }` from the server\n * ```\n */\nexport class ResiraApiError extends ResiraError {\n /** HTTP status code. */\n readonly status: number;\n /** HTTP status text (e.g. `\"Bad Request\"`). */\n readonly code: string;\n /** Parsed error body from the API. */\n readonly body: ApiErrorBody;\n /** The full `Response` object (headers are still accessible). */\n readonly response: Response;\n\n constructor(status: number, code: string, body: ApiErrorBody, response: Response) {\n super(body.error || `API error ${status}`);\n this.name = \"ResiraApiError\";\n this.status = status;\n this.code = code;\n this.body = body;\n this.response = response;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n /** Whether this error is retryable (5xx or 429). */\n get retryable(): boolean {\n return this.status === 429 || this.status >= 500;\n }\n}\n\n// ═══════════════════════════════════════════════════════════════\n// Rate limit error (429)\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Thrown when the API returns 429 Too Many Requests.\n *\n * ```ts\n * err.retryAfter // Seconds until the client should retry\n * ```\n */\nexport class ResiraRateLimitError extends ResiraApiError {\n /** Seconds to wait before retrying (from `Retry-After` header). */\n readonly retryAfter: number;\n\n constructor(body: ApiErrorBody, response: Response) {\n super(429, \"Too Many Requests\", body, response);\n this.name = \"ResiraRateLimitError\";\n this.retryAfter =\n body.retryAfter ??\n (Number.parseInt(response.headers.get(\"retry-after\") ?? \"60\", 10) ||\n 60);\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n// ═══════════════════════════════════════════════════════════════\n// Network error (fetch failed, DNS, timeout, etc.)\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Thrown when the request never reached the server.\n *\n * This wraps the underlying `TypeError` or `DOMException` from\n * `fetch()` failures (DNS resolution, TLS, network offline, etc.).\n */\nexport class ResiraNetworkError extends ResiraError {\n /** The original error thrown by `fetch`. */\n readonly cause: unknown;\n\n constructor(message: string, cause: unknown) {\n super(message);\n this.name = \"ResiraNetworkError\";\n this.cause = cause;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","import type { ResiraConfig, WeightedBaseUrl } from \"./types.js\";\n\ninterface ResolvedBaseUrl {\n url: string;\n weight: number;\n}\n\nconst DEFAULT_LOCAL_BASE_URLS: ResolvedBaseUrl[] = [\n { url: \"http://localhost:3001\", weight: 100 },\n];\n\nconst DEFAULT_PRODUCTION_BASE_URLS: ResolvedBaseUrl[] = [\n { url: \"https://api.resira.app\", weight: 100 },\n];\n\nfunction readEnv(name: string): string | undefined {\n if (typeof process === \"undefined\") return undefined;\n return process.env?.[name];\n}\n\n\nfunction normalizeUrl(url: string): string {\n return url.trim().replace(/\\/+$/, \"\");\n}\n\nfunction isPositiveNumber(value: unknown): value is number {\n return typeof value === \"number\" && Number.isFinite(value) && value > 0;\n}\n\nfunction toResolvedBaseUrl(value: string | WeightedBaseUrl): ResolvedBaseUrl | null {\n if (typeof value === \"string\") {\n const url = normalizeUrl(value);\n return url ? { url, weight: 1 } : null;\n }\n\n const url = normalizeUrl(value.url);\n if (!url) return null;\n\n return {\n url,\n weight: isPositiveNumber(value.weight) ? value.weight : 1,\n };\n}\n\nfunction parseJsonBaseUrls(value: string): ResolvedBaseUrl[] {\n const parsed = JSON.parse(value) as unknown;\n if (!Array.isArray(parsed)) return [];\n\n return parsed.flatMap((entry) => {\n const resolved =\n typeof entry === \"string\"\n ? toResolvedBaseUrl(entry)\n : entry && typeof entry === \"object\"\n ? toResolvedBaseUrl(entry as WeightedBaseUrl)\n : null;\n\n return resolved ? [resolved] : [];\n });\n}\n\nfunction parseDelimitedBaseUrls(value: string): ResolvedBaseUrl[] {\n return value\n .split(\",\")\n .flatMap((entry) => {\n const [rawUrl, rawWeight] = entry.split(\"|\");\n const url = normalizeUrl(rawUrl ?? \"\");\n if (!url) return [];\n\n const parsedWeight = rawWeight ? Number(rawWeight.trim()) : 1;\n return [{\n url,\n weight: isPositiveNumber(parsedWeight) ? parsedWeight : 1,\n }];\n });\n}\n\nfunction parseEnvBaseUrls(value: string | undefined): ResolvedBaseUrl[] {\n if (!value) return [];\n\n try {\n const parsed = parseJsonBaseUrls(value);\n if (parsed.length > 0) return parsed;\n } catch {\n // Fall back to delimiter parsing.\n }\n\n return parseDelimitedBaseUrls(value);\n}\n\nexport function resolveBaseUrls(config: ResiraConfig): ResolvedBaseUrl[] {\n if (config.baseUrl) {\n return [{ url: normalizeUrl(config.baseUrl), weight: 100 }];\n }\n\n if (config.baseUrls?.length) {\n const explicit = config.baseUrls.flatMap((entry) => {\n const resolved = toResolvedBaseUrl(entry);\n return resolved ? [resolved] : [];\n });\n\n if (explicit.length > 0) return explicit;\n }\n\n const envBaseUrls = parseEnvBaseUrls(readEnv(\"RESIRA_API_BASE_URLS\"));\n if (envBaseUrls.length > 0) {\n return envBaseUrls;\n }\n\n if (readEnv(\"NODE_ENV\") === \"development\") {\n return DEFAULT_LOCAL_BASE_URLS;\n }\n\n return DEFAULT_PRODUCTION_BASE_URLS;\n}\n\nexport function pickBaseUrl(baseUrls: ResolvedBaseUrl[]): string {\n return baseUrls[0]?.url ?? DEFAULT_PRODUCTION_BASE_URLS[0].url;\n}\n\nexport function getRoutingStats(): Record<string, never> {\n return {};\n}","import { ResiraApiError, ResiraNetworkError, ResiraRateLimitError } from \"./errors.js\";\nimport { pickBaseUrl, resolveBaseUrls } from \"./routing.js\";\nimport type {\n ApiErrorBody,\n Availability,\n AvailabilityParams,\n ConfirmPaymentRequest,\n ConfirmPaymentResponse,\n CreatePaymentIntentRequest,\n CreateReservationRequest,\n Dish,\n DishListResponse,\n DishResponse,\n ListReservationsParams,\n PaginatedReservations,\n PaymentIntentResponse,\n ProductListResponse,\n PropertyConfig,\n Reservation,\n ReservationResponse,\n ResiraConfig,\n ResourceListResponse,\n ValidatePromoCodeResponse,\n} from \"./types.js\";\n\n// ═══════════════════════════════════════════════════════════════\n// Constants\n// ═══════════════════════════════════════════════════════════════\n\nconst DEFAULT_MAX_RETRIES = 3;\nconst DEFAULT_RETRY_BASE_DELAY = 500; // ms\nconst API_PREFIX = \"/v1/public\";\n\n// ═══════════════════════════════════════════════════════════════\n// Helpers\n// ═══════════════════════════════════════════════════════════════\n\n/** Generate a random UUID v4 for idempotency keys. */\nfunction uuid(): string {\n // Use crypto.randomUUID when available (Node 19+, all modern browsers)\n if (typeof globalThis.crypto?.randomUUID === \"function\") {\n return globalThis.crypto.randomUUID();\n }\n // Fallback: manual v4 UUID\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n return (c === \"x\" ? r : (r & 0x3) | 0x8).toString(16);\n });\n}\n\n/** Build a query string from a flat params object. Skips `undefined` values. */\nfunction toQueryString(params: Record<string, string | number | boolean | undefined>): string {\n const parts: string[] = [];\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined && value !== null) {\n parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);\n }\n }\n return parts.length > 0 ? `?${parts.join(\"&\")}` : \"\";\n}\n\n/** Sleep for `ms` milliseconds. */\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Convert camelCase keys to snake_case for query parameters.\n * The API expects snake_case query params (`start_date`, `party_size`).\n */\nfunction camelToSnake(str: string): string {\n return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);\n}\n\n/** Convert an object's keys from camelCase to snake_case. */\nfunction keysToSnake(\n obj: Record<string, string | number | boolean | undefined>,\n): Record<string, string | number | boolean | undefined> {\n const result: Record<string, string | number | boolean | undefined> = {};\n for (const [key, value] of Object.entries(obj)) {\n result[camelToSnake(key)] = value;\n }\n return result;\n}\n\n/** Convert snake_case string to camelCase. */\nfunction snakeToCamel(str: string): string {\n return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());\n}\n\n/** Deep-convert all keys in an object/array from camelCase to snake_case. */\nfunction deepKeysToSnake(obj: unknown): unknown {\n if (Array.isArray(obj)) return obj.map(deepKeysToSnake);\n if (obj !== null && typeof obj === \"object\") {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n result[camelToSnake(key)] = deepKeysToSnake(value);\n }\n return result;\n }\n return obj;\n}\n\n/** Deep-convert all keys in an object/array from snake_case to camelCase. */\nfunction deepKeysToCamel(obj: unknown): unknown {\n if (Array.isArray(obj)) return obj.map(deepKeysToCamel);\n if (obj !== null && typeof obj === \"object\") {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n result[snakeToCamel(key)] = deepKeysToCamel(value);\n }\n return result;\n }\n return obj;\n}\n\n// ═══════════════════════════════════════════════════════════════\n// Client\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Resira SDK client.\n *\n * ```ts\n * const resira = new Resira({\n * apiKey: \"resira_live_abc123…\",\n * baseUrl: \"https://api.example.com\", // optional\n * });\n *\n * const availability = await resira.getAvailability(\"prop-1\", {\n * startDate: \"2026-07-01\",\n * endDate: \"2026-07-07\",\n * });\n * ```\n */\nexport class Resira {\n private readonly apiKey: string;\n private readonly baseUrls: ReturnType<typeof resolveBaseUrls>;\n private readonly maxRetries: number;\n private readonly retryBaseDelay: number;\n private readonly _fetch: typeof globalThis.fetch;\n\n constructor(config: ResiraConfig) {\n if (!config.apiKey) {\n throw new Error(\"Resira: apiKey is required\");\n }\n this.apiKey = config.apiKey;\n this.baseUrls = resolveBaseUrls(config);\n this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;\n this.retryBaseDelay = config.retryBaseDelay ?? DEFAULT_RETRY_BASE_DELAY;\n this._fetch = config.fetch ?? globalThis.fetch.bind(globalThis);\n }\n\n /** The resolved API origin used by this client instance. */\n getBaseUrl(): string {\n return pickBaseUrl(this.baseUrls);\n }\n\n // ─────────────────────────────────────────────────────────────\n // Public API methods\n // ─────────────────────────────────────────────────────────────\n\n /**\n * Fetch the public property configuration.\n *\n * Returns non-sensitive settings such as the Stripe publishable\n * key, deposit percentage, currency, and branding info.\n *\n * ```ts\n * const config = await resira.getConfig();\n * console.log(config.stripePublishableKey); // \"pk_test_…\"\n * ```\n */\n async getConfig(): Promise<PropertyConfig> {\n return this.request<PropertyConfig>(\"GET\", \"/config\");\n }\n\n /**\n * Validate a promo/discount code.\n *\n * Returns discount details if valid, or an error message if not.\n *\n * ```ts\n * const result = await resira.validatePromoCode(\"SUMMER20\");\n * if (result.valid) {\n * console.log(result.discountType, result.discountValue);\n * }\n * ```\n */\n async validatePromoCode(code: string): Promise<ValidatePromoCodeResponse> {\n try {\n const raw = await this.request<Record<string, unknown>>(\n \"POST\",\n \"/promo-codes/validate\",\n { code },\n );\n const data = deepKeysToCamel(raw) as Record<string, unknown>;\n return {\n valid: true,\n discountType: data.discountType as \"percent\" | \"fixed\" | undefined,\n discountValue: data.discountValue as number | undefined,\n currency: data.currency as string | undefined,\n minOrderCents: data.minOrderCents as number | undefined,\n };\n } catch (err: unknown) {\n // 4xx errors mean invalid code\n if (\n err &&\n typeof err === \"object\" &&\n \"status\" in err &&\n (err as { status: number }).status < 500\n ) {\n const apiErr = err as { body?: { error?: string } };\n return {\n valid: false,\n error: apiErr.body?.error ?? \"Invalid promo code\",\n };\n }\n throw err;\n }\n }\n\n /**\n * List all active resources for the organization.\n *\n * Returns resource cards with name, description, price, duration,\n * image, and type — suitable for building a resource picker.\n *\n * ```ts\n * const { resources } = await resira.listResources();\n * ```\n */\n async listResources(): Promise<ResourceListResponse> {\n return this.request<ResourceListResponse>(\"GET\", \"/resources\");\n }\n\n /**\n * Query availability for a resource.\n *\n * **Properties** (date-based):\n * ```ts\n * await resira.getAvailability(\"prop-1\", {\n * startDate: \"2026-07-01\",\n * endDate: \"2026-07-07\",\n * });\n * ```\n *\n * **Restaurants** (time-slot):\n * ```ts\n * await resira.getAvailability(\"table-5\", {\n * date: \"2026-07-01\",\n * partySize: 4,\n * });\n * ```\n *\n * Omit params to get all blocked dates (for calendar rendering).\n */\n async getAvailability(\n resourceId: string,\n params: AvailabilityParams = {},\n ): Promise<Availability> {\n if (!resourceId) {\n throw new Error(\"resourceId is required for getAvailability\");\n }\n const qs = toQueryString(keysToSnake(params as Record<string, string | number | undefined>));\n return this.request<Availability>(\n \"GET\",\n `/resources/${encodeURIComponent(resourceId)}/availability${qs}`,\n );\n }\n\n /**\n * Query availability for a product/service.\n *\n * Aggregates capacity across all linked equipment. If a product\n * has 2 jet skis (each capacity=1), slots show capacity: 2.\n * Both pending and confirmed reservations count as occupied.\n *\n * ```ts\n * await resira.getProductAvailability(\"prod-123\", {\n * date: \"2026-07-01\",\n * durationMinutes: 30,\n * });\n * ```\n */\n async getProductAvailability(\n productId: string,\n params: AvailabilityParams = {},\n ): Promise<Availability> {\n if (!productId) {\n throw new Error(\"productId is required for getProductAvailability\");\n }\n const qs = toQueryString(keysToSnake(params as Record<string, string | number | undefined>));\n return this.request<Availability>(\n \"GET\",\n `/products/${encodeURIComponent(productId)}/availability${qs}`,\n );\n }\n\n /**\n * Create a reservation.\n *\n * Uses `POST /v2/api/reservations` — the `resourceId` is sent in\n * the request body, **not** in the URL path.\n *\n * An `Idempotency-Key` header is automatically attached so that\n * retries (including those from exponential backoff) are safe.\n *\n * ```ts\n * const { reservation } = await resira.createReservation({\n * resourceId: \"prop-1\",\n * guestName: \"Jane Doe\",\n * guestEmail: \"jane@example.com\",\n * startDate: \"2026-07-01\",\n * endDate: \"2026-07-07\",\n * partySize: 3,\n * });\n * ```\n */\n async createReservation(\n payload: CreateReservationRequest,\n options?: { idempotencyKey?: string },\n ): Promise<ReservationResponse> {\n if (!payload.resourceId) {\n throw new Error(\"resourceId is required for createReservation\");\n }\n const idempotencyKey = options?.idempotencyKey ?? uuid();\n\n // Note: uses /v2/api prefix, not /v1/public\n const url = `${this.getBaseUrl()}/v2/api/reservations`;\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.apiKey}`,\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n \"Idempotency-Key\": idempotencyKey,\n };\n\n const init: RequestInit = {\n method: \"POST\",\n headers,\n body: JSON.stringify(payload),\n };\n\n let lastError: ResiraApiError | ResiraNetworkError | ResiraRateLimitError | undefined;\n\n for (let attempt = 0; attempt <= this.maxRetries; attempt++) {\n if (attempt > 0) {\n const backoff = this.calculateBackoff(attempt, lastError);\n await sleep(backoff);\n }\n\n let response: Response;\n try {\n response = await this._fetch(url, init);\n } catch (err) {\n lastError = new ResiraNetworkError(\n `Network request failed: ${err instanceof Error ? err.message : String(err)}`,\n err,\n );\n continue;\n }\n\n if (response.ok) {\n const raw = (await response.json()) as Record<string, unknown>;\n // The API may nest under \"reservation\" or \"booking\" depending on endpoint\n const resData = (raw.reservation ?? raw.booking ?? {}) as Record<string, unknown>;\n\n // Augment with request data: API response doesn't include\n // startTime/endTime/startDate/endDate/resourceId for v2 reservations\n const reservation: Reservation = {\n id: (resData.id as string) ?? \"\",\n resourceId: (resData.resourceId as string) ?? payload.resourceId,\n status: (resData.status as string) ?? \"pending\",\n startDate: (resData.startDate as string) ?? payload.startDate,\n endDate: (resData.endDate as string) ?? payload.endDate,\n startTime: (resData.startTime as string) ?? payload.startTime,\n endTime: (resData.endTime as string) ?? payload.endTime,\n startDatetime: (resData.startDatetime as string) ?? payload.startTime,\n endDatetime: (resData.endDatetime as string) ?? payload.endTime,\n guestName: (resData.guestName as string) ?? payload.guestName,\n partySize: (resData.partySize as number) ?? (resData.guests as number) ?? payload.partySize ?? 2,\n totalPrice: (resData.totalPrice as number) ?? 0,\n currency: (resData.currency as string) ?? \"EUR\",\n createdAt: (resData.createdAt as string) ?? new Date().toISOString(),\n };\n\n return { reservation };\n }\n\n let errorBody: ApiErrorBody;\n try {\n errorBody = (await response.json()) as ApiErrorBody;\n } catch {\n errorBody = { error: response.statusText || `HTTP ${response.status}` };\n }\n\n if (response.status === 429) {\n lastError = new ResiraRateLimitError(errorBody, response);\n continue;\n }\n\n if (response.status >= 500) {\n lastError = new ResiraApiError(response.status, response.statusText, errorBody, response);\n continue;\n }\n\n throw new ResiraApiError(response.status, response.statusText, errorBody, response);\n }\n\n throw lastError!;\n }\n\n /**\n * List reservations for a resource (paginated).\n *\n * ```ts\n * const page = await resira.listReservations(\"prop-1\", {\n * status: \"confirmed\",\n * page: 1,\n * limit: 50,\n * });\n * console.log(page.data); // Reservation[]\n * console.log(page.totalPages); // number\n * ```\n */\n async listReservations(\n resourceId: string,\n params: ListReservationsParams = {},\n ): Promise<PaginatedReservations> {\n const qs = toQueryString(params as Record<string, string | number | undefined>);\n return this.request<PaginatedReservations>(\n \"GET\",\n `/resources/${encodeURIComponent(resourceId)}/reservations${qs}`,\n );\n }\n\n /**\n * Get a single reservation by ID.\n *\n * ```ts\n * const { reservation } = await resira.getReservation(\"prop-1\", \"res-uuid\");\n * ```\n */\n async getReservation(\n resourceId: string,\n reservationId: string,\n ): Promise<ReservationResponse> {\n return this.request<ReservationResponse>(\n \"GET\",\n `/resources/${encodeURIComponent(resourceId)}/reservations/${encodeURIComponent(reservationId)}`,\n );\n }\n\n /**\n * List all active products/services for the organisation.\n *\n * Calls `GET /v1/public/products` which returns products with\n * pricing, duration, and linked equipment.\n *\n * Falls back to mapping resources → products if the endpoint\n * returns 404 (backend hasn't been updated yet).\n *\n * ```ts\n * const { products } = await resira.listProducts();\n * ```\n */\n async listProducts(): Promise<ProductListResponse> {\n try {\n return await this.request<ProductListResponse>(\"GET\", \"/products\");\n } catch (err: unknown) {\n // Fallback: if /products endpoint doesn't exist yet (404),\n // map resources → products shape\n if (\n err &&\n typeof err === \"object\" &&\n \"status\" in err &&\n (err as { status: number }).status === 404\n ) {\n const resourceResponse = await this.listResources();\n const products = resourceResponse.resources.map((r) => ({\n id: r.id,\n name: r.name,\n description: r.description,\n durationMinutes: r.durationMinutes ?? 60,\n priceCents: r.pricePerSession ?? 0,\n currency: r.currency ?? \"EUR\",\n imageUrl: r.imageUrl,\n active: r.active,\n sortOrder: 0,\n pricingModel: \"per_session\" as const,\n equipmentIds: [r.id],\n equipmentNames: [r.name],\n }));\n return { products, count: products.length };\n }\n throw err;\n }\n }\n\n /**\n * List all dishes with 3D model data for the organisation.\n *\n * Returns dishes with name, description, price, image, and\n * 3D model URLs for AR/preview rendering.\n *\n * ```ts\n * const { dishes } = await resira.listDishes();\n * ```\n */\n async listDishes(): Promise<DishListResponse> {\n return this.request<DishListResponse>(\"GET\", \"/dishes\");\n }\n\n /**\n * Get a single dish by ID.\n *\n * ```ts\n * const { dish } = await resira.getDish(\"dish-uuid\");\n * console.log(dish.name, dish.model?.glbUrl);\n * ```\n */\n async getDish(dishId: string): Promise<DishResponse> {\n if (!dishId) {\n throw new Error(\"dishId is required for getDish\");\n }\n return this.request<DishResponse>(\n \"GET\",\n `/dishes/${encodeURIComponent(dishId)}`,\n );\n }\n\n /**\n * Create a Stripe payment intent for a booking.\n *\n * The server creates the payment intent, calculates the amount,\n * and returns a `clientSecret` to confirm payment on the frontend\n * using Stripe Elements.\n *\n * ```ts\n * const { clientSecret, amountNow, amountAtVenue } =\n * await resira.createPaymentIntent({\n * productId: \"prod-123\",\n * resourceId: \"res-456\",\n * partySize: 2,\n * startDate: \"2026-07-01\",\n * startTime: \"2026-07-01T10:00:00Z\",\n * endTime: \"2026-07-01T11:00:00Z\",\n * guestName: \"Jane Doe\",\n * guestEmail: \"jane@example.com\",\n * });\n * ```\n */\n async createPaymentIntent(\n payload: CreatePaymentIntentRequest,\n options?: { idempotencyKey?: string },\n ): Promise<PaymentIntentResponse> {\n const idempotencyKey = options?.idempotencyKey ?? uuid();\n const raw = await this.request<Record<string, unknown>>(\n \"POST\",\n \"/payments/create-intent\",\n payload,\n { \"Idempotency-Key\": idempotencyKey },\n );\n // Normalize response keys to camelCase\n const data = deepKeysToCamel(raw) as Record<string, unknown>;\n // Map backend field names to SDK field names:\n // backend: amountDue, totalPrice\n // SDK: amountNow, totalAmount, amountAtVenue\n const totalAmount = (data.totalAmount as number) ?? (data.totalPrice as number) ?? 0;\n const amountNow = (data.amountNow as number) ?? (data.amountDue as number) ?? totalAmount;\n const amountAtVenue = (data.amountAtVenue as number) ?? (totalAmount - amountNow);\n return {\n clientSecret: data.clientSecret as string,\n paymentIntentId: data.paymentIntentId as string,\n amountNow,\n amountAtVenue,\n totalAmount,\n currency: (data.currency as string) ?? \"EUR\",\n paymentOption: data.paymentOption as string | undefined,\n reservationId: data.reservationId as string | undefined,\n discountAmount: (data.discountAmount as number) ?? undefined,\n originalPrice: (data.originalPrice as number) ?? undefined,\n promoCodeApplied: (data.promoCodeApplied as string) ?? undefined,\n };\n }\n\n /**\n * Confirm that a payment was successful after the guest\n * completes the Stripe payment flow.\n *\n * This transitions the reservation from `pending` to `confirmed`.\n *\n * ```ts\n * const { reservation, paymentStatus } =\n * await resira.confirmPayment({\n * paymentIntentId: \"pi_xxx\",\n * reservationId: \"res-uuid\",\n * });\n * ```\n */\n async confirmPayment(\n payload: ConfirmPaymentRequest,\n ): Promise<ConfirmPaymentResponse> {\n const raw = await this.request<Record<string, unknown>>(\n \"POST\",\n \"/payments/confirm\",\n payload,\n );\n // Normalize response keys to camelCase in case backend returns snake_case\n return deepKeysToCamel(raw) as ConfirmPaymentResponse;\n }\n\n // ─────────────────────────────────────────────────────────────\n // Internal HTTP layer\n // ─────────────────────────────────────────────────────────────\n\n /**\n * Execute an HTTP request with retry logic and error normalization.\n *\n * - Attaches `Authorization: Bearer <apiKey>` on every request.\n * - Retries on 429 and 5xx with exponential backoff + jitter.\n * - Parses error bodies and throws typed error classes.\n */\n private async request<T>(\n method: string,\n path: string,\n body?: unknown,\n extraHeaders?: Record<string, string>,\n ): Promise<T> {\n const url = `${this.getBaseUrl()}${API_PREFIX}${path}`;\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.apiKey}`,\n Accept: \"application/json\",\n ...extraHeaders,\n };\n\n if (body !== undefined) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n\n const init: RequestInit = {\n method,\n headers,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n };\n\n let lastError: ResiraApiError | ResiraNetworkError | undefined;\n\n for (let attempt = 0; attempt <= this.maxRetries; attempt++) {\n // ── Wait before retry ────────────────────────────────\n if (attempt > 0) {\n const backoff = this.calculateBackoff(attempt, lastError);\n await sleep(backoff);\n }\n\n // ── Execute the request ──────────────────────────────\n let response: Response;\n try {\n response = await this._fetch(url, init);\n } catch (err) {\n lastError = new ResiraNetworkError(\n `Network request failed: ${err instanceof Error ? err.message : String(err)}`,\n err,\n );\n // Network errors are always retryable\n continue;\n }\n\n // ── Success ──────────────────────────────────────────\n if (response.ok) {\n return (await response.json()) as T;\n }\n\n // ── Parse error body ─────────────────────────────────\n let errorBody: ApiErrorBody;\n try {\n errorBody = (await response.json()) as ApiErrorBody;\n } catch {\n errorBody = { error: response.statusText || `HTTP ${response.status}` };\n }\n\n // ── Rate limit ───────────────────────────────────────\n if (response.status === 429) {\n lastError = new ResiraRateLimitError(errorBody, response);\n // Always retry 429s (up to maxRetries)\n continue;\n }\n\n // ── Server error (5xx) — retryable ───────────────────\n if (response.status >= 500) {\n lastError = new ResiraApiError(\n response.status,\n response.statusText,\n errorBody,\n response,\n );\n continue;\n }\n\n // ── Client error (4xx) — NOT retryable ───────────────\n throw new ResiraApiError(\n response.status,\n response.statusText,\n errorBody,\n response,\n );\n }\n\n // All retries exhausted — throw the last error\n throw lastError!;\n }\n\n /**\n * Calculate backoff delay in milliseconds for a given retry attempt.\n *\n * Uses exponential backoff with full jitter:\n * delay = random(0, base * 2^attempt)\n *\n * For 429 responses, respects the `Retry-After` header as a minimum.\n */\n private calculateBackoff(\n attempt: number,\n lastError?: ResiraApiError | ResiraNetworkError,\n ): number {\n const exponential = this.retryBaseDelay * 2 ** (attempt - 1);\n const jittered = Math.random() * exponential;\n\n // If the server told us to wait, use that as the floor\n if (lastError instanceof ResiraRateLimitError) {\n const serverDelay = lastError.retryAfter * 1000;\n return Math.max(serverDelay, jittered);\n }\n\n return jittered;\n }\n\n}\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -152,7 +152,7 @@ interface Availability {
|
|
|
152
152
|
interface WeightedBaseUrl {
|
|
153
153
|
/** Base URL for an API origin. */
|
|
154
154
|
url: string;
|
|
155
|
-
/**
|
|
155
|
+
/** Legacy weight field. Ignored by the SDK in deterministic routing mode. */
|
|
156
156
|
weight?: number;
|
|
157
157
|
}
|
|
158
158
|
interface ResiraConfig {
|
|
@@ -160,12 +160,12 @@ interface ResiraConfig {
|
|
|
160
160
|
apiKey: string;
|
|
161
161
|
/**
|
|
162
162
|
* Base URL of the API.
|
|
163
|
-
|
|
163
|
+
* Overrides default endpoint resolution when set.
|
|
164
164
|
*/
|
|
165
165
|
baseUrl?: string;
|
|
166
166
|
/**
|
|
167
|
-
|
|
168
|
-
*
|
|
167
|
+
* Ordered fallback API origins.
|
|
168
|
+
* The SDK uses the first valid entry deterministically and does not persist routing state in browser storage.
|
|
169
169
|
*/
|
|
170
170
|
baseUrls?: Array<string | WeightedBaseUrl>;
|
|
171
171
|
/**
|
|
@@ -474,6 +474,8 @@ declare class Resira {
|
|
|
474
474
|
private readonly retryBaseDelay;
|
|
475
475
|
private readonly _fetch;
|
|
476
476
|
constructor(config: ResiraConfig);
|
|
477
|
+
/** The resolved API origin used by this client instance. */
|
|
478
|
+
getBaseUrl(): string;
|
|
477
479
|
/**
|
|
478
480
|
* Fetch the public property configuration.
|
|
479
481
|
*
|
|
@@ -682,9 +684,10 @@ declare class Resira {
|
|
|
682
684
|
* For 429 responses, respects the `Retry-After` header as a minimum.
|
|
683
685
|
*/
|
|
684
686
|
private calculateBackoff;
|
|
685
|
-
private getBaseUrl;
|
|
686
687
|
}
|
|
687
688
|
|
|
689
|
+
declare function getRoutingStats(): Record<string, never>;
|
|
690
|
+
|
|
688
691
|
/**
|
|
689
692
|
* Base error class for all SDK errors.
|
|
690
693
|
*
|
|
@@ -747,4 +750,4 @@ declare class ResiraNetworkError extends ResiraError {
|
|
|
747
750
|
constructor(message: string, cause: unknown);
|
|
748
751
|
}
|
|
749
752
|
|
|
750
|
-
export { type ApiErrorBody, type Availability, type AvailabilityParams, type ConfirmPaymentRequest, type ConfirmPaymentResponse, type CreatePaymentIntentRequest, type CreateReservationRequest, type DateAvailability, type Dish, type DishListResponse, type DishModel, type DishResponse, type DurationPrice, type ListReservationsParams, type PaginatedReservations, type PaymentIntentResponse, type PricingModel, type Product, type ProductListResponse, type PromoCode, type PropertyConfig, type PublicResource, type RefundRule, type Reservation, type ReservationResponse, Resira, ResiraApiError, type ResiraConfig, ResiraError, ResiraNetworkError, ResiraRateLimitError, type ResourceListResponse, type RiderTierPrice, type TimeSlotAvailability, type ValidatePromoCodeResponse, type WeightedBaseUrl };
|
|
753
|
+
export { type ApiErrorBody, type Availability, type AvailabilityParams, type ConfirmPaymentRequest, type ConfirmPaymentResponse, type CreatePaymentIntentRequest, type CreateReservationRequest, type DateAvailability, type Dish, type DishListResponse, type DishModel, type DishResponse, type DurationPrice, type ListReservationsParams, type PaginatedReservations, type PaymentIntentResponse, type PricingModel, type Product, type ProductListResponse, type PromoCode, type PropertyConfig, type PublicResource, type RefundRule, type Reservation, type ReservationResponse, Resira, ResiraApiError, type ResiraConfig, ResiraError, ResiraNetworkError, ResiraRateLimitError, type ResourceListResponse, type RiderTierPrice, type TimeSlotAvailability, type ValidatePromoCodeResponse, type WeightedBaseUrl, getRoutingStats };
|
package/dist/index.d.ts
CHANGED
|
@@ -152,7 +152,7 @@ interface Availability {
|
|
|
152
152
|
interface WeightedBaseUrl {
|
|
153
153
|
/** Base URL for an API origin. */
|
|
154
154
|
url: string;
|
|
155
|
-
/**
|
|
155
|
+
/** Legacy weight field. Ignored by the SDK in deterministic routing mode. */
|
|
156
156
|
weight?: number;
|
|
157
157
|
}
|
|
158
158
|
interface ResiraConfig {
|
|
@@ -160,12 +160,12 @@ interface ResiraConfig {
|
|
|
160
160
|
apiKey: string;
|
|
161
161
|
/**
|
|
162
162
|
* Base URL of the API.
|
|
163
|
-
|
|
163
|
+
* Overrides default endpoint resolution when set.
|
|
164
164
|
*/
|
|
165
165
|
baseUrl?: string;
|
|
166
166
|
/**
|
|
167
|
-
|
|
168
|
-
*
|
|
167
|
+
* Ordered fallback API origins.
|
|
168
|
+
* The SDK uses the first valid entry deterministically and does not persist routing state in browser storage.
|
|
169
169
|
*/
|
|
170
170
|
baseUrls?: Array<string | WeightedBaseUrl>;
|
|
171
171
|
/**
|
|
@@ -474,6 +474,8 @@ declare class Resira {
|
|
|
474
474
|
private readonly retryBaseDelay;
|
|
475
475
|
private readonly _fetch;
|
|
476
476
|
constructor(config: ResiraConfig);
|
|
477
|
+
/** The resolved API origin used by this client instance. */
|
|
478
|
+
getBaseUrl(): string;
|
|
477
479
|
/**
|
|
478
480
|
* Fetch the public property configuration.
|
|
479
481
|
*
|
|
@@ -682,9 +684,10 @@ declare class Resira {
|
|
|
682
684
|
* For 429 responses, respects the `Retry-After` header as a minimum.
|
|
683
685
|
*/
|
|
684
686
|
private calculateBackoff;
|
|
685
|
-
private getBaseUrl;
|
|
686
687
|
}
|
|
687
688
|
|
|
689
|
+
declare function getRoutingStats(): Record<string, never>;
|
|
690
|
+
|
|
688
691
|
/**
|
|
689
692
|
* Base error class for all SDK errors.
|
|
690
693
|
*
|
|
@@ -747,4 +750,4 @@ declare class ResiraNetworkError extends ResiraError {
|
|
|
747
750
|
constructor(message: string, cause: unknown);
|
|
748
751
|
}
|
|
749
752
|
|
|
750
|
-
export { type ApiErrorBody, type Availability, type AvailabilityParams, type ConfirmPaymentRequest, type ConfirmPaymentResponse, type CreatePaymentIntentRequest, type CreateReservationRequest, type DateAvailability, type Dish, type DishListResponse, type DishModel, type DishResponse, type DurationPrice, type ListReservationsParams, type PaginatedReservations, type PaymentIntentResponse, type PricingModel, type Product, type ProductListResponse, type PromoCode, type PropertyConfig, type PublicResource, type RefundRule, type Reservation, type ReservationResponse, Resira, ResiraApiError, type ResiraConfig, ResiraError, ResiraNetworkError, ResiraRateLimitError, type ResourceListResponse, type RiderTierPrice, type TimeSlotAvailability, type ValidatePromoCodeResponse, type WeightedBaseUrl };
|
|
753
|
+
export { type ApiErrorBody, type Availability, type AvailabilityParams, type ConfirmPaymentRequest, type ConfirmPaymentResponse, type CreatePaymentIntentRequest, type CreateReservationRequest, type DateAvailability, type Dish, type DishListResponse, type DishModel, type DishResponse, type DurationPrice, type ListReservationsParams, type PaginatedReservations, type PaymentIntentResponse, type PricingModel, type Product, type ProductListResponse, type PromoCode, type PropertyConfig, type PublicResource, type RefundRule, type Reservation, type ReservationResponse, Resira, ResiraApiError, type ResiraConfig, ResiraError, ResiraNetworkError, ResiraRateLimitError, type ResourceListResponse, type RiderTierPrice, type TimeSlotAvailability, type ValidatePromoCodeResponse, type WeightedBaseUrl, getRoutingStats };
|
package/dist/index.js
CHANGED
|
@@ -43,130 +43,12 @@ var DEFAULT_LOCAL_BASE_URLS = [
|
|
|
43
43
|
{ url: "http://localhost:3001", weight: 100 }
|
|
44
44
|
];
|
|
45
45
|
var DEFAULT_PRODUCTION_BASE_URLS = [
|
|
46
|
-
{ url: "https://
|
|
47
|
-
{ url: "https://api.resira.app", weight: 50 }
|
|
46
|
+
{ url: "https://api.resira.app", weight: 100 }
|
|
48
47
|
];
|
|
49
|
-
var STICKY_SELECTION_KEY = "resira_sdk_api_origin_sticky_v1";
|
|
50
|
-
var ROUTING_STATS_KEY = "resira_sdk_api_routing_stats_v1";
|
|
51
|
-
var stickySelectionCache = /* @__PURE__ */ new Map();
|
|
52
48
|
function readEnv(name) {
|
|
53
49
|
if (typeof process === "undefined") return void 0;
|
|
54
50
|
return process.env?.[name];
|
|
55
51
|
}
|
|
56
|
-
function isBrowser() {
|
|
57
|
-
return typeof window !== "undefined";
|
|
58
|
-
}
|
|
59
|
-
function getStorage(type) {
|
|
60
|
-
if (!isBrowser()) return null;
|
|
61
|
-
try {
|
|
62
|
-
return type === "session" ? window.sessionStorage : window.localStorage;
|
|
63
|
-
} catch {
|
|
64
|
-
return null;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
function getBaseUrlSignature(baseUrls) {
|
|
68
|
-
return baseUrls.map((entry) => `${entry.url}|${entry.weight}`).join(",");
|
|
69
|
-
}
|
|
70
|
-
function pickWeightedBaseUrl(baseUrls) {
|
|
71
|
-
const totalWeight = baseUrls.reduce((sum, entry) => sum + entry.weight, 0);
|
|
72
|
-
if (totalWeight <= 0) {
|
|
73
|
-
return DEFAULT_PRODUCTION_BASE_URLS[0];
|
|
74
|
-
}
|
|
75
|
-
let cursor = Math.random() * totalWeight;
|
|
76
|
-
for (const entry of baseUrls) {
|
|
77
|
-
cursor -= entry.weight;
|
|
78
|
-
if (cursor < 0) {
|
|
79
|
-
return entry;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return baseUrls[baseUrls.length - 1] ?? DEFAULT_PRODUCTION_BASE_URLS[0];
|
|
83
|
-
}
|
|
84
|
-
function ensureAnalyticsQueue() {
|
|
85
|
-
if (!isBrowser()) return null;
|
|
86
|
-
const analyticsWindow = window;
|
|
87
|
-
if (!analyticsWindow.va) {
|
|
88
|
-
analyticsWindow.va = (...params) => {
|
|
89
|
-
analyticsWindow.vaq = analyticsWindow.vaq || [];
|
|
90
|
-
analyticsWindow.vaq.push(params);
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
return analyticsWindow;
|
|
94
|
-
}
|
|
95
|
-
function readStickySelection(signature) {
|
|
96
|
-
const cached = stickySelectionCache.get(signature);
|
|
97
|
-
if (cached) return cached;
|
|
98
|
-
const storage = getStorage("session");
|
|
99
|
-
const raw = storage?.getItem(STICKY_SELECTION_KEY);
|
|
100
|
-
if (!raw) return null;
|
|
101
|
-
try {
|
|
102
|
-
const parsed = JSON.parse(raw);
|
|
103
|
-
if (!parsed?.signature || !parsed?.url || parsed.signature !== signature) {
|
|
104
|
-
return null;
|
|
105
|
-
}
|
|
106
|
-
stickySelectionCache.set(signature, parsed);
|
|
107
|
-
return parsed;
|
|
108
|
-
} catch {
|
|
109
|
-
return null;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
function persistStickySelection(signature, url) {
|
|
113
|
-
const selection = {
|
|
114
|
-
signature,
|
|
115
|
-
url,
|
|
116
|
-
assignedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
117
|
-
};
|
|
118
|
-
stickySelectionCache.set(signature, selection);
|
|
119
|
-
getStorage("session")?.setItem(STICKY_SELECTION_KEY, JSON.stringify(selection));
|
|
120
|
-
}
|
|
121
|
-
function recordRoutingStats(signature, entry) {
|
|
122
|
-
const storage = getStorage("local");
|
|
123
|
-
if (!storage) return;
|
|
124
|
-
let stats = {};
|
|
125
|
-
try {
|
|
126
|
-
stats = JSON.parse(storage.getItem(ROUTING_STATS_KEY) ?? "{}");
|
|
127
|
-
} catch {
|
|
128
|
-
stats = {};
|
|
129
|
-
}
|
|
130
|
-
const current = stats[signature] ?? {
|
|
131
|
-
lastAssignedAt: "",
|
|
132
|
-
lastOrigin: "",
|
|
133
|
-
selections: 0,
|
|
134
|
-
origins: {}
|
|
135
|
-
};
|
|
136
|
-
current.lastAssignedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
137
|
-
current.lastOrigin = entry.url;
|
|
138
|
-
current.selections += 1;
|
|
139
|
-
current.origins[entry.url] = (current.origins[entry.url] ?? 0) + 1;
|
|
140
|
-
stats[signature] = current;
|
|
141
|
-
storage.setItem(ROUTING_STATS_KEY, JSON.stringify(stats));
|
|
142
|
-
}
|
|
143
|
-
function emitRoutingSelection(entry, signature, totalWeight) {
|
|
144
|
-
if (!isBrowser()) return;
|
|
145
|
-
const detail = {
|
|
146
|
-
source: "sdk",
|
|
147
|
-
strategy: "session-sticky",
|
|
148
|
-
originHost: new URL(entry.url).host,
|
|
149
|
-
originUrl: entry.url,
|
|
150
|
-
originWeight: entry.weight,
|
|
151
|
-
totalWeight,
|
|
152
|
-
signature
|
|
153
|
-
};
|
|
154
|
-
ensureAnalyticsQueue()?.va?.("event", {
|
|
155
|
-
name: "api_origin_selected",
|
|
156
|
-
data: detail
|
|
157
|
-
});
|
|
158
|
-
window.dispatchEvent(
|
|
159
|
-
new CustomEvent("resira:api-origin-selected", {
|
|
160
|
-
detail
|
|
161
|
-
})
|
|
162
|
-
);
|
|
163
|
-
const debugEnabled = /^(1|true)$/i.test(
|
|
164
|
-
readEnv("NEXT_PUBLIC_RESIRA_API_ROUTING_DEBUG") ?? readEnv("NEXT_PUBLIC_API_ROUTING_DEBUG") ?? ""
|
|
165
|
-
);
|
|
166
|
-
if (debugEnabled) {
|
|
167
|
-
console.info("[resira-sdk] sticky origin selected", detail);
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
52
|
function normalizeUrl(url) {
|
|
171
53
|
return url.trim().replace(/\/+$/, "");
|
|
172
54
|
}
|
|
@@ -185,6 +67,35 @@ function toResolvedBaseUrl(value) {
|
|
|
185
67
|
weight: isPositiveNumber(value.weight) ? value.weight : 1
|
|
186
68
|
};
|
|
187
69
|
}
|
|
70
|
+
function parseJsonBaseUrls(value) {
|
|
71
|
+
const parsed = JSON.parse(value);
|
|
72
|
+
if (!Array.isArray(parsed)) return [];
|
|
73
|
+
return parsed.flatMap((entry) => {
|
|
74
|
+
const resolved = typeof entry === "string" ? toResolvedBaseUrl(entry) : entry && typeof entry === "object" ? toResolvedBaseUrl(entry) : null;
|
|
75
|
+
return resolved ? [resolved] : [];
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
function parseDelimitedBaseUrls(value) {
|
|
79
|
+
return value.split(",").flatMap((entry) => {
|
|
80
|
+
const [rawUrl, rawWeight] = entry.split("|");
|
|
81
|
+
const url = normalizeUrl(rawUrl ?? "");
|
|
82
|
+
if (!url) return [];
|
|
83
|
+
const parsedWeight = rawWeight ? Number(rawWeight.trim()) : 1;
|
|
84
|
+
return [{
|
|
85
|
+
url,
|
|
86
|
+
weight: isPositiveNumber(parsedWeight) ? parsedWeight : 1
|
|
87
|
+
}];
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function parseEnvBaseUrls(value) {
|
|
91
|
+
if (!value) return [];
|
|
92
|
+
try {
|
|
93
|
+
const parsed = parseJsonBaseUrls(value);
|
|
94
|
+
if (parsed.length > 0) return parsed;
|
|
95
|
+
} catch {
|
|
96
|
+
}
|
|
97
|
+
return parseDelimitedBaseUrls(value);
|
|
98
|
+
}
|
|
188
99
|
function resolveBaseUrls(config) {
|
|
189
100
|
if (config.baseUrl) {
|
|
190
101
|
return [{ url: normalizeUrl(config.baseUrl), weight: 100 }];
|
|
@@ -196,27 +107,20 @@ function resolveBaseUrls(config) {
|
|
|
196
107
|
});
|
|
197
108
|
if (explicit.length > 0) return explicit;
|
|
198
109
|
}
|
|
110
|
+
const envBaseUrls = parseEnvBaseUrls(readEnv("RESIRA_API_BASE_URLS"));
|
|
111
|
+
if (envBaseUrls.length > 0) {
|
|
112
|
+
return envBaseUrls;
|
|
113
|
+
}
|
|
199
114
|
if (readEnv("NODE_ENV") === "development") {
|
|
200
115
|
return DEFAULT_LOCAL_BASE_URLS;
|
|
201
116
|
}
|
|
202
117
|
return DEFAULT_PRODUCTION_BASE_URLS;
|
|
203
118
|
}
|
|
204
119
|
function pickBaseUrl(baseUrls) {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
return stickyEntry.url;
|
|
210
|
-
}
|
|
211
|
-
const selectedEntry = pickWeightedBaseUrl(baseUrls);
|
|
212
|
-
persistStickySelection(signature, selectedEntry.url);
|
|
213
|
-
recordRoutingStats(signature, selectedEntry);
|
|
214
|
-
emitRoutingSelection(
|
|
215
|
-
selectedEntry,
|
|
216
|
-
signature,
|
|
217
|
-
baseUrls.reduce((sum, entry) => sum + entry.weight, 0)
|
|
218
|
-
);
|
|
219
|
-
return selectedEntry.url;
|
|
120
|
+
return baseUrls[0]?.url ?? DEFAULT_PRODUCTION_BASE_URLS[0].url;
|
|
121
|
+
}
|
|
122
|
+
function getRoutingStats() {
|
|
123
|
+
return {};
|
|
220
124
|
}
|
|
221
125
|
|
|
222
126
|
// src/client.ts
|
|
@@ -279,6 +183,10 @@ var Resira = class {
|
|
|
279
183
|
this.retryBaseDelay = config.retryBaseDelay ?? DEFAULT_RETRY_BASE_DELAY;
|
|
280
184
|
this._fetch = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
281
185
|
}
|
|
186
|
+
/** The resolved API origin used by this client instance. */
|
|
187
|
+
getBaseUrl() {
|
|
188
|
+
return pickBaseUrl(this.baseUrls);
|
|
189
|
+
}
|
|
282
190
|
// ─────────────────────────────────────────────────────────────
|
|
283
191
|
// Public API methods
|
|
284
192
|
// ─────────────────────────────────────────────────────────────
|
|
@@ -753,11 +661,8 @@ var Resira = class {
|
|
|
753
661
|
}
|
|
754
662
|
return jittered;
|
|
755
663
|
}
|
|
756
|
-
getBaseUrl() {
|
|
757
|
-
return pickBaseUrl(this.baseUrls);
|
|
758
|
-
}
|
|
759
664
|
};
|
|
760
665
|
|
|
761
|
-
export { Resira, ResiraApiError, ResiraError, ResiraNetworkError, ResiraRateLimitError };
|
|
666
|
+
export { Resira, ResiraApiError, ResiraError, ResiraNetworkError, ResiraRateLimitError, getRoutingStats };
|
|
762
667
|
//# sourceMappingURL=index.js.map
|
|
763
668
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/errors.ts","../src/routing.ts","../src/client.ts"],"names":["url"],"mappings":"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","file":"index.js","sourcesContent":["import type { ApiErrorBody } from \"./types.js\";\n\n// ═══════════════════════════════════════════════════════════════\n// Base error\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Base error class for all SDK errors.\n *\n * Use `instanceof` checks to distinguish error types:\n *\n * ```ts\n * try { … } catch (e) {\n * if (e instanceof ResiraRateLimitError) { … }\n * if (e instanceof ResiraApiError) { … }\n * if (e instanceof ResiraNetworkError) { … }\n * }\n * ```\n */\nexport class ResiraError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ResiraError\";\n // Fix prototype chain for instanceof to work after transpilation\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n// ═══════════════════════════════════════════════════════════════\n// API error (4xx / 5xx with a parsed body)\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Thrown when the API returns a non-2xx response with a JSON body.\n *\n * ```ts\n * err.status // 400, 404, 409, 422, 500, …\n * err.code // HTTP status text (e.g. \"Not Found\")\n * err.body // Parsed `{ error: \"…\" }` from the server\n * ```\n */\nexport class ResiraApiError extends ResiraError {\n /** HTTP status code. */\n readonly status: number;\n /** HTTP status text (e.g. `\"Bad Request\"`). */\n readonly code: string;\n /** Parsed error body from the API. */\n readonly body: ApiErrorBody;\n /** The full `Response` object (headers are still accessible). */\n readonly response: Response;\n\n constructor(status: number, code: string, body: ApiErrorBody, response: Response) {\n super(body.error || `API error ${status}`);\n this.name = \"ResiraApiError\";\n this.status = status;\n this.code = code;\n this.body = body;\n this.response = response;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n /** Whether this error is retryable (5xx or 429). */\n get retryable(): boolean {\n return this.status === 429 || this.status >= 500;\n }\n}\n\n// ═══════════════════════════════════════════════════════════════\n// Rate limit error (429)\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Thrown when the API returns 429 Too Many Requests.\n *\n * ```ts\n * err.retryAfter // Seconds until the client should retry\n * ```\n */\nexport class ResiraRateLimitError extends ResiraApiError {\n /** Seconds to wait before retrying (from `Retry-After` header). */\n readonly retryAfter: number;\n\n constructor(body: ApiErrorBody, response: Response) {\n super(429, \"Too Many Requests\", body, response);\n this.name = \"ResiraRateLimitError\";\n this.retryAfter =\n body.retryAfter ??\n (Number.parseInt(response.headers.get(\"retry-after\") ?? \"60\", 10) ||\n 60);\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n// ═══════════════════════════════════════════════════════════════\n// Network error (fetch failed, DNS, timeout, etc.)\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Thrown when the request never reached the server.\n *\n * This wraps the underlying `TypeError` or `DOMException` from\n * `fetch()` failures (DNS resolution, TLS, network offline, etc.).\n */\nexport class ResiraNetworkError extends ResiraError {\n /** The original error thrown by `fetch`. */\n readonly cause: unknown;\n\n constructor(message: string, cause: unknown) {\n super(message);\n this.name = \"ResiraNetworkError\";\n this.cause = cause;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","import type { ResiraConfig, WeightedBaseUrl } from \"./types.js\";\n\ninterface ResolvedBaseUrl {\n url: string;\n weight: number;\n}\n\ninterface StickySelection {\n signature: string;\n url: string;\n assignedAt: string;\n}\n\ninterface RoutingStatsEntry {\n lastAssignedAt: string;\n lastOrigin: string;\n selections: number;\n origins: Record<string, number>;\n}\n\ntype AnalyticsWindow = Window & {\n va?: (...args: unknown[]) => void;\n vaq?: unknown[][];\n};\n\nconst DEFAULT_LOCAL_BASE_URLS: ResolvedBaseUrl[] = [\n { url: \"http://localhost:3001\", weight: 100 },\n];\n\nconst DEFAULT_PRODUCTION_BASE_URLS: ResolvedBaseUrl[] = [\n { url: \"https://resira-api-sips-production.up.railway.app\", weight: 50 },\n { url: \"https://api.resira.app\", weight: 50 },\n];\n\nconst STICKY_SELECTION_KEY = \"resira_sdk_api_origin_sticky_v1\";\nconst ROUTING_STATS_KEY = \"resira_sdk_api_routing_stats_v1\";\n\nconst stickySelectionCache = new Map<string, StickySelection>();\n\nfunction readEnv(name: string): string | undefined {\n if (typeof process === \"undefined\") return undefined;\n return process.env?.[name];\n}\n\nfunction isBrowser(): boolean {\n return typeof window !== \"undefined\";\n}\n\nfunction getStorage(type: \"session\" | \"local\"): Storage | null {\n if (!isBrowser()) return null;\n\n try {\n return type === \"session\" ? window.sessionStorage : window.localStorage;\n } catch {\n return null;\n }\n}\n\nfunction getBaseUrlSignature(baseUrls: ResolvedBaseUrl[]): string {\n return baseUrls.map((entry) => `${entry.url}|${entry.weight}`).join(\",\");\n}\n\nfunction pickWeightedBaseUrl(baseUrls: ResolvedBaseUrl[]): ResolvedBaseUrl {\n const totalWeight = baseUrls.reduce((sum, entry) => sum + entry.weight, 0);\n if (totalWeight <= 0) {\n return DEFAULT_PRODUCTION_BASE_URLS[0];\n }\n\n let cursor = Math.random() * totalWeight;\n for (const entry of baseUrls) {\n cursor -= entry.weight;\n if (cursor < 0) {\n return entry;\n }\n }\n\n return baseUrls[baseUrls.length - 1] ?? DEFAULT_PRODUCTION_BASE_URLS[0];\n}\n\nfunction ensureAnalyticsQueue(): AnalyticsWindow | null {\n if (!isBrowser()) return null;\n\n const analyticsWindow = window as AnalyticsWindow;\n if (!analyticsWindow.va) {\n analyticsWindow.va = (...params: unknown[]) => {\n analyticsWindow.vaq = analyticsWindow.vaq || [];\n analyticsWindow.vaq.push(params);\n };\n }\n\n return analyticsWindow;\n}\n\nfunction readStickySelection(signature: string): StickySelection | null {\n const cached = stickySelectionCache.get(signature);\n if (cached) return cached;\n\n const storage = getStorage(\"session\");\n const raw = storage?.getItem(STICKY_SELECTION_KEY);\n if (!raw) return null;\n\n try {\n const parsed = JSON.parse(raw) as StickySelection;\n if (!parsed?.signature || !parsed?.url || parsed.signature !== signature) {\n return null;\n }\n stickySelectionCache.set(signature, parsed);\n return parsed;\n } catch {\n return null;\n }\n}\n\nfunction persistStickySelection(signature: string, url: string): void {\n const selection: StickySelection = {\n signature,\n url,\n assignedAt: new Date().toISOString(),\n };\n\n stickySelectionCache.set(signature, selection);\n getStorage(\"session\")?.setItem(STICKY_SELECTION_KEY, JSON.stringify(selection));\n}\n\nfunction recordRoutingStats(signature: string, entry: ResolvedBaseUrl): void {\n const storage = getStorage(\"local\");\n if (!storage) return;\n\n let stats: Record<string, RoutingStatsEntry> = {};\n try {\n stats = JSON.parse(storage.getItem(ROUTING_STATS_KEY) ?? \"{}\") as Record<string, RoutingStatsEntry>;\n } catch {\n stats = {};\n }\n\n const current = stats[signature] ?? {\n lastAssignedAt: \"\",\n lastOrigin: \"\",\n selections: 0,\n origins: {},\n };\n\n current.lastAssignedAt = new Date().toISOString();\n current.lastOrigin = entry.url;\n current.selections += 1;\n current.origins[entry.url] = (current.origins[entry.url] ?? 0) + 1;\n\n stats[signature] = current;\n storage.setItem(ROUTING_STATS_KEY, JSON.stringify(stats));\n}\n\nfunction emitRoutingSelection(entry: ResolvedBaseUrl, signature: string, totalWeight: number): void {\n if (!isBrowser()) return;\n\n const detail = {\n source: \"sdk\",\n strategy: \"session-sticky\",\n originHost: new URL(entry.url).host,\n originUrl: entry.url,\n originWeight: entry.weight,\n totalWeight,\n signature,\n };\n\n ensureAnalyticsQueue()?.va?.(\"event\", {\n name: \"api_origin_selected\",\n data: detail,\n });\n\n window.dispatchEvent(\n new CustomEvent(\"resira:api-origin-selected\", {\n detail,\n }),\n );\n\n const debugEnabled = /^(1|true)$/i.test(\n readEnv(\"NEXT_PUBLIC_RESIRA_API_ROUTING_DEBUG\") ?? readEnv(\"NEXT_PUBLIC_API_ROUTING_DEBUG\") ?? \"\",\n );\n if (debugEnabled) {\n console.info(\"[resira-sdk] sticky origin selected\", detail);\n }\n}\n\nfunction normalizeUrl(url: string): string {\n return url.trim().replace(/\\/+$/, \"\");\n}\n\nfunction isPositiveNumber(value: unknown): value is number {\n return typeof value === \"number\" && Number.isFinite(value) && value > 0;\n}\n\nfunction toResolvedBaseUrl(value: string | WeightedBaseUrl): ResolvedBaseUrl | null {\n if (typeof value === \"string\") {\n const url = normalizeUrl(value);\n return url ? { url, weight: 1 } : null;\n }\n\n const url = normalizeUrl(value.url);\n if (!url) return null;\n\n return {\n url,\n weight: isPositiveNumber(value.weight) ? value.weight : 1,\n };\n}\n\nfunction parseJsonBaseUrls(value: string): ResolvedBaseUrl[] {\n const parsed = JSON.parse(value) as unknown;\n if (!Array.isArray(parsed)) return [];\n\n return parsed.flatMap((entry) => {\n const resolved =\n typeof entry === \"string\"\n ? toResolvedBaseUrl(entry)\n : entry && typeof entry === \"object\"\n ? toResolvedBaseUrl(entry as WeightedBaseUrl)\n : null;\n\n return resolved ? [resolved] : [];\n });\n}\n\nfunction parseDelimitedBaseUrls(value: string): ResolvedBaseUrl[] {\n return value\n .split(\",\")\n .flatMap((entry) => {\n const [rawUrl, rawWeight] = entry.split(\"|\");\n const url = normalizeUrl(rawUrl ?? \"\");\n if (!url) return [];\n\n const parsedWeight = rawWeight ? Number(rawWeight.trim()) : 1;\n return [{\n url,\n weight: isPositiveNumber(parsedWeight) ? parsedWeight : 1,\n }];\n });\n}\n\nfunction parseEnvBaseUrls(value: string | undefined): ResolvedBaseUrl[] {\n if (!value) return [];\n\n try {\n const parsed = parseJsonBaseUrls(value);\n if (parsed.length > 0) return parsed;\n } catch {\n // Fall back to delimiter parsing.\n }\n\n return parseDelimitedBaseUrls(value);\n}\n\nexport function resolveBaseUrls(config: ResiraConfig): ResolvedBaseUrl[] {\n if (config.baseUrl) {\n return [{ url: normalizeUrl(config.baseUrl), weight: 100 }];\n }\n\n if (config.baseUrls?.length) {\n const explicit = config.baseUrls.flatMap((entry) => {\n const resolved = toResolvedBaseUrl(entry);\n return resolved ? [resolved] : [];\n });\n\n if (explicit.length > 0) return explicit;\n }\n\n // Intentionally ignore ambient host-app env vars here.\n // Widgets using the SDK should not inherit a parent app's\n // NEXT_PUBLIC_API_URL / endpoint overrides by accident.\n // Only explicit SDK config (baseUrl/baseUrls) may override\n // the built-in routing defaults.\n\n if (readEnv(\"NODE_ENV\") === \"development\") {\n return DEFAULT_LOCAL_BASE_URLS;\n }\n\n return DEFAULT_PRODUCTION_BASE_URLS;\n}\n\nexport function pickBaseUrl(baseUrls: ResolvedBaseUrl[]): string {\n const signature = getBaseUrlSignature(baseUrls);\n const stickySelection = readStickySelection(signature);\n const stickyEntry = stickySelection\n ? baseUrls.find((entry) => entry.url === stickySelection.url)\n : null;\n if (stickyEntry) {\n return stickyEntry.url;\n }\n\n const selectedEntry = pickWeightedBaseUrl(baseUrls);\n persistStickySelection(signature, selectedEntry.url);\n recordRoutingStats(signature, selectedEntry);\n emitRoutingSelection(\n selectedEntry,\n signature,\n baseUrls.reduce((sum, entry) => sum + entry.weight, 0),\n );\n\n return selectedEntry.url;\n}\n\nexport function getRoutingStats(): Record<string, RoutingStatsEntry> {\n const storage = getStorage(\"local\");\n if (!storage) return {};\n\n try {\n return JSON.parse(storage.getItem(ROUTING_STATS_KEY) ?? \"{}\") as Record<string, RoutingStatsEntry>;\n } catch {\n return {};\n }\n}","import { ResiraApiError, ResiraNetworkError, ResiraRateLimitError } from \"./errors.js\";\nimport { pickBaseUrl, resolveBaseUrls } from \"./routing.js\";\nimport type {\n ApiErrorBody,\n Availability,\n AvailabilityParams,\n ConfirmPaymentRequest,\n ConfirmPaymentResponse,\n CreatePaymentIntentRequest,\n CreateReservationRequest,\n Dish,\n DishListResponse,\n DishResponse,\n ListReservationsParams,\n PaginatedReservations,\n PaymentIntentResponse,\n ProductListResponse,\n PropertyConfig,\n Reservation,\n ReservationResponse,\n ResiraConfig,\n ResourceListResponse,\n ValidatePromoCodeResponse,\n} from \"./types.js\";\n\n// ═══════════════════════════════════════════════════════════════\n// Constants\n// ═══════════════════════════════════════════════════════════════\n\nconst DEFAULT_MAX_RETRIES = 3;\nconst DEFAULT_RETRY_BASE_DELAY = 500; // ms\nconst API_PREFIX = \"/v1/public\";\n\n// ═══════════════════════════════════════════════════════════════\n// Helpers\n// ═══════════════════════════════════════════════════════════════\n\n/** Generate a random UUID v4 for idempotency keys. */\nfunction uuid(): string {\n // Use crypto.randomUUID when available (Node 19+, all modern browsers)\n if (typeof globalThis.crypto?.randomUUID === \"function\") {\n return globalThis.crypto.randomUUID();\n }\n // Fallback: manual v4 UUID\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n return (c === \"x\" ? r : (r & 0x3) | 0x8).toString(16);\n });\n}\n\n/** Build a query string from a flat params object. Skips `undefined` values. */\nfunction toQueryString(params: Record<string, string | number | boolean | undefined>): string {\n const parts: string[] = [];\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined && value !== null) {\n parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);\n }\n }\n return parts.length > 0 ? `?${parts.join(\"&\")}` : \"\";\n}\n\n/** Sleep for `ms` milliseconds. */\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Convert camelCase keys to snake_case for query parameters.\n * The API expects snake_case query params (`start_date`, `party_size`).\n */\nfunction camelToSnake(str: string): string {\n return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);\n}\n\n/** Convert an object's keys from camelCase to snake_case. */\nfunction keysToSnake(\n obj: Record<string, string | number | boolean | undefined>,\n): Record<string, string | number | boolean | undefined> {\n const result: Record<string, string | number | boolean | undefined> = {};\n for (const [key, value] of Object.entries(obj)) {\n result[camelToSnake(key)] = value;\n }\n return result;\n}\n\n/** Convert snake_case string to camelCase. */\nfunction snakeToCamel(str: string): string {\n return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());\n}\n\n/** Deep-convert all keys in an object/array from camelCase to snake_case. */\nfunction deepKeysToSnake(obj: unknown): unknown {\n if (Array.isArray(obj)) return obj.map(deepKeysToSnake);\n if (obj !== null && typeof obj === \"object\") {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n result[camelToSnake(key)] = deepKeysToSnake(value);\n }\n return result;\n }\n return obj;\n}\n\n/** Deep-convert all keys in an object/array from snake_case to camelCase. */\nfunction deepKeysToCamel(obj: unknown): unknown {\n if (Array.isArray(obj)) return obj.map(deepKeysToCamel);\n if (obj !== null && typeof obj === \"object\") {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n result[snakeToCamel(key)] = deepKeysToCamel(value);\n }\n return result;\n }\n return obj;\n}\n\n// ═══════════════════════════════════════════════════════════════\n// Client\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Resira SDK client.\n *\n * ```ts\n * const resira = new Resira({\n * apiKey: \"resira_live_abc123…\",\n * baseUrl: \"https://api.example.com\", // optional\n * });\n *\n * const availability = await resira.getAvailability(\"prop-1\", {\n * startDate: \"2026-07-01\",\n * endDate: \"2026-07-07\",\n * });\n * ```\n */\nexport class Resira {\n private readonly apiKey: string;\n private readonly baseUrls: ReturnType<typeof resolveBaseUrls>;\n private readonly maxRetries: number;\n private readonly retryBaseDelay: number;\n private readonly _fetch: typeof globalThis.fetch;\n\n constructor(config: ResiraConfig) {\n if (!config.apiKey) {\n throw new Error(\"Resira: apiKey is required\");\n }\n this.apiKey = config.apiKey;\n this.baseUrls = resolveBaseUrls(config);\n this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;\n this.retryBaseDelay = config.retryBaseDelay ?? DEFAULT_RETRY_BASE_DELAY;\n this._fetch = config.fetch ?? globalThis.fetch.bind(globalThis);\n }\n\n // ─────────────────────────────────────────────────────────────\n // Public API methods\n // ─────────────────────────────────────────────────────────────\n\n /**\n * Fetch the public property configuration.\n *\n * Returns non-sensitive settings such as the Stripe publishable\n * key, deposit percentage, currency, and branding info.\n *\n * ```ts\n * const config = await resira.getConfig();\n * console.log(config.stripePublishableKey); // \"pk_test_…\"\n * ```\n */\n async getConfig(): Promise<PropertyConfig> {\n return this.request<PropertyConfig>(\"GET\", \"/config\");\n }\n\n /**\n * Validate a promo/discount code.\n *\n * Returns discount details if valid, or an error message if not.\n *\n * ```ts\n * const result = await resira.validatePromoCode(\"SUMMER20\");\n * if (result.valid) {\n * console.log(result.discountType, result.discountValue);\n * }\n * ```\n */\n async validatePromoCode(code: string): Promise<ValidatePromoCodeResponse> {\n try {\n const raw = await this.request<Record<string, unknown>>(\n \"POST\",\n \"/promo-codes/validate\",\n { code },\n );\n const data = deepKeysToCamel(raw) as Record<string, unknown>;\n return {\n valid: true,\n discountType: data.discountType as \"percent\" | \"fixed\" | undefined,\n discountValue: data.discountValue as number | undefined,\n currency: data.currency as string | undefined,\n minOrderCents: data.minOrderCents as number | undefined,\n };\n } catch (err: unknown) {\n // 4xx errors mean invalid code\n if (\n err &&\n typeof err === \"object\" &&\n \"status\" in err &&\n (err as { status: number }).status < 500\n ) {\n const apiErr = err as { body?: { error?: string } };\n return {\n valid: false,\n error: apiErr.body?.error ?? \"Invalid promo code\",\n };\n }\n throw err;\n }\n }\n\n /**\n * List all active resources for the organization.\n *\n * Returns resource cards with name, description, price, duration,\n * image, and type — suitable for building a resource picker.\n *\n * ```ts\n * const { resources } = await resira.listResources();\n * ```\n */\n async listResources(): Promise<ResourceListResponse> {\n return this.request<ResourceListResponse>(\"GET\", \"/resources\");\n }\n\n /**\n * Query availability for a resource.\n *\n * **Properties** (date-based):\n * ```ts\n * await resira.getAvailability(\"prop-1\", {\n * startDate: \"2026-07-01\",\n * endDate: \"2026-07-07\",\n * });\n * ```\n *\n * **Restaurants** (time-slot):\n * ```ts\n * await resira.getAvailability(\"table-5\", {\n * date: \"2026-07-01\",\n * partySize: 4,\n * });\n * ```\n *\n * Omit params to get all blocked dates (for calendar rendering).\n */\n async getAvailability(\n resourceId: string,\n params: AvailabilityParams = {},\n ): Promise<Availability> {\n if (!resourceId) {\n throw new Error(\"resourceId is required for getAvailability\");\n }\n const qs = toQueryString(keysToSnake(params as Record<string, string | number | undefined>));\n return this.request<Availability>(\n \"GET\",\n `/resources/${encodeURIComponent(resourceId)}/availability${qs}`,\n );\n }\n\n /**\n * Query availability for a product/service.\n *\n * Aggregates capacity across all linked equipment. If a product\n * has 2 jet skis (each capacity=1), slots show capacity: 2.\n * Both pending and confirmed reservations count as occupied.\n *\n * ```ts\n * await resira.getProductAvailability(\"prod-123\", {\n * date: \"2026-07-01\",\n * durationMinutes: 30,\n * });\n * ```\n */\n async getProductAvailability(\n productId: string,\n params: AvailabilityParams = {},\n ): Promise<Availability> {\n if (!productId) {\n throw new Error(\"productId is required for getProductAvailability\");\n }\n const qs = toQueryString(keysToSnake(params as Record<string, string | number | undefined>));\n return this.request<Availability>(\n \"GET\",\n `/products/${encodeURIComponent(productId)}/availability${qs}`,\n );\n }\n\n /**\n * Create a reservation.\n *\n * Uses `POST /v2/api/reservations` — the `resourceId` is sent in\n * the request body, **not** in the URL path.\n *\n * An `Idempotency-Key` header is automatically attached so that\n * retries (including those from exponential backoff) are safe.\n *\n * ```ts\n * const { reservation } = await resira.createReservation({\n * resourceId: \"prop-1\",\n * guestName: \"Jane Doe\",\n * guestEmail: \"jane@example.com\",\n * startDate: \"2026-07-01\",\n * endDate: \"2026-07-07\",\n * partySize: 3,\n * });\n * ```\n */\n async createReservation(\n payload: CreateReservationRequest,\n options?: { idempotencyKey?: string },\n ): Promise<ReservationResponse> {\n if (!payload.resourceId) {\n throw new Error(\"resourceId is required for createReservation\");\n }\n const idempotencyKey = options?.idempotencyKey ?? uuid();\n\n // Note: uses /v2/api prefix, not /v1/public\n const url = `${this.getBaseUrl()}/v2/api/reservations`;\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.apiKey}`,\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n \"Idempotency-Key\": idempotencyKey,\n };\n\n const init: RequestInit = {\n method: \"POST\",\n headers,\n body: JSON.stringify(payload),\n };\n\n let lastError: ResiraApiError | ResiraNetworkError | ResiraRateLimitError | undefined;\n\n for (let attempt = 0; attempt <= this.maxRetries; attempt++) {\n if (attempt > 0) {\n const backoff = this.calculateBackoff(attempt, lastError);\n await sleep(backoff);\n }\n\n let response: Response;\n try {\n response = await this._fetch(url, init);\n } catch (err) {\n lastError = new ResiraNetworkError(\n `Network request failed: ${err instanceof Error ? err.message : String(err)}`,\n err,\n );\n continue;\n }\n\n if (response.ok) {\n const raw = (await response.json()) as Record<string, unknown>;\n // The API may nest under \"reservation\" or \"booking\" depending on endpoint\n const resData = (raw.reservation ?? raw.booking ?? {}) as Record<string, unknown>;\n\n // Augment with request data: API response doesn't include\n // startTime/endTime/startDate/endDate/resourceId for v2 reservations\n const reservation: Reservation = {\n id: (resData.id as string) ?? \"\",\n resourceId: (resData.resourceId as string) ?? payload.resourceId,\n status: (resData.status as string) ?? \"pending\",\n startDate: (resData.startDate as string) ?? payload.startDate,\n endDate: (resData.endDate as string) ?? payload.endDate,\n startTime: (resData.startTime as string) ?? payload.startTime,\n endTime: (resData.endTime as string) ?? payload.endTime,\n startDatetime: (resData.startDatetime as string) ?? payload.startTime,\n endDatetime: (resData.endDatetime as string) ?? payload.endTime,\n guestName: (resData.guestName as string) ?? payload.guestName,\n partySize: (resData.partySize as number) ?? (resData.guests as number) ?? payload.partySize ?? 2,\n totalPrice: (resData.totalPrice as number) ?? 0,\n currency: (resData.currency as string) ?? \"EUR\",\n createdAt: (resData.createdAt as string) ?? new Date().toISOString(),\n };\n\n return { reservation };\n }\n\n let errorBody: ApiErrorBody;\n try {\n errorBody = (await response.json()) as ApiErrorBody;\n } catch {\n errorBody = { error: response.statusText || `HTTP ${response.status}` };\n }\n\n if (response.status === 429) {\n lastError = new ResiraRateLimitError(errorBody, response);\n continue;\n }\n\n if (response.status >= 500) {\n lastError = new ResiraApiError(response.status, response.statusText, errorBody, response);\n continue;\n }\n\n throw new ResiraApiError(response.status, response.statusText, errorBody, response);\n }\n\n throw lastError!;\n }\n\n /**\n * List reservations for a resource (paginated).\n *\n * ```ts\n * const page = await resira.listReservations(\"prop-1\", {\n * status: \"confirmed\",\n * page: 1,\n * limit: 50,\n * });\n * console.log(page.data); // Reservation[]\n * console.log(page.totalPages); // number\n * ```\n */\n async listReservations(\n resourceId: string,\n params: ListReservationsParams = {},\n ): Promise<PaginatedReservations> {\n const qs = toQueryString(params as Record<string, string | number | undefined>);\n return this.request<PaginatedReservations>(\n \"GET\",\n `/resources/${encodeURIComponent(resourceId)}/reservations${qs}`,\n );\n }\n\n /**\n * Get a single reservation by ID.\n *\n * ```ts\n * const { reservation } = await resira.getReservation(\"prop-1\", \"res-uuid\");\n * ```\n */\n async getReservation(\n resourceId: string,\n reservationId: string,\n ): Promise<ReservationResponse> {\n return this.request<ReservationResponse>(\n \"GET\",\n `/resources/${encodeURIComponent(resourceId)}/reservations/${encodeURIComponent(reservationId)}`,\n );\n }\n\n /**\n * List all active products/services for the organisation.\n *\n * Calls `GET /v1/public/products` which returns products with\n * pricing, duration, and linked equipment.\n *\n * Falls back to mapping resources → products if the endpoint\n * returns 404 (backend hasn't been updated yet).\n *\n * ```ts\n * const { products } = await resira.listProducts();\n * ```\n */\n async listProducts(): Promise<ProductListResponse> {\n try {\n return await this.request<ProductListResponse>(\"GET\", \"/products\");\n } catch (err: unknown) {\n // Fallback: if /products endpoint doesn't exist yet (404),\n // map resources → products shape\n if (\n err &&\n typeof err === \"object\" &&\n \"status\" in err &&\n (err as { status: number }).status === 404\n ) {\n const resourceResponse = await this.listResources();\n const products = resourceResponse.resources.map((r) => ({\n id: r.id,\n name: r.name,\n description: r.description,\n durationMinutes: r.durationMinutes ?? 60,\n priceCents: r.pricePerSession ?? 0,\n currency: r.currency ?? \"EUR\",\n imageUrl: r.imageUrl,\n active: r.active,\n sortOrder: 0,\n pricingModel: \"per_session\" as const,\n equipmentIds: [r.id],\n equipmentNames: [r.name],\n }));\n return { products, count: products.length };\n }\n throw err;\n }\n }\n\n /**\n * List all dishes with 3D model data for the organisation.\n *\n * Returns dishes with name, description, price, image, and\n * 3D model URLs for AR/preview rendering.\n *\n * ```ts\n * const { dishes } = await resira.listDishes();\n * ```\n */\n async listDishes(): Promise<DishListResponse> {\n return this.request<DishListResponse>(\"GET\", \"/dishes\");\n }\n\n /**\n * Get a single dish by ID.\n *\n * ```ts\n * const { dish } = await resira.getDish(\"dish-uuid\");\n * console.log(dish.name, dish.model?.glbUrl);\n * ```\n */\n async getDish(dishId: string): Promise<DishResponse> {\n if (!dishId) {\n throw new Error(\"dishId is required for getDish\");\n }\n return this.request<DishResponse>(\n \"GET\",\n `/dishes/${encodeURIComponent(dishId)}`,\n );\n }\n\n /**\n * Create a Stripe payment intent for a booking.\n *\n * The server creates the payment intent, calculates the amount,\n * and returns a `clientSecret` to confirm payment on the frontend\n * using Stripe Elements.\n *\n * ```ts\n * const { clientSecret, amountNow, amountAtVenue } =\n * await resira.createPaymentIntent({\n * productId: \"prod-123\",\n * resourceId: \"res-456\",\n * partySize: 2,\n * startDate: \"2026-07-01\",\n * startTime: \"2026-07-01T10:00:00Z\",\n * endTime: \"2026-07-01T11:00:00Z\",\n * guestName: \"Jane Doe\",\n * guestEmail: \"jane@example.com\",\n * });\n * ```\n */\n async createPaymentIntent(\n payload: CreatePaymentIntentRequest,\n options?: { idempotencyKey?: string },\n ): Promise<PaymentIntentResponse> {\n const idempotencyKey = options?.idempotencyKey ?? uuid();\n const raw = await this.request<Record<string, unknown>>(\n \"POST\",\n \"/payments/create-intent\",\n payload,\n { \"Idempotency-Key\": idempotencyKey },\n );\n // Normalize response keys to camelCase\n const data = deepKeysToCamel(raw) as Record<string, unknown>;\n // Map backend field names to SDK field names:\n // backend: amountDue, totalPrice\n // SDK: amountNow, totalAmount, amountAtVenue\n const totalAmount = (data.totalAmount as number) ?? (data.totalPrice as number) ?? 0;\n const amountNow = (data.amountNow as number) ?? (data.amountDue as number) ?? totalAmount;\n const amountAtVenue = (data.amountAtVenue as number) ?? (totalAmount - amountNow);\n return {\n clientSecret: data.clientSecret as string,\n paymentIntentId: data.paymentIntentId as string,\n amountNow,\n amountAtVenue,\n totalAmount,\n currency: (data.currency as string) ?? \"EUR\",\n paymentOption: data.paymentOption as string | undefined,\n reservationId: data.reservationId as string | undefined,\n discountAmount: (data.discountAmount as number) ?? undefined,\n originalPrice: (data.originalPrice as number) ?? undefined,\n promoCodeApplied: (data.promoCodeApplied as string) ?? undefined,\n };\n }\n\n /**\n * Confirm that a payment was successful after the guest\n * completes the Stripe payment flow.\n *\n * This transitions the reservation from `pending` to `confirmed`.\n *\n * ```ts\n * const { reservation, paymentStatus } =\n * await resira.confirmPayment({\n * paymentIntentId: \"pi_xxx\",\n * reservationId: \"res-uuid\",\n * });\n * ```\n */\n async confirmPayment(\n payload: ConfirmPaymentRequest,\n ): Promise<ConfirmPaymentResponse> {\n const raw = await this.request<Record<string, unknown>>(\n \"POST\",\n \"/payments/confirm\",\n payload,\n );\n // Normalize response keys to camelCase in case backend returns snake_case\n return deepKeysToCamel(raw) as ConfirmPaymentResponse;\n }\n\n // ─────────────────────────────────────────────────────────────\n // Internal HTTP layer\n // ─────────────────────────────────────────────────────────────\n\n /**\n * Execute an HTTP request with retry logic and error normalization.\n *\n * - Attaches `Authorization: Bearer <apiKey>` on every request.\n * - Retries on 429 and 5xx with exponential backoff + jitter.\n * - Parses error bodies and throws typed error classes.\n */\n private async request<T>(\n method: string,\n path: string,\n body?: unknown,\n extraHeaders?: Record<string, string>,\n ): Promise<T> {\n const url = `${this.getBaseUrl()}${API_PREFIX}${path}`;\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.apiKey}`,\n Accept: \"application/json\",\n ...extraHeaders,\n };\n\n if (body !== undefined) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n\n const init: RequestInit = {\n method,\n headers,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n };\n\n let lastError: ResiraApiError | ResiraNetworkError | undefined;\n\n for (let attempt = 0; attempt <= this.maxRetries; attempt++) {\n // ── Wait before retry ────────────────────────────────\n if (attempt > 0) {\n const backoff = this.calculateBackoff(attempt, lastError);\n await sleep(backoff);\n }\n\n // ── Execute the request ──────────────────────────────\n let response: Response;\n try {\n response = await this._fetch(url, init);\n } catch (err) {\n lastError = new ResiraNetworkError(\n `Network request failed: ${err instanceof Error ? err.message : String(err)}`,\n err,\n );\n // Network errors are always retryable\n continue;\n }\n\n // ── Success ──────────────────────────────────────────\n if (response.ok) {\n return (await response.json()) as T;\n }\n\n // ── Parse error body ─────────────────────────────────\n let errorBody: ApiErrorBody;\n try {\n errorBody = (await response.json()) as ApiErrorBody;\n } catch {\n errorBody = { error: response.statusText || `HTTP ${response.status}` };\n }\n\n // ── Rate limit ───────────────────────────────────────\n if (response.status === 429) {\n lastError = new ResiraRateLimitError(errorBody, response);\n // Always retry 429s (up to maxRetries)\n continue;\n }\n\n // ── Server error (5xx) — retryable ───────────────────\n if (response.status >= 500) {\n lastError = new ResiraApiError(\n response.status,\n response.statusText,\n errorBody,\n response,\n );\n continue;\n }\n\n // ── Client error (4xx) — NOT retryable ───────────────\n throw new ResiraApiError(\n response.status,\n response.statusText,\n errorBody,\n response,\n );\n }\n\n // All retries exhausted — throw the last error\n throw lastError!;\n }\n\n /**\n * Calculate backoff delay in milliseconds for a given retry attempt.\n *\n * Uses exponential backoff with full jitter:\n * delay = random(0, base * 2^attempt)\n *\n * For 429 responses, respects the `Retry-After` header as a minimum.\n */\n private calculateBackoff(\n attempt: number,\n lastError?: ResiraApiError | ResiraNetworkError,\n ): number {\n const exponential = this.retryBaseDelay * 2 ** (attempt - 1);\n const jittered = Math.random() * exponential;\n\n // If the server told us to wait, use that as the floor\n if (lastError instanceof ResiraRateLimitError) {\n const serverDelay = lastError.retryAfter * 1000;\n return Math.max(serverDelay, jittered);\n }\n\n return jittered;\n }\n\n private getBaseUrl(): string {\n return pickBaseUrl(this.baseUrls);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/routing.ts","../src/client.ts"],"names":["url"],"mappings":"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","file":"index.js","sourcesContent":["import type { ApiErrorBody } from \"./types.js\";\n\n// ═══════════════════════════════════════════════════════════════\n// Base error\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Base error class for all SDK errors.\n *\n * Use `instanceof` checks to distinguish error types:\n *\n * ```ts\n * try { … } catch (e) {\n * if (e instanceof ResiraRateLimitError) { … }\n * if (e instanceof ResiraApiError) { … }\n * if (e instanceof ResiraNetworkError) { … }\n * }\n * ```\n */\nexport class ResiraError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"ResiraError\";\n // Fix prototype chain for instanceof to work after transpilation\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n// ═══════════════════════════════════════════════════════════════\n// API error (4xx / 5xx with a parsed body)\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Thrown when the API returns a non-2xx response with a JSON body.\n *\n * ```ts\n * err.status // 400, 404, 409, 422, 500, …\n * err.code // HTTP status text (e.g. \"Not Found\")\n * err.body // Parsed `{ error: \"…\" }` from the server\n * ```\n */\nexport class ResiraApiError extends ResiraError {\n /** HTTP status code. */\n readonly status: number;\n /** HTTP status text (e.g. `\"Bad Request\"`). */\n readonly code: string;\n /** Parsed error body from the API. */\n readonly body: ApiErrorBody;\n /** The full `Response` object (headers are still accessible). */\n readonly response: Response;\n\n constructor(status: number, code: string, body: ApiErrorBody, response: Response) {\n super(body.error || `API error ${status}`);\n this.name = \"ResiraApiError\";\n this.status = status;\n this.code = code;\n this.body = body;\n this.response = response;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n /** Whether this error is retryable (5xx or 429). */\n get retryable(): boolean {\n return this.status === 429 || this.status >= 500;\n }\n}\n\n// ═══════════════════════════════════════════════════════════════\n// Rate limit error (429)\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Thrown when the API returns 429 Too Many Requests.\n *\n * ```ts\n * err.retryAfter // Seconds until the client should retry\n * ```\n */\nexport class ResiraRateLimitError extends ResiraApiError {\n /** Seconds to wait before retrying (from `Retry-After` header). */\n readonly retryAfter: number;\n\n constructor(body: ApiErrorBody, response: Response) {\n super(429, \"Too Many Requests\", body, response);\n this.name = \"ResiraRateLimitError\";\n this.retryAfter =\n body.retryAfter ??\n (Number.parseInt(response.headers.get(\"retry-after\") ?? \"60\", 10) ||\n 60);\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n// ═══════════════════════════════════════════════════════════════\n// Network error (fetch failed, DNS, timeout, etc.)\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Thrown when the request never reached the server.\n *\n * This wraps the underlying `TypeError` or `DOMException` from\n * `fetch()` failures (DNS resolution, TLS, network offline, etc.).\n */\nexport class ResiraNetworkError extends ResiraError {\n /** The original error thrown by `fetch`. */\n readonly cause: unknown;\n\n constructor(message: string, cause: unknown) {\n super(message);\n this.name = \"ResiraNetworkError\";\n this.cause = cause;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n","import type { ResiraConfig, WeightedBaseUrl } from \"./types.js\";\n\ninterface ResolvedBaseUrl {\n url: string;\n weight: number;\n}\n\nconst DEFAULT_LOCAL_BASE_URLS: ResolvedBaseUrl[] = [\n { url: \"http://localhost:3001\", weight: 100 },\n];\n\nconst DEFAULT_PRODUCTION_BASE_URLS: ResolvedBaseUrl[] = [\n { url: \"https://api.resira.app\", weight: 100 },\n];\n\nfunction readEnv(name: string): string | undefined {\n if (typeof process === \"undefined\") return undefined;\n return process.env?.[name];\n}\n\n\nfunction normalizeUrl(url: string): string {\n return url.trim().replace(/\\/+$/, \"\");\n}\n\nfunction isPositiveNumber(value: unknown): value is number {\n return typeof value === \"number\" && Number.isFinite(value) && value > 0;\n}\n\nfunction toResolvedBaseUrl(value: string | WeightedBaseUrl): ResolvedBaseUrl | null {\n if (typeof value === \"string\") {\n const url = normalizeUrl(value);\n return url ? { url, weight: 1 } : null;\n }\n\n const url = normalizeUrl(value.url);\n if (!url) return null;\n\n return {\n url,\n weight: isPositiveNumber(value.weight) ? value.weight : 1,\n };\n}\n\nfunction parseJsonBaseUrls(value: string): ResolvedBaseUrl[] {\n const parsed = JSON.parse(value) as unknown;\n if (!Array.isArray(parsed)) return [];\n\n return parsed.flatMap((entry) => {\n const resolved =\n typeof entry === \"string\"\n ? toResolvedBaseUrl(entry)\n : entry && typeof entry === \"object\"\n ? toResolvedBaseUrl(entry as WeightedBaseUrl)\n : null;\n\n return resolved ? [resolved] : [];\n });\n}\n\nfunction parseDelimitedBaseUrls(value: string): ResolvedBaseUrl[] {\n return value\n .split(\",\")\n .flatMap((entry) => {\n const [rawUrl, rawWeight] = entry.split(\"|\");\n const url = normalizeUrl(rawUrl ?? \"\");\n if (!url) return [];\n\n const parsedWeight = rawWeight ? Number(rawWeight.trim()) : 1;\n return [{\n url,\n weight: isPositiveNumber(parsedWeight) ? parsedWeight : 1,\n }];\n });\n}\n\nfunction parseEnvBaseUrls(value: string | undefined): ResolvedBaseUrl[] {\n if (!value) return [];\n\n try {\n const parsed = parseJsonBaseUrls(value);\n if (parsed.length > 0) return parsed;\n } catch {\n // Fall back to delimiter parsing.\n }\n\n return parseDelimitedBaseUrls(value);\n}\n\nexport function resolveBaseUrls(config: ResiraConfig): ResolvedBaseUrl[] {\n if (config.baseUrl) {\n return [{ url: normalizeUrl(config.baseUrl), weight: 100 }];\n }\n\n if (config.baseUrls?.length) {\n const explicit = config.baseUrls.flatMap((entry) => {\n const resolved = toResolvedBaseUrl(entry);\n return resolved ? [resolved] : [];\n });\n\n if (explicit.length > 0) return explicit;\n }\n\n const envBaseUrls = parseEnvBaseUrls(readEnv(\"RESIRA_API_BASE_URLS\"));\n if (envBaseUrls.length > 0) {\n return envBaseUrls;\n }\n\n if (readEnv(\"NODE_ENV\") === \"development\") {\n return DEFAULT_LOCAL_BASE_URLS;\n }\n\n return DEFAULT_PRODUCTION_BASE_URLS;\n}\n\nexport function pickBaseUrl(baseUrls: ResolvedBaseUrl[]): string {\n return baseUrls[0]?.url ?? DEFAULT_PRODUCTION_BASE_URLS[0].url;\n}\n\nexport function getRoutingStats(): Record<string, never> {\n return {};\n}","import { ResiraApiError, ResiraNetworkError, ResiraRateLimitError } from \"./errors.js\";\nimport { pickBaseUrl, resolveBaseUrls } from \"./routing.js\";\nimport type {\n ApiErrorBody,\n Availability,\n AvailabilityParams,\n ConfirmPaymentRequest,\n ConfirmPaymentResponse,\n CreatePaymentIntentRequest,\n CreateReservationRequest,\n Dish,\n DishListResponse,\n DishResponse,\n ListReservationsParams,\n PaginatedReservations,\n PaymentIntentResponse,\n ProductListResponse,\n PropertyConfig,\n Reservation,\n ReservationResponse,\n ResiraConfig,\n ResourceListResponse,\n ValidatePromoCodeResponse,\n} from \"./types.js\";\n\n// ═══════════════════════════════════════════════════════════════\n// Constants\n// ═══════════════════════════════════════════════════════════════\n\nconst DEFAULT_MAX_RETRIES = 3;\nconst DEFAULT_RETRY_BASE_DELAY = 500; // ms\nconst API_PREFIX = \"/v1/public\";\n\n// ═══════════════════════════════════════════════════════════════\n// Helpers\n// ═══════════════════════════════════════════════════════════════\n\n/** Generate a random UUID v4 for idempotency keys. */\nfunction uuid(): string {\n // Use crypto.randomUUID when available (Node 19+, all modern browsers)\n if (typeof globalThis.crypto?.randomUUID === \"function\") {\n return globalThis.crypto.randomUUID();\n }\n // Fallback: manual v4 UUID\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n return (c === \"x\" ? r : (r & 0x3) | 0x8).toString(16);\n });\n}\n\n/** Build a query string from a flat params object. Skips `undefined` values. */\nfunction toQueryString(params: Record<string, string | number | boolean | undefined>): string {\n const parts: string[] = [];\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined && value !== null) {\n parts.push(`${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`);\n }\n }\n return parts.length > 0 ? `?${parts.join(\"&\")}` : \"\";\n}\n\n/** Sleep for `ms` milliseconds. */\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Convert camelCase keys to snake_case for query parameters.\n * The API expects snake_case query params (`start_date`, `party_size`).\n */\nfunction camelToSnake(str: string): string {\n return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);\n}\n\n/** Convert an object's keys from camelCase to snake_case. */\nfunction keysToSnake(\n obj: Record<string, string | number | boolean | undefined>,\n): Record<string, string | number | boolean | undefined> {\n const result: Record<string, string | number | boolean | undefined> = {};\n for (const [key, value] of Object.entries(obj)) {\n result[camelToSnake(key)] = value;\n }\n return result;\n}\n\n/** Convert snake_case string to camelCase. */\nfunction snakeToCamel(str: string): string {\n return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());\n}\n\n/** Deep-convert all keys in an object/array from camelCase to snake_case. */\nfunction deepKeysToSnake(obj: unknown): unknown {\n if (Array.isArray(obj)) return obj.map(deepKeysToSnake);\n if (obj !== null && typeof obj === \"object\") {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n result[camelToSnake(key)] = deepKeysToSnake(value);\n }\n return result;\n }\n return obj;\n}\n\n/** Deep-convert all keys in an object/array from snake_case to camelCase. */\nfunction deepKeysToCamel(obj: unknown): unknown {\n if (Array.isArray(obj)) return obj.map(deepKeysToCamel);\n if (obj !== null && typeof obj === \"object\") {\n const result: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(obj as Record<string, unknown>)) {\n result[snakeToCamel(key)] = deepKeysToCamel(value);\n }\n return result;\n }\n return obj;\n}\n\n// ═══════════════════════════════════════════════════════════════\n// Client\n// ═══════════════════════════════════════════════════════════════\n\n/**\n * Resira SDK client.\n *\n * ```ts\n * const resira = new Resira({\n * apiKey: \"resira_live_abc123…\",\n * baseUrl: \"https://api.example.com\", // optional\n * });\n *\n * const availability = await resira.getAvailability(\"prop-1\", {\n * startDate: \"2026-07-01\",\n * endDate: \"2026-07-07\",\n * });\n * ```\n */\nexport class Resira {\n private readonly apiKey: string;\n private readonly baseUrls: ReturnType<typeof resolveBaseUrls>;\n private readonly maxRetries: number;\n private readonly retryBaseDelay: number;\n private readonly _fetch: typeof globalThis.fetch;\n\n constructor(config: ResiraConfig) {\n if (!config.apiKey) {\n throw new Error(\"Resira: apiKey is required\");\n }\n this.apiKey = config.apiKey;\n this.baseUrls = resolveBaseUrls(config);\n this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;\n this.retryBaseDelay = config.retryBaseDelay ?? DEFAULT_RETRY_BASE_DELAY;\n this._fetch = config.fetch ?? globalThis.fetch.bind(globalThis);\n }\n\n /** The resolved API origin used by this client instance. */\n getBaseUrl(): string {\n return pickBaseUrl(this.baseUrls);\n }\n\n // ─────────────────────────────────────────────────────────────\n // Public API methods\n // ─────────────────────────────────────────────────────────────\n\n /**\n * Fetch the public property configuration.\n *\n * Returns non-sensitive settings such as the Stripe publishable\n * key, deposit percentage, currency, and branding info.\n *\n * ```ts\n * const config = await resira.getConfig();\n * console.log(config.stripePublishableKey); // \"pk_test_…\"\n * ```\n */\n async getConfig(): Promise<PropertyConfig> {\n return this.request<PropertyConfig>(\"GET\", \"/config\");\n }\n\n /**\n * Validate a promo/discount code.\n *\n * Returns discount details if valid, or an error message if not.\n *\n * ```ts\n * const result = await resira.validatePromoCode(\"SUMMER20\");\n * if (result.valid) {\n * console.log(result.discountType, result.discountValue);\n * }\n * ```\n */\n async validatePromoCode(code: string): Promise<ValidatePromoCodeResponse> {\n try {\n const raw = await this.request<Record<string, unknown>>(\n \"POST\",\n \"/promo-codes/validate\",\n { code },\n );\n const data = deepKeysToCamel(raw) as Record<string, unknown>;\n return {\n valid: true,\n discountType: data.discountType as \"percent\" | \"fixed\" | undefined,\n discountValue: data.discountValue as number | undefined,\n currency: data.currency as string | undefined,\n minOrderCents: data.minOrderCents as number | undefined,\n };\n } catch (err: unknown) {\n // 4xx errors mean invalid code\n if (\n err &&\n typeof err === \"object\" &&\n \"status\" in err &&\n (err as { status: number }).status < 500\n ) {\n const apiErr = err as { body?: { error?: string } };\n return {\n valid: false,\n error: apiErr.body?.error ?? \"Invalid promo code\",\n };\n }\n throw err;\n }\n }\n\n /**\n * List all active resources for the organization.\n *\n * Returns resource cards with name, description, price, duration,\n * image, and type — suitable for building a resource picker.\n *\n * ```ts\n * const { resources } = await resira.listResources();\n * ```\n */\n async listResources(): Promise<ResourceListResponse> {\n return this.request<ResourceListResponse>(\"GET\", \"/resources\");\n }\n\n /**\n * Query availability for a resource.\n *\n * **Properties** (date-based):\n * ```ts\n * await resira.getAvailability(\"prop-1\", {\n * startDate: \"2026-07-01\",\n * endDate: \"2026-07-07\",\n * });\n * ```\n *\n * **Restaurants** (time-slot):\n * ```ts\n * await resira.getAvailability(\"table-5\", {\n * date: \"2026-07-01\",\n * partySize: 4,\n * });\n * ```\n *\n * Omit params to get all blocked dates (for calendar rendering).\n */\n async getAvailability(\n resourceId: string,\n params: AvailabilityParams = {},\n ): Promise<Availability> {\n if (!resourceId) {\n throw new Error(\"resourceId is required for getAvailability\");\n }\n const qs = toQueryString(keysToSnake(params as Record<string, string | number | undefined>));\n return this.request<Availability>(\n \"GET\",\n `/resources/${encodeURIComponent(resourceId)}/availability${qs}`,\n );\n }\n\n /**\n * Query availability for a product/service.\n *\n * Aggregates capacity across all linked equipment. If a product\n * has 2 jet skis (each capacity=1), slots show capacity: 2.\n * Both pending and confirmed reservations count as occupied.\n *\n * ```ts\n * await resira.getProductAvailability(\"prod-123\", {\n * date: \"2026-07-01\",\n * durationMinutes: 30,\n * });\n * ```\n */\n async getProductAvailability(\n productId: string,\n params: AvailabilityParams = {},\n ): Promise<Availability> {\n if (!productId) {\n throw new Error(\"productId is required for getProductAvailability\");\n }\n const qs = toQueryString(keysToSnake(params as Record<string, string | number | undefined>));\n return this.request<Availability>(\n \"GET\",\n `/products/${encodeURIComponent(productId)}/availability${qs}`,\n );\n }\n\n /**\n * Create a reservation.\n *\n * Uses `POST /v2/api/reservations` — the `resourceId` is sent in\n * the request body, **not** in the URL path.\n *\n * An `Idempotency-Key` header is automatically attached so that\n * retries (including those from exponential backoff) are safe.\n *\n * ```ts\n * const { reservation } = await resira.createReservation({\n * resourceId: \"prop-1\",\n * guestName: \"Jane Doe\",\n * guestEmail: \"jane@example.com\",\n * startDate: \"2026-07-01\",\n * endDate: \"2026-07-07\",\n * partySize: 3,\n * });\n * ```\n */\n async createReservation(\n payload: CreateReservationRequest,\n options?: { idempotencyKey?: string },\n ): Promise<ReservationResponse> {\n if (!payload.resourceId) {\n throw new Error(\"resourceId is required for createReservation\");\n }\n const idempotencyKey = options?.idempotencyKey ?? uuid();\n\n // Note: uses /v2/api prefix, not /v1/public\n const url = `${this.getBaseUrl()}/v2/api/reservations`;\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.apiKey}`,\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n \"Idempotency-Key\": idempotencyKey,\n };\n\n const init: RequestInit = {\n method: \"POST\",\n headers,\n body: JSON.stringify(payload),\n };\n\n let lastError: ResiraApiError | ResiraNetworkError | ResiraRateLimitError | undefined;\n\n for (let attempt = 0; attempt <= this.maxRetries; attempt++) {\n if (attempt > 0) {\n const backoff = this.calculateBackoff(attempt, lastError);\n await sleep(backoff);\n }\n\n let response: Response;\n try {\n response = await this._fetch(url, init);\n } catch (err) {\n lastError = new ResiraNetworkError(\n `Network request failed: ${err instanceof Error ? err.message : String(err)}`,\n err,\n );\n continue;\n }\n\n if (response.ok) {\n const raw = (await response.json()) as Record<string, unknown>;\n // The API may nest under \"reservation\" or \"booking\" depending on endpoint\n const resData = (raw.reservation ?? raw.booking ?? {}) as Record<string, unknown>;\n\n // Augment with request data: API response doesn't include\n // startTime/endTime/startDate/endDate/resourceId for v2 reservations\n const reservation: Reservation = {\n id: (resData.id as string) ?? \"\",\n resourceId: (resData.resourceId as string) ?? payload.resourceId,\n status: (resData.status as string) ?? \"pending\",\n startDate: (resData.startDate as string) ?? payload.startDate,\n endDate: (resData.endDate as string) ?? payload.endDate,\n startTime: (resData.startTime as string) ?? payload.startTime,\n endTime: (resData.endTime as string) ?? payload.endTime,\n startDatetime: (resData.startDatetime as string) ?? payload.startTime,\n endDatetime: (resData.endDatetime as string) ?? payload.endTime,\n guestName: (resData.guestName as string) ?? payload.guestName,\n partySize: (resData.partySize as number) ?? (resData.guests as number) ?? payload.partySize ?? 2,\n totalPrice: (resData.totalPrice as number) ?? 0,\n currency: (resData.currency as string) ?? \"EUR\",\n createdAt: (resData.createdAt as string) ?? new Date().toISOString(),\n };\n\n return { reservation };\n }\n\n let errorBody: ApiErrorBody;\n try {\n errorBody = (await response.json()) as ApiErrorBody;\n } catch {\n errorBody = { error: response.statusText || `HTTP ${response.status}` };\n }\n\n if (response.status === 429) {\n lastError = new ResiraRateLimitError(errorBody, response);\n continue;\n }\n\n if (response.status >= 500) {\n lastError = new ResiraApiError(response.status, response.statusText, errorBody, response);\n continue;\n }\n\n throw new ResiraApiError(response.status, response.statusText, errorBody, response);\n }\n\n throw lastError!;\n }\n\n /**\n * List reservations for a resource (paginated).\n *\n * ```ts\n * const page = await resira.listReservations(\"prop-1\", {\n * status: \"confirmed\",\n * page: 1,\n * limit: 50,\n * });\n * console.log(page.data); // Reservation[]\n * console.log(page.totalPages); // number\n * ```\n */\n async listReservations(\n resourceId: string,\n params: ListReservationsParams = {},\n ): Promise<PaginatedReservations> {\n const qs = toQueryString(params as Record<string, string | number | undefined>);\n return this.request<PaginatedReservations>(\n \"GET\",\n `/resources/${encodeURIComponent(resourceId)}/reservations${qs}`,\n );\n }\n\n /**\n * Get a single reservation by ID.\n *\n * ```ts\n * const { reservation } = await resira.getReservation(\"prop-1\", \"res-uuid\");\n * ```\n */\n async getReservation(\n resourceId: string,\n reservationId: string,\n ): Promise<ReservationResponse> {\n return this.request<ReservationResponse>(\n \"GET\",\n `/resources/${encodeURIComponent(resourceId)}/reservations/${encodeURIComponent(reservationId)}`,\n );\n }\n\n /**\n * List all active products/services for the organisation.\n *\n * Calls `GET /v1/public/products` which returns products with\n * pricing, duration, and linked equipment.\n *\n * Falls back to mapping resources → products if the endpoint\n * returns 404 (backend hasn't been updated yet).\n *\n * ```ts\n * const { products } = await resira.listProducts();\n * ```\n */\n async listProducts(): Promise<ProductListResponse> {\n try {\n return await this.request<ProductListResponse>(\"GET\", \"/products\");\n } catch (err: unknown) {\n // Fallback: if /products endpoint doesn't exist yet (404),\n // map resources → products shape\n if (\n err &&\n typeof err === \"object\" &&\n \"status\" in err &&\n (err as { status: number }).status === 404\n ) {\n const resourceResponse = await this.listResources();\n const products = resourceResponse.resources.map((r) => ({\n id: r.id,\n name: r.name,\n description: r.description,\n durationMinutes: r.durationMinutes ?? 60,\n priceCents: r.pricePerSession ?? 0,\n currency: r.currency ?? \"EUR\",\n imageUrl: r.imageUrl,\n active: r.active,\n sortOrder: 0,\n pricingModel: \"per_session\" as const,\n equipmentIds: [r.id],\n equipmentNames: [r.name],\n }));\n return { products, count: products.length };\n }\n throw err;\n }\n }\n\n /**\n * List all dishes with 3D model data for the organisation.\n *\n * Returns dishes with name, description, price, image, and\n * 3D model URLs for AR/preview rendering.\n *\n * ```ts\n * const { dishes } = await resira.listDishes();\n * ```\n */\n async listDishes(): Promise<DishListResponse> {\n return this.request<DishListResponse>(\"GET\", \"/dishes\");\n }\n\n /**\n * Get a single dish by ID.\n *\n * ```ts\n * const { dish } = await resira.getDish(\"dish-uuid\");\n * console.log(dish.name, dish.model?.glbUrl);\n * ```\n */\n async getDish(dishId: string): Promise<DishResponse> {\n if (!dishId) {\n throw new Error(\"dishId is required for getDish\");\n }\n return this.request<DishResponse>(\n \"GET\",\n `/dishes/${encodeURIComponent(dishId)}`,\n );\n }\n\n /**\n * Create a Stripe payment intent for a booking.\n *\n * The server creates the payment intent, calculates the amount,\n * and returns a `clientSecret` to confirm payment on the frontend\n * using Stripe Elements.\n *\n * ```ts\n * const { clientSecret, amountNow, amountAtVenue } =\n * await resira.createPaymentIntent({\n * productId: \"prod-123\",\n * resourceId: \"res-456\",\n * partySize: 2,\n * startDate: \"2026-07-01\",\n * startTime: \"2026-07-01T10:00:00Z\",\n * endTime: \"2026-07-01T11:00:00Z\",\n * guestName: \"Jane Doe\",\n * guestEmail: \"jane@example.com\",\n * });\n * ```\n */\n async createPaymentIntent(\n payload: CreatePaymentIntentRequest,\n options?: { idempotencyKey?: string },\n ): Promise<PaymentIntentResponse> {\n const idempotencyKey = options?.idempotencyKey ?? uuid();\n const raw = await this.request<Record<string, unknown>>(\n \"POST\",\n \"/payments/create-intent\",\n payload,\n { \"Idempotency-Key\": idempotencyKey },\n );\n // Normalize response keys to camelCase\n const data = deepKeysToCamel(raw) as Record<string, unknown>;\n // Map backend field names to SDK field names:\n // backend: amountDue, totalPrice\n // SDK: amountNow, totalAmount, amountAtVenue\n const totalAmount = (data.totalAmount as number) ?? (data.totalPrice as number) ?? 0;\n const amountNow = (data.amountNow as number) ?? (data.amountDue as number) ?? totalAmount;\n const amountAtVenue = (data.amountAtVenue as number) ?? (totalAmount - amountNow);\n return {\n clientSecret: data.clientSecret as string,\n paymentIntentId: data.paymentIntentId as string,\n amountNow,\n amountAtVenue,\n totalAmount,\n currency: (data.currency as string) ?? \"EUR\",\n paymentOption: data.paymentOption as string | undefined,\n reservationId: data.reservationId as string | undefined,\n discountAmount: (data.discountAmount as number) ?? undefined,\n originalPrice: (data.originalPrice as number) ?? undefined,\n promoCodeApplied: (data.promoCodeApplied as string) ?? undefined,\n };\n }\n\n /**\n * Confirm that a payment was successful after the guest\n * completes the Stripe payment flow.\n *\n * This transitions the reservation from `pending` to `confirmed`.\n *\n * ```ts\n * const { reservation, paymentStatus } =\n * await resira.confirmPayment({\n * paymentIntentId: \"pi_xxx\",\n * reservationId: \"res-uuid\",\n * });\n * ```\n */\n async confirmPayment(\n payload: ConfirmPaymentRequest,\n ): Promise<ConfirmPaymentResponse> {\n const raw = await this.request<Record<string, unknown>>(\n \"POST\",\n \"/payments/confirm\",\n payload,\n );\n // Normalize response keys to camelCase in case backend returns snake_case\n return deepKeysToCamel(raw) as ConfirmPaymentResponse;\n }\n\n // ─────────────────────────────────────────────────────────────\n // Internal HTTP layer\n // ─────────────────────────────────────────────────────────────\n\n /**\n * Execute an HTTP request with retry logic and error normalization.\n *\n * - Attaches `Authorization: Bearer <apiKey>` on every request.\n * - Retries on 429 and 5xx with exponential backoff + jitter.\n * - Parses error bodies and throws typed error classes.\n */\n private async request<T>(\n method: string,\n path: string,\n body?: unknown,\n extraHeaders?: Record<string, string>,\n ): Promise<T> {\n const url = `${this.getBaseUrl()}${API_PREFIX}${path}`;\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.apiKey}`,\n Accept: \"application/json\",\n ...extraHeaders,\n };\n\n if (body !== undefined) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n\n const init: RequestInit = {\n method,\n headers,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n };\n\n let lastError: ResiraApiError | ResiraNetworkError | undefined;\n\n for (let attempt = 0; attempt <= this.maxRetries; attempt++) {\n // ── Wait before retry ────────────────────────────────\n if (attempt > 0) {\n const backoff = this.calculateBackoff(attempt, lastError);\n await sleep(backoff);\n }\n\n // ── Execute the request ──────────────────────────────\n let response: Response;\n try {\n response = await this._fetch(url, init);\n } catch (err) {\n lastError = new ResiraNetworkError(\n `Network request failed: ${err instanceof Error ? err.message : String(err)}`,\n err,\n );\n // Network errors are always retryable\n continue;\n }\n\n // ── Success ──────────────────────────────────────────\n if (response.ok) {\n return (await response.json()) as T;\n }\n\n // ── Parse error body ─────────────────────────────────\n let errorBody: ApiErrorBody;\n try {\n errorBody = (await response.json()) as ApiErrorBody;\n } catch {\n errorBody = { error: response.statusText || `HTTP ${response.status}` };\n }\n\n // ── Rate limit ───────────────────────────────────────\n if (response.status === 429) {\n lastError = new ResiraRateLimitError(errorBody, response);\n // Always retry 429s (up to maxRetries)\n continue;\n }\n\n // ── Server error (5xx) — retryable ───────────────────\n if (response.status >= 500) {\n lastError = new ResiraApiError(\n response.status,\n response.statusText,\n errorBody,\n response,\n );\n continue;\n }\n\n // ── Client error (4xx) — NOT retryable ───────────────\n throw new ResiraApiError(\n response.status,\n response.statusText,\n errorBody,\n response,\n );\n }\n\n // All retries exhausted — throw the last error\n throw lastError!;\n }\n\n /**\n * Calculate backoff delay in milliseconds for a given retry attempt.\n *\n * Uses exponential backoff with full jitter:\n * delay = random(0, base * 2^attempt)\n *\n * For 429 responses, respects the `Retry-After` header as a minimum.\n */\n private calculateBackoff(\n attempt: number,\n lastError?: ResiraApiError | ResiraNetworkError,\n ): number {\n const exponential = this.retryBaseDelay * 2 ** (attempt - 1);\n const jittered = Math.random() * exponential;\n\n // If the server told us to wait, use that as the floor\n if (lastError instanceof ResiraRateLimitError) {\n const serverDelay = lastError.retryAfter * 1000;\n return Math.max(serverDelay, jittered);\n }\n\n return jittered;\n }\n\n}\n"]}
|