@resira/sdk 0.2.3 → 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 +38 -0
- package/README.md +112 -96
- 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 +3 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable SDK and public API tracking updates are documented here.
|
|
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
|
+
|
|
16
|
+
## 0.2.3
|
|
17
|
+
|
|
18
|
+
- Current workspace release for the public reservation API package.
|
|
19
|
+
- Tracks the typed payment intent, payment confirmation, promo-code validation, product catalog, and product-availability flows exposed by the SDK.
|
|
20
|
+
- Keeps the SDK aligned with the current `v1/public` endpoints plus reservation creation via `v2/api/reservations`.
|
|
21
|
+
|
|
22
|
+
## 0.2.1
|
|
23
|
+
|
|
24
|
+
- Published package update after the initial TypeScript SDK rollout.
|
|
25
|
+
- Stabilized the package metadata for the public reservation API client.
|
|
26
|
+
|
|
27
|
+
## 0.2.0
|
|
28
|
+
|
|
29
|
+
- Introduced the first public TypeScript SDK for Resira reservations.
|
|
30
|
+
- Added typed availability, reservation creation, reservation lookup, and retry-aware error handling.
|
|
31
|
+
|
|
32
|
+
## 0.1.1
|
|
33
|
+
|
|
34
|
+
- Internal follow-up release used to align the early SDK package with the UI package work.
|
|
35
|
+
|
|
36
|
+
## 0.1.0
|
|
37
|
+
|
|
38
|
+
- Initial SDK package scaffold.
|
package/README.md
CHANGED
|
@@ -1,16 +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 history lives in [CHANGELOG.md](./CHANGELOG.md).
|
|
6
|
+
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
|
-
npm install @resira/sdk@0.2.
|
|
10
|
+
npm install @resira/sdk@0.2.6
|
|
9
11
|
```
|
|
10
12
|
|
|
11
13
|
## Quick start
|
|
12
14
|
|
|
13
|
-
```
|
|
15
|
+
```ts
|
|
14
16
|
import { Resira } from "@resira/sdk";
|
|
15
17
|
|
|
16
18
|
const resira = new Resira({
|
|
@@ -18,57 +20,115 @@ const resira = new Resira({
|
|
|
18
20
|
});
|
|
19
21
|
```
|
|
20
22
|
|
|
21
|
-
##
|
|
23
|
+
## Client config
|
|
22
24
|
|
|
23
|
-
|
|
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
|
+
```
|
|
24
38
|
|
|
25
|
-
| Option
|
|
26
|
-
|
|
|
27
|
-
| `apiKey`
|
|
28
|
-
| `baseUrl`
|
|
29
|
-
| `
|
|
30
|
-
| `
|
|
31
|
-
| `
|
|
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 |
|
|
32
47
|
|
|
33
|
-
|
|
48
|
+
## Routing behavior
|
|
34
49
|
|
|
35
|
-
|
|
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
|
|
36
56
|
|
|
37
|
-
|
|
57
|
+
### `resira.getConfig()`
|
|
58
|
+
|
|
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
|
+
```
|
|
38
74
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
75
|
+
### `resira.getAvailability(resourceId, params?)`
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
const availability = await resira.getAvailability("prop-1", {
|
|
42
79
|
startDate: "2026-07-01",
|
|
43
80
|
endDate: "2026-07-07",
|
|
44
81
|
});
|
|
45
|
-
|
|
46
|
-
console.log(avail.dates?.available); // true
|
|
47
|
-
console.log(avail.dates?.totalPrice); // 85000 (cents)
|
|
48
|
-
console.log(avail.dates?.blockedDates); // ["2026-07-15", …]
|
|
49
82
|
```
|
|
50
83
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const avail = await resira.getAvailability("table-5", {
|
|
84
|
+
```ts
|
|
85
|
+
const slots = await resira.getAvailability("table-5", {
|
|
54
86
|
date: "2026-07-01",
|
|
55
87
|
partySize: 4,
|
|
56
88
|
});
|
|
89
|
+
```
|
|
57
90
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
91
|
+
### `resira.listResources()`
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
const { resources } = await resira.listResources();
|
|
61
95
|
```
|
|
62
96
|
|
|
63
|
-
|
|
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
|
+
});
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### `resira.confirmPayment(payload)`
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
const confirmation = await resira.confirmPayment({
|
|
122
|
+
paymentIntentId: "pi_xxx",
|
|
123
|
+
reservationId: "res_123",
|
|
124
|
+
});
|
|
125
|
+
```
|
|
64
126
|
|
|
65
127
|
### `resira.createReservation(payload, options?)`
|
|
66
128
|
|
|
67
|
-
|
|
68
|
-
An `Idempotency-Key` header is automatically generated to prevent
|
|
69
|
-
duplicate bookings on retries.
|
|
129
|
+
Creates a reservation via the public booking flow. An idempotency key is automatically generated unless you pass one explicitly.
|
|
70
130
|
|
|
71
|
-
```
|
|
131
|
+
```ts
|
|
72
132
|
const { reservation } = await resira.createReservation({
|
|
73
133
|
resourceId: "prop-1",
|
|
74
134
|
guestName: "Jane Doe",
|
|
@@ -77,103 +137,59 @@ const { reservation } = await resira.createReservation({
|
|
|
77
137
|
endDate: "2026-07-07",
|
|
78
138
|
partySize: 3,
|
|
79
139
|
});
|
|
80
|
-
|
|
81
|
-
console.log(reservation.id); // "uuid-…"
|
|
82
|
-
console.log(reservation.status); // "pending"
|
|
83
|
-
console.log(reservation.totalPrice);// 85000
|
|
84
140
|
```
|
|
85
141
|
|
|
86
|
-
**Custom idempotency key:**
|
|
87
|
-
```typescript
|
|
88
|
-
const { reservation } = await resira.createReservation(
|
|
89
|
-
payload,
|
|
90
|
-
{ idempotencyKey: "form-submit-abc123" },
|
|
91
|
-
);
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
---
|
|
95
|
-
|
|
96
142
|
### `resira.listReservations(resourceId, params?)`
|
|
97
143
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
```typescript
|
|
144
|
+
```ts
|
|
101
145
|
const page = await resira.listReservations("prop-1", {
|
|
102
146
|
status: "confirmed",
|
|
103
147
|
page: 1,
|
|
104
148
|
limit: 50,
|
|
105
149
|
});
|
|
106
|
-
|
|
107
|
-
console.log(page.data); // Reservation[]
|
|
108
|
-
console.log(page.total); // 142
|
|
109
|
-
console.log(page.totalPages); // 3
|
|
110
150
|
```
|
|
111
151
|
|
|
112
|
-
---
|
|
113
|
-
|
|
114
152
|
### `resira.getReservation(resourceId, reservationId)`
|
|
115
153
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
```typescript
|
|
119
|
-
const { reservation } = await resira.getReservation("prop-1", "res-uuid");
|
|
154
|
+
```ts
|
|
155
|
+
const result = await resira.getReservation("prop-1", "res-uuid");
|
|
120
156
|
```
|
|
121
157
|
|
|
122
|
-
---
|
|
123
|
-
|
|
124
158
|
## Error handling
|
|
125
159
|
|
|
126
|
-
All errors extend `ResiraError`.
|
|
160
|
+
All SDK errors extend `ResiraError`.
|
|
127
161
|
|
|
128
|
-
```
|
|
162
|
+
```ts
|
|
129
163
|
import {
|
|
130
|
-
Resira,
|
|
131
164
|
ResiraApiError,
|
|
132
|
-
ResiraRateLimitError,
|
|
133
165
|
ResiraNetworkError,
|
|
166
|
+
ResiraRateLimitError,
|
|
134
167
|
} from "@resira/sdk";
|
|
135
168
|
|
|
136
169
|
try {
|
|
137
170
|
await resira.createReservation(payload);
|
|
138
171
|
} catch (error) {
|
|
139
172
|
if (error instanceof ResiraRateLimitError) {
|
|
140
|
-
|
|
141
|
-
console.log(`Rate limited. Retry after ${error.retryAfter}s`);
|
|
173
|
+
console.log(error.retryAfter);
|
|
142
174
|
} else if (error instanceof ResiraApiError) {
|
|
143
|
-
|
|
144
|
-
console.log(`${error.status}: ${error.message}`);
|
|
145
|
-
console.log(error.retryable); // true for 5xx
|
|
175
|
+
console.log(error.status, error.message);
|
|
146
176
|
} else if (error instanceof ResiraNetworkError) {
|
|
147
|
-
|
|
148
|
-
console.log("Network error:", error.message);
|
|
177
|
+
console.log(error.message);
|
|
149
178
|
}
|
|
150
179
|
}
|
|
151
180
|
```
|
|
152
181
|
|
|
153
|
-
| Error class
|
|
154
|
-
|
|
|
155
|
-
| `ResiraRateLimitError` | 429
|
|
156
|
-
| `ResiraApiError`
|
|
157
|
-
| `ResiraNetworkError`
|
|
158
|
-
|
|
159
|
-
---
|
|
160
|
-
|
|
161
|
-
## Retry behaviour
|
|
162
|
-
|
|
163
|
-
The SDK retries failed requests with **exponential backoff + jitter**:
|
|
164
|
-
|
|
165
|
-
- **Attempt 1**: 0 – 500 ms
|
|
166
|
-
- **Attempt 2**: 0 – 1 000 ms
|
|
167
|
-
- **Attempt 3**: 0 – 2 000 ms
|
|
168
|
-
|
|
169
|
-
For 429 responses, the `Retry-After` header value is used as a minimum delay.
|
|
170
|
-
|
|
171
|
-
Idempotency keys ensure that retried POST requests don't create
|
|
172
|
-
duplicate reservations.
|
|
173
|
-
|
|
174
|
-
---
|
|
182
|
+
| Error class | Meaning |
|
|
183
|
+
| --- | --- |
|
|
184
|
+
| `ResiraRateLimitError` | 429 response after retry handling |
|
|
185
|
+
| `ResiraApiError` | Non-2xx API response |
|
|
186
|
+
| `ResiraNetworkError` | Network/DNS/TLS failure |
|
|
175
187
|
|
|
176
188
|
## Requirements
|
|
177
189
|
|
|
178
|
-
- Node.js
|
|
190
|
+
- Node.js >= 18
|
|
179
191
|
- No runtime dependencies
|
|
192
|
+
|
|
193
|
+
## Release notes
|
|
194
|
+
|
|
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;
|