@seatlayer/js 0.1.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.
package/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # @seatlayer/js
2
+
3
+ The framework-agnostic [SeatLayer](https://seatlayer.io) embed SDK. Render an
4
+ interactive seat map, let buyers select and **hold** seats in the browser, then
5
+ **book** them from your server. Full docs: <https://docs.seatlayer.io>
6
+
7
+ Works in plain HTML and any framework (React, Vue, Svelte, Angular…). For React,
8
+ prefer the [`@seatlayer/react`](https://www.npmjs.com/package/@seatlayer/react) wrapper.
9
+
10
+ ```bash
11
+ npm install @seatlayer/js
12
+ ```
13
+
14
+ ## Usage
15
+
16
+ ```js
17
+ import { SeatingChart } from '@seatlayer/js';
18
+
19
+ const chart = new SeatingChart({
20
+ container: '#chart',
21
+ event: 'ev_9f3a',
22
+ onHold: ({ holdId }) => bookOnYourServer(holdId),
23
+ });
24
+
25
+ await chart.render();
26
+ const hold = await chart.hold(); // null on a 409 conflict
27
+ // ... later
28
+ chart.destroy();
29
+ ```
30
+
31
+ ## Use in any framework
32
+
33
+ The SDK only needs a DOM element and mount/unmount hooks:
34
+
35
+ ```js
36
+ // Vue
37
+ onMounted(() => { chart = new SeatingChart({ container: '#chart', event: 'ev_9f3a' }); chart.render(); });
38
+ onUnmounted(() => chart?.destroy());
39
+ ```
40
+
41
+ Svelte → `onMount` / `onDestroy`. Angular → `ngAfterViewInit` / `ngOnDestroy`.
42
+
43
+ ## API
44
+
45
+ `new SeatingChart(options)` — options: `container` (selector or element, required),
46
+ `event` (key, required), `apiBase?`, `maxSelection?` (default 10), `onSelectionChange?`,
47
+ `onHold?`, `onError?`.
48
+
49
+ Methods: `render()`, `getSelection()`, `hold()`, `bestAvailable(qty, categoryKey?)`,
50
+ `release()`, `destroy()`.
51
+
52
+ The browser **holds**; your **server books** with a secret key. See the
53
+ [integration guide](https://docs.seatlayer.io/getting-started/how-it-works/).
package/dist/index.cjs ADDED
@@ -0,0 +1,184 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ ApiError: () => ApiError,
24
+ SeatingChart: () => SeatingChart
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+
28
+ // src/SeatingChart.ts
29
+ var import_core = require("@seatlayer/core");
30
+
31
+ // src/api.ts
32
+ var ApiError = class extends Error {
33
+ constructor(status, message, code, conflicts, reason) {
34
+ super(message);
35
+ this.name = "ApiError";
36
+ this.status = status;
37
+ this.code = code;
38
+ this.conflicts = conflicts;
39
+ this.reason = reason;
40
+ }
41
+ };
42
+ async function request(base, path, init = {}) {
43
+ const method = init.method ?? "GET";
44
+ const headers = {};
45
+ let body;
46
+ if (init.body !== void 0) {
47
+ headers["Content-Type"] = "application/json";
48
+ body = JSON.stringify(init.body);
49
+ }
50
+ const res = await fetch(`${base}${path}`, { method, headers, body, credentials: "omit" });
51
+ const isJson = (res.headers.get("content-type") ?? "").includes("application/json");
52
+ const data = isJson ? await res.json().catch(() => null) : null;
53
+ if (!res.ok) {
54
+ const err = data;
55
+ throw new ApiError(res.status, err?.error ?? `request_failed_${res.status}`, err?.code, err?.conflicts, err?.reason);
56
+ }
57
+ return data;
58
+ }
59
+ var PubApi = class {
60
+ constructor(base) {
61
+ this.base = base;
62
+ }
63
+ chart(key) {
64
+ return request(this.base, `/pub/events/${encodeURIComponent(key)}/chart`);
65
+ }
66
+ objects(key) {
67
+ return request(this.base, `/pub/events/${encodeURIComponent(key)}/objects`);
68
+ }
69
+ hold(key, labels) {
70
+ return request(this.base, `/pub/events/${encodeURIComponent(key)}/hold`, {
71
+ method: "POST",
72
+ body: { labels }
73
+ });
74
+ }
75
+ bestAvailable(key, qty, categoryKey) {
76
+ return request(this.base, `/pub/events/${encodeURIComponent(key)}/best-available`, {
77
+ method: "POST",
78
+ body: { qty, ...categoryKey ? { categoryKey } : {} }
79
+ });
80
+ }
81
+ release(key, labels, holdId) {
82
+ return request(this.base, `/pub/events/${encodeURIComponent(key)}/release`, {
83
+ method: "POST",
84
+ body: { labels, holdId }
85
+ });
86
+ }
87
+ socketUrl(key) {
88
+ const wsBase = this.base.replace(/^http/, "ws");
89
+ return `${wsBase}/pub/events/${encodeURIComponent(key)}/subscribe`;
90
+ }
91
+ };
92
+
93
+ // src/SeatingChart.ts
94
+ var DEFAULT_API_BASE = "https://seatmap-api.paiteq.in";
95
+ var DEFAULT_MAX_SELECTION = 10;
96
+ function resolveContainer(container) {
97
+ if (typeof container === "string") {
98
+ const el = document.querySelector(container);
99
+ if (!el) throw new Error(`seatmap: container "${container}" not found`);
100
+ return el;
101
+ }
102
+ if (!(container instanceof HTMLElement)) {
103
+ throw new Error("seatmap: container must be a CSS selector or an HTMLElement");
104
+ }
105
+ return container;
106
+ }
107
+ var SeatingChart = class {
108
+ constructor(options) {
109
+ this.mount = null;
110
+ this.hostEl = null;
111
+ this.rendered = false;
112
+ if (!options || typeof options !== "object") throw new Error("seatmap: options object is required");
113
+ if (!options.container) throw new Error("seatmap: `container` is required");
114
+ if (!options.event || typeof options.event !== "string") throw new Error("seatmap: `event` key is required");
115
+ this.opts = options;
116
+ this.publicKey = options.publicKey;
117
+ const api = new PubApi((options.apiBase ?? DEFAULT_API_BASE).replace(/\/+$/, ""));
118
+ this.controller = new import_core.PickerController({
119
+ transport: api,
120
+ eventKey: options.event,
121
+ maxSelection: options.maxSelection ?? DEFAULT_MAX_SELECTION,
122
+ onSelectionChange: (seats) => this.opts.onSelectionChange?.(seats),
123
+ onHold: (h) => this.opts.onHold?.({ holdId: h.holdId, expiresAt: h.expiresAt }),
124
+ onError: (err) => this.opts.onError?.(err)
125
+ });
126
+ }
127
+ /** Fetch the chart, mount the renderer, seed statuses and go live. Idempotent. */
128
+ async render() {
129
+ if (this.rendered) return this;
130
+ this.rendered = true;
131
+ this.mount = resolveContainer(this.opts.container);
132
+ const host = document.createElement("div");
133
+ host.style.width = "100%";
134
+ host.style.height = "100%";
135
+ host.style.position = "relative";
136
+ this.mount.appendChild(host);
137
+ this.hostEl = host;
138
+ const ok = await this.controller.render(host);
139
+ if (!ok) this.rendered = false;
140
+ return this;
141
+ }
142
+ /** Current selection with prices resolved from the chart categories. */
143
+ getSelection() {
144
+ return this.controller.getSelection();
145
+ }
146
+ /** Hold the current selection. Resolves the hold, or null on a 409 conflict. */
147
+ async hold() {
148
+ try {
149
+ const h = await this.controller.hold();
150
+ return h ? { holdId: h.holdId, expiresAt: h.expiresAt } : null;
151
+ } catch (err) {
152
+ this.opts.onError?.(err);
153
+ return null;
154
+ }
155
+ }
156
+ /** Ask the server for the `qty` best free seats and hold them atomically. */
157
+ async bestAvailable(qty, categoryKey) {
158
+ try {
159
+ const h = await this.controller.bestAvailable(qty, categoryKey);
160
+ return h ? { holdId: h.holdId, expiresAt: h.expiresAt, labels: h.labels } : null;
161
+ } catch (err) {
162
+ this.opts.onError?.(err);
163
+ return null;
164
+ }
165
+ }
166
+ /** Release the current hold (if any). No-op when nothing is held. */
167
+ async release() {
168
+ await this.controller.release();
169
+ }
170
+ /** Tear everything down: close the socket, stop timers, drop the canvas. */
171
+ destroy() {
172
+ this.controller.destroy();
173
+ if (this.hostEl && this.hostEl.parentNode) this.hostEl.parentNode.removeChild(this.hostEl);
174
+ this.hostEl = null;
175
+ this.mount = null;
176
+ this.rendered = false;
177
+ }
178
+ };
179
+ // Annotate the CommonJS export names for ESM import in node:
180
+ 0 && (module.exports = {
181
+ ApiError,
182
+ SeatingChart
183
+ });
184
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/SeatingChart.ts","../src/api.ts"],"sourcesContent":["/**\n * @seatlayer/js — the framework-agnostic SeatLayer embed SDK.\n *\n * Works in any JS environment (plain HTML, React, Vue, Svelte, Angular, …).\n * Framework wrappers (@seatlayer/react, …) build on top of this.\n */\nexport { SeatingChart } from './SeatingChart';\nexport type { SeatingChartOptions, SelectedSeat } from './SeatingChart';\nexport { ApiError } from './api';\nexport type { HoldResult, HoldConflict, BestAvailableResult } from './api';\n","/**\n * SeatingChart — the embeddable buyer picker.\n *\n * A thin wrapper over the shared PickerController (src/picker/PickerController):\n * it owns the mount <div> + the public embed contract (hold-only — the SDK hands\n * the holdId to the host page for a server-side book) and delegates all transport\n * + booking to the controller, so the SDK inherits every fix made for the live\n * buyer page and the demo picker.\n */\nimport { PickerController, type PickerSeat } from '@seatlayer/core';\nimport { PubApi, type BestAvailableResult, type HoldResult } from './api';\n\nconst DEFAULT_API_BASE = 'https://seatmap-api.paiteq.in';\nconst DEFAULT_MAX_SELECTION = 10;\n\n/** A seat as surfaced to the host page (prices resolved from the chart's categories). */\nexport type SelectedSeat = PickerSeat;\n\nexport interface SeatingChartOptions {\n /** CSS selector or an HTMLElement to render into. */\n container: string | HTMLElement;\n /** Event key, e.g. `ev_xxx`. */\n event: string;\n /** API origin. Defaults to https://seatmap-api.paiteq.in. */\n apiBase?: string;\n /** Reserved for future authenticated rendering — accepted + stored, not yet sent. */\n publicKey?: string;\n /** Max seats selectable at once (default 10). */\n maxSelection?: number;\n onSelectionChange?: (seats: SelectedSeat[]) => void;\n onHold?: (result: HoldResult) => void;\n onError?: (err: unknown) => void;\n}\n\nfunction resolveContainer(container: string | HTMLElement): HTMLElement {\n if (typeof container === 'string') {\n const el = document.querySelector(container);\n if (!el) throw new Error(`seatmap: container \"${container}\" not found`);\n return el as HTMLElement;\n }\n if (!(container instanceof HTMLElement)) {\n throw new Error('seatmap: container must be a CSS selector or an HTMLElement');\n }\n return container;\n}\n\nexport class SeatingChart {\n private readonly opts: SeatingChartOptions;\n private readonly controller: PickerController;\n /** Reserved for future authenticated rendering — stored, not yet sent on any request. */\n readonly publicKey?: string;\n\n private mount: HTMLElement | null = null;\n private hostEl: HTMLDivElement | null = null;\n private rendered = false;\n\n constructor(options: SeatingChartOptions) {\n if (!options || typeof options !== 'object') throw new Error('seatmap: options object is required');\n if (!options.container) throw new Error('seatmap: `container` is required');\n if (!options.event || typeof options.event !== 'string') throw new Error('seatmap: `event` key is required');\n\n this.opts = options;\n this.publicKey = options.publicKey;\n const api = new PubApi((options.apiBase ?? DEFAULT_API_BASE).replace(/\\/+$/, ''));\n this.controller = new PickerController({\n transport: api,\n eventKey: options.event,\n maxSelection: options.maxSelection ?? DEFAULT_MAX_SELECTION,\n onSelectionChange: (seats) => this.opts.onSelectionChange?.(seats),\n onHold: (h) => this.opts.onHold?.({ holdId: h.holdId, expiresAt: h.expiresAt }),\n onError: (err) => this.opts.onError?.(err),\n });\n }\n\n /** Fetch the chart, mount the renderer, seed statuses and go live. Idempotent. */\n async render(): Promise<this> {\n if (this.rendered) return this;\n this.rendered = true;\n\n // Mount an owned <div> inside the caller's container so we never fight their\n // layout and can cleanly remove it on destroy().\n this.mount = resolveContainer(this.opts.container);\n const host = document.createElement('div');\n host.style.width = '100%';\n host.style.height = '100%';\n host.style.position = 'relative';\n this.mount.appendChild(host);\n this.hostEl = host;\n\n const ok = await this.controller.render(host);\n if (!ok) this.rendered = false; // render() already emitted the error\n return this;\n }\n\n /** Current selection with prices resolved from the chart categories. */\n getSelection(): SelectedSeat[] {\n return this.controller.getSelection();\n }\n\n /** Hold the current selection. Resolves the hold, or null on a 409 conflict. */\n async hold(): Promise<HoldResult | null> {\n try {\n const h = await this.controller.hold();\n return h ? { holdId: h.holdId, expiresAt: h.expiresAt } : null;\n } catch (err) {\n this.opts.onError?.(err);\n return null;\n }\n }\n\n /** Ask the server for the `qty` best free seats and hold them atomically. */\n async bestAvailable(qty: number, categoryKey?: string): Promise<BestAvailableResult | null> {\n try {\n const h = await this.controller.bestAvailable(qty, categoryKey);\n return h ? { holdId: h.holdId, expiresAt: h.expiresAt, labels: h.labels } : null;\n } catch (err) {\n this.opts.onError?.(err);\n return null;\n }\n }\n\n /** Release the current hold (if any). No-op when nothing is held. */\n async release(): Promise<void> {\n await this.controller.release();\n }\n\n /** Tear everything down: close the socket, stop timers, drop the canvas. */\n destroy(): void {\n this.controller.destroy();\n if (this.hostEl && this.hostEl.parentNode) this.hostEl.parentNode.removeChild(this.hostEl);\n this.hostEl = null;\n this.mount = null;\n this.rendered = false;\n }\n}\n","/**\n * Minimal client for the public embed surface of workers/api (the `/pub/*`\n * routes). Deliberately self-contained — it does NOT reuse src/lib/api.ts,\n * which bakes in a build-time API base and dashboard session credentials. The\n * SDK runs cross-origin on a third-party ticketing page, so:\n * - apiBase is per-instance (constructor option), not a build constant;\n * - credentials are omitted (no cookie to send, avoids CORS-credential setup);\n * - no custom headers on mutating calls (keeps the CORS preflight trivial).\n */\nimport type { ChartDoc } from '@seatlayer/core';\n\nexport interface HoldConflict {\n label: string;\n reason: string;\n}\n\nexport class ApiError extends Error {\n status: number;\n code?: string;\n /** Present when a hold 409s because seats were just taken/held. */\n conflicts?: HoldConflict[];\n /** Present when best-available 409s ('not_enough_together' | 'sold_out'). */\n reason?: string;\n\n constructor(status: number, message: string, code?: string, conflicts?: HoldConflict[], reason?: string) {\n super(message);\n this.name = 'ApiError';\n this.status = status;\n this.code = code;\n this.conflicts = conflicts;\n this.reason = reason;\n }\n}\n\nexport interface PubChartResult {\n event: { key: string; name: string };\n doc: ChartDoc;\n}\n\nexport interface PubObjectsResult {\n /** Every non-free seat's status, keyed by seat label. */\n seats: Record<string, string>;\n updatedAt: number;\n}\n\nexport interface HoldResult {\n holdId: string;\n expiresAt: number;\n}\n\n/** Best-available response — the server-picked seats plus the hold they landed in. */\nexport interface BestAvailableResult {\n holdId: string;\n expiresAt: number;\n labels: string[];\n}\n\nasync function request<T>(\n base: string,\n path: string,\n init: { method?: 'GET' | 'POST'; body?: unknown } = {},\n): Promise<T> {\n const method = init.method ?? 'GET';\n const headers: Record<string, string> = {};\n let body: string | undefined;\n if (init.body !== undefined) {\n headers['Content-Type'] = 'application/json';\n body = JSON.stringify(init.body);\n }\n\n const res = await fetch(`${base}${path}`, { method, headers, body, credentials: 'omit' });\n\n const isJson = (res.headers.get('content-type') ?? '').includes('application/json');\n const data = isJson ? await res.json().catch(() => null) : null;\n\n if (!res.ok) {\n const err = data as\n | { error?: string; code?: string; conflicts?: HoldConflict[]; reason?: string }\n | null;\n throw new ApiError(res.status, err?.error ?? `request_failed_${res.status}`, err?.code, err?.conflicts, err?.reason);\n }\n return data as T;\n}\n\n/** Public-surface client bound to one apiBase (e.g. https://seatmap-api.paiteq.in). */\nexport class PubApi {\n constructor(private readonly base: string) {}\n\n chart(key: string): Promise<PubChartResult> {\n return request(this.base, `/pub/events/${encodeURIComponent(key)}/chart`);\n }\n\n objects(key: string): Promise<PubObjectsResult> {\n return request(this.base, `/pub/events/${encodeURIComponent(key)}/objects`);\n }\n\n hold(key: string, labels: string[]): Promise<HoldResult> {\n return request(this.base, `/pub/events/${encodeURIComponent(key)}/hold`, {\n method: 'POST',\n body: { labels },\n });\n }\n\n bestAvailable(key: string, qty: number, categoryKey?: string): Promise<BestAvailableResult> {\n return request(this.base, `/pub/events/${encodeURIComponent(key)}/best-available`, {\n method: 'POST',\n body: { qty, ...(categoryKey ? { categoryKey } : {}) },\n });\n }\n\n release(key: string, labels: string[], holdId: string): Promise<{ ok: true }> {\n return request(this.base, `/pub/events/${encodeURIComponent(key)}/release`, {\n method: 'POST',\n body: { labels, holdId },\n });\n }\n\n socketUrl(key: string): string {\n const wsBase = this.base.replace(/^http/, 'ws');\n return `${wsBase}/pub/events/${encodeURIComponent(key)}/subscribe`;\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,87 @@
1
+ import { PickerSeat } from '@seatlayer/core';
2
+
3
+ /**
4
+ * Minimal client for the public embed surface of workers/api (the `/pub/*`
5
+ * routes). Deliberately self-contained — it does NOT reuse src/lib/api.ts,
6
+ * which bakes in a build-time API base and dashboard session credentials. The
7
+ * SDK runs cross-origin on a third-party ticketing page, so:
8
+ * - apiBase is per-instance (constructor option), not a build constant;
9
+ * - credentials are omitted (no cookie to send, avoids CORS-credential setup);
10
+ * - no custom headers on mutating calls (keeps the CORS preflight trivial).
11
+ */
12
+
13
+ interface HoldConflict {
14
+ label: string;
15
+ reason: string;
16
+ }
17
+ declare class ApiError extends Error {
18
+ status: number;
19
+ code?: string;
20
+ /** Present when a hold 409s because seats were just taken/held. */
21
+ conflicts?: HoldConflict[];
22
+ /** Present when best-available 409s ('not_enough_together' | 'sold_out'). */
23
+ reason?: string;
24
+ constructor(status: number, message: string, code?: string, conflicts?: HoldConflict[], reason?: string);
25
+ }
26
+ interface HoldResult {
27
+ holdId: string;
28
+ expiresAt: number;
29
+ }
30
+ /** Best-available response — the server-picked seats plus the hold they landed in. */
31
+ interface BestAvailableResult {
32
+ holdId: string;
33
+ expiresAt: number;
34
+ labels: string[];
35
+ }
36
+
37
+ /**
38
+ * SeatingChart — the embeddable buyer picker.
39
+ *
40
+ * A thin wrapper over the shared PickerController (src/picker/PickerController):
41
+ * it owns the mount <div> + the public embed contract (hold-only — the SDK hands
42
+ * the holdId to the host page for a server-side book) and delegates all transport
43
+ * + booking to the controller, so the SDK inherits every fix made for the live
44
+ * buyer page and the demo picker.
45
+ */
46
+
47
+ /** A seat as surfaced to the host page (prices resolved from the chart's categories). */
48
+ type SelectedSeat = PickerSeat;
49
+ interface SeatingChartOptions {
50
+ /** CSS selector or an HTMLElement to render into. */
51
+ container: string | HTMLElement;
52
+ /** Event key, e.g. `ev_xxx`. */
53
+ event: string;
54
+ /** API origin. Defaults to https://seatmap-api.paiteq.in. */
55
+ apiBase?: string;
56
+ /** Reserved for future authenticated rendering — accepted + stored, not yet sent. */
57
+ publicKey?: string;
58
+ /** Max seats selectable at once (default 10). */
59
+ maxSelection?: number;
60
+ onSelectionChange?: (seats: SelectedSeat[]) => void;
61
+ onHold?: (result: HoldResult) => void;
62
+ onError?: (err: unknown) => void;
63
+ }
64
+ declare class SeatingChart {
65
+ private readonly opts;
66
+ private readonly controller;
67
+ /** Reserved for future authenticated rendering — stored, not yet sent on any request. */
68
+ readonly publicKey?: string;
69
+ private mount;
70
+ private hostEl;
71
+ private rendered;
72
+ constructor(options: SeatingChartOptions);
73
+ /** Fetch the chart, mount the renderer, seed statuses and go live. Idempotent. */
74
+ render(): Promise<this>;
75
+ /** Current selection with prices resolved from the chart categories. */
76
+ getSelection(): SelectedSeat[];
77
+ /** Hold the current selection. Resolves the hold, or null on a 409 conflict. */
78
+ hold(): Promise<HoldResult | null>;
79
+ /** Ask the server for the `qty` best free seats and hold them atomically. */
80
+ bestAvailable(qty: number, categoryKey?: string): Promise<BestAvailableResult | null>;
81
+ /** Release the current hold (if any). No-op when nothing is held. */
82
+ release(): Promise<void>;
83
+ /** Tear everything down: close the socket, stop timers, drop the canvas. */
84
+ destroy(): void;
85
+ }
86
+
87
+ export { ApiError, type BestAvailableResult, type HoldConflict, type HoldResult, SeatingChart, type SeatingChartOptions, type SelectedSeat };
@@ -0,0 +1,87 @@
1
+ import { PickerSeat } from '@seatlayer/core';
2
+
3
+ /**
4
+ * Minimal client for the public embed surface of workers/api (the `/pub/*`
5
+ * routes). Deliberately self-contained — it does NOT reuse src/lib/api.ts,
6
+ * which bakes in a build-time API base and dashboard session credentials. The
7
+ * SDK runs cross-origin on a third-party ticketing page, so:
8
+ * - apiBase is per-instance (constructor option), not a build constant;
9
+ * - credentials are omitted (no cookie to send, avoids CORS-credential setup);
10
+ * - no custom headers on mutating calls (keeps the CORS preflight trivial).
11
+ */
12
+
13
+ interface HoldConflict {
14
+ label: string;
15
+ reason: string;
16
+ }
17
+ declare class ApiError extends Error {
18
+ status: number;
19
+ code?: string;
20
+ /** Present when a hold 409s because seats were just taken/held. */
21
+ conflicts?: HoldConflict[];
22
+ /** Present when best-available 409s ('not_enough_together' | 'sold_out'). */
23
+ reason?: string;
24
+ constructor(status: number, message: string, code?: string, conflicts?: HoldConflict[], reason?: string);
25
+ }
26
+ interface HoldResult {
27
+ holdId: string;
28
+ expiresAt: number;
29
+ }
30
+ /** Best-available response — the server-picked seats plus the hold they landed in. */
31
+ interface BestAvailableResult {
32
+ holdId: string;
33
+ expiresAt: number;
34
+ labels: string[];
35
+ }
36
+
37
+ /**
38
+ * SeatingChart — the embeddable buyer picker.
39
+ *
40
+ * A thin wrapper over the shared PickerController (src/picker/PickerController):
41
+ * it owns the mount <div> + the public embed contract (hold-only — the SDK hands
42
+ * the holdId to the host page for a server-side book) and delegates all transport
43
+ * + booking to the controller, so the SDK inherits every fix made for the live
44
+ * buyer page and the demo picker.
45
+ */
46
+
47
+ /** A seat as surfaced to the host page (prices resolved from the chart's categories). */
48
+ type SelectedSeat = PickerSeat;
49
+ interface SeatingChartOptions {
50
+ /** CSS selector or an HTMLElement to render into. */
51
+ container: string | HTMLElement;
52
+ /** Event key, e.g. `ev_xxx`. */
53
+ event: string;
54
+ /** API origin. Defaults to https://seatmap-api.paiteq.in. */
55
+ apiBase?: string;
56
+ /** Reserved for future authenticated rendering — accepted + stored, not yet sent. */
57
+ publicKey?: string;
58
+ /** Max seats selectable at once (default 10). */
59
+ maxSelection?: number;
60
+ onSelectionChange?: (seats: SelectedSeat[]) => void;
61
+ onHold?: (result: HoldResult) => void;
62
+ onError?: (err: unknown) => void;
63
+ }
64
+ declare class SeatingChart {
65
+ private readonly opts;
66
+ private readonly controller;
67
+ /** Reserved for future authenticated rendering — stored, not yet sent on any request. */
68
+ readonly publicKey?: string;
69
+ private mount;
70
+ private hostEl;
71
+ private rendered;
72
+ constructor(options: SeatingChartOptions);
73
+ /** Fetch the chart, mount the renderer, seed statuses and go live. Idempotent. */
74
+ render(): Promise<this>;
75
+ /** Current selection with prices resolved from the chart categories. */
76
+ getSelection(): SelectedSeat[];
77
+ /** Hold the current selection. Resolves the hold, or null on a 409 conflict. */
78
+ hold(): Promise<HoldResult | null>;
79
+ /** Ask the server for the `qty` best free seats and hold them atomically. */
80
+ bestAvailable(qty: number, categoryKey?: string): Promise<BestAvailableResult | null>;
81
+ /** Release the current hold (if any). No-op when nothing is held. */
82
+ release(): Promise<void>;
83
+ /** Tear everything down: close the socket, stop timers, drop the canvas. */
84
+ destroy(): void;
85
+ }
86
+
87
+ export { ApiError, type BestAvailableResult, type HoldConflict, type HoldResult, SeatingChart, type SeatingChartOptions, type SelectedSeat };
package/dist/index.js ADDED
@@ -0,0 +1,156 @@
1
+ // src/SeatingChart.ts
2
+ import { PickerController } from "@seatlayer/core";
3
+
4
+ // src/api.ts
5
+ var ApiError = class extends Error {
6
+ constructor(status, message, code, conflicts, reason) {
7
+ super(message);
8
+ this.name = "ApiError";
9
+ this.status = status;
10
+ this.code = code;
11
+ this.conflicts = conflicts;
12
+ this.reason = reason;
13
+ }
14
+ };
15
+ async function request(base, path, init = {}) {
16
+ const method = init.method ?? "GET";
17
+ const headers = {};
18
+ let body;
19
+ if (init.body !== void 0) {
20
+ headers["Content-Type"] = "application/json";
21
+ body = JSON.stringify(init.body);
22
+ }
23
+ const res = await fetch(`${base}${path}`, { method, headers, body, credentials: "omit" });
24
+ const isJson = (res.headers.get("content-type") ?? "").includes("application/json");
25
+ const data = isJson ? await res.json().catch(() => null) : null;
26
+ if (!res.ok) {
27
+ const err = data;
28
+ throw new ApiError(res.status, err?.error ?? `request_failed_${res.status}`, err?.code, err?.conflicts, err?.reason);
29
+ }
30
+ return data;
31
+ }
32
+ var PubApi = class {
33
+ constructor(base) {
34
+ this.base = base;
35
+ }
36
+ chart(key) {
37
+ return request(this.base, `/pub/events/${encodeURIComponent(key)}/chart`);
38
+ }
39
+ objects(key) {
40
+ return request(this.base, `/pub/events/${encodeURIComponent(key)}/objects`);
41
+ }
42
+ hold(key, labels) {
43
+ return request(this.base, `/pub/events/${encodeURIComponent(key)}/hold`, {
44
+ method: "POST",
45
+ body: { labels }
46
+ });
47
+ }
48
+ bestAvailable(key, qty, categoryKey) {
49
+ return request(this.base, `/pub/events/${encodeURIComponent(key)}/best-available`, {
50
+ method: "POST",
51
+ body: { qty, ...categoryKey ? { categoryKey } : {} }
52
+ });
53
+ }
54
+ release(key, labels, holdId) {
55
+ return request(this.base, `/pub/events/${encodeURIComponent(key)}/release`, {
56
+ method: "POST",
57
+ body: { labels, holdId }
58
+ });
59
+ }
60
+ socketUrl(key) {
61
+ const wsBase = this.base.replace(/^http/, "ws");
62
+ return `${wsBase}/pub/events/${encodeURIComponent(key)}/subscribe`;
63
+ }
64
+ };
65
+
66
+ // src/SeatingChart.ts
67
+ var DEFAULT_API_BASE = "https://seatmap-api.paiteq.in";
68
+ var DEFAULT_MAX_SELECTION = 10;
69
+ function resolveContainer(container) {
70
+ if (typeof container === "string") {
71
+ const el = document.querySelector(container);
72
+ if (!el) throw new Error(`seatmap: container "${container}" not found`);
73
+ return el;
74
+ }
75
+ if (!(container instanceof HTMLElement)) {
76
+ throw new Error("seatmap: container must be a CSS selector or an HTMLElement");
77
+ }
78
+ return container;
79
+ }
80
+ var SeatingChart = class {
81
+ constructor(options) {
82
+ this.mount = null;
83
+ this.hostEl = null;
84
+ this.rendered = false;
85
+ if (!options || typeof options !== "object") throw new Error("seatmap: options object is required");
86
+ if (!options.container) throw new Error("seatmap: `container` is required");
87
+ if (!options.event || typeof options.event !== "string") throw new Error("seatmap: `event` key is required");
88
+ this.opts = options;
89
+ this.publicKey = options.publicKey;
90
+ const api = new PubApi((options.apiBase ?? DEFAULT_API_BASE).replace(/\/+$/, ""));
91
+ this.controller = new PickerController({
92
+ transport: api,
93
+ eventKey: options.event,
94
+ maxSelection: options.maxSelection ?? DEFAULT_MAX_SELECTION,
95
+ onSelectionChange: (seats) => this.opts.onSelectionChange?.(seats),
96
+ onHold: (h) => this.opts.onHold?.({ holdId: h.holdId, expiresAt: h.expiresAt }),
97
+ onError: (err) => this.opts.onError?.(err)
98
+ });
99
+ }
100
+ /** Fetch the chart, mount the renderer, seed statuses and go live. Idempotent. */
101
+ async render() {
102
+ if (this.rendered) return this;
103
+ this.rendered = true;
104
+ this.mount = resolveContainer(this.opts.container);
105
+ const host = document.createElement("div");
106
+ host.style.width = "100%";
107
+ host.style.height = "100%";
108
+ host.style.position = "relative";
109
+ this.mount.appendChild(host);
110
+ this.hostEl = host;
111
+ const ok = await this.controller.render(host);
112
+ if (!ok) this.rendered = false;
113
+ return this;
114
+ }
115
+ /** Current selection with prices resolved from the chart categories. */
116
+ getSelection() {
117
+ return this.controller.getSelection();
118
+ }
119
+ /** Hold the current selection. Resolves the hold, or null on a 409 conflict. */
120
+ async hold() {
121
+ try {
122
+ const h = await this.controller.hold();
123
+ return h ? { holdId: h.holdId, expiresAt: h.expiresAt } : null;
124
+ } catch (err) {
125
+ this.opts.onError?.(err);
126
+ return null;
127
+ }
128
+ }
129
+ /** Ask the server for the `qty` best free seats and hold them atomically. */
130
+ async bestAvailable(qty, categoryKey) {
131
+ try {
132
+ const h = await this.controller.bestAvailable(qty, categoryKey);
133
+ return h ? { holdId: h.holdId, expiresAt: h.expiresAt, labels: h.labels } : null;
134
+ } catch (err) {
135
+ this.opts.onError?.(err);
136
+ return null;
137
+ }
138
+ }
139
+ /** Release the current hold (if any). No-op when nothing is held. */
140
+ async release() {
141
+ await this.controller.release();
142
+ }
143
+ /** Tear everything down: close the socket, stop timers, drop the canvas. */
144
+ destroy() {
145
+ this.controller.destroy();
146
+ if (this.hostEl && this.hostEl.parentNode) this.hostEl.parentNode.removeChild(this.hostEl);
147
+ this.hostEl = null;
148
+ this.mount = null;
149
+ this.rendered = false;
150
+ }
151
+ };
152
+ export {
153
+ ApiError,
154
+ SeatingChart
155
+ };
156
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/SeatingChart.ts","../src/api.ts"],"sourcesContent":["/**\n * SeatingChart — the embeddable buyer picker.\n *\n * A thin wrapper over the shared PickerController (src/picker/PickerController):\n * it owns the mount <div> + the public embed contract (hold-only — the SDK hands\n * the holdId to the host page for a server-side book) and delegates all transport\n * + booking to the controller, so the SDK inherits every fix made for the live\n * buyer page and the demo picker.\n */\nimport { PickerController, type PickerSeat } from '@seatlayer/core';\nimport { PubApi, type BestAvailableResult, type HoldResult } from './api';\n\nconst DEFAULT_API_BASE = 'https://seatmap-api.paiteq.in';\nconst DEFAULT_MAX_SELECTION = 10;\n\n/** A seat as surfaced to the host page (prices resolved from the chart's categories). */\nexport type SelectedSeat = PickerSeat;\n\nexport interface SeatingChartOptions {\n /** CSS selector or an HTMLElement to render into. */\n container: string | HTMLElement;\n /** Event key, e.g. `ev_xxx`. */\n event: string;\n /** API origin. Defaults to https://seatmap-api.paiteq.in. */\n apiBase?: string;\n /** Reserved for future authenticated rendering — accepted + stored, not yet sent. */\n publicKey?: string;\n /** Max seats selectable at once (default 10). */\n maxSelection?: number;\n onSelectionChange?: (seats: SelectedSeat[]) => void;\n onHold?: (result: HoldResult) => void;\n onError?: (err: unknown) => void;\n}\n\nfunction resolveContainer(container: string | HTMLElement): HTMLElement {\n if (typeof container === 'string') {\n const el = document.querySelector(container);\n if (!el) throw new Error(`seatmap: container \"${container}\" not found`);\n return el as HTMLElement;\n }\n if (!(container instanceof HTMLElement)) {\n throw new Error('seatmap: container must be a CSS selector or an HTMLElement');\n }\n return container;\n}\n\nexport class SeatingChart {\n private readonly opts: SeatingChartOptions;\n private readonly controller: PickerController;\n /** Reserved for future authenticated rendering — stored, not yet sent on any request. */\n readonly publicKey?: string;\n\n private mount: HTMLElement | null = null;\n private hostEl: HTMLDivElement | null = null;\n private rendered = false;\n\n constructor(options: SeatingChartOptions) {\n if (!options || typeof options !== 'object') throw new Error('seatmap: options object is required');\n if (!options.container) throw new Error('seatmap: `container` is required');\n if (!options.event || typeof options.event !== 'string') throw new Error('seatmap: `event` key is required');\n\n this.opts = options;\n this.publicKey = options.publicKey;\n const api = new PubApi((options.apiBase ?? DEFAULT_API_BASE).replace(/\\/+$/, ''));\n this.controller = new PickerController({\n transport: api,\n eventKey: options.event,\n maxSelection: options.maxSelection ?? DEFAULT_MAX_SELECTION,\n onSelectionChange: (seats) => this.opts.onSelectionChange?.(seats),\n onHold: (h) => this.opts.onHold?.({ holdId: h.holdId, expiresAt: h.expiresAt }),\n onError: (err) => this.opts.onError?.(err),\n });\n }\n\n /** Fetch the chart, mount the renderer, seed statuses and go live. Idempotent. */\n async render(): Promise<this> {\n if (this.rendered) return this;\n this.rendered = true;\n\n // Mount an owned <div> inside the caller's container so we never fight their\n // layout and can cleanly remove it on destroy().\n this.mount = resolveContainer(this.opts.container);\n const host = document.createElement('div');\n host.style.width = '100%';\n host.style.height = '100%';\n host.style.position = 'relative';\n this.mount.appendChild(host);\n this.hostEl = host;\n\n const ok = await this.controller.render(host);\n if (!ok) this.rendered = false; // render() already emitted the error\n return this;\n }\n\n /** Current selection with prices resolved from the chart categories. */\n getSelection(): SelectedSeat[] {\n return this.controller.getSelection();\n }\n\n /** Hold the current selection. Resolves the hold, or null on a 409 conflict. */\n async hold(): Promise<HoldResult | null> {\n try {\n const h = await this.controller.hold();\n return h ? { holdId: h.holdId, expiresAt: h.expiresAt } : null;\n } catch (err) {\n this.opts.onError?.(err);\n return null;\n }\n }\n\n /** Ask the server for the `qty` best free seats and hold them atomically. */\n async bestAvailable(qty: number, categoryKey?: string): Promise<BestAvailableResult | null> {\n try {\n const h = await this.controller.bestAvailable(qty, categoryKey);\n return h ? { holdId: h.holdId, expiresAt: h.expiresAt, labels: h.labels } : null;\n } catch (err) {\n this.opts.onError?.(err);\n return null;\n }\n }\n\n /** Release the current hold (if any). No-op when nothing is held. */\n async release(): Promise<void> {\n await this.controller.release();\n }\n\n /** Tear everything down: close the socket, stop timers, drop the canvas. */\n destroy(): void {\n this.controller.destroy();\n if (this.hostEl && this.hostEl.parentNode) this.hostEl.parentNode.removeChild(this.hostEl);\n this.hostEl = null;\n this.mount = null;\n this.rendered = false;\n }\n}\n","/**\n * Minimal client for the public embed surface of workers/api (the `/pub/*`\n * routes). Deliberately self-contained — it does NOT reuse src/lib/api.ts,\n * which bakes in a build-time API base and dashboard session credentials. The\n * SDK runs cross-origin on a third-party ticketing page, so:\n * - apiBase is per-instance (constructor option), not a build constant;\n * - credentials are omitted (no cookie to send, avoids CORS-credential setup);\n * - no custom headers on mutating calls (keeps the CORS preflight trivial).\n */\nimport type { ChartDoc } from '@seatlayer/core';\n\nexport interface HoldConflict {\n label: string;\n reason: string;\n}\n\nexport class ApiError extends Error {\n status: number;\n code?: string;\n /** Present when a hold 409s because seats were just taken/held. */\n conflicts?: HoldConflict[];\n /** Present when best-available 409s ('not_enough_together' | 'sold_out'). */\n reason?: string;\n\n constructor(status: number, message: string, code?: string, conflicts?: HoldConflict[], reason?: string) {\n super(message);\n this.name = 'ApiError';\n this.status = status;\n this.code = code;\n this.conflicts = conflicts;\n this.reason = reason;\n }\n}\n\nexport interface PubChartResult {\n event: { key: string; name: string };\n doc: ChartDoc;\n}\n\nexport interface PubObjectsResult {\n /** Every non-free seat's status, keyed by seat label. */\n seats: Record<string, string>;\n updatedAt: number;\n}\n\nexport interface HoldResult {\n holdId: string;\n expiresAt: number;\n}\n\n/** Best-available response — the server-picked seats plus the hold they landed in. */\nexport interface BestAvailableResult {\n holdId: string;\n expiresAt: number;\n labels: string[];\n}\n\nasync function request<T>(\n base: string,\n path: string,\n init: { method?: 'GET' | 'POST'; body?: unknown } = {},\n): Promise<T> {\n const method = init.method ?? 'GET';\n const headers: Record<string, string> = {};\n let body: string | undefined;\n if (init.body !== undefined) {\n headers['Content-Type'] = 'application/json';\n body = JSON.stringify(init.body);\n }\n\n const res = await fetch(`${base}${path}`, { method, headers, body, credentials: 'omit' });\n\n const isJson = (res.headers.get('content-type') ?? '').includes('application/json');\n const data = isJson ? await res.json().catch(() => null) : null;\n\n if (!res.ok) {\n const err = data as\n | { error?: string; code?: string; conflicts?: HoldConflict[]; reason?: string }\n | null;\n throw new ApiError(res.status, err?.error ?? `request_failed_${res.status}`, err?.code, err?.conflicts, err?.reason);\n }\n return data as T;\n}\n\n/** Public-surface client bound to one apiBase (e.g. https://seatmap-api.paiteq.in). */\nexport class PubApi {\n constructor(private readonly base: string) {}\n\n chart(key: string): Promise<PubChartResult> {\n return request(this.base, `/pub/events/${encodeURIComponent(key)}/chart`);\n }\n\n objects(key: string): Promise<PubObjectsResult> {\n return request(this.base, `/pub/events/${encodeURIComponent(key)}/objects`);\n }\n\n hold(key: string, labels: string[]): Promise<HoldResult> {\n return request(this.base, `/pub/events/${encodeURIComponent(key)}/hold`, {\n method: 'POST',\n body: { labels },\n });\n }\n\n bestAvailable(key: string, qty: number, categoryKey?: string): Promise<BestAvailableResult> {\n return request(this.base, `/pub/events/${encodeURIComponent(key)}/best-available`, {\n method: 'POST',\n body: { qty, ...(categoryKey ? { categoryKey } : {}) },\n });\n }\n\n release(key: string, labels: string[], holdId: string): Promise<{ ok: true }> {\n return request(this.base, `/pub/events/${encodeURIComponent(key)}/release`, {\n method: 'POST',\n body: { labels, holdId },\n });\n }\n\n socketUrl(key: string): string {\n const wsBase = this.base.replace(/^http/, 'ws');\n return `${wsBase}/pub/events/${encodeURIComponent(key)}/subscribe`;\n }\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@seatlayer/js",
3
+ "version": "0.1.0",
4
+ "description": "The SeatLayer embed SDK — render an interactive seat picker and hold seats from the browser. Works in any JS framework.",
5
+ "license": "UNLICENSED",
6
+ "type": "module",
7
+ "main": "./dist/index.cjs",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "require": "./dist/index.cjs"
15
+ }
16
+ },
17
+ "files": ["dist"],
18
+ "sideEffects": false,
19
+ "keywords": ["seating", "seat-map", "seatlayer", "ticketing", "reserved-seating"],
20
+ "scripts": {
21
+ "build": "tsup",
22
+ "typecheck": "tsc --noEmit"
23
+ },
24
+ "dependencies": {
25
+ "@seatlayer/core": "workspace:*"
26
+ },
27
+ "devDependencies": {
28
+ "tsup": "^8.5.1",
29
+ "typescript": "^5.9.0"
30
+ }
31
+ }