@lagunacreek/hogan-pms-client 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -2
- package/dist/index.cjs +299 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +257 -1
- package/dist/index.d.ts +257 -1
- package/dist/index.js +295 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,8 +22,33 @@ console.log(VERSION); // -> "0.1.0"
|
|
|
22
22
|
import type { WhoAmIType } from "@lagunacreek/hogan-pms-client";
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
## Media manager
|
|
26
|
+
|
|
27
|
+
Upload/list/delete S3-backed media (browser or server). See
|
|
28
|
+
[`docs/MediaManager.md`](docs/MediaManager.md) and runnable
|
|
29
|
+
[`examples/`](examples).
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
import { MediaManager } from "@lagunacreek/hogan-pms-client";
|
|
33
|
+
|
|
34
|
+
const media = new MediaManager({
|
|
35
|
+
mediaApiUrl: process.env.MEDIA_API_URL!,
|
|
36
|
+
bucket: process.env.MEDIA_BUCKET!,
|
|
37
|
+
publicPath: process.env.MEDIA_PUBLIC_PATH,
|
|
38
|
+
getHeaders: () => ({ "X-API-KEY": process.env.MEDIA_API_KEY! }),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const { url } = await media.uploadFile(file, { keyPath: "products/123" });
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Internal OTA client
|
|
45
|
+
|
|
46
|
+
Typed access to the Hogan PMS internal OTA API (availability, pricing, property
|
|
47
|
+
data, reservations). Full reference and examples in
|
|
48
|
+
[`docs/HoganInternalOTAClient.md`](docs/HoganInternalOTAClient.md).
|
|
49
|
+
|
|
50
|
+
Constructor args: `apiKey`, `env`, `version`; `env` is one of
|
|
51
|
+
`LOCAL` | `DEV` | `TEST` | `PROD`:
|
|
27
52
|
|
|
28
53
|
```ts
|
|
29
54
|
import { HoganInternalOTAClient } from "@lagunacreek/hogan-pms-client";
|
package/dist/index.cjs
CHANGED
|
@@ -23,13 +23,17 @@ __export(index_exports, {
|
|
|
23
23
|
HoganApiError: () => HoganApiError,
|
|
24
24
|
HoganInternalOTAClient: () => internal_ota_default,
|
|
25
25
|
HoganNetworkError: () => HoganNetworkError,
|
|
26
|
+
MediaError: () => MediaError,
|
|
27
|
+
MediaManager: () => MediaManager,
|
|
26
28
|
VERSION: () => VERSION,
|
|
29
|
+
bytesToBase64: () => bytesToBase64,
|
|
30
|
+
inferMediaType: () => inferMediaType,
|
|
27
31
|
version: () => version
|
|
28
32
|
});
|
|
29
33
|
module.exports = __toCommonJS(index_exports);
|
|
30
34
|
|
|
31
35
|
// src/version.ts
|
|
32
|
-
var VERSION = true ? "0.1.
|
|
36
|
+
var VERSION = true ? "0.1.5" : "0.0.0";
|
|
33
37
|
function version() {
|
|
34
38
|
return VERSION;
|
|
35
39
|
}
|
|
@@ -233,14 +237,308 @@ var internalOta = class {
|
|
|
233
237
|
const qs = query.toString();
|
|
234
238
|
return getRequest(`${this.baseUrl}/getReplicant${qs ? `?${qs}` : ""}`, this.apiKey);
|
|
235
239
|
}
|
|
240
|
+
// Fetches every page of the replicant feed and returns all properties in a
|
|
241
|
+
// single array. Walks pages sequentially until next_page is false.
|
|
242
|
+
async getAllReplicantPages() {
|
|
243
|
+
const all = [];
|
|
244
|
+
let page = 1;
|
|
245
|
+
let hasNext = true;
|
|
246
|
+
while (hasNext) {
|
|
247
|
+
const response = await this.getReplicant({ page });
|
|
248
|
+
all.push(...response.results);
|
|
249
|
+
hasNext = response.next_page;
|
|
250
|
+
page += 1;
|
|
251
|
+
}
|
|
252
|
+
return all;
|
|
253
|
+
}
|
|
236
254
|
};
|
|
237
255
|
var internal_ota_default = internalOta;
|
|
256
|
+
|
|
257
|
+
// src/media-manager.ts
|
|
258
|
+
var MediaError = class extends Error {
|
|
259
|
+
status;
|
|
260
|
+
url;
|
|
261
|
+
body;
|
|
262
|
+
constructor(args) {
|
|
263
|
+
super(args.message);
|
|
264
|
+
this.name = "MediaError";
|
|
265
|
+
this.status = args.status;
|
|
266
|
+
this.url = args.url;
|
|
267
|
+
this.body = args.body;
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
271
|
+
var IMAGE_EXTENSIONS = ["jpg", "jpeg", "png", "gif", "webp", "svg"];
|
|
272
|
+
var BASE64_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
273
|
+
function bytesToBase64(bytes) {
|
|
274
|
+
const enc = (n) => BASE64_ALPHABET[n];
|
|
275
|
+
let out = "";
|
|
276
|
+
let i = 0;
|
|
277
|
+
for (; i + 2 < bytes.length; i += 3) {
|
|
278
|
+
const n = bytes[i] << 16 | bytes[i + 1] << 8 | bytes[i + 2];
|
|
279
|
+
out += enc(n >> 18 & 63) + enc(n >> 12 & 63) + enc(n >> 6 & 63) + enc(n & 63);
|
|
280
|
+
}
|
|
281
|
+
const rem = bytes.length - i;
|
|
282
|
+
if (rem === 1) {
|
|
283
|
+
const n = bytes[i] << 16;
|
|
284
|
+
out += enc(n >> 18 & 63) + enc(n >> 12 & 63) + "==";
|
|
285
|
+
} else if (rem === 2) {
|
|
286
|
+
const n = bytes[i] << 16 | bytes[i + 1] << 8;
|
|
287
|
+
out += enc(n >> 18 & 63) + enc(n >> 12 & 63) + enc(n >> 6 & 63) + "=";
|
|
288
|
+
}
|
|
289
|
+
return out;
|
|
290
|
+
}
|
|
291
|
+
function inferMediaType(key) {
|
|
292
|
+
const ext = key.split(".").pop()?.toLowerCase() ?? "";
|
|
293
|
+
if (IMAGE_EXTENSIONS.includes(ext)) return "image";
|
|
294
|
+
if (ext === "pdf") return "document";
|
|
295
|
+
if (ext === "txt" || ext === "csv") return "text";
|
|
296
|
+
return "unknown";
|
|
297
|
+
}
|
|
298
|
+
var stripTrailingSlash = (s) => s.replace(/\/+$/, "");
|
|
299
|
+
var stripSlashes = (s) => s.replace(/^\/+|\/+$/g, "");
|
|
300
|
+
var MediaManager = class {
|
|
301
|
+
mediaApiUrl;
|
|
302
|
+
bucket;
|
|
303
|
+
publicPath;
|
|
304
|
+
getHeaders;
|
|
305
|
+
fetchImpl;
|
|
306
|
+
uploadPath;
|
|
307
|
+
uploadBodyMode;
|
|
308
|
+
listPath;
|
|
309
|
+
deletePath;
|
|
310
|
+
timeoutMs;
|
|
311
|
+
constructor(config) {
|
|
312
|
+
if (!config.mediaApiUrl) {
|
|
313
|
+
throw new Error("MediaManager: `mediaApiUrl` is required");
|
|
314
|
+
}
|
|
315
|
+
if (!config.bucket) {
|
|
316
|
+
throw new Error("MediaManager: `bucket` is required");
|
|
317
|
+
}
|
|
318
|
+
const resolvedFetch = config.fetch ?? globalThis.fetch;
|
|
319
|
+
if (!resolvedFetch) {
|
|
320
|
+
throw new Error(
|
|
321
|
+
"MediaManager: no `fetch` available; pass one via config.fetch"
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
this.mediaApiUrl = stripTrailingSlash(config.mediaApiUrl);
|
|
325
|
+
this.bucket = config.bucket;
|
|
326
|
+
this.publicPath = config.publicPath ? stripTrailingSlash(config.publicPath) : "";
|
|
327
|
+
this.getHeaders = config.getHeaders ?? (() => ({}));
|
|
328
|
+
this.fetchImpl = resolvedFetch;
|
|
329
|
+
this.uploadPath = config.uploadPath ?? "/upload/base64";
|
|
330
|
+
this.uploadBodyMode = config.uploadBodyMode ?? "json";
|
|
331
|
+
this.listPath = config.listPath ?? "/list";
|
|
332
|
+
this.deletePath = config.deletePath ?? "/delete";
|
|
333
|
+
this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Build the destination S3 key for a file.
|
|
337
|
+
*
|
|
338
|
+
* - With `fileName`: `${keyPath}/${fileName}.${ext}` (ext taken from original).
|
|
339
|
+
* - Without: `${keyPath}/${originalName}`.
|
|
340
|
+
*
|
|
341
|
+
* `keyPath` may be empty to upload at the bucket root.
|
|
342
|
+
*/
|
|
343
|
+
buildKey(originalName, keyPath = "", fileName) {
|
|
344
|
+
const dir = stripSlashes(keyPath);
|
|
345
|
+
if (fileName) {
|
|
346
|
+
const ext = originalName.includes(".") ? originalName.split(".").pop() : "";
|
|
347
|
+
const named = ext ? `${fileName}.${ext}` : fileName;
|
|
348
|
+
return dir ? `${dir}/${named}` : named;
|
|
349
|
+
}
|
|
350
|
+
return dir ? `${dir}/${originalName}` : originalName;
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Convert a blob/file to a raw base64 string (no data-URL prefix).
|
|
354
|
+
*/
|
|
355
|
+
async toBase64(blob) {
|
|
356
|
+
const buffer = await blob.arrayBuffer();
|
|
357
|
+
return bytesToBase64(new Uint8Array(buffer));
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Upload raw base64 content to the media API.
|
|
361
|
+
*
|
|
362
|
+
* @param args.base64 Raw base64 (no `data:...;base64,` prefix).
|
|
363
|
+
* @param args.key Destination S3 key.
|
|
364
|
+
* @param args.contentType MIME type; defaults to "application/octet-stream".
|
|
365
|
+
* @param args.bucket Overrides the default bucket.
|
|
366
|
+
*/
|
|
367
|
+
async uploadBase64(args) {
|
|
368
|
+
const bucket = args.bucket ?? this.bucket;
|
|
369
|
+
const url = `${this.mediaApiUrl}${this.uploadPath}?bucket=${encodeURIComponent(bucket)}&key=${encodeURIComponent(args.key)}`;
|
|
370
|
+
const data = this.uploadBodyMode === "raw" ? (
|
|
371
|
+
// Send the bare base64 string; the endpoint base64-decodes the whole
|
|
372
|
+
// body and ignores the content type.
|
|
373
|
+
await this.request(
|
|
374
|
+
url,
|
|
375
|
+
{ method: "POST", body: args.base64 },
|
|
376
|
+
{ contentType: "text/plain" }
|
|
377
|
+
)
|
|
378
|
+
) : await this.request(url, {
|
|
379
|
+
method: "POST",
|
|
380
|
+
body: JSON.stringify({
|
|
381
|
+
base64: args.base64,
|
|
382
|
+
contentType: args.contentType ?? "application/octet-stream"
|
|
383
|
+
})
|
|
384
|
+
});
|
|
385
|
+
const returnedUrl = this.extractUrl(data);
|
|
386
|
+
return {
|
|
387
|
+
key: args.key,
|
|
388
|
+
url: returnedUrl ?? this.publicUrl(args.key),
|
|
389
|
+
raw: data
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Upload a single file (browser `File`/`Blob` or any {@link NamedBlobLike}).
|
|
394
|
+
*
|
|
395
|
+
* @param file The file to upload.
|
|
396
|
+
* @param opts.keyPath Folder/prefix the file is stored under.
|
|
397
|
+
* @param opts.fileName Optional base name to use instead of the original.
|
|
398
|
+
* @param opts.contentType Overrides the file's MIME type.
|
|
399
|
+
* @param opts.bucket Overrides the default bucket.
|
|
400
|
+
*/
|
|
401
|
+
async uploadFile(file, opts = {}) {
|
|
402
|
+
const key = this.buildKey(file.name, opts.keyPath, opts.fileName);
|
|
403
|
+
const base64 = await this.toBase64(file);
|
|
404
|
+
return this.uploadBase64({
|
|
405
|
+
base64,
|
|
406
|
+
key,
|
|
407
|
+
contentType: opts.contentType ?? file.type ?? "application/octet-stream",
|
|
408
|
+
bucket: opts.bucket
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Upload several files. Failures are collected per-file rather than aborting
|
|
413
|
+
* the whole batch. `onProgress` (0-100) is called as each file completes.
|
|
414
|
+
*/
|
|
415
|
+
async uploadFiles(files, opts = {}) {
|
|
416
|
+
const results = [];
|
|
417
|
+
const errors = [];
|
|
418
|
+
for (let i = 0; i < files.length; i++) {
|
|
419
|
+
const file = files[i];
|
|
420
|
+
try {
|
|
421
|
+
const result = await this.uploadFile(file, {
|
|
422
|
+
keyPath: opts.keyPath,
|
|
423
|
+
fileName: opts.fileName,
|
|
424
|
+
bucket: opts.bucket
|
|
425
|
+
});
|
|
426
|
+
results.push(result);
|
|
427
|
+
} catch (err) {
|
|
428
|
+
errors.push({
|
|
429
|
+
name: file.name,
|
|
430
|
+
error: err instanceof Error ? err.message : "Unknown error"
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
opts.onProgress?.(Math.round((i + 1) / files.length * 100));
|
|
434
|
+
}
|
|
435
|
+
return { results, errors };
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* List media under a prefix, returning normalized {@link MediaFile}s. Folder
|
|
439
|
+
* entries are filtered out. Pass `type` to filter by coarse media type.
|
|
440
|
+
*/
|
|
441
|
+
async listMedia(params) {
|
|
442
|
+
const query = new URLSearchParams();
|
|
443
|
+
query.append("bucket", params?.bucket ?? this.bucket);
|
|
444
|
+
if (params?.key) query.append("key", params.key);
|
|
445
|
+
const url = `${this.mediaApiUrl}${this.listPath}?${query.toString()}`;
|
|
446
|
+
const data = await this.request(url, { method: "GET" });
|
|
447
|
+
const s3Objects = Array.isArray(data) ? data : data?.results ?? [];
|
|
448
|
+
const mediaFiles = s3Objects.filter((obj) => obj.Type !== "folder").map((obj) => this.toMediaFile(obj));
|
|
449
|
+
const filtered = params?.type ? mediaFiles.filter((f) => f.type === params.type) : mediaFiles;
|
|
450
|
+
return { results: filtered, total: filtered.length, s3Objects };
|
|
451
|
+
}
|
|
452
|
+
/**
|
|
453
|
+
* Delete an object by its S3 key.
|
|
454
|
+
*/
|
|
455
|
+
async deleteMedia(key, bucket) {
|
|
456
|
+
const query = new URLSearchParams();
|
|
457
|
+
query.append("key", key);
|
|
458
|
+
query.append("bucket", bucket ?? this.bucket);
|
|
459
|
+
const url = `${this.mediaApiUrl}${this.deletePath}?${query.toString()}`;
|
|
460
|
+
const data = await this.request(url, { method: "DELETE" });
|
|
461
|
+
return { success: true, raw: data };
|
|
462
|
+
}
|
|
463
|
+
/** Build the public URL for a key from `publicPath`. */
|
|
464
|
+
publicUrl(key) {
|
|
465
|
+
return this.publicPath ? `${this.publicPath}/${key}` : key;
|
|
466
|
+
}
|
|
467
|
+
/** Normalize a raw S3 object into a {@link MediaFile}. */
|
|
468
|
+
toMediaFile(obj) {
|
|
469
|
+
const fileName = obj.Key.split("/").pop() || obj.Key;
|
|
470
|
+
return {
|
|
471
|
+
id: obj.Key,
|
|
472
|
+
fileName,
|
|
473
|
+
originalName: fileName,
|
|
474
|
+
url: obj.url ?? this.publicUrl(obj.Key),
|
|
475
|
+
type: inferMediaType(obj.Key),
|
|
476
|
+
size: obj.Size ?? 0,
|
|
477
|
+
uploadedAt: obj.LastModified ?? "",
|
|
478
|
+
path: obj.Key,
|
|
479
|
+
s3Key: obj.Key
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
/** Pull a URL out of the various shapes the API might return. */
|
|
483
|
+
extractUrl(data) {
|
|
484
|
+
if (!data || typeof data !== "object") return void 0;
|
|
485
|
+
const d = data;
|
|
486
|
+
return d.results?.url ?? d.url;
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Perform a request with merged headers, timeout, and error handling.
|
|
490
|
+
* Returns the parsed JSON body (or `{}` if the body is not JSON).
|
|
491
|
+
*/
|
|
492
|
+
async request(url, init, opts) {
|
|
493
|
+
const provided = await this.getHeaders();
|
|
494
|
+
const headers = {
|
|
495
|
+
"Content-Type": "application/json",
|
|
496
|
+
...provided,
|
|
497
|
+
// A per-call content type wins over the default and any provided header,
|
|
498
|
+
// since it reflects how this request's body is actually encoded.
|
|
499
|
+
...opts?.contentType ? { "Content-Type": opts.contentType } : {}
|
|
500
|
+
};
|
|
501
|
+
const controller = this.timeoutMs > 0 && typeof AbortController !== "undefined" ? new AbortController() : void 0;
|
|
502
|
+
const timer = controller && this.timeoutMs > 0 ? setTimeout(() => controller.abort(), this.timeoutMs) : void 0;
|
|
503
|
+
let response;
|
|
504
|
+
try {
|
|
505
|
+
response = await this.fetchImpl(url, {
|
|
506
|
+
...init,
|
|
507
|
+
headers,
|
|
508
|
+
signal: controller?.signal
|
|
509
|
+
});
|
|
510
|
+
} catch (err) {
|
|
511
|
+
throw new MediaError({
|
|
512
|
+
message: err instanceof Error && err.name === "AbortError" ? `Media request timed out after ${this.timeoutMs}ms: ${url}` : `Media request failed to send: ${url}`,
|
|
513
|
+
status: 0,
|
|
514
|
+
url,
|
|
515
|
+
body: err
|
|
516
|
+
});
|
|
517
|
+
} finally {
|
|
518
|
+
if (timer) clearTimeout(timer);
|
|
519
|
+
}
|
|
520
|
+
const body = await response.json().catch(() => ({}));
|
|
521
|
+
if (!response.ok) {
|
|
522
|
+
throw new MediaError({
|
|
523
|
+
message: `Media request failed: ${response.status} ${response.statusText}`,
|
|
524
|
+
status: response.status,
|
|
525
|
+
url,
|
|
526
|
+
body
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
return body;
|
|
530
|
+
}
|
|
531
|
+
};
|
|
238
532
|
// Annotate the CommonJS export names for ESM import in node:
|
|
239
533
|
0 && (module.exports = {
|
|
240
534
|
HoganApiError,
|
|
241
535
|
HoganInternalOTAClient,
|
|
242
536
|
HoganNetworkError,
|
|
537
|
+
MediaError,
|
|
538
|
+
MediaManager,
|
|
243
539
|
VERSION,
|
|
540
|
+
bytesToBase64,
|
|
541
|
+
inferMediaType,
|
|
244
542
|
version
|
|
245
543
|
});
|
|
246
544
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/version.ts","../src/errors.ts","../src/resources/requests.ts","../src/internal-ota.ts"],"sourcesContent":["// Barrel export for @lagunacreek/hogan-pms-client.\n//\n// As resources, the HoganClient, auth providers and error types are added,\n// re-export them from here so consumers have a single import surface.\nexport { version, VERSION } from \"./version.js\";\nexport { default as HoganInternalOTAClient } from \"./internal-ota.js\";\nexport * from \"./types/internal-ota/whoami.js\";\nexport * from \"./types/internal-ota/org.js\";\nexport * from \"./types/internal-ota/property.js\";\nexport * from \"./types/internal-ota/property-detail.js\";\nexport * from \"./types/internal-ota/availability.js\";\nexport * from \"./types/internal-ota/amenity.js\";\nexport * from \"./types/internal-ota/room-type.js\";\nexport * from \"./types/internal-ota/room-availability.js\";\nexport * from \"./types/internal-ota/calculate-stay.js\";\nexport * from \"./types/internal-ota/reservation.js\";\nexport * from \"./types/internal-ota/make-reservation.js\";\nexport * from \"./types/internal-ota/replicant.js\";\nexport * from \"./errors.js\";","// `__PACKAGE_VERSION__` is replaced at build time by tsup's `define` (see\n// tsup.config.ts) and by vitest's `define` during tests, both sourced from\n// package.json. The fallback keeps the value sane if the package is consumed\n// in an unexpected way where the replacement did not happen.\ndeclare const __PACKAGE_VERSION__: string | undefined;\n\n/**\n * The version of this package, as declared in its package.json.\n */\nexport const VERSION: string =\n typeof __PACKAGE_VERSION__ === \"string\" ? __PACKAGE_VERSION__ : \"0.0.0\";\n\n/**\n * Returns the version of the hogan-pms-client package.\n */\nexport function version(): string {\n return VERSION;\n}\n","// Error types thrown by the Hogan client. Consumers can branch on these\n// with `instanceof` instead of inspecting raw responses.\n\n/**\n * Thrown when the Hogan API responds with a non-2xx status. Carries the\n * status code and the parsed body (when available) for inspection.\n */\nexport class HoganApiError extends Error {\n readonly status: number;\n readonly statusText: string;\n readonly method: string;\n readonly url: string;\n /** Parsed response body, or the raw text if it was not JSON. */\n readonly body: unknown;\n\n constructor(args: {\n status: number;\n statusText: string;\n method: string;\n url: string;\n body: unknown;\n }) {\n super(\n `Hogan API ${args.method} ${args.url} failed: ${args.status} ${args.statusText}`,\n );\n this.name = \"HoganApiError\";\n this.status = args.status;\n this.statusText = args.statusText;\n this.method = args.method;\n this.url = args.url;\n this.body = args.body;\n }\n}\n\n/**\n * Thrown when the request never produced an HTTP response at all — a network\n * failure, DNS error, timeout/abort, etc.\n */\nexport class HoganNetworkError extends Error {\n readonly method: string;\n readonly url: string;\n\n constructor(args: { method: string; url: string; cause: unknown }) {\n super(`Hogan API request to ${args.method} ${args.url} failed to send`, {\n cause: args.cause,\n });\n this.name = \"HoganNetworkError\";\n this.method = args.method;\n this.url = args.url;\n }\n}\n","/**\n * @description Utility functions for making API requests to the Hogan PMS API.\n * Each verb (GET, POST, PUT, DELETE) delegates to a single `request` helper that\n * sets the auth header, reads the response body exactly once, and converts\n * failures into typed errors:\n * - non-2xx responses throw `HoganApiError` (with status + parsed body)\n * - transport failures (DNS, offline, abort/timeout) throw `HoganNetworkError`\n */\n\nimport { HoganApiError, HoganNetworkError } from \"../errors.js\";\n\n/**\n * @description Build the headers sent on every request. Auth is the OTA\n * `x-api-key` header (the API does not use a bearer token).\n * @param apiKey string\n */\nexport const requestHeaders = (apiKey: string): Record<string, string> => ({\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n \"x-api-key\": `${apiKey}`,\n});\n\n/**\n * @description Core request configuration for the `request` helper.\n * @param method string - HTTP verb (GET, POST, etc.)\n * @param url string - The URL to send the request to.\n * @param apiKey string - The API key for authentication.\n * @body JSON-serialisable request body, body Omitted for GET/DELETE.\n */\ninterface RequestConfig {\n method: string;\n url: string;\n apiKey: string;\n body?: object;\n}\n\n/**\n * @description Read a fetch Response body a single time. Returns parsed JSON when\n * the body is JSON, the raw text when it is not, and `undefined` for empty/no\n * content (e.g. 204). Never throws on parse failure.\n */\nconst parseBody = async (response: Response): Promise<unknown> => {\n if (response.status === 204 || response.status === 205) {\n return undefined;\n }\n const text = await response.text();\n if (text === \"\") {\n return undefined;\n }\n try {\n return JSON.parse(text);\n } catch {\n // Error pages are sometimes plain text or HTML; hand back the raw text.\n return text;\n }\n};\n\n/**\n * @description Core request helper shared by every verb.\n * @returns Promise<T> - the parsed response body.\n * @throws HoganApiError when the server responds with a non-2xx status.\n * @throws HoganNetworkError when the request never reaches the server.\n */\nconst request = async <T>({\n method,\n url,\n apiKey,\n body,\n}: RequestConfig): Promise<T> => {\n const headers = requestHeaders(apiKey);\n\n let response: Response;\n try {\n response = await fetch(url, {\n method,\n headers,\n ...(body !== undefined ? { body: JSON.stringify(body) } : {}),\n });\n } catch (cause) {\n throw new HoganNetworkError({ method, url, cause });\n }\n\n const parsed = await parseBody(response);\n\n if (!response.ok) {\n throw new HoganApiError({\n status: response.status,\n statusText: response.statusText,\n method,\n url,\n body: parsed,\n });\n }\n\n return parsed as T;\n};\n\n/**\n * @description Make a GET request to the Hogan PMS API.\n * @param url string - The URL to send the request to.\n * @param apiKey string - The API key for authentication.\n */\nexport const getRequest = <T = unknown>(\n url: string,\n apiKey: string,\n): Promise<T> => request<T>({ method: \"GET\", url, apiKey });\n\n/**\n * @description Make a POST request to the Hogan PMS API.\n * @param url string - The URL to send the request to.\n * @param apiKey string - The API key for authentication.\n * @param body object - The data to send in the request body.\n */\nexport const postRequest = <T = unknown>(\n url: string,\n apiKey: string,\n body: object,\n): Promise<T> => request<T>({ method: \"POST\", url, apiKey, body });\n\n/**\n * @description Make a PUT request to the Hogan PMS API.\n * @param url string - The URL to send the request to.\n * @param apiKey string - The API key for authentication.\n * @param body object - The data to send in the request body.\n */\nexport const putRequest = <T = unknown>(\n url: string,\n apiKey: string,\n body: object,\n): Promise<T> => request<T>({ method: \"PUT\", url, apiKey, body });\n\n/**\n * @description Make a DELETE request to the Hogan PMS API.\n * @param url string - The URL to send the request to.\n * @param apiKey string - The API key for authentication.\n */\nexport const deleteRequest = <T = unknown>(\n url: string,\n apiKey: string,\n): Promise<T> => request<T>({ method: \"DELETE\", url, apiKey });\n","\n\nimport type { WhoAmIType } from \"./types/internal-ota/whoami\";\nimport type { orgType } from \"./types/internal-ota/org\";\nimport type { propertyDetailType } from \"./types/internal-ota/property-detail\";\nimport type { availabilityType, availabilityQueryType } from \"./types/internal-ota/availability\";\nimport type { propertyAmenityType } from \"./types/internal-ota/amenity\";\nimport type { roomTypeType } from \"./types/internal-ota/room-type\";\nimport type { roomAvailabilityType, roomAvailabilityQueryType } from \"./types/internal-ota/room-availability\";\nimport type { paymentCalculationType, calculateStayQueryType } from \"./types/internal-ota/calculate-stay\";\nimport type { ReservationDetailOutput } from \"./types/internal-ota/reservation\";\nimport type { MakeReservationPayloadOTA, MakeReservationResponse } from \"./types/internal-ota/make-reservation\";\nimport type { replicantType, replicantQueryType } from \"./types/internal-ota/replicant\";\nimport { getRequest, postRequest } from \"./resources/requests\";\n\n\nconst internalOta = class {\n\n apiKey: string;\n env: string;\n version: string;\n baseUrl: string;\n\n getBaseUrl() :string {\n this.env = this.env.toUpperCase();\n switch ( this.env ) {\n case \"PROD\":\n case \"PRODUCTION\":\n return `https://production-api.lagunacreek.cloud/ota/${this.version}`;\n case \"TEST\":\n case \"TESTING\":\n return `https://testing-api.lagunacreek.net/ota/${this.version}`;\n case \"DEV\":\n case \"DEVELOPMENT\":\n return `https://development-api.lagunacreek.net/ota/${this.version}`;\n case \"LOCAL\":\n default:\n return `http://localhost:50000/ota/${this.version}`;\n }\n }\n\n\n constructor( apiKey=\"\", env=\"LOCAL\", version=\"v1\" ) {\n this.apiKey = apiKey;\n this.env = env;\n this.version = version;\n this.baseUrl = this.getBaseUrl();\n }\n\n overrideBaseUrl( baseUrl: string ) {\n this.baseUrl = baseUrl;\n }\n\n setAttribute( key:\"apiKey\" | \"env\" | \"version\" | \"baseUrl\", value:string ) {\n this[`${key}`] = value;\n }\n\n getAttribute( key:\"apiKey\" | \"env\" | \"version\" | \"baseUrl\" ) :string {\n return this[key];\n }\n\n\n async whoami(): Promise<WhoAmIType> {\n return getRequest<WhoAmIType>( `${this.baseUrl}/getWhoami`, this.apiKey );\n }\n\n\n async getOrg(): Promise<orgType> {\n return getRequest<orgType>( `${this.baseUrl}/getOrg`, this.apiKey );\n }\n\n\n async getProperties(): Promise<orgType> {\n return getRequest<orgType>( `${this.baseUrl}/getProperties`, this.apiKey );\n }\n\n\n async getPropertyDetail( id: number | string ): Promise<propertyDetailType> {\n return getRequest<propertyDetailType>( `${this.baseUrl}/getPropertyDetail/${id}`, this.apiKey );\n }\n\n\n async getAvailability( params: availabilityQueryType ): Promise<availabilityType> {\n // rate_code is resolved server-side from the API key, so it is not sent here.\n const query = new URLSearchParams({\n start_date: params.start_date,\n end_date: params.end_date,\n number_of_guests: String( params.number_of_guests ),\n property_id: String( params.property_id ),\n });\n return getRequest<availabilityType>( `${this.baseUrl}/getAvailability?${query}`, this.apiKey );\n }\n\n\n async getPropertyAmenities( property_id: number | string ): Promise<propertyAmenityType[]> {\n return getRequest<propertyAmenityType[]>( `${this.baseUrl}/getPropertyAmenities/${property_id}`, this.apiKey );\n }\n\n\n async getRoomTypes( property_id: number | string ): Promise<roomTypeType[]> {\n const query = new URLSearchParams({ property_id: String( property_id ) });\n return getRequest<roomTypeType[]>( `${this.baseUrl}/getRoomTypes?${query}`, this.apiKey );\n }\n\n\n async getRoomAvailability( params: roomAvailabilityQueryType ): Promise<roomAvailabilityType[]> {\n const query = new URLSearchParams({\n start_date: params.start_date,\n end_date: params.end_date,\n });\n // room_type_ids is a repeated query param (?room_type_ids=1&room_type_ids=2).\n for ( const id of params.room_type_ids ) {\n query.append( \"room_type_ids\", String( id ) );\n }\n return getRequest<roomAvailabilityType[]>( `${this.baseUrl}/getRoomAvailability?${query}`, this.apiKey );\n }\n\n\n async getCalculateStay( params: calculateStayQueryType ): Promise<paymentCalculationType> {\n // rate_code is resolved server-side from the API key, so it is not sent here.\n const query = new URLSearchParams({\n check_in: params.check_in,\n check_out: params.check_out,\n no_of_rooms: String( params.no_of_rooms ),\n });\n for ( const id of params.room_type_ids ) {\n query.append( \"room_type_ids\", String( id ) );\n }\n return getRequest<paymentCalculationType>( `${this.baseUrl}/getCalculateStay?${query}`, this.apiKey );\n }\n\n\n // Property maps are returned as plain-text SVG markup (media_type text/plain).\n // getMaps concatenates every property's SVG; getMap returns one property's SVG,\n // or undefined when the property has no map (the server replies 204).\n async getPropertyMaps(): Promise<string | undefined> {\n return getRequest<string | undefined>( `${this.baseUrl}/properties/getMaps`, this.apiKey );\n }\n\n\n async getPropertyMap( property_id: number | string ): Promise<string | undefined> {\n return getRequest<string | undefined>( `${this.baseUrl}/properties/getMap/${property_id}`, this.apiKey );\n }\n\n\n async getReservation( reservation_id: number ): Promise<ReservationDetailOutput> {\n const query = new URLSearchParams({ reservation_id: String( reservation_id ) });\n return getRequest<ReservationDetailOutput>( `${this.baseUrl}/getReservation?${query}`, this.apiKey );\n }\n\n\n async postReservation( payload: MakeReservationPayloadOTA ): Promise<MakeReservationResponse> {\n return postRequest<MakeReservationResponse>( `${this.baseUrl}/postReservation`, this.apiKey, payload );\n }\n\n\n // Replicant feed: one page of the cross-tenant property list an OTA partner\n // caches to resell our clients' inventory. Page size is fixed server-side;\n // walk pages by following `next_page` / `next_link` until next_page is false.\n async getReplicant( params: replicantQueryType = {} ): Promise<replicantType> {\n const query = new URLSearchParams();\n if ( params.page !== undefined ) {\n query.set( \"page\", String( params.page ) );\n }\n const qs = query.toString();\n return getRequest<replicantType>( `${this.baseUrl}/getReplicant${qs ? `?${qs}` : \"\"}`, this.apiKey );\n }\n\n\n};\n\nexport default internalOta;"],"mappings":"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","names":["version"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/version.ts","../src/errors.ts","../src/resources/requests.ts","../src/internal-ota.ts","../src/media-manager.ts"],"sourcesContent":["// Barrel export for @lagunacreek/hogan-pms-client.\n//\n// As resources, the HoganClient, auth providers and error types are added,\n// re-export them from here so consumers have a single import surface.\nexport { version, VERSION } from \"./version.js\";\nexport { default as HoganInternalOTAClient } from \"./internal-ota.js\";\nexport { MediaManager, MediaError, bytesToBase64, inferMediaType } from \"./media-manager.js\";\nexport type {\n MediaManagerConfig,\n MediaFile,\n MediaListResponse,\n S3MediaObject,\n UploadResult,\n MultiUploadResult,\n BlobLike,\n NamedBlobLike,\n FetchLike,\n HeadersProvider,\n} from \"./types/media-manager.js\";\nexport * from \"./types/internal-ota/whoami.js\";\nexport * from \"./types/internal-ota/org.js\";\nexport * from \"./types/internal-ota/property.js\";\nexport * from \"./types/internal-ota/property-detail.js\";\nexport * from \"./types/internal-ota/availability.js\";\nexport * from \"./types/internal-ota/amenity.js\";\nexport * from \"./types/internal-ota/room-type.js\";\nexport * from \"./types/internal-ota/room-availability.js\";\nexport * from \"./types/internal-ota/calculate-stay.js\";\nexport * from \"./types/internal-ota/reservation.js\";\nexport * from \"./types/internal-ota/make-reservation.js\";\nexport * from \"./types/internal-ota/replicant.js\";\nexport * from \"./errors.js\";","// `__PACKAGE_VERSION__` is replaced at build time by tsup's `define` (see\n// tsup.config.ts) and by vitest's `define` during tests, both sourced from\n// package.json. The fallback keeps the value sane if the package is consumed\n// in an unexpected way where the replacement did not happen.\ndeclare const __PACKAGE_VERSION__: string | undefined;\n\n/**\n * The version of this package, as declared in its package.json.\n */\nexport const VERSION: string =\n typeof __PACKAGE_VERSION__ === \"string\" ? __PACKAGE_VERSION__ : \"0.0.0\";\n\n/**\n * Returns the version of the hogan-pms-client package.\n */\nexport function version(): string {\n return VERSION;\n}\n","// Error types thrown by the Hogan client. Consumers can branch on these\n// with `instanceof` instead of inspecting raw responses.\n\n/**\n * Thrown when the Hogan API responds with a non-2xx status. Carries the\n * status code and the parsed body (when available) for inspection.\n */\nexport class HoganApiError extends Error {\n readonly status: number;\n readonly statusText: string;\n readonly method: string;\n readonly url: string;\n /** Parsed response body, or the raw text if it was not JSON. */\n readonly body: unknown;\n\n constructor(args: {\n status: number;\n statusText: string;\n method: string;\n url: string;\n body: unknown;\n }) {\n super(\n `Hogan API ${args.method} ${args.url} failed: ${args.status} ${args.statusText}`,\n );\n this.name = \"HoganApiError\";\n this.status = args.status;\n this.statusText = args.statusText;\n this.method = args.method;\n this.url = args.url;\n this.body = args.body;\n }\n}\n\n/**\n * Thrown when the request never produced an HTTP response at all — a network\n * failure, DNS error, timeout/abort, etc.\n */\nexport class HoganNetworkError extends Error {\n readonly method: string;\n readonly url: string;\n\n constructor(args: { method: string; url: string; cause: unknown }) {\n super(`Hogan API request to ${args.method} ${args.url} failed to send`, {\n cause: args.cause,\n });\n this.name = \"HoganNetworkError\";\n this.method = args.method;\n this.url = args.url;\n }\n}\n","/**\n * @description Utility functions for making API requests to the Hogan PMS API.\n * Each verb (GET, POST, PUT, DELETE) delegates to a single `request` helper that\n * sets the auth header, reads the response body exactly once, and converts\n * failures into typed errors:\n * - non-2xx responses throw `HoganApiError` (with status + parsed body)\n * - transport failures (DNS, offline, abort/timeout) throw `HoganNetworkError`\n */\n\nimport { HoganApiError, HoganNetworkError } from \"../errors.js\";\n\n/**\n * @description Build the headers sent on every request. Auth is the OTA\n * `x-api-key` header (the API does not use a bearer token).\n * @param apiKey string\n */\nexport const requestHeaders = (apiKey: string): Record<string, string> => ({\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n \"x-api-key\": `${apiKey}`,\n});\n\n/**\n * @description Core request configuration for the `request` helper.\n * @param method string - HTTP verb (GET, POST, etc.)\n * @param url string - The URL to send the request to.\n * @param apiKey string - The API key for authentication.\n * @body JSON-serialisable request body, body Omitted for GET/DELETE.\n */\ninterface RequestConfig {\n method: string;\n url: string;\n apiKey: string;\n body?: object;\n}\n\n/**\n * @description Read a fetch Response body a single time. Returns parsed JSON when\n * the body is JSON, the raw text when it is not, and `undefined` for empty/no\n * content (e.g. 204). Never throws on parse failure.\n */\nconst parseBody = async (response: Response): Promise<unknown> => {\n if (response.status === 204 || response.status === 205) {\n return undefined;\n }\n const text = await response.text();\n if (text === \"\") {\n return undefined;\n }\n try {\n return JSON.parse(text);\n } catch {\n // Error pages are sometimes plain text or HTML; hand back the raw text.\n return text;\n }\n};\n\n/**\n * @description Core request helper shared by every verb.\n * @returns Promise<T> - the parsed response body.\n * @throws HoganApiError when the server responds with a non-2xx status.\n * @throws HoganNetworkError when the request never reaches the server.\n */\nconst request = async <T>({\n method,\n url,\n apiKey,\n body,\n}: RequestConfig): Promise<T> => {\n const headers = requestHeaders(apiKey);\n\n let response: Response;\n try {\n response = await fetch(url, {\n method,\n headers,\n ...(body !== undefined ? { body: JSON.stringify(body) } : {}),\n });\n } catch (cause) {\n throw new HoganNetworkError({ method, url, cause });\n }\n\n const parsed = await parseBody(response);\n\n if (!response.ok) {\n throw new HoganApiError({\n status: response.status,\n statusText: response.statusText,\n method,\n url,\n body: parsed,\n });\n }\n\n return parsed as T;\n};\n\n/**\n * @description Make a GET request to the Hogan PMS API.\n * @param url string - The URL to send the request to.\n * @param apiKey string - The API key for authentication.\n */\nexport const getRequest = <T = unknown>(\n url: string,\n apiKey: string,\n): Promise<T> => request<T>({ method: \"GET\", url, apiKey });\n\n/**\n * @description Make a POST request to the Hogan PMS API.\n * @param url string - The URL to send the request to.\n * @param apiKey string - The API key for authentication.\n * @param body object - The data to send in the request body.\n */\nexport const postRequest = <T = unknown>(\n url: string,\n apiKey: string,\n body: object,\n): Promise<T> => request<T>({ method: \"POST\", url, apiKey, body });\n\n/**\n * @description Make a PUT request to the Hogan PMS API.\n * @param url string - The URL to send the request to.\n * @param apiKey string - The API key for authentication.\n * @param body object - The data to send in the request body.\n */\nexport const putRequest = <T = unknown>(\n url: string,\n apiKey: string,\n body: object,\n): Promise<T> => request<T>({ method: \"PUT\", url, apiKey, body });\n\n/**\n * @description Make a DELETE request to the Hogan PMS API.\n * @param url string - The URL to send the request to.\n * @param apiKey string - The API key for authentication.\n */\nexport const deleteRequest = <T = unknown>(\n url: string,\n apiKey: string,\n): Promise<T> => request<T>({ method: \"DELETE\", url, apiKey });\n","\n\nimport type { WhoAmIType } from \"./types/internal-ota/whoami\";\nimport type { orgType } from \"./types/internal-ota/org\";\nimport type { propertyDetailType } from \"./types/internal-ota/property-detail\";\nimport type { availabilityType, availabilityQueryType } from \"./types/internal-ota/availability\";\nimport type { propertyAmenityType } from \"./types/internal-ota/amenity\";\nimport type { roomTypeType } from \"./types/internal-ota/room-type\";\nimport type { roomAvailabilityType, roomAvailabilityQueryType } from \"./types/internal-ota/room-availability\";\nimport type { paymentCalculationType, calculateStayQueryType } from \"./types/internal-ota/calculate-stay\";\nimport type { ReservationDetailOutput } from \"./types/internal-ota/reservation\";\nimport type { MakeReservationPayloadOTA, MakeReservationResponse } from \"./types/internal-ota/make-reservation\";\nimport type { replicantType, replicantQueryType } from \"./types/internal-ota/replicant\";\nimport { getRequest, postRequest } from \"./resources/requests\";\n\n\nconst internalOta = class {\n\n apiKey: string;\n env: string;\n version: string;\n baseUrl: string;\n\n getBaseUrl() :string {\n this.env = this.env.toUpperCase();\n switch ( this.env ) {\n case \"PROD\":\n case \"PRODUCTION\":\n return `https://production-api.lagunacreek.cloud/ota/${this.version}`;\n case \"TEST\":\n case \"TESTING\":\n return `https://testing-api.lagunacreek.net/ota/${this.version}`;\n case \"DEV\":\n case \"DEVELOPMENT\":\n return `https://development-api.lagunacreek.net/ota/${this.version}`;\n case \"LOCAL\":\n default:\n return `http://localhost:50000/ota/${this.version}`;\n }\n }\n\n\n constructor( apiKey=\"\", env=\"LOCAL\", version=\"v1\" ) {\n this.apiKey = apiKey;\n this.env = env;\n this.version = version;\n this.baseUrl = this.getBaseUrl();\n }\n\n overrideBaseUrl( baseUrl: string ) {\n this.baseUrl = baseUrl;\n }\n\n setAttribute( key:\"apiKey\" | \"env\" | \"version\" | \"baseUrl\", value:string ) {\n this[`${key}`] = value;\n }\n\n getAttribute( key:\"apiKey\" | \"env\" | \"version\" | \"baseUrl\" ) :string {\n return this[key];\n }\n\n\n async whoami(): Promise<WhoAmIType> {\n return getRequest<WhoAmIType>( `${this.baseUrl}/getWhoami`, this.apiKey );\n }\n\n\n async getOrg(): Promise<orgType> {\n return getRequest<orgType>( `${this.baseUrl}/getOrg`, this.apiKey );\n }\n\n\n async getProperties(): Promise<orgType> {\n return getRequest<orgType>( `${this.baseUrl}/getProperties`, this.apiKey );\n }\n\n\n async getPropertyDetail( id: number | string ): Promise<propertyDetailType> {\n return getRequest<propertyDetailType>( `${this.baseUrl}/getPropertyDetail/${id}`, this.apiKey );\n }\n\n\n async getAvailability( params: availabilityQueryType ): Promise<availabilityType> {\n // rate_code is resolved server-side from the API key, so it is not sent here.\n const query = new URLSearchParams({\n start_date: params.start_date,\n end_date: params.end_date,\n number_of_guests: String( params.number_of_guests ),\n property_id: String( params.property_id ),\n });\n return getRequest<availabilityType>( `${this.baseUrl}/getAvailability?${query}`, this.apiKey );\n }\n\n\n async getPropertyAmenities( property_id: number | string ): Promise<propertyAmenityType[]> {\n return getRequest<propertyAmenityType[]>( `${this.baseUrl}/getPropertyAmenities/${property_id}`, this.apiKey );\n }\n\n\n async getRoomTypes( property_id: number | string ): Promise<roomTypeType[]> {\n const query = new URLSearchParams({ property_id: String( property_id ) });\n return getRequest<roomTypeType[]>( `${this.baseUrl}/getRoomTypes?${query}`, this.apiKey );\n }\n\n\n async getRoomAvailability( params: roomAvailabilityQueryType ): Promise<roomAvailabilityType[]> {\n const query = new URLSearchParams({\n start_date: params.start_date,\n end_date: params.end_date,\n });\n // room_type_ids is a repeated query param (?room_type_ids=1&room_type_ids=2).\n for ( const id of params.room_type_ids ) {\n query.append( \"room_type_ids\", String( id ) );\n }\n return getRequest<roomAvailabilityType[]>( `${this.baseUrl}/getRoomAvailability?${query}`, this.apiKey );\n }\n\n\n async getCalculateStay( params: calculateStayQueryType ): Promise<paymentCalculationType> {\n // rate_code is resolved server-side from the API key, so it is not sent here.\n const query = new URLSearchParams({\n check_in: params.check_in,\n check_out: params.check_out,\n no_of_rooms: String( params.no_of_rooms ),\n });\n for ( const id of params.room_type_ids ) {\n query.append( \"room_type_ids\", String( id ) );\n }\n return getRequest<paymentCalculationType>( `${this.baseUrl}/getCalculateStay?${query}`, this.apiKey );\n }\n\n\n // Property maps are returned as plain-text SVG markup (media_type text/plain).\n // getMaps concatenates every property's SVG; getMap returns one property's SVG,\n // or undefined when the property has no map (the server replies 204).\n async getPropertyMaps(): Promise<string | undefined> {\n return getRequest<string | undefined>( `${this.baseUrl}/properties/getMaps`, this.apiKey );\n }\n\n\n async getPropertyMap( property_id: number | string ): Promise<string | undefined> {\n return getRequest<string | undefined>( `${this.baseUrl}/properties/getMap/${property_id}`, this.apiKey );\n }\n\n\n async getReservation( reservation_id: number ): Promise<ReservationDetailOutput> {\n const query = new URLSearchParams({ reservation_id: String( reservation_id ) });\n return getRequest<ReservationDetailOutput>( `${this.baseUrl}/getReservation?${query}`, this.apiKey );\n }\n\n\n async postReservation( payload: MakeReservationPayloadOTA ): Promise<MakeReservationResponse> {\n return postRequest<MakeReservationResponse>( `${this.baseUrl}/postReservation`, this.apiKey, payload );\n }\n\n\n // Replicant feed: one page of the cross-tenant property list an OTA partner\n // caches to resell our clients' inventory. Page size is fixed server-side;\n // walk pages by following `next_page` / `next_link` until next_page is false.\n async getReplicant( params: replicantQueryType = {} ): Promise<replicantType> {\n const query = new URLSearchParams();\n if ( params.page !== undefined ) {\n query.set( \"page\", String( params.page ) );\n }\n const qs = query.toString();\n return getRequest<replicantType>( `${this.baseUrl}/getReplicant${qs ? `?${qs}` : \"\"}`, this.apiKey );\n }\n\n\n // Fetches every page of the replicant feed and returns all properties in a\n // single array. Walks pages sequentially until next_page is false.\n async getAllReplicantPages(): Promise<replicantType[\"results\"]> {\n const all: replicantType[\"results\"] = [];\n let page = 1;\n let hasNext = true;\n while ( hasNext ) {\n const response = await this.getReplicant( { page } );\n all.push( ...response.results );\n hasNext = response.next_page;\n page += 1;\n }\n return all;\n }\n\n\n};\n\nexport default internalOta;","// Reusable media manager for uploading, listing and deleting objects against\n// the Hogan/POS media API (S3-backed). Extracted from the POS client so the\n// same logic can be shared across projects.\n//\n// This module is intentionally runtime-agnostic: it has no React/UI, no\n// dependency on the app `config`, and no DOM-only APIs. It relies only on a\n// `fetch` implementation (global in browsers and Node 18+) and on structural\n// \"blob-like\" inputs, so it compiles under a DOM-free `lib` and runs anywhere.\n\nimport type {\n S3MediaObject,\n MediaFile,\n MediaListResponse,\n UploadResult,\n MultiUploadResult,\n BlobLike,\n NamedBlobLike,\n FetchLike,\n HeadersProvider,\n MediaManagerConfig,\n} from \"./types/media-manager.js\";\n\n/**\n * Error thrown when the media API responds with a non-2xx status.\n */\nexport class MediaError extends Error {\n readonly status: number;\n readonly url: string;\n readonly body: unknown;\n\n constructor(args: {\n message: string;\n status: number;\n url: string;\n body?: unknown;\n }) {\n super(args.message);\n this.name = \"MediaError\";\n this.status = args.status;\n this.url = args.url;\n this.body = args.body;\n }\n}\n\nconst DEFAULT_TIMEOUT_MS = 30_000;\nconst IMAGE_EXTENSIONS = [\"jpg\", \"jpeg\", \"png\", \"gif\", \"webp\", \"svg\"];\n\nconst BASE64_ALPHABET =\n \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n\n/**\n * Encode bytes to a standard (non-data-URL) base64 string. Implemented inline\n * so the class does not depend on `Buffer` (Node) or `btoa` (browser).\n */\nexport function bytesToBase64(bytes: Uint8Array): string {\n // Indices are always in range 0-63, so the lookups are non-null.\n const enc = (n: number): string => BASE64_ALPHABET[n] as string;\n let out = \"\";\n let i = 0;\n for (; i + 2 < bytes.length; i += 3) {\n const n =\n ((bytes[i] as number) << 16) |\n ((bytes[i + 1] as number) << 8) |\n (bytes[i + 2] as number);\n out += enc((n >> 18) & 63) + enc((n >> 12) & 63) + enc((n >> 6) & 63) + enc(n & 63);\n }\n const rem = bytes.length - i;\n if (rem === 1) {\n const n = (bytes[i] as number) << 16;\n out += enc((n >> 18) & 63) + enc((n >> 12) & 63) + \"==\";\n } else if (rem === 2) {\n const n = ((bytes[i] as number) << 16) | ((bytes[i + 1] as number) << 8);\n out += enc((n >> 18) & 63) + enc((n >> 12) & 63) + enc((n >> 6) & 63) + \"=\";\n }\n return out;\n}\n\n/**\n * Infer a coarse file type (\"image\" | \"document\" | \"text\" | \"unknown\") from a\n * key/filename extension.\n */\nexport function inferMediaType(key: string): string {\n const ext = key.split(\".\").pop()?.toLowerCase() ?? \"\";\n if (IMAGE_EXTENSIONS.includes(ext)) return \"image\";\n if (ext === \"pdf\") return \"document\";\n if (ext === \"txt\" || ext === \"csv\") return \"text\";\n return \"unknown\";\n}\n\nconst stripTrailingSlash = (s: string): string => s.replace(/\\/+$/, \"\");\nconst stripSlashes = (s: string): string => s.replace(/^\\/+|\\/+$/g, \"\");\n\n/**\n * MediaManager wraps the media API endpoints for upload/list/delete and the\n * conversion logic needed to turn browser files into base64 payloads.\n *\n * @example\n * ```ts\n * const media = new MediaManager({\n * mediaApiUrl: \"https://api.example.com/media\",\n * bucket: \"my-bucket\",\n * publicPath: \"https://cdn.example.com/my-bucket\",\n * getHeaders: async () => ({ Authorization: token, \"X-API-KEY\": apiKey }),\n * });\n * const { url } = await media.uploadFile(file, { keyPath: \"products/123\" });\n * ```\n */\nexport class MediaManager {\n readonly mediaApiUrl: string;\n readonly bucket: string;\n readonly publicPath: string;\n private readonly getHeaders: HeadersProvider;\n private readonly fetchImpl: FetchLike;\n private readonly uploadPath: string;\n private readonly uploadBodyMode: \"json\" | \"raw\";\n private readonly listPath: string;\n private readonly deletePath: string;\n private readonly timeoutMs: number;\n\n constructor(config: MediaManagerConfig) {\n if (!config.mediaApiUrl) {\n throw new Error(\"MediaManager: `mediaApiUrl` is required\");\n }\n if (!config.bucket) {\n throw new Error(\"MediaManager: `bucket` is required\");\n }\n\n const resolvedFetch = config.fetch ?? (globalThis as { fetch?: FetchLike }).fetch;\n if (!resolvedFetch) {\n throw new Error(\n \"MediaManager: no `fetch` available; pass one via config.fetch\",\n );\n }\n\n this.mediaApiUrl = stripTrailingSlash(config.mediaApiUrl);\n this.bucket = config.bucket;\n this.publicPath = config.publicPath\n ? stripTrailingSlash(config.publicPath)\n : \"\";\n this.getHeaders = config.getHeaders ?? (() => ({}));\n this.fetchImpl = resolvedFetch;\n this.uploadPath = config.uploadPath ?? \"/upload/base64\";\n this.uploadBodyMode = config.uploadBodyMode ?? \"json\";\n this.listPath = config.listPath ?? \"/list\";\n this.deletePath = config.deletePath ?? \"/delete\";\n this.timeoutMs = config.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n }\n\n /**\n * Build the destination S3 key for a file.\n *\n * - With `fileName`: `${keyPath}/${fileName}.${ext}` (ext taken from original).\n * - Without: `${keyPath}/${originalName}`.\n *\n * `keyPath` may be empty to upload at the bucket root.\n */\n buildKey(originalName: string, keyPath = \"\", fileName?: string): string {\n const dir = stripSlashes(keyPath);\n if (fileName) {\n const ext = originalName.includes(\".\")\n ? originalName.split(\".\").pop()\n : \"\";\n const named = ext ? `${fileName}.${ext}` : fileName;\n return dir ? `${dir}/${named}` : named;\n }\n return dir ? `${dir}/${originalName}` : originalName;\n }\n\n /**\n * Convert a blob/file to a raw base64 string (no data-URL prefix).\n */\n async toBase64(blob: BlobLike): Promise<string> {\n const buffer = await blob.arrayBuffer();\n return bytesToBase64(new Uint8Array(buffer));\n }\n\n /**\n * Upload raw base64 content to the media API.\n *\n * @param args.base64 Raw base64 (no `data:...;base64,` prefix).\n * @param args.key Destination S3 key.\n * @param args.contentType MIME type; defaults to \"application/octet-stream\".\n * @param args.bucket Overrides the default bucket.\n */\n async uploadBase64(args: {\n base64: string;\n key: string;\n contentType?: string;\n bucket?: string;\n }): Promise<UploadResult> {\n const bucket = args.bucket ?? this.bucket;\n const url =\n `${this.mediaApiUrl}${this.uploadPath}` +\n `?bucket=${encodeURIComponent(bucket)}&key=${encodeURIComponent(args.key)}`;\n\n const data =\n this.uploadBodyMode === \"raw\"\n ? // Send the bare base64 string; the endpoint base64-decodes the whole\n // body and ignores the content type.\n await this.request(\n url,\n { method: \"POST\", body: args.base64 },\n { contentType: \"text/plain\" },\n )\n : await this.request(url, {\n method: \"POST\",\n body: JSON.stringify({\n base64: args.base64,\n contentType: args.contentType ?? \"application/octet-stream\",\n }),\n });\n\n const returnedUrl = this.extractUrl(data);\n return {\n key: args.key,\n url: returnedUrl ?? this.publicUrl(args.key),\n raw: data,\n };\n }\n\n /**\n * Upload a single file (browser `File`/`Blob` or any {@link NamedBlobLike}).\n *\n * @param file The file to upload.\n * @param opts.keyPath Folder/prefix the file is stored under.\n * @param opts.fileName Optional base name to use instead of the original.\n * @param opts.contentType Overrides the file's MIME type.\n * @param opts.bucket Overrides the default bucket.\n */\n async uploadFile(\n file: NamedBlobLike,\n opts: {\n keyPath?: string;\n fileName?: string;\n contentType?: string;\n bucket?: string;\n } = {},\n ): Promise<UploadResult> {\n const key = this.buildKey(file.name, opts.keyPath, opts.fileName);\n const base64 = await this.toBase64(file);\n return this.uploadBase64({\n base64,\n key,\n contentType: opts.contentType ?? file.type ?? \"application/octet-stream\",\n bucket: opts.bucket,\n });\n }\n\n /**\n * Upload several files. Failures are collected per-file rather than aborting\n * the whole batch. `onProgress` (0-100) is called as each file completes.\n */\n async uploadFiles(\n files: NamedBlobLike[],\n opts: {\n keyPath?: string;\n fileName?: string;\n bucket?: string;\n onProgress?: (percent: number) => void;\n } = {},\n ): Promise<MultiUploadResult> {\n const results: UploadResult[] = [];\n const errors: Array<{ name: string; error: string }> = [];\n\n for (let i = 0; i < files.length; i++) {\n const file = files[i] as NamedBlobLike;\n try {\n const result = await this.uploadFile(file, {\n keyPath: opts.keyPath,\n fileName: opts.fileName,\n bucket: opts.bucket,\n });\n results.push(result);\n } catch (err) {\n errors.push({\n name: file.name,\n error: err instanceof Error ? err.message : \"Unknown error\",\n });\n }\n opts.onProgress?.(Math.round(((i + 1) / files.length) * 100));\n }\n\n return { results, errors };\n }\n\n /**\n * List media under a prefix, returning normalized {@link MediaFile}s. Folder\n * entries are filtered out. Pass `type` to filter by coarse media type.\n */\n async listMedia(params?: {\n bucket?: string;\n key?: string;\n type?: string;\n }): Promise<MediaListResponse> {\n const query = new URLSearchParams();\n query.append(\"bucket\", params?.bucket ?? this.bucket);\n if (params?.key) query.append(\"key\", params.key);\n\n const url = `${this.mediaApiUrl}${this.listPath}?${query.toString()}`;\n const data = await this.request(url, { method: \"GET\" });\n\n const s3Objects: S3MediaObject[] = Array.isArray(data)\n ? (data as S3MediaObject[])\n : (((data as { results?: S3MediaObject[] })?.results ?? []) as S3MediaObject[]);\n\n const mediaFiles = s3Objects\n .filter((obj) => obj.Type !== \"folder\")\n .map((obj) => this.toMediaFile(obj));\n\n const filtered = params?.type\n ? mediaFiles.filter((f) => f.type === params.type)\n : mediaFiles;\n\n return { results: filtered, total: filtered.length, s3Objects };\n }\n\n /**\n * Delete an object by its S3 key.\n */\n async deleteMedia(\n key: string,\n bucket?: string,\n ): Promise<{ success: boolean; raw: unknown }> {\n const query = new URLSearchParams();\n query.append(\"key\", key);\n query.append(\"bucket\", bucket ?? this.bucket);\n\n const url = `${this.mediaApiUrl}${this.deletePath}?${query.toString()}`;\n const data = await this.request(url, { method: \"DELETE\" });\n return { success: true, raw: data };\n }\n\n /** Build the public URL for a key from `publicPath`. */\n publicUrl(key: string): string {\n return this.publicPath ? `${this.publicPath}/${key}` : key;\n }\n\n /** Normalize a raw S3 object into a {@link MediaFile}. */\n private toMediaFile(obj: S3MediaObject): MediaFile {\n const fileName = obj.Key.split(\"/\").pop() || obj.Key;\n return {\n id: obj.Key,\n fileName,\n originalName: fileName,\n url: obj.url ?? this.publicUrl(obj.Key),\n type: inferMediaType(obj.Key),\n size: obj.Size ?? 0,\n uploadedAt: obj.LastModified ?? \"\",\n path: obj.Key,\n s3Key: obj.Key,\n };\n }\n\n /** Pull a URL out of the various shapes the API might return. */\n private extractUrl(data: unknown): string | undefined {\n if (!data || typeof data !== \"object\") return undefined;\n const d = data as { url?: string; results?: { url?: string } };\n return d.results?.url ?? d.url;\n }\n\n /**\n * Perform a request with merged headers, timeout, and error handling.\n * Returns the parsed JSON body (or `{}` if the body is not JSON).\n */\n private async request(\n url: string,\n init: RequestInit,\n opts?: { contentType?: string },\n ): Promise<unknown> {\n const provided = await this.getHeaders();\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n ...provided,\n // A per-call content type wins over the default and any provided header,\n // since it reflects how this request's body is actually encoded.\n ...(opts?.contentType ? { \"Content-Type\": opts.contentType } : {}),\n };\n\n const controller =\n this.timeoutMs > 0 && typeof AbortController !== \"undefined\"\n ? new AbortController()\n : undefined;\n const timer =\n controller && this.timeoutMs > 0\n ? setTimeout(() => controller.abort(), this.timeoutMs)\n : undefined;\n\n let response: Response;\n try {\n response = await this.fetchImpl(url, {\n ...init,\n headers,\n signal: controller?.signal,\n });\n } catch (err) {\n throw new MediaError({\n message:\n err instanceof Error && err.name === \"AbortError\"\n ? `Media request timed out after ${this.timeoutMs}ms: ${url}`\n : `Media request failed to send: ${url}`,\n status: 0,\n url,\n body: err,\n });\n } finally {\n if (timer) clearTimeout(timer);\n }\n\n const body = await response.json().catch(() => ({}));\n\n if (!response.ok) {\n throw new MediaError({\n message: `Media request failed: ${response.status} ${response.statusText}`,\n status: response.status,\n url,\n body,\n });\n }\n\n return body;\n }\n}\n\nexport default MediaManager;\n"],"mappings":"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","names":["version"]}
|