@spoot/hostfully-api 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FeesApi = void 0;
4
+ const zod_1 = require("zod");
5
+ const enums_1 = require("./enums");
6
+ const PropertyFeeSchema = zod_1.z.object({
7
+ uid: zod_1.z.string().uuid(),
8
+ propertyUid: zod_1.z.string().uuid(),
9
+ amount: zod_1.z.number(),
10
+ amountType: zod_1.z.enum(["TAX", "AMOUNT"]),
11
+ name: zod_1.z.string(),
12
+ optional: zod_1.z.boolean(),
13
+ scope: zod_1.z.enum(["PER_STAY", "PER_GUEST", "PER_NIGHT", "PER_GUEST_PER_NIGHT"]),
14
+ taxationRate: zod_1.z.number(),
15
+ type: zod_1.z.enum([
16
+ "CLEANING",
17
+ "EARLY_ARRIVAL",
18
+ "LATE_ARRIVAL",
19
+ "PET",
20
+ "TAX",
21
+ "CUSTOM",
22
+ ]),
23
+ airbnbType: enums_1.AirbnbFeeTypeSchema.nullable(),
24
+ bookingDotComType: enums_1.BookingDotComFeeTypeSchema.nullable(),
25
+ hostfullyType: zod_1.z.literal("DEFAULT").nullable(),
26
+ hvmiType: enums_1.MarriottFeeTypeSchema.nullable(),
27
+ vrboType: enums_1.VrboFeeTypeSchema.nullable(),
28
+ });
29
+ const GetPropertyFeeRepsonseSchema = zod_1.z.object({
30
+ fees: zod_1.z.array(PropertyFeeSchema),
31
+ _metadata: zod_1.z.object({
32
+ count: zod_1.z.number(),
33
+ }),
34
+ });
35
+ class FeesApi {
36
+ api;
37
+ constructor(api) {
38
+ this.api = api;
39
+ }
40
+ async list(property) {
41
+ const resp = await this.api.transport.fetch({
42
+ method: "GET",
43
+ path: "/api/v3.1/fees",
44
+ query: { propertyUid: property.uid },
45
+ response: GetPropertyFeeRepsonseSchema,
46
+ });
47
+ if (resp._metadata.count > resp.fees.length) {
48
+ throw new Error(`Expected API to return ${resp._metadata.count} fees, got ${resp.fees.length}`);
49
+ }
50
+ return resp.fees;
51
+ }
52
+ async create(property, fields) {
53
+ const body = {
54
+ propertyUid: property.uid,
55
+ amount: fields.amount,
56
+ amountType: fields.amountType,
57
+ name: fields.name,
58
+ optional: fields.optional,
59
+ scope: fields.scope,
60
+ taxationRate: fields.taxationRate,
61
+ type: fields.type,
62
+ // OTS stuff
63
+ airbnbType: fields.airbnbType ?? null,
64
+ bookingDotComType: fields.bookingDotComType ?? null,
65
+ hostfullyType: fields.hostfullyType ?? null,
66
+ hvmiType: fields.hvmiType ?? null,
67
+ vrboType: "TAX",
68
+ // required, even though it's not used
69
+ attestation: false,
70
+ longTermStayExemption: 30,
71
+ };
72
+ await this.api.transport.fetch({
73
+ method: "POST",
74
+ path: "/api/v3.1/fees",
75
+ body: body,
76
+ response: null,
77
+ });
78
+ }
79
+ async delete(uid) {
80
+ await this.api.transport.fetch({
81
+ path: `/api/v3.1/fees/${uid}`,
82
+ method: "DELETE",
83
+ response: null,
84
+ });
85
+ }
86
+ }
87
+ exports.FeesApi = FeesApi;
@@ -0,0 +1,17 @@
1
+ import { GqlQuery } from "@spoot/gql";
2
+ import { z } from "zod";
3
+ import { FetchOptions } from "./Transport";
4
+ export declare class FetchTransport {
5
+ private readonly graphqlUrl;
6
+ private readonly headers;
7
+ private readonly rateLimiting;
8
+ private readonly apiHost;
9
+ private readonly _fetch;
10
+ constructor(options: {
11
+ apiHost: URL;
12
+ apiKey: string;
13
+ globalFetch?: FetchTransport["_fetch"];
14
+ });
15
+ fetchGql<Response>(query: GqlQuery, schema: z.Schema<Response>): Promise<Response>;
16
+ fetch<Response>(options: FetchOptions<Response>): Promise<Response>;
17
+ }
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FetchTransport = void 0;
4
+ const gql_1 = require("@spoot/gql");
5
+ const zod_1 = require("zod");
6
+ const RateLimiting_1 = require("./RateLimiting");
7
+ class FetchTransport {
8
+ graphqlUrl;
9
+ headers;
10
+ rateLimiting = new RateLimiting_1.RateLimiting();
11
+ apiHost;
12
+ _fetch;
13
+ constructor(options) {
14
+ this.apiHost = options.apiHost;
15
+ this._fetch =
16
+ options.globalFetch ??
17
+ (async (url, init) => {
18
+ return await global.fetch(url, init).then(async (resp) => {
19
+ const headers = {};
20
+ resp.headers.forEach((v, k) => {
21
+ headers[`${k}`.toLocaleLowerCase()] = `${v}`;
22
+ });
23
+ return {
24
+ ok: resp.ok,
25
+ status: resp.status,
26
+ statusText: resp.statusText,
27
+ headers,
28
+ async json() {
29
+ return await resp.json();
30
+ },
31
+ async text() {
32
+ return await resp.text();
33
+ },
34
+ };
35
+ });
36
+ });
37
+ this.graphqlUrl = new URL("/api/v3/graphql", options.apiHost);
38
+ this.headers = {
39
+ "X-HOSTFULLY-APIKEY": options.apiKey,
40
+ "content-type": "application/json",
41
+ };
42
+ }
43
+ async fetchGql(query, schema) {
44
+ await this.rateLimiting.take();
45
+ const reqBody = JSON.stringify({
46
+ query: (0, gql_1.printGql)(query),
47
+ }, null, 2);
48
+ const resp = await this._fetch(this.graphqlUrl, {
49
+ method: "POST",
50
+ headers: this.headers,
51
+ body: reqBody,
52
+ });
53
+ this.rateLimiting.onResponse(resp.headers);
54
+ if (!resp.ok) {
55
+ throw new Error(`GraphQL query failed: ${reqBody}\n-- Response --\n ${resp.status} ${resp.statusText}: ${await resp.text()}`);
56
+ }
57
+ const respBody = zod_1.z
58
+ .object({
59
+ errors: zod_1.z.unknown().optional(),
60
+ data: zod_1.z.unknown().optional(),
61
+ })
62
+ .parse(await resp.json());
63
+ if (respBody.errors) {
64
+ const errs = JSON.stringify(respBody.errors, null, 2);
65
+ throw new Error(`GraphQL query failed: ${reqBody}\n-- Errors --\n ${errs}`);
66
+ }
67
+ return schema.parse(respBody.data);
68
+ }
69
+ async fetch(options) {
70
+ await this.rateLimiting.take();
71
+ const url = new URL(options.path, this.apiHost);
72
+ if (options.query) {
73
+ for (const [key, value] of Object.entries(options.query)) {
74
+ if (value != null) {
75
+ url.searchParams.set(key, value);
76
+ }
77
+ }
78
+ }
79
+ const resp = await this._fetch(url, {
80
+ method: options.method,
81
+ headers: this.headers,
82
+ body: options.body ? JSON.stringify(options.body) : undefined,
83
+ });
84
+ this.rateLimiting.onResponse(resp.headers);
85
+ if (!resp.ok) {
86
+ throw new Error(`Failed to fetch ${options.path}: ${resp.statusText}: ${await resp.text()}`);
87
+ }
88
+ if (!options.response) {
89
+ return {};
90
+ }
91
+ const parsed = options.response.safeParse(await resp.json());
92
+ if (parsed.success) {
93
+ return parsed.data;
94
+ }
95
+ throw new Error(`Failed to ${options.method} ${options.path}: ${JSON.stringify(parsed.error, null, 2)}`);
96
+ }
97
+ }
98
+ exports.FetchTransport = FetchTransport;
@@ -0,0 +1,22 @@
1
+ import { PropertiesApi } from "./PropertiesApi";
2
+ import { FeesApi } from "./FeesApi";
3
+ import { PricingApi } from "./PricingApi";
4
+ import { LeadsApi } from "./LeadsApi";
5
+ import { MessagesApi } from "./MessagesApi";
6
+ import { AgenciesApi } from "./AgenciesApi";
7
+ import { Transport } from "./Transport";
8
+ import { OrderApi } from "./OrderApi";
9
+ import { AmenitiesApi } from "./AmenitiesApi";
10
+ export declare class HostfullyApi {
11
+ readonly agencyUid: string;
12
+ readonly transport: Transport;
13
+ constructor(agencyUid: string, transport: Transport);
14
+ get agencies(): AgenciesApi;
15
+ get properties(): PropertiesApi;
16
+ get fees(): FeesApi;
17
+ get pricing(): PricingApi;
18
+ get leads(): LeadsApi;
19
+ get messages(): MessagesApi;
20
+ get orders(): OrderApi;
21
+ get amenities(): AmenitiesApi;
22
+ }
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HostfullyApi = void 0;
4
+ const PropertiesApi_1 = require("./PropertiesApi");
5
+ const FeesApi_1 = require("./FeesApi");
6
+ const PricingApi_1 = require("./PricingApi");
7
+ const LeadsApi_1 = require("./LeadsApi");
8
+ const MessagesApi_1 = require("./MessagesApi");
9
+ const AgenciesApi_1 = require("./AgenciesApi");
10
+ const OrderApi_1 = require("./OrderApi");
11
+ const AmenitiesApi_1 = require("./AmenitiesApi");
12
+ class HostfullyApi {
13
+ agencyUid;
14
+ transport;
15
+ constructor(agencyUid, transport) {
16
+ this.agencyUid = agencyUid;
17
+ this.transport = transport;
18
+ }
19
+ get agencies() {
20
+ return new AgenciesApi_1.AgenciesApi(this);
21
+ }
22
+ get properties() {
23
+ return new PropertiesApi_1.PropertiesApi(this);
24
+ }
25
+ get fees() {
26
+ return new FeesApi_1.FeesApi(this);
27
+ }
28
+ get pricing() {
29
+ return new PricingApi_1.PricingApi(this);
30
+ }
31
+ get leads() {
32
+ return new LeadsApi_1.LeadsApi(this);
33
+ }
34
+ get messages() {
35
+ return new MessagesApi_1.MessagesApi(this);
36
+ }
37
+ get orders() {
38
+ return new OrderApi_1.OrderApi(this);
39
+ }
40
+ get amenities() {
41
+ return new AmenitiesApi_1.AmenitiesApi(this);
42
+ }
43
+ }
44
+ exports.HostfullyApi = HostfullyApi;