@resira/sdk 0.2.4 → 0.2.6
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 +11 -0
- package/README.md +109 -99
- package/dist/index.cjs +187 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +15 -3
- package/dist/index.d.ts +15 -3
- package/dist/index.js +187 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
All notable SDK and public API tracking updates are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.2.6
|
|
6
|
+
|
|
7
|
+
- Updated SDK routing to ignore ambient host-app env API overrides by default.
|
|
8
|
+
- Kept explicit `baseUrl` and `baseUrls` support for integrator-controlled routing.
|
|
9
|
+
- Added DOM libs to the SDK package tsconfig so standalone typechecking passes cleanly.
|
|
10
|
+
|
|
11
|
+
## 0.2.5
|
|
12
|
+
|
|
13
|
+
- Added weighted-origin session stickiness so a browser session stays pinned to the same API origin instead of re-randomizing on every request.
|
|
14
|
+
- Added lightweight client-side routing telemetry helpers for monitoring origin assignments during rollout.
|
|
15
|
+
|
|
5
16
|
## 0.2.3
|
|
6
17
|
|
|
7
18
|
- Current workspace release for the public reservation API package.
|
package/README.md
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
# @resira/sdk v0.2.
|
|
1
|
+
# @resira/sdk v0.2.6
|
|
2
2
|
|
|
3
3
|
TypeScript SDK for the Resira public reservation API.
|
|
4
4
|
|
|
5
|
-
Version
|
|
5
|
+
Version history lives in [CHANGELOG.md](./CHANGELOG.md).
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
npm install @resira/sdk@0.2.
|
|
10
|
+
npm install @resira/sdk@0.2.6
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
## Quick start
|
|
14
14
|
|
|
15
|
-
```
|
|
15
|
+
```ts
|
|
16
16
|
import { Resira } from "@resira/sdk";
|
|
17
17
|
|
|
18
18
|
const resira = new Resira({
|
|
@@ -20,57 +20,115 @@ const resira = new Resira({
|
|
|
20
20
|
});
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
##
|
|
23
|
+
## Client config
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
```ts
|
|
26
|
+
const resira = new Resira({
|
|
27
|
+
apiKey: "resira_live_your_api_key_here",
|
|
28
|
+
maxRetries: 3,
|
|
29
|
+
retryBaseDelay: 500,
|
|
30
|
+
// Optional explicit overrides:
|
|
31
|
+
// baseUrl: "https://your-api.example.com",
|
|
32
|
+
// baseUrls: [
|
|
33
|
+
// { url: "https://origin-a.example.com", weight: 1 },
|
|
34
|
+
// { url: "https://origin-b.example.com", weight: 1 },
|
|
35
|
+
// ],
|
|
36
|
+
});
|
|
37
|
+
```
|
|
26
38
|
|
|
27
|
-
| Option
|
|
28
|
-
|
|
|
29
|
-
| `apiKey`
|
|
30
|
-
| `baseUrl`
|
|
31
|
-
| `
|
|
32
|
-
| `
|
|
33
|
-
| `
|
|
39
|
+
| Option | Type | Default | Description |
|
|
40
|
+
| --- | --- | --- | --- |
|
|
41
|
+
| `apiKey` | `string` | required | Public Resira API key |
|
|
42
|
+
| `baseUrl` | `string` | unset | Pin all SDK requests to one origin |
|
|
43
|
+
| `baseUrls` | `Array<string \| { url: string; weight?: number }>` | unset | Explicit client-side routing override |
|
|
44
|
+
| `maxRetries` | `number` | `3` | Retry attempts for 429 and 5xx responses |
|
|
45
|
+
| `retryBaseDelay` | `number` | `500` | Base exponential backoff delay in ms |
|
|
46
|
+
| `fetch` | `typeof fetch` | `globalThis.fetch` | Custom fetch implementation |
|
|
34
47
|
|
|
35
|
-
|
|
48
|
+
## Routing behavior
|
|
36
49
|
|
|
37
|
-
|
|
50
|
+
- The SDK keeps a browser session sticky to the selected origin.
|
|
51
|
+
- Ambient host-app env vars such as `NEXT_PUBLIC_API_URL` are ignored.
|
|
52
|
+
- Only explicit SDK config via `baseUrl` or `baseUrls` overrides the built-in production defaults.
|
|
53
|
+
- In development, the SDK defaults to `http://localhost:3001`.
|
|
54
|
+
|
|
55
|
+
## Core methods
|
|
56
|
+
|
|
57
|
+
### `resira.getConfig()`
|
|
38
58
|
|
|
39
|
-
|
|
59
|
+
Fetch non-sensitive public property config such as Stripe publishable key, deposit percentage, currency, and branding.
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
const config = await resira.getConfig();
|
|
63
|
+
console.log(config.stripePublishableKey);
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### `resira.validatePromoCode(code)`
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
const promo = await resira.validatePromoCode("SUMMER20");
|
|
70
|
+
if (promo.valid) {
|
|
71
|
+
console.log(promo.discountType, promo.discountValue);
|
|
72
|
+
}
|
|
73
|
+
```
|
|
40
74
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
75
|
+
### `resira.getAvailability(resourceId, params?)`
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
const availability = await resira.getAvailability("prop-1", {
|
|
44
79
|
startDate: "2026-07-01",
|
|
45
80
|
endDate: "2026-07-07",
|
|
46
81
|
});
|
|
47
|
-
|
|
48
|
-
console.log(avail.dates?.available); // true
|
|
49
|
-
console.log(avail.dates?.totalPrice); // 85000 (cents)
|
|
50
|
-
console.log(avail.dates?.blockedDates); // ["2026-07-15", …]
|
|
51
82
|
```
|
|
52
83
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const avail = await resira.getAvailability("table-5", {
|
|
84
|
+
```ts
|
|
85
|
+
const slots = await resira.getAvailability("table-5", {
|
|
56
86
|
date: "2026-07-01",
|
|
57
87
|
partySize: 4,
|
|
58
88
|
});
|
|
89
|
+
```
|
|
59
90
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
91
|
+
### `resira.listResources()`
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
const { resources } = await resira.listResources();
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### `resira.listProducts()`
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
const { products } = await resira.listProducts();
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### `resira.createPaymentIntent(payload, options?)`
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
const paymentIntent = await resira.createPaymentIntent({
|
|
107
|
+
productId: "prod-123",
|
|
108
|
+
resourceId: "res-456",
|
|
109
|
+
partySize: 2,
|
|
110
|
+
startDate: "2026-07-01",
|
|
111
|
+
startTime: "2026-07-01T10:00:00Z",
|
|
112
|
+
endTime: "2026-07-01T11:00:00Z",
|
|
113
|
+
guestName: "Jane Doe",
|
|
114
|
+
guestEmail: "jane@example.com",
|
|
115
|
+
});
|
|
63
116
|
```
|
|
64
117
|
|
|
65
|
-
|
|
118
|
+
### `resira.confirmPayment(payload)`
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
const confirmation = await resira.confirmPayment({
|
|
122
|
+
paymentIntentId: "pi_xxx",
|
|
123
|
+
reservationId: "res_123",
|
|
124
|
+
});
|
|
125
|
+
```
|
|
66
126
|
|
|
67
127
|
### `resira.createReservation(payload, options?)`
|
|
68
128
|
|
|
69
|
-
|
|
70
|
-
An `Idempotency-Key` header is automatically generated to prevent
|
|
71
|
-
duplicate bookings on retries.
|
|
129
|
+
Creates a reservation via the public booking flow. An idempotency key is automatically generated unless you pass one explicitly.
|
|
72
130
|
|
|
73
|
-
```
|
|
131
|
+
```ts
|
|
74
132
|
const { reservation } = await resira.createReservation({
|
|
75
133
|
resourceId: "prop-1",
|
|
76
134
|
guestName: "Jane Doe",
|
|
@@ -79,107 +137,59 @@ const { reservation } = await resira.createReservation({
|
|
|
79
137
|
endDate: "2026-07-07",
|
|
80
138
|
partySize: 3,
|
|
81
139
|
});
|
|
82
|
-
|
|
83
|
-
console.log(reservation.id); // "uuid-…"
|
|
84
|
-
console.log(reservation.status); // "pending"
|
|
85
|
-
console.log(reservation.totalPrice);// 85000
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
**Custom idempotency key:**
|
|
89
|
-
```typescript
|
|
90
|
-
const { reservation } = await resira.createReservation(
|
|
91
|
-
payload,
|
|
92
|
-
{ idempotencyKey: "form-submit-abc123" },
|
|
93
|
-
);
|
|
94
140
|
```
|
|
95
141
|
|
|
96
|
-
---
|
|
97
|
-
|
|
98
142
|
### `resira.listReservations(resourceId, params?)`
|
|
99
143
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
```typescript
|
|
144
|
+
```ts
|
|
103
145
|
const page = await resira.listReservations("prop-1", {
|
|
104
146
|
status: "confirmed",
|
|
105
147
|
page: 1,
|
|
106
148
|
limit: 50,
|
|
107
149
|
});
|
|
108
|
-
|
|
109
|
-
console.log(page.data); // Reservation[]
|
|
110
|
-
console.log(page.total); // 142
|
|
111
|
-
console.log(page.totalPages); // 3
|
|
112
150
|
```
|
|
113
151
|
|
|
114
|
-
---
|
|
115
|
-
|
|
116
152
|
### `resira.getReservation(resourceId, reservationId)`
|
|
117
153
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
```typescript
|
|
121
|
-
const { reservation } = await resira.getReservation("prop-1", "res-uuid");
|
|
154
|
+
```ts
|
|
155
|
+
const result = await resira.getReservation("prop-1", "res-uuid");
|
|
122
156
|
```
|
|
123
157
|
|
|
124
|
-
---
|
|
125
|
-
|
|
126
158
|
## Error handling
|
|
127
159
|
|
|
128
|
-
All errors extend `ResiraError`.
|
|
160
|
+
All SDK errors extend `ResiraError`.
|
|
129
161
|
|
|
130
|
-
```
|
|
162
|
+
```ts
|
|
131
163
|
import {
|
|
132
|
-
Resira,
|
|
133
164
|
ResiraApiError,
|
|
134
|
-
ResiraRateLimitError,
|
|
135
165
|
ResiraNetworkError,
|
|
166
|
+
ResiraRateLimitError,
|
|
136
167
|
} from "@resira/sdk";
|
|
137
168
|
|
|
138
169
|
try {
|
|
139
170
|
await resira.createReservation(payload);
|
|
140
171
|
} catch (error) {
|
|
141
172
|
if (error instanceof ResiraRateLimitError) {
|
|
142
|
-
|
|
143
|
-
console.log(`Rate limited. Retry after ${error.retryAfter}s`);
|
|
173
|
+
console.log(error.retryAfter);
|
|
144
174
|
} else if (error instanceof ResiraApiError) {
|
|
145
|
-
|
|
146
|
-
console.log(`${error.status}: ${error.message}`);
|
|
147
|
-
console.log(error.retryable); // true for 5xx
|
|
175
|
+
console.log(error.status, error.message);
|
|
148
176
|
} else if (error instanceof ResiraNetworkError) {
|
|
149
|
-
|
|
150
|
-
console.log("Network error:", error.message);
|
|
177
|
+
console.log(error.message);
|
|
151
178
|
}
|
|
152
179
|
}
|
|
153
180
|
```
|
|
154
181
|
|
|
155
|
-
| Error class
|
|
156
|
-
|
|
|
157
|
-
| `ResiraRateLimitError` | 429
|
|
158
|
-
| `ResiraApiError`
|
|
159
|
-
| `ResiraNetworkError`
|
|
160
|
-
|
|
161
|
-
---
|
|
162
|
-
|
|
163
|
-
## Retry behaviour
|
|
164
|
-
|
|
165
|
-
The SDK retries failed requests with **exponential backoff + jitter**:
|
|
166
|
-
|
|
167
|
-
- **Attempt 1**: 0 – 500 ms
|
|
168
|
-
- **Attempt 2**: 0 – 1 000 ms
|
|
169
|
-
- **Attempt 3**: 0 – 2 000 ms
|
|
170
|
-
|
|
171
|
-
For 429 responses, the `Retry-After` header value is used as a minimum delay.
|
|
172
|
-
|
|
173
|
-
Idempotency keys ensure that retried POST requests don't create
|
|
174
|
-
duplicate reservations.
|
|
175
|
-
|
|
176
|
-
---
|
|
182
|
+
| Error class | Meaning |
|
|
183
|
+
| --- | --- |
|
|
184
|
+
| `ResiraRateLimitError` | 429 response after retry handling |
|
|
185
|
+
| `ResiraApiError` | Non-2xx API response |
|
|
186
|
+
| `ResiraNetworkError` | Network/DNS/TLS failure |
|
|
177
187
|
|
|
178
188
|
## Requirements
|
|
179
189
|
|
|
180
|
-
- Node.js
|
|
190
|
+
- Node.js >= 18
|
|
181
191
|
- No runtime dependencies
|
|
182
192
|
|
|
183
|
-
##
|
|
193
|
+
## Release notes
|
|
184
194
|
|
|
185
|
-
|
|
195
|
+
See [CHANGELOG.md](./CHANGELOG.md) for release-by-release details.
|
package/dist/index.cjs
CHANGED
|
@@ -40,8 +40,188 @@ var ResiraNetworkError = class extends ResiraError {
|
|
|
40
40
|
}
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
+
// src/routing.ts
|
|
44
|
+
var DEFAULT_LOCAL_BASE_URLS = [
|
|
45
|
+
{ url: "http://localhost:3001", weight: 100 }
|
|
46
|
+
];
|
|
47
|
+
var DEFAULT_PRODUCTION_BASE_URLS = [
|
|
48
|
+
{ url: "https://resira-api-sips-production.up.railway.app", weight: 50 },
|
|
49
|
+
{ url: "https://api.resira.app", weight: 50 }
|
|
50
|
+
];
|
|
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
|
+
function readEnv(name) {
|
|
55
|
+
if (typeof process === "undefined") return void 0;
|
|
56
|
+
return process.env?.[name];
|
|
57
|
+
}
|
|
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
|
+
function normalizeUrl(url) {
|
|
173
|
+
return url.trim().replace(/\/+$/, "");
|
|
174
|
+
}
|
|
175
|
+
function isPositiveNumber(value) {
|
|
176
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
177
|
+
}
|
|
178
|
+
function toResolvedBaseUrl(value) {
|
|
179
|
+
if (typeof value === "string") {
|
|
180
|
+
const url2 = normalizeUrl(value);
|
|
181
|
+
return url2 ? { url: url2, weight: 1 } : null;
|
|
182
|
+
}
|
|
183
|
+
const url = normalizeUrl(value.url);
|
|
184
|
+
if (!url) return null;
|
|
185
|
+
return {
|
|
186
|
+
url,
|
|
187
|
+
weight: isPositiveNumber(value.weight) ? value.weight : 1
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function resolveBaseUrls(config) {
|
|
191
|
+
if (config.baseUrl) {
|
|
192
|
+
return [{ url: normalizeUrl(config.baseUrl), weight: 100 }];
|
|
193
|
+
}
|
|
194
|
+
if (config.baseUrls?.length) {
|
|
195
|
+
const explicit = config.baseUrls.flatMap((entry) => {
|
|
196
|
+
const resolved = toResolvedBaseUrl(entry);
|
|
197
|
+
return resolved ? [resolved] : [];
|
|
198
|
+
});
|
|
199
|
+
if (explicit.length > 0) return explicit;
|
|
200
|
+
}
|
|
201
|
+
if (readEnv("NODE_ENV") === "development") {
|
|
202
|
+
return DEFAULT_LOCAL_BASE_URLS;
|
|
203
|
+
}
|
|
204
|
+
return DEFAULT_PRODUCTION_BASE_URLS;
|
|
205
|
+
}
|
|
206
|
+
function pickBaseUrl(baseUrls) {
|
|
207
|
+
const signature = getBaseUrlSignature(baseUrls);
|
|
208
|
+
const stickySelection = readStickySelection(signature);
|
|
209
|
+
const stickyEntry = stickySelection ? baseUrls.find((entry) => entry.url === stickySelection.url) : null;
|
|
210
|
+
if (stickyEntry) {
|
|
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;
|
|
222
|
+
}
|
|
223
|
+
|
|
43
224
|
// src/client.ts
|
|
44
|
-
var DEFAULT_BASE_URL = "https://api-res-production.up.railway.app";
|
|
45
225
|
var DEFAULT_MAX_RETRIES = 3;
|
|
46
226
|
var DEFAULT_RETRY_BASE_DELAY = 500;
|
|
47
227
|
var API_PREFIX = "/v1/public";
|
|
@@ -96,7 +276,7 @@ var Resira = class {
|
|
|
96
276
|
throw new Error("Resira: apiKey is required");
|
|
97
277
|
}
|
|
98
278
|
this.apiKey = config.apiKey;
|
|
99
|
-
this.
|
|
279
|
+
this.baseUrls = resolveBaseUrls(config);
|
|
100
280
|
this.maxRetries = config.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
101
281
|
this.retryBaseDelay = config.retryBaseDelay ?? DEFAULT_RETRY_BASE_DELAY;
|
|
102
282
|
this._fetch = config.fetch ?? globalThis.fetch.bind(globalThis);
|
|
@@ -249,7 +429,7 @@ var Resira = class {
|
|
|
249
429
|
throw new Error("resourceId is required for createReservation");
|
|
250
430
|
}
|
|
251
431
|
const idempotencyKey = options?.idempotencyKey ?? uuid();
|
|
252
|
-
const url = `${this.
|
|
432
|
+
const url = `${this.getBaseUrl()}/v2/api/reservations`;
|
|
253
433
|
const headers = {
|
|
254
434
|
Authorization: `Bearer ${this.apiKey}`,
|
|
255
435
|
Accept: "application/json",
|
|
@@ -467,7 +647,7 @@ var Resira = class {
|
|
|
467
647
|
* - Parses error bodies and throws typed error classes.
|
|
468
648
|
*/
|
|
469
649
|
async request(method, path, body, extraHeaders) {
|
|
470
|
-
const url = `${this.
|
|
650
|
+
const url = `${this.getBaseUrl()}${API_PREFIX}${path}`;
|
|
471
651
|
const headers = {
|
|
472
652
|
Authorization: `Bearer ${this.apiKey}`,
|
|
473
653
|
Accept: "application/json",
|
|
@@ -545,6 +725,9 @@ var Resira = class {
|
|
|
545
725
|
}
|
|
546
726
|
return jittered;
|
|
547
727
|
}
|
|
728
|
+
getBaseUrl() {
|
|
729
|
+
return pickBaseUrl(this.baseUrls);
|
|
730
|
+
}
|
|
548
731
|
};
|
|
549
732
|
|
|
550
733
|
exports.Resira = Resira;
|