@bookitive/js 0.1.0 → 0.1.2

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/dist/index.d.ts CHANGED
@@ -43,8 +43,7 @@ interface DtoPublicFloor {
43
43
  cons?: string[];
44
44
  description?: string;
45
45
  id?: string;
46
- /** "single" | "sections" */
47
- mode?: string;
46
+ mode?: 'single' | 'sections';
48
47
  name?: string;
49
48
  position?: number;
50
49
  pros?: string[];
@@ -54,8 +53,7 @@ interface DtoPublicFloorInfo {
54
53
  cons?: string[];
55
54
  description?: string;
56
55
  id?: string;
57
- /** "single" | "sections" */
58
- mode?: string;
56
+ mode?: 'single' | 'sections';
59
57
  name?: string;
60
58
  position?: number;
61
59
  pros?: string[];
@@ -76,7 +74,7 @@ interface DtoPublicOrder {
76
74
  expiresAt?: string;
77
75
  payment?: DtoPublicPaymentDetail;
78
76
  reference?: string;
79
- status?: string;
77
+ status?: 'pending' | 'paid' | 'cancelled' | 'expired';
80
78
  totalCents?: number;
81
79
  }
82
80
  interface DtoPublicOrg {
@@ -124,8 +122,7 @@ interface DtoPublicTable {
124
122
  number?: number;
125
123
  radius?: number;
126
124
  rotation?: number;
127
- /** "round" | "rect" | "row" */
128
- shape?: string;
125
+ shape?: 'round' | 'rect' | 'row';
129
126
  sides?: number[];
130
127
  spacing?: number;
131
128
  w?: number;
@@ -137,8 +134,7 @@ interface DtoPublicTicketType {
137
134
  cons?: string[];
138
135
  description?: string;
139
136
  id?: string;
140
- /** "seated" | "standing" */
141
- kind?: string;
137
+ kind?: 'seated' | 'standing';
142
138
  maxPerOrder?: number;
143
139
  /**
144
140
  * MinPerOrder appears only when it is a real constraint (> 1) — the
@@ -215,7 +211,9 @@ interface BookitiveClientOptions {
215
211
  /** API failure with the backend's error envelope attached. */
216
212
  declare class BookitiveError extends Error {
217
213
  readonly status: number;
218
- readonly type: string;
214
+ /** The backend error code (e.g. ResponseType.ErrConflict), or "UNKNOWN" when
215
+ * the response carried no JSON envelope. */
216
+ readonly type: ResponseType | 'UNKNOWN';
219
217
  constructor(status: number, body: ResponseAPIError | undefined, fallback: string);
220
218
  /** The selection raced another buyer — refresh availability and reselect. */
221
219
  get isConflict(): boolean;
package/dist/index.js CHANGED
@@ -1,3 +1,18 @@
1
+ // src/generated/api.ts
2
+ var ResponseType = /* @__PURE__ */ ((ResponseType2) => {
3
+ ResponseType2["ErrInvalidPayload"] = "INVALID_PAYLOAD";
4
+ ResponseType2["ErrValidationFailed"] = "VALIDATION_FAILED";
5
+ ResponseType2["ErrNotFound"] = "NOT_FOUND";
6
+ ResponseType2["ErrNotImplemented"] = "NOT_IMPLEMENTED";
7
+ ResponseType2["ErrConflict"] = "CONFLICT";
8
+ ResponseType2["ErrInternal"] = "INTERNAL_ERROR";
9
+ ResponseType2["ErrUnauthorized"] = "UNAUTHORIZED";
10
+ ResponseType2["ErrForbidden"] = "FORBIDDEN";
11
+ ResponseType2["ErrMissingOrganizationID"] = "MISSING_ORGANIZATION_ID";
12
+ ResponseType2["ErrMissingUserID"] = "MISSING_USER_ID";
13
+ return ResponseType2;
14
+ })(ResponseType || {});
15
+
1
16
  // src/live.ts
2
17
  var INITIAL_BACKOFF_MS = 1e3;
3
18
  var MAX_BACKOFF_MS = 15e3;
@@ -27,7 +42,12 @@ var LiveSubscription = class {
27
42
  this.#handlers.onConnectionChange?.(true);
28
43
  };
29
44
  ws.onmessage = (event) => {
30
- const message = JSON.parse(event.data);
45
+ let message;
46
+ try {
47
+ message = JSON.parse(event.data);
48
+ } catch {
49
+ return;
50
+ }
31
51
  if (message.event === "startstate") {
32
52
  this.#handlers.onStartstate?.(message.data);
33
53
  } else if (message.event === "seat") {
@@ -37,7 +57,8 @@ var LiveSubscription = class {
37
57
  ws.onclose = () => {
38
58
  this.#handlers.onConnectionChange?.(false);
39
59
  if (this.#closed) return;
40
- this.#retry = setTimeout(() => this.#connect(), this.#backoff);
60
+ const delay = this.#backoff / 2 + Math.random() * (this.#backoff / 2);
61
+ this.#retry = setTimeout(() => this.#connect(), delay);
41
62
  this.#backoff = Math.min(this.#backoff * 2, MAX_BACKOFF_MS);
42
63
  };
43
64
  }
@@ -46,6 +67,8 @@ var LiveSubscription = class {
46
67
  // src/client.ts
47
68
  var BookitiveError = class extends Error {
48
69
  status;
70
+ /** The backend error code (e.g. ResponseType.ErrConflict), or "UNKNOWN" when
71
+ * the response carried no JSON envelope. */
49
72
  type;
50
73
  constructor(status, body, fallback) {
51
74
  super(body?.msg ?? fallback);
@@ -55,7 +78,7 @@ var BookitiveError = class extends Error {
55
78
  }
56
79
  /** The selection raced another buyer — refresh availability and reselect. */
57
80
  get isConflict() {
58
- return this.status === 409;
81
+ return this.type === "CONFLICT" /* ErrConflict */ || this.status === 409;
59
82
  }
60
83
  };
61
84
  var BookitiveClient = class {
@@ -114,12 +137,16 @@ var BookitiveClient = class {
114
137
  }
115
138
  throw new BookitiveError(res.status, body, fallback);
116
139
  }
140
+ if (res.status === 204 || res.headers.get("content-length") === "0") {
141
+ return void 0;
142
+ }
117
143
  return await res.json();
118
144
  }
119
145
  };
120
146
  export {
121
147
  BookitiveClient,
122
148
  BookitiveError,
123
- LiveSubscription
149
+ LiveSubscription,
150
+ ResponseType
124
151
  };
125
152
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/live.ts","../src/client.ts"],"sourcesContent":["// Live availability over websocket: {\"event\":\"startstate\"|\"seat\", data}.\n// Startstate re-fires on every reconnect, so consumers treat it as the\n// resync point and apply deltas on top.\nimport type { DtoPublicAvailability } from './generated/api.js';\n\n/** One seat changing state. \"reserved\" appears only when the event shares\n * reserved status publicly — a reserved seat may free up again. */\nexport interface SeatDelta {\n\tseatId: string;\n\ttaken: boolean;\n\tstatus: 'free' | 'reserved' | 'taken';\n}\n\nexport interface LiveHandlers {\n\t/** Full snapshot — on connect and on every reconnect. */\n\tonStartstate?: (availability: DtoPublicAvailability) => void;\n\t/** One seat changed. */\n\tonSeat?: (delta: SeatDelta) => void;\n\t/** Connection state changes (drive a \"live\" indicator if you like). */\n\tonConnectionChange?: (connected: boolean) => void;\n}\n\nconst INITIAL_BACKOFF_MS = 1000;\nconst MAX_BACKOFF_MS = 15000;\n\nexport class LiveSubscription {\n\t#url: string;\n\t#handlers: LiveHandlers;\n\t#ws: WebSocket | null = null;\n\t#backoff = INITIAL_BACKOFF_MS;\n\t#retry: ReturnType<typeof setTimeout> | null = null;\n\t#closed = false;\n\n\tconstructor(url: string, handlers: LiveHandlers) {\n\t\tthis.#url = url.replace(/^http/, 'ws');\n\t\tthis.#handlers = handlers;\n\t\tthis.#connect();\n\t}\n\n\tclose(): void {\n\t\tthis.#closed = true;\n\t\tif (this.#retry) clearTimeout(this.#retry);\n\t\tthis.#ws?.close();\n\t}\n\n\t#connect(): void {\n\t\tif (this.#closed) return;\n\t\tconst ws = new WebSocket(this.#url);\n\t\tthis.#ws = ws;\n\t\tws.onopen = () => {\n\t\t\tthis.#backoff = INITIAL_BACKOFF_MS;\n\t\t\tthis.#handlers.onConnectionChange?.(true);\n\t\t};\n\t\tws.onmessage = (event) => {\n\t\t\tconst message = JSON.parse(event.data as string) as {\n\t\t\t\tevent: string;\n\t\t\t\tdata: unknown;\n\t\t\t};\n\t\t\tif (message.event === 'startstate') {\n\t\t\t\tthis.#handlers.onStartstate?.(message.data as DtoPublicAvailability);\n\t\t\t} else if (message.event === 'seat') {\n\t\t\t\tthis.#handlers.onSeat?.(message.data as SeatDelta);\n\t\t\t}\n\t\t};\n\t\tws.onclose = () => {\n\t\t\tthis.#handlers.onConnectionChange?.(false);\n\t\t\tif (this.#closed) return;\n\t\t\tthis.#retry = setTimeout(() => this.#connect(), this.#backoff);\n\t\t\tthis.#backoff = Math.min(this.#backoff * 2, MAX_BACKOFF_MS);\n\t\t};\n\t}\n}\n","// The typed core of @bookitive/js: a thin fetch wrapper over the public\n// checkout API, addressed by org + event slug. All response and payload types\n// are generated from the backend's public swagger — they cannot drift.\nimport type {\n\tDtoPublicAvailability,\n\tDtoPublicEvent,\n\tDtoPublicOrder,\n\tDtoPublicSeating,\n\tPublichandlerPayloadPublicOrder,\n\tResponseAPIError,\n} from './generated/api.js';\nimport { LiveSubscription, type LiveHandlers } from './live.js';\n\nexport interface BookitiveClientOptions {\n\t/** API origin, e.g. \"https://api.bookitive.com\" (no trailing slash). */\n\tbaseUrl: string;\n\t/** Organization slug — the {org} in events.bookitive.com/{org}/{event}. */\n\torg: string;\n\t/** Event slug. */\n\tevent: string;\n\t/** Custom fetch (SSR frameworks pass their own). Defaults to global fetch. */\n\tfetch?: typeof fetch;\n}\n\n/** API failure with the backend's error envelope attached. */\nexport class BookitiveError extends Error {\n\treadonly status: number;\n\treadonly type: string;\n\n\tconstructor(status: number, body: ResponseAPIError | undefined, fallback: string) {\n\t\tsuper(body?.msg ?? fallback);\n\t\tthis.name = 'BookitiveError';\n\t\tthis.status = status;\n\t\tthis.type = body?.type ?? 'UNKNOWN';\n\t}\n\n\t/** The selection raced another buyer — refresh availability and reselect. */\n\tget isConflict(): boolean {\n\t\treturn this.status === 409;\n\t}\n}\n\nexport class BookitiveClient {\n\treadonly #base: string;\n\treadonly #fetch: typeof fetch;\n\n\tconstructor(options: BookitiveClientOptions) {\n\t\tthis.#base = `${options.baseUrl.replace(/\\/$/, '')}/public/v1/orgs/${encodeURIComponent(\n\t\t\toptions.org\n\t\t)}/events/${encodeURIComponent(options.event)}`;\n\t\t// bind, or a browser's fetch throws \"Illegal invocation\" when called\n\t\t// detached from window\n\t\tthis.#fetch = options.fetch ?? ((...args) => fetch(...args));\n\t}\n\n\t/** Event identity, org branding, priced ticket types, and floor tabs. */\n\tevent(): Promise<DtoPublicEvent> {\n\t\treturn this.#get<DtoPublicEvent>('');\n\t}\n\n\t/** Static chart geometry: floors → sections → tables, seats, GA areas. */\n\tseating(): Promise<DtoPublicSeating> {\n\t\treturn this.#get<DtoPublicSeating>('/seating');\n\t}\n\n\t/** Live sellability snapshot (poll it, or use live() for push updates). */\n\tavailability(): Promise<DtoPublicAvailability> {\n\t\treturn this.#get<DtoPublicAvailability>('/availability');\n\t}\n\n\t/**\n\t * Places a bank-transfer reservation: seats are held atomically for the\n\t * event's hold TTL and payment instructions are emailed (and returned).\n\t * A 409 (error.isConflict) means a seat was just taken — refresh and\n\t * reselect.\n\t */\n\tasync checkout(order: PublichandlerPayloadPublicOrder): Promise<DtoPublicOrder> {\n\t\tconst res = await this.#fetch(`${this.#base}/orders`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\tbody: JSON.stringify(order),\n\t\t});\n\t\treturn this.#parse<DtoPublicOrder>(res, 'checkout failed');\n\t}\n\n\t/**\n\t * Subscribes to live availability over websocket: `startstate` fires with\n\t * the full snapshot on every (re)connect, then `seat` deltas stream in.\n\t * Reconnects automatically with backoff; call close() when done.\n\t */\n\tlive(handlers: LiveHandlers): LiveSubscription {\n\t\treturn new LiveSubscription(`${this.#base}/live`, handlers);\n\t}\n\n\tasync #get<T>(path: string): Promise<T> {\n\t\tconst res = await this.#fetch(`${this.#base}${path}`);\n\t\treturn this.#parse<T>(res, `request failed: ${path || '/'}`);\n\t}\n\n\tasync #parse<T>(res: Response, fallback: string): Promise<T> {\n\t\tif (!res.ok) {\n\t\t\tlet body: ResponseAPIError | undefined;\n\t\t\ttry {\n\t\t\t\tbody = (await res.json()) as ResponseAPIError;\n\t\t\t} catch {\n\t\t\t\t// non-JSON error body — the fallback message covers it\n\t\t\t}\n\t\t\tthrow new BookitiveError(res.status, body, fallback);\n\t\t}\n\t\treturn (await res.json()) as T;\n\t}\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/generated/api.ts","../src/live.ts","../src/client.ts"],"sourcesContent":["/* eslint-disable */\n/* tslint:disable */\n// @ts-nocheck\n/*\n * ---------------------------------------------------------------\n * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##\n * ## ##\n * ## AUTHOR: acacode ##\n * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##\n * ---------------------------------------------------------------\n */\n\nexport enum ResponseType {\n\tErrInvalidPayload = 'INVALID_PAYLOAD',\n\tErrValidationFailed = 'VALIDATION_FAILED',\n\tErrNotFound = 'NOT_FOUND',\n\tErrNotImplemented = 'NOT_IMPLEMENTED',\n\tErrConflict = 'CONFLICT',\n\tErrInternal = 'INTERNAL_ERROR',\n\tErrUnauthorized = 'UNAUTHORIZED',\n\tErrForbidden = 'FORBIDDEN',\n\tErrMissingOrganizationID = 'MISSING_ORGANIZATION_ID',\n\tErrMissingUserID = 'MISSING_USER_ID',\n}\n\nexport interface DtoPublicAvailability {\n\tgaRemaining?: Record<string, number>;\n\t/**\n\t * ReservedSeats marks which unavailable seats are merely reserved and may\n\t * free up again — present only when the event shares reserved status.\n\t */\n\treservedSeats?: string[];\n\tticketTypeRemaining?: Record<string, number>;\n\tunavailableSeats?: string[];\n}\n\nexport interface DtoPublicEvent {\n\tevent?: DtoPublicEventInfo;\n\t/**\n\t * Floors lets a host page render its floor tabs without loading the full\n\t * seating payload; ids match the seating endpoint's floors.\n\t */\n\tfloors?: DtoPublicFloorInfo[];\n\torg?: DtoPublicOrg;\n\tticketTypes?: DtoPublicTicketType[];\n}\n\nexport interface DtoPublicEventInfo {\n\tcurrency?: string;\n\tendTime?: string;\n\tholdTtlSeconds?: number;\n\tlocationName?: string;\n\tname?: string;\n\tslug?: string;\n\tstartTime?: string;\n}\n\nexport interface DtoPublicFloor {\n\tcons?: string[];\n\tdescription?: string;\n\tid?: string;\n\tmode?: 'single' | 'sections';\n\tname?: string;\n\tposition?: number;\n\tpros?: string[];\n\tsections?: DtoPublicSection[];\n}\n\nexport interface DtoPublicFloorInfo {\n\tcons?: string[];\n\tdescription?: string;\n\tid?: string;\n\tmode?: 'single' | 'sections';\n\tname?: string;\n\tposition?: number;\n\tpros?: string[];\n}\n\nexport interface DtoPublicGAArea {\n\tcapacity?: number;\n\th?: number;\n\tid?: string;\n\tlabel?: string;\n\trotation?: number;\n\tticketTypeId?: string;\n\tw?: number;\n\tx?: number;\n\ty?: number;\n}\n\nexport interface DtoPublicOrder {\n\tcurrency?: string;\n\texpiresAt?: string;\n\tpayment?: DtoPublicPaymentDetail;\n\treference?: string;\n\tstatus?: 'pending' | 'paid' | 'cancelled' | 'expired';\n\ttotalCents?: number;\n}\n\nexport interface DtoPublicOrg {\n\taccentColor?: string;\n\tlogoUrl?: string;\n\tname?: string;\n\tslug?: string;\n}\n\nexport interface DtoPublicPaymentDetail {\n\t/** BankAccount is the organizer's free-form payment destination. */\n\tbankAccount?: string;\n\t/** VariableSymbol identifies the payment — it equals the order reference. */\n\tvariableSymbol?: string;\n}\n\nexport interface DtoPublicSeat {\n\tcode?: string;\n\tid?: string;\n\ttableId?: string;\n\tticketTypeId?: string;\n\tx?: number;\n\ty?: number;\n}\n\nexport interface DtoPublicSeating {\n\tfloors?: DtoPublicFloor[];\n}\n\nexport interface DtoPublicSection {\n\tdecorations?: object;\n\tgaAreas?: DtoPublicGAArea[];\n\th?: number;\n\tid?: string;\n\tname?: string;\n\tpolygon?: object;\n\tprefix?: string;\n\tseats?: DtoPublicSeat[];\n\tsettings?: object;\n\ttables?: DtoPublicTable[];\n\tw?: number;\n\tx?: number;\n\ty?: number;\n}\n\nexport interface DtoPublicTable {\n\tcurve?: number;\n\th?: number;\n\tid?: string;\n\tnumber?: number;\n\tradius?: number;\n\trotation?: number;\n\tshape?: 'round' | 'rect' | 'row';\n\tsides?: number[];\n\tspacing?: number;\n\tw?: number;\n\tx?: number;\n\ty?: number;\n}\n\nexport interface DtoPublicTicketType {\n\tcolor?: string;\n\tcons?: string[];\n\tdescription?: string;\n\tid?: string;\n\tkind?: 'seated' | 'standing';\n\tmaxPerOrder?: number;\n\t/**\n\t * MinPerOrder appears only when it is a real constraint (> 1) — the\n\t * limit applies to orders that include this type at all.\n\t */\n\tminPerOrder?: number;\n\tname?: string;\n\tpriceCents?: number;\n\tpros?: string[];\n\tsaleEndTime?: string;\n\tsaleStartTime?: string;\n}\n\nexport interface PublichandlerPayloadPublicOrder {\n\tcaptchaToken?: string;\n\tcustomer: {\n\t\temail: string;\n\t\t/** @maxLength 200 */\n\t\tname: string;\n\t\t/** @maxLength 50 */\n\t\tphone?: string;\n\t};\n\t/** @minItems 1 */\n\titems: PublichandlerPayloadPublicOrderItem[];\n}\n\nexport interface PublichandlerPayloadPublicOrderItem {\n\tgaAreaId?: string;\n\t/** @min 1 */\n\tquantity: number;\n\tseatId?: string;\n\tticketTypeId: string;\n}\n\nexport interface ResponseAPIError {\n\tdata?: any;\n\tfields?: Record<string, string>;\n\t/** @example \"see fields\" */\n\tmsg: string;\n\ttimestamp?: string;\n\ttraceId?: string;\n\t/** @example \"VALIDATION_FAILED\" */\n\ttype: ResponseType;\n}\n","// Live availability over websocket: {\"event\":\"startstate\"|\"seat\", data}.\n// Startstate re-fires on every reconnect, so consumers treat it as the\n// resync point and apply deltas on top.\nimport type { DtoPublicAvailability } from './generated/api.js';\n\n/** One seat changing state. \"reserved\" appears only when the event shares\n * reserved status publicly — a reserved seat may free up again. */\nexport interface SeatDelta {\n\tseatId: string;\n\ttaken: boolean;\n\tstatus: 'free' | 'reserved' | 'taken';\n}\n\nexport interface LiveHandlers {\n\t/** Full snapshot — on connect and on every reconnect. */\n\tonStartstate?: (availability: DtoPublicAvailability) => void;\n\t/** One seat changed. */\n\tonSeat?: (delta: SeatDelta) => void;\n\t/** Connection state changes (drive a \"live\" indicator if you like). */\n\tonConnectionChange?: (connected: boolean) => void;\n}\n\nconst INITIAL_BACKOFF_MS = 1000;\nconst MAX_BACKOFF_MS = 15000;\n\nexport class LiveSubscription {\n\t#url: string;\n\t#handlers: LiveHandlers;\n\t#ws: WebSocket | null = null;\n\t#backoff = INITIAL_BACKOFF_MS;\n\t#retry: ReturnType<typeof setTimeout> | null = null;\n\t#closed = false;\n\n\tconstructor(url: string, handlers: LiveHandlers) {\n\t\tthis.#url = url.replace(/^http/, 'ws');\n\t\tthis.#handlers = handlers;\n\t\tthis.#connect();\n\t}\n\n\tclose(): void {\n\t\tthis.#closed = true;\n\t\tif (this.#retry) clearTimeout(this.#retry);\n\t\tthis.#ws?.close();\n\t}\n\n\t#connect(): void {\n\t\tif (this.#closed) return;\n\t\tconst ws = new WebSocket(this.#url);\n\t\tthis.#ws = ws;\n\t\tws.onopen = () => {\n\t\t\tthis.#backoff = INITIAL_BACKOFF_MS;\n\t\t\tthis.#handlers.onConnectionChange?.(true);\n\t\t};\n\t\tws.onmessage = (event) => {\n\t\t\tlet message: { event?: string; data?: unknown };\n\t\t\ttry {\n\t\t\t\tmessage = JSON.parse(event.data as string);\n\t\t\t} catch {\n\t\t\t\treturn; // ignore malformed / non-JSON frames rather than throwing\n\t\t\t}\n\t\t\tif (message.event === 'startstate') {\n\t\t\t\tthis.#handlers.onStartstate?.(message.data as DtoPublicAvailability);\n\t\t\t} else if (message.event === 'seat') {\n\t\t\t\tthis.#handlers.onSeat?.(message.data as SeatDelta);\n\t\t\t}\n\t\t};\n\t\tws.onclose = () => {\n\t\t\tthis.#handlers.onConnectionChange?.(false);\n\t\t\tif (this.#closed) return;\n\t\t\t// Equal-jitter backoff: reconnect somewhere in [backoff/2, backoff) so a\n\t\t\t// mass disconnect doesn't produce a synchronized reconnect thundering herd.\n\t\t\tconst delay = this.#backoff / 2 + Math.random() * (this.#backoff / 2);\n\t\t\tthis.#retry = setTimeout(() => this.#connect(), delay);\n\t\t\tthis.#backoff = Math.min(this.#backoff * 2, MAX_BACKOFF_MS);\n\t\t};\n\t}\n}\n","// The typed core of @bookitive/js: a thin fetch wrapper over the public\n// checkout API, addressed by org + event slug. All response and payload types\n// are generated from the backend's public swagger — they cannot drift.\nimport { ResponseType } from './generated/api.js';\nimport type {\n\tDtoPublicAvailability,\n\tDtoPublicEvent,\n\tDtoPublicOrder,\n\tDtoPublicSeating,\n\tPublichandlerPayloadPublicOrder,\n\tResponseAPIError,\n} from './generated/api.js';\nimport { LiveSubscription, type LiveHandlers } from './live.js';\n\nexport interface BookitiveClientOptions {\n\t/** API origin, e.g. \"https://api.bookitive.com\" (no trailing slash). */\n\tbaseUrl: string;\n\t/** Organization slug — the {org} in events.bookitive.com/{org}/{event}. */\n\torg: string;\n\t/** Event slug. */\n\tevent: string;\n\t/** Custom fetch (SSR frameworks pass their own). Defaults to global fetch. */\n\tfetch?: typeof fetch;\n}\n\n/** API failure with the backend's error envelope attached. */\nexport class BookitiveError extends Error {\n\treadonly status: number;\n\t/** The backend error code (e.g. ResponseType.ErrConflict), or \"UNKNOWN\" when\n\t * the response carried no JSON envelope. */\n\treadonly type: ResponseType | 'UNKNOWN';\n\n\tconstructor(status: number, body: ResponseAPIError | undefined, fallback: string) {\n\t\tsuper(body?.msg ?? fallback);\n\t\tthis.name = 'BookitiveError';\n\t\tthis.status = status;\n\t\tthis.type = body?.type ?? 'UNKNOWN';\n\t}\n\n\t/** The selection raced another buyer — refresh availability and reselect. */\n\tget isConflict(): boolean {\n\t\treturn this.type === ResponseType.ErrConflict || this.status === 409;\n\t}\n}\n\nexport class BookitiveClient {\n\treadonly #base: string;\n\treadonly #fetch: typeof fetch;\n\n\tconstructor(options: BookitiveClientOptions) {\n\t\tthis.#base = `${options.baseUrl.replace(/\\/$/, '')}/public/v1/orgs/${encodeURIComponent(\n\t\t\toptions.org\n\t\t)}/events/${encodeURIComponent(options.event)}`;\n\t\t// bind, or a browser's fetch throws \"Illegal invocation\" when called\n\t\t// detached from window\n\t\tthis.#fetch = options.fetch ?? ((...args) => fetch(...args));\n\t}\n\n\t/** Event identity, org branding, priced ticket types, and floor tabs. */\n\tevent(): Promise<DtoPublicEvent> {\n\t\treturn this.#get<DtoPublicEvent>('');\n\t}\n\n\t/** Static chart geometry: floors → sections → tables, seats, GA areas. */\n\tseating(): Promise<DtoPublicSeating> {\n\t\treturn this.#get<DtoPublicSeating>('/seating');\n\t}\n\n\t/** Live sellability snapshot (poll it, or use live() for push updates). */\n\tavailability(): Promise<DtoPublicAvailability> {\n\t\treturn this.#get<DtoPublicAvailability>('/availability');\n\t}\n\n\t/**\n\t * Places a bank-transfer reservation: seats are held atomically for the\n\t * event's hold TTL and payment instructions are emailed (and returned).\n\t * A 409 (error.isConflict) means a seat was just taken — refresh and\n\t * reselect.\n\t */\n\tasync checkout(order: PublichandlerPayloadPublicOrder): Promise<DtoPublicOrder> {\n\t\tconst res = await this.#fetch(`${this.#base}/orders`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: { 'Content-Type': 'application/json' },\n\t\t\tbody: JSON.stringify(order),\n\t\t});\n\t\treturn this.#parse<DtoPublicOrder>(res, 'checkout failed');\n\t}\n\n\t/**\n\t * Subscribes to live availability over websocket: `startstate` fires with\n\t * the full snapshot on every (re)connect, then `seat` deltas stream in.\n\t * Reconnects automatically with backoff; call close() when done.\n\t */\n\tlive(handlers: LiveHandlers): LiveSubscription {\n\t\treturn new LiveSubscription(`${this.#base}/live`, handlers);\n\t}\n\n\tasync #get<T>(path: string): Promise<T> {\n\t\tconst res = await this.#fetch(`${this.#base}${path}`);\n\t\treturn this.#parse<T>(res, `request failed: ${path || '/'}`);\n\t}\n\n\tasync #parse<T>(res: Response, fallback: string): Promise<T> {\n\t\tif (!res.ok) {\n\t\t\tlet body: ResponseAPIError | undefined;\n\t\t\ttry {\n\t\t\t\tbody = (await res.json()) as ResponseAPIError;\n\t\t\t} catch {\n\t\t\t\t// non-JSON error body — the fallback message covers it\n\t\t\t}\n\t\t\tthrow new BookitiveError(res.status, body, fallback);\n\t\t}\n\t\t// No content (204) or an explicitly empty body — don't call res.json(),\n\t\t// which would throw on an empty stream.\n\t\tif (res.status === 204 || res.headers.get('content-length') === '0') {\n\t\t\treturn undefined as T;\n\t\t}\n\t\treturn (await res.json()) as T;\n\t}\n}\n"],"mappings":"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","names":["ResponseType"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bookitive/js",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Typed client for the Bookitive public checkout API — event info, seating, live availability, and orders.",
5
5
  "license": "MIT",
6
6
  "type": "module",