@ingram-tech/luma 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ingram Technologies
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,138 @@
1
+ # @ingram-tech/luma
2
+
3
+ TypeScript client for the [Luma](https://lu.ma) (lu.ma) public API.
4
+
5
+ Small, dependency-free, ESM-only. Wraps the endpoints needed to mirror a Luma
6
+ calendar — events, guests, people, coupons — and exposes typed escape hatches
7
+ for everything else.
8
+
9
+ > Unofficial. Not affiliated with or endorsed by Luma. "Luma" is a trademark of
10
+ > its respective owner.
11
+
12
+ ## Install
13
+
14
+ ```sh
15
+ bun add @ingram-tech/luma
16
+ # or: npm install @ingram-tech/luma
17
+ ```
18
+
19
+ Requires Node.js 18+ (uses the global `fetch`). A Luma **API key** is needed —
20
+ available under *Calendar Settings → API* on a Luma Plus plan.
21
+
22
+ ## Usage
23
+
24
+ ```ts
25
+ import { LumaClient } from "@ingram-tech/luma";
26
+
27
+ const luma = new LumaClient({ apiKey: process.env.LUMA_API_KEY! });
28
+ // or: const luma = LumaClient.fromEnv(); // reads LUMA_API_KEY
29
+
30
+ // Iterate every event on a calendar (auto-paginates).
31
+ for await (const entry of luma.calendar.listEvents({ calendarApiId })) {
32
+ console.log(entry.event?.name);
33
+ }
34
+
35
+ // …or collect them into an array.
36
+ const events = await luma.calendar.listAllEvents({ calendarApiId });
37
+
38
+ // Guests of an event.
39
+ const guests = await luma.events.listAllGuests({ eventApiId });
40
+
41
+ // A single guest, by email.
42
+ const guest = await luma.events.getGuest({ eventApiId, email: "a@b.com" });
43
+
44
+ // Approve a guest.
45
+ await luma.events.updateGuestStatus({
46
+ eventApiId,
47
+ guestApiId: guest.api_id,
48
+ status: "approved",
49
+ });
50
+
51
+ // Coupons.
52
+ const coupon = await luma.calendar.createCoupon({
53
+ code: "SUMMIT-2027",
54
+ remainingCount: 1,
55
+ discount: { type: "amount", centsOff: 4000, currency: "EUR" },
56
+ });
57
+ ```
58
+
59
+ ### Pagination
60
+
61
+ Cursor-paginated methods (`listEvents`, `listPeople`, `listGuests`,
62
+ `listCoupons`) return an `AsyncGenerator` that follows `next_cursor`
63
+ automatically. Each has a `listAll…` sibling that drains it into an array.
64
+ `collect()` is exported for draining any async iterable.
65
+
66
+ ### Escape hatches
67
+
68
+ The typed methods cover the endpoints this client is built around. For
69
+ anything not modelled, call the API directly — both methods are public:
70
+
71
+ ```ts
72
+ // Any endpoint, typed by you.
73
+ const data = await luma.request<MyType>("/v1/event/get", {
74
+ query: { api_id: eventApiId },
75
+ });
76
+
77
+ // Any cursor-paginated endpoint.
78
+ for await (const entry of luma.paginate<MyEntry>("/v1/some/list", { foo: "bar" })) {
79
+ // …
80
+ }
81
+ ```
82
+
83
+ `request` also accepts `fetchInit` for passing through framework-specific
84
+ options, e.g. Next.js cache hints:
85
+
86
+ ```ts
87
+ await luma.request("/v1/calendar/list-events", {
88
+ query: { calendar_api_id },
89
+ fetchInit: { next: { revalidate: 300 } },
90
+ });
91
+ ```
92
+
93
+ ### Errors
94
+
95
+ Non-2xx responses (and non-JSON bodies) throw `LumaApiError`, which carries the
96
+ `status`, raw `body`, and `path`, plus convenience getters:
97
+
98
+ ```ts
99
+ import { LumaApiError } from "@ingram-tech/luma";
100
+
101
+ try {
102
+ await luma.calendar.createCoupon({ code: "DUP", discount: { type: "percent", percentOff: 10 } });
103
+ } catch (err) {
104
+ if (err instanceof LumaApiError && err.isDuplicateCouponCode) {
105
+ // coupon already exists — treat as idempotent
106
+ }
107
+ }
108
+ ```
109
+
110
+ `isAuthError` (401/403) and `isRateLimited` (429) are also provided.
111
+
112
+ ## API coverage
113
+
114
+ | Area | Methods |
115
+ | --- | --- |
116
+ | Calendar events | `calendar.listEvents`, `calendar.listAllEvents` |
117
+ | Calendar people | `calendar.listPeople`, `calendar.listAllPeople` |
118
+ | Coupons | `calendar.listCoupons`, `calendar.findCouponByCode`, `calendar.createCoupon` |
119
+ | Events | `events.get` |
120
+ | Guests | `events.listGuests`, `events.listAllGuests`, `events.getGuest`, `events.updateGuestStatus` |
121
+ | Anything else | `request`, `paginate` |
122
+
123
+ Luma does not publish a machine-readable schema. The calendar-events and
124
+ coupon paths are exercised in production; the guest and people paths are
125
+ modelled from Luma's public API documentation. Response objects carry an index
126
+ signature, so unmodelled fields are always reachable. PRs welcome.
127
+
128
+ ## Development
129
+
130
+ ```sh
131
+ bun install
132
+ bun run ci # type-check, lint, test, build
133
+ bun run test # watch mode
134
+ ```
135
+
136
+ ## License
137
+
138
+ MIT © Ingram Technologies
@@ -0,0 +1,272 @@
1
+ /**
2
+ * Types for the Luma public API.
3
+ *
4
+ * Luma does not publish a machine-readable schema, and several fields are
5
+ * optional or vary between plan tiers. Object types therefore carry an index
6
+ * signature so unmodelled fields remain accessible without a cast. When in
7
+ * doubt, reach for {@link import("./client").LumaClient.request} and type the
8
+ * response yourself.
9
+ */
10
+ /** A cursor-paginated list response. */
11
+ interface LumaPaginatedResponse<T> {
12
+ entries: T[];
13
+ has_more?: boolean;
14
+ next_cursor?: string | null;
15
+ }
16
+ /** Options shared by every cursor-paginated endpoint. */
17
+ interface LumaPaginationOptions {
18
+ /** Opaque cursor returned as `next_cursor` by a previous page. */
19
+ paginationCursor?: string;
20
+ /** Page size. Luma's default and maximum vary by endpoint. */
21
+ paginationLimit?: number;
22
+ }
23
+ interface LumaEventTag {
24
+ api_id: string;
25
+ name: string;
26
+ [key: string]: unknown;
27
+ }
28
+ interface LumaEventLocation {
29
+ type?: string;
30
+ name?: string;
31
+ address?: string;
32
+ [key: string]: unknown;
33
+ }
34
+ interface LumaEvent {
35
+ api_id: string;
36
+ name: string;
37
+ description?: string;
38
+ start_at: string;
39
+ end_at?: string;
40
+ timezone?: string;
41
+ url?: string;
42
+ cover_url?: string;
43
+ visibility?: string;
44
+ location?: LumaEventLocation;
45
+ guest_limit?: number | null;
46
+ guest_count?: number;
47
+ [key: string]: unknown;
48
+ }
49
+ /**
50
+ * An entry in `calendar/list-events`. Luma nests the event under `event` and
51
+ * places calendar-scoped metadata (tags) alongside it.
52
+ */
53
+ interface LumaCalendarEntry {
54
+ api_id?: string;
55
+ event?: LumaEvent;
56
+ tags?: LumaEventTag[];
57
+ [key: string]: unknown;
58
+ }
59
+ interface ListCalendarEventsOptions extends LumaPaginationOptions {
60
+ /** Calendar to list events for. Required. */
61
+ calendarApiId: string;
62
+ /** Only events starting at or after this instant. */
63
+ after?: Date | string;
64
+ /** Only events starting at or before this instant. */
65
+ before?: Date | string;
66
+ }
67
+ interface LumaPerson {
68
+ api_id: string;
69
+ name?: string;
70
+ email?: string;
71
+ avatar_url?: string;
72
+ [key: string]: unknown;
73
+ }
74
+ interface ListCalendarPeopleOptions extends LumaPaginationOptions {
75
+ calendarApiId: string;
76
+ }
77
+ type LumaGuestApprovalStatus = "approved" | "declined" | "pending_approval" | "waitlist" | "invited" | (string & {});
78
+ interface LumaGuest {
79
+ api_id: string;
80
+ name?: string;
81
+ email?: string;
82
+ user_api_id?: string;
83
+ approval_status?: LumaGuestApprovalStatus;
84
+ registered_at?: string;
85
+ checked_in_at?: string | null;
86
+ event_ticket?: {
87
+ api_id?: string;
88
+ name?: string;
89
+ [key: string]: unknown;
90
+ };
91
+ /** Answers to the event's registration questions, when present. */
92
+ registration_answers?: Array<{
93
+ label?: string;
94
+ answer?: string;
95
+ question_id?: string;
96
+ [key: string]: unknown;
97
+ }>;
98
+ [key: string]: unknown;
99
+ }
100
+ /** An entry in `event/get-guests`. Luma nests the guest under `guest`. */
101
+ interface LumaGuestEntry {
102
+ api_id?: string;
103
+ guest?: LumaGuest;
104
+ [key: string]: unknown;
105
+ }
106
+ interface ListEventGuestsOptions extends LumaPaginationOptions {
107
+ eventApiId: string;
108
+ /** Filter by approval status, when supported by the endpoint. */
109
+ approvalStatus?: LumaGuestApprovalStatus;
110
+ }
111
+ interface GetEventGuestOptions {
112
+ eventApiId: string;
113
+ /** Look the guest up by their guest api_id… */
114
+ guestApiId?: string;
115
+ /** …or by the email they registered with. One of the two is required. */
116
+ email?: string;
117
+ }
118
+ interface UpdateGuestStatusOptions {
119
+ eventApiId: string;
120
+ guestApiId: string;
121
+ status: LumaGuestApprovalStatus;
122
+ }
123
+ interface LumaCoupon {
124
+ api_id: string;
125
+ code: string;
126
+ [key: string]: unknown;
127
+ }
128
+ interface LumaCouponEntry {
129
+ api_id?: string;
130
+ id?: string;
131
+ code?: string;
132
+ [key: string]: unknown;
133
+ }
134
+ interface CreateCalendarCouponInput {
135
+ code: string;
136
+ /** Number of times the coupon may be redeemed. */
137
+ remainingCount?: number;
138
+ validStartAt?: Date | string;
139
+ validEndAt?: Date | string;
140
+ discount: {
141
+ type: "percent";
142
+ /** Whole-percent value, e.g. `25` for 25% off. */
143
+ percentOff: number;
144
+ } | {
145
+ type: "amount";
146
+ /** Discount in the currency's minor unit, e.g. `4000` = €40. */
147
+ centsOff: number;
148
+ /** ISO 4217 code, lowercased by the client (e.g. `eur`). */
149
+ currency: string;
150
+ };
151
+ }
152
+
153
+ interface LumaClientOptions {
154
+ /** Luma API key. Found under Calendar Settings → API on a Luma Plus plan. */
155
+ apiKey: string;
156
+ /** Override the API base URL. Defaults to {@link LUMA_API_BASE_URL}. */
157
+ baseUrl?: string;
158
+ /**
159
+ * Fetch implementation to use. Defaults to the global `fetch`. Pass a
160
+ * custom one to inject Next.js cache hints, retries, or a test double.
161
+ */
162
+ fetch?: typeof fetch;
163
+ }
164
+ /** Per-request options for {@link LumaClient.request}. */
165
+ interface LumaRequestInit {
166
+ method?: string;
167
+ /** Query parameters. `undefined`/`null` values are dropped; `Date`s are
168
+ * serialised as ISO strings. */
169
+ query?: Record<string, unknown>;
170
+ /** JSON request body. Sets `content-type: application/json`. */
171
+ body?: unknown;
172
+ signal?: AbortSignal;
173
+ /** Extra `fetch` init merged last — e.g. Next.js `{ next: { revalidate } }`. */
174
+ fetchInit?: RequestInit;
175
+ }
176
+ /** Drain an async generator into an array. */
177
+ declare const collect: <T>(source: AsyncIterable<T>) => Promise<T[]>;
178
+ /**
179
+ * Typed client for the Luma (lu.ma) public API.
180
+ *
181
+ * The typed resource methods cover the endpoints this client is built around;
182
+ * for anything not modelled here, {@link LumaClient.request} and
183
+ * {@link LumaClient.paginate} are public escape hatches that work against any
184
+ * endpoint.
185
+ */
186
+ declare class LumaClient {
187
+ readonly baseUrl: string;
188
+ private readonly apiKey;
189
+ private readonly fetchImpl;
190
+ constructor(options: LumaClientOptions);
191
+ /**
192
+ * Construct a client from environment variables. Reads `LUMA_API_KEY` and,
193
+ * optionally, `LUMA_API_BASE_URL`.
194
+ */
195
+ static fromEnv(env?: Record<string, string | undefined>): LumaClient;
196
+ /**
197
+ * Low-level request against any Luma endpoint. `path` is taken relative to
198
+ * the base URL, e.g. `/v1/event/get`.
199
+ */
200
+ request<T>(path: string, init?: LumaRequestInit): Promise<T>;
201
+ /**
202
+ * Iterate a cursor-paginated endpoint, yielding each entry. Follows
203
+ * `next_cursor` while `has_more` is true.
204
+ */
205
+ paginate<T>(path: string, query?: Record<string, unknown>): AsyncGenerator<T>;
206
+ readonly calendar: {
207
+ /** Iterate every event on a calendar. */
208
+ listEvents: (options: ListCalendarEventsOptions) => AsyncGenerator<LumaCalendarEntry, any, any>;
209
+ /** Collect every event on a calendar into an array. */
210
+ listAllEvents: (options: ListCalendarEventsOptions) => Promise<LumaCalendarEntry[]>;
211
+ /** Iterate every person who has interacted with a calendar. */
212
+ listPeople: (options: ListCalendarPeopleOptions) => AsyncGenerator<LumaPerson, any, any>;
213
+ /** Collect every person on a calendar into an array. */
214
+ listAllPeople: (options: ListCalendarPeopleOptions) => Promise<LumaPerson[]>;
215
+ /** Iterate every coupon on the calendar tied to the API key. */
216
+ listCoupons: () => AsyncGenerator<LumaCoupon, any, any>;
217
+ /** Find a calendar coupon by its code, or `null` if none matches. */
218
+ findCouponByCode: (code: string) => Promise<LumaCoupon | null>;
219
+ /** Create a calendar coupon. */
220
+ createCoupon: (input: CreateCalendarCouponInput) => Promise<LumaCoupon>;
221
+ };
222
+ readonly events: {
223
+ /** Fetch a single event by its `api_id`. */
224
+ get: (eventApiId: string) => Promise<LumaEvent>;
225
+ /** Iterate every guest of an event. */
226
+ listGuests: (options: ListEventGuestsOptions) => AsyncGenerator<LumaGuest, any, any>;
227
+ /** Collect every guest of an event into an array. */
228
+ listAllGuests: (options: ListEventGuestsOptions) => Promise<LumaGuest[]>;
229
+ /** Fetch a single guest, by guest `api_id` or by registration email. */
230
+ getGuest: (options: GetEventGuestOptions) => Promise<LumaGuest>;
231
+ /** Update a guest's approval status (approve, decline, waitlist…). */
232
+ updateGuestStatus: (options: UpdateGuestStatusOptions) => Promise<void>;
233
+ };
234
+ }
235
+
236
+ /** Base URL for the Luma public API. */
237
+ declare const LUMA_API_BASE_URL = "https://public-api.luma.com";
238
+ /** Header Luma expects the API key in. */
239
+ declare const LUMA_API_KEY_HEADER = "x-luma-api-key";
240
+ /** API version path segment prepended to every endpoint. */
241
+ declare const LUMA_API_VERSION = "v1";
242
+
243
+ /**
244
+ * Thrown when the Luma API responds with a non-2xx status, or when a response
245
+ * body cannot be parsed as JSON.
246
+ */
247
+ declare class LumaApiError extends Error {
248
+ /** HTTP status code returned by Luma (0 if the request never completed). */
249
+ readonly status: number;
250
+ /** Raw, untruncated response body. */
251
+ readonly body: string;
252
+ /** API path that produced the error, e.g. `/v1/event/get-guests`. */
253
+ readonly path: string;
254
+ constructor(params: {
255
+ message: string;
256
+ status: number;
257
+ body: string;
258
+ path: string;
259
+ });
260
+ /** True for 401/403 — the API key is missing, invalid, or lacks scope. */
261
+ get isAuthError(): boolean;
262
+ /** True for 429 — the caller is being rate limited. */
263
+ get isRateLimited(): boolean;
264
+ /**
265
+ * True when Luma rejected a coupon create because the code already exists.
266
+ * Luma surfaces this as a 400/409 whose body mentions the code already
267
+ * existing; matched permissively so callers can treat it as idempotent.
268
+ */
269
+ get isDuplicateCouponCode(): boolean;
270
+ }
271
+
272
+ export { type CreateCalendarCouponInput, type GetEventGuestOptions, LUMA_API_BASE_URL, LUMA_API_KEY_HEADER, LUMA_API_VERSION, type ListCalendarEventsOptions, type ListCalendarPeopleOptions, type ListEventGuestsOptions, LumaApiError, type LumaCalendarEntry, LumaClient, type LumaClientOptions, type LumaCoupon, type LumaCouponEntry, type LumaEvent, type LumaEventLocation, type LumaEventTag, type LumaGuest, type LumaGuestApprovalStatus, type LumaGuestEntry, type LumaPaginatedResponse, type LumaPaginationOptions, type LumaPerson, type LumaRequestInit, type UpdateGuestStatusOptions, collect };
package/dist/index.js ADDED
@@ -0,0 +1,298 @@
1
+ // src/constants.ts
2
+ var LUMA_API_BASE_URL = "https://public-api.luma.com";
3
+ var LUMA_API_KEY_HEADER = "x-luma-api-key";
4
+ var LUMA_API_VERSION = "v1";
5
+
6
+ // src/errors.ts
7
+ var LumaApiError = class extends Error {
8
+ /** HTTP status code returned by Luma (0 if the request never completed). */
9
+ status;
10
+ /** Raw, untruncated response body. */
11
+ body;
12
+ /** API path that produced the error, e.g. `/v1/event/get-guests`. */
13
+ path;
14
+ constructor(params) {
15
+ super(params.message);
16
+ this.name = "LumaApiError";
17
+ this.status = params.status;
18
+ this.body = params.body;
19
+ this.path = params.path;
20
+ }
21
+ /** True for 401/403 — the API key is missing, invalid, or lacks scope. */
22
+ get isAuthError() {
23
+ return this.status === 401 || this.status === 403;
24
+ }
25
+ /** True for 429 — the caller is being rate limited. */
26
+ get isRateLimited() {
27
+ return this.status === 429;
28
+ }
29
+ /**
30
+ * True when Luma rejected a coupon create because the code already exists.
31
+ * Luma surfaces this as a 400/409 whose body mentions the code already
32
+ * existing; matched permissively so callers can treat it as idempotent.
33
+ */
34
+ get isDuplicateCouponCode() {
35
+ const lower = this.body.toLowerCase();
36
+ return (this.status === 400 || this.status === 409) && lower.includes("code") && (lower.includes("exist") || lower.includes("already"));
37
+ }
38
+ };
39
+
40
+ // src/client.ts
41
+ var serializeQueryValue = (value) => value instanceof Date ? value.toISOString() : String(value);
42
+ var toIso = (value) => value instanceof Date ? value.toISOString() : value;
43
+ var collect = async (source) => {
44
+ const out = [];
45
+ for await (const item of source) {
46
+ out.push(item);
47
+ }
48
+ return out;
49
+ };
50
+ var LumaClient = class _LumaClient {
51
+ baseUrl;
52
+ apiKey;
53
+ fetchImpl;
54
+ constructor(options) {
55
+ if (!options.apiKey) {
56
+ throw new Error("LumaClient requires an `apiKey`");
57
+ }
58
+ this.apiKey = options.apiKey;
59
+ this.baseUrl = options.baseUrl ?? LUMA_API_BASE_URL;
60
+ const fetchImpl = options.fetch ?? globalThis.fetch;
61
+ if (typeof fetchImpl !== "function") {
62
+ throw new Error(
63
+ "No global `fetch` available; pass one via LumaClientOptions.fetch"
64
+ );
65
+ }
66
+ this.fetchImpl = fetchImpl;
67
+ }
68
+ /**
69
+ * Construct a client from environment variables. Reads `LUMA_API_KEY` and,
70
+ * optionally, `LUMA_API_BASE_URL`.
71
+ */
72
+ static fromEnv(env = typeof process !== "undefined" ? process.env : {}) {
73
+ const apiKey = env.LUMA_API_KEY;
74
+ if (!apiKey) {
75
+ throw new Error("LUMA_API_KEY is not set");
76
+ }
77
+ return new _LumaClient({ apiKey, baseUrl: env.LUMA_API_BASE_URL });
78
+ }
79
+ /**
80
+ * Low-level request against any Luma endpoint. `path` is taken relative to
81
+ * the base URL, e.g. `/v1/event/get`.
82
+ */
83
+ async request(path, init = {}) {
84
+ const url = new URL(path.startsWith("/") ? path : `/${path}`, this.baseUrl);
85
+ if (init.query) {
86
+ for (const [key, value] of Object.entries(init.query)) {
87
+ if (value !== void 0 && value !== null) {
88
+ url.searchParams.set(key, serializeQueryValue(value));
89
+ }
90
+ }
91
+ }
92
+ const headers = {
93
+ [LUMA_API_KEY_HEADER]: this.apiKey,
94
+ accept: "application/json"
95
+ };
96
+ let body;
97
+ if (init.body !== void 0) {
98
+ headers["content-type"] = "application/json";
99
+ body = JSON.stringify(init.body);
100
+ }
101
+ let response;
102
+ try {
103
+ response = await this.fetchImpl(url, {
104
+ method: init.method ?? "GET",
105
+ headers,
106
+ body,
107
+ signal: init.signal,
108
+ ...init.fetchInit
109
+ });
110
+ } catch (cause) {
111
+ throw new LumaApiError({
112
+ message: `Luma API request to ${path} failed: ${cause instanceof Error ? cause.message : String(cause)}`,
113
+ status: 0,
114
+ body: "",
115
+ path
116
+ });
117
+ }
118
+ const text = await response.text();
119
+ if (!response.ok) {
120
+ throw new LumaApiError({
121
+ message: `Luma API responded ${response.status} for ${path}`,
122
+ status: response.status,
123
+ body: text,
124
+ path
125
+ });
126
+ }
127
+ if (text.length === 0) {
128
+ return void 0;
129
+ }
130
+ try {
131
+ return JSON.parse(text);
132
+ } catch {
133
+ throw new LumaApiError({
134
+ message: `Luma API returned a non-JSON body for ${path}`,
135
+ status: response.status,
136
+ body: text,
137
+ path
138
+ });
139
+ }
140
+ }
141
+ /**
142
+ * Iterate a cursor-paginated endpoint, yielding each entry. Follows
143
+ * `next_cursor` while `has_more` is true.
144
+ */
145
+ async *paginate(path, query = {}) {
146
+ let cursor;
147
+ for (let page = 0; page < 1e4; page += 1) {
148
+ const pageQuery = { ...query };
149
+ if (cursor) {
150
+ pageQuery.pagination_cursor = cursor;
151
+ }
152
+ const result = await this.request(path, {
153
+ query: pageQuery
154
+ });
155
+ for (const entry of result.entries ?? []) {
156
+ yield entry;
157
+ }
158
+ if (!result.has_more || !result.next_cursor) {
159
+ return;
160
+ }
161
+ cursor = result.next_cursor;
162
+ }
163
+ }
164
+ // ─── calendar ────────────────────────────────────────────────────────
165
+ calendar = {
166
+ /** Iterate every event on a calendar. */
167
+ listEvents: (options) => this.paginate("/v1/calendar/list-events", {
168
+ calendar_api_id: options.calendarApiId,
169
+ after: options.after,
170
+ before: options.before,
171
+ pagination_limit: options.paginationLimit,
172
+ pagination_cursor: options.paginationCursor
173
+ }),
174
+ /** Collect every event on a calendar into an array. */
175
+ listAllEvents: (options) => collect(this.calendar.listEvents(options)),
176
+ /** Iterate every person who has interacted with a calendar. */
177
+ listPeople: (options) => this.paginate("/v1/calendar/list-people", {
178
+ calendar_api_id: options.calendarApiId,
179
+ pagination_limit: options.paginationLimit,
180
+ pagination_cursor: options.paginationCursor
181
+ }),
182
+ /** Collect every person on a calendar into an array. */
183
+ listAllPeople: (options) => collect(this.calendar.listPeople(options)),
184
+ /** Iterate every coupon on the calendar tied to the API key. */
185
+ listCoupons: async function* () {
186
+ for await (const entry of this.paginate(
187
+ "/v1/calendar/coupons"
188
+ )) {
189
+ yield normalizeCoupon(entry);
190
+ }
191
+ }.bind(this),
192
+ /** Find a calendar coupon by its code, or `null` if none matches. */
193
+ findCouponByCode: async (code) => {
194
+ const target = code.toLowerCase();
195
+ for await (const coupon of this.calendar.listCoupons()) {
196
+ if (coupon.code.toLowerCase() === target) {
197
+ return coupon;
198
+ }
199
+ }
200
+ return null;
201
+ },
202
+ /** Create a calendar coupon. */
203
+ createCoupon: async (input) => {
204
+ const discount = input.discount.type === "percent" ? {
205
+ discount_type: "percent",
206
+ percent_off: input.discount.percentOff
207
+ } : {
208
+ discount_type: "amount",
209
+ cents_off: input.discount.centsOff,
210
+ currency: input.discount.currency.toLowerCase()
211
+ };
212
+ const response = await this.request(
213
+ "/v1/calendar/coupons/create",
214
+ {
215
+ method: "POST",
216
+ body: {
217
+ code: input.code,
218
+ remaining_count: input.remainingCount,
219
+ valid_start_at: input.validStartAt ? toIso(input.validStartAt) : void 0,
220
+ valid_end_at: input.validEndAt ? toIso(input.validEndAt) : void 0,
221
+ discount
222
+ }
223
+ }
224
+ );
225
+ return normalizeCoupon(response.coupon ?? {}, input.code);
226
+ }
227
+ };
228
+ // ─── events ──────────────────────────────────────────────────────────
229
+ events = {
230
+ /** Fetch a single event by its `api_id`. */
231
+ get: async (eventApiId) => {
232
+ const response = await this.request(
233
+ "/v1/event/get",
234
+ { query: { api_id: eventApiId } }
235
+ );
236
+ return response.event ?? response;
237
+ },
238
+ /** Iterate every guest of an event. */
239
+ listGuests: (options) => async function* () {
240
+ for await (const entry of this.paginate(
241
+ "/v1/event/get-guests",
242
+ {
243
+ event_api_id: options.eventApiId,
244
+ approval_status: options.approvalStatus,
245
+ pagination_limit: options.paginationLimit,
246
+ pagination_cursor: options.paginationCursor
247
+ }
248
+ )) {
249
+ const guest = entry.guest ?? entry;
250
+ yield guest;
251
+ }
252
+ }.call(this),
253
+ /** Collect every guest of an event into an array. */
254
+ listAllGuests: (options) => collect(this.events.listGuests(options)),
255
+ /** Fetch a single guest, by guest `api_id` or by registration email. */
256
+ getGuest: async (options) => {
257
+ if (!options.guestApiId && !options.email) {
258
+ throw new Error("getGuest requires either `guestApiId` or `email`");
259
+ }
260
+ const response = await this.request(
261
+ "/v1/event/get-guest",
262
+ {
263
+ query: {
264
+ event_api_id: options.eventApiId,
265
+ api_id: options.guestApiId,
266
+ email: options.email
267
+ }
268
+ }
269
+ );
270
+ return response.guest ?? response;
271
+ },
272
+ /** Update a guest's approval status (approve, decline, waitlist…). */
273
+ updateGuestStatus: async (options) => {
274
+ await this.request("/v1/event/update-guest-status", {
275
+ method: "POST",
276
+ body: {
277
+ event_api_id: options.eventApiId,
278
+ guest_api_id: options.guestApiId,
279
+ status: options.status
280
+ }
281
+ });
282
+ }
283
+ };
284
+ };
285
+ var normalizeCoupon = (entry, fallbackCode) => ({
286
+ ...entry,
287
+ api_id: entry.api_id ?? entry.id ?? "",
288
+ code: entry.code ?? fallbackCode ?? ""
289
+ });
290
+ export {
291
+ LUMA_API_BASE_URL,
292
+ LUMA_API_KEY_HEADER,
293
+ LUMA_API_VERSION,
294
+ LumaApiError,
295
+ LumaClient,
296
+ collect
297
+ };
298
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/constants.ts","../src/errors.ts","../src/client.ts"],"sourcesContent":["/** Base URL for the Luma public API. */\nexport const LUMA_API_BASE_URL = \"https://public-api.luma.com\";\n\n/** Header Luma expects the API key in. */\nexport const LUMA_API_KEY_HEADER = \"x-luma-api-key\";\n\n/** API version path segment prepended to every endpoint. */\nexport const LUMA_API_VERSION = \"v1\";\n","/**\n * Thrown when the Luma API responds with a non-2xx status, or when a response\n * body cannot be parsed as JSON.\n */\nexport class LumaApiError extends Error {\n\t/** HTTP status code returned by Luma (0 if the request never completed). */\n\treadonly status: number;\n\t/** Raw, untruncated response body. */\n\treadonly body: string;\n\t/** API path that produced the error, e.g. `/v1/event/get-guests`. */\n\treadonly path: string;\n\n\tconstructor(params: {\n\t\tmessage: string;\n\t\tstatus: number;\n\t\tbody: string;\n\t\tpath: string;\n\t}) {\n\t\tsuper(params.message);\n\t\tthis.name = \"LumaApiError\";\n\t\tthis.status = params.status;\n\t\tthis.body = params.body;\n\t\tthis.path = params.path;\n\t}\n\n\t/** True for 401/403 — the API key is missing, invalid, or lacks scope. */\n\tget isAuthError(): boolean {\n\t\treturn this.status === 401 || this.status === 403;\n\t}\n\n\t/** True for 429 — the caller is being rate limited. */\n\tget isRateLimited(): boolean {\n\t\treturn this.status === 429;\n\t}\n\n\t/**\n\t * True when Luma rejected a coupon create because the code already exists.\n\t * Luma surfaces this as a 400/409 whose body mentions the code already\n\t * existing; matched permissively so callers can treat it as idempotent.\n\t */\n\tget isDuplicateCouponCode(): boolean {\n\t\tconst lower = this.body.toLowerCase();\n\t\treturn (\n\t\t\t(this.status === 400 || this.status === 409) &&\n\t\t\tlower.includes(\"code\") &&\n\t\t\t(lower.includes(\"exist\") || lower.includes(\"already\"))\n\t\t);\n\t}\n}\n","import { LUMA_API_BASE_URL, LUMA_API_KEY_HEADER } from \"./constants\";\nimport { LumaApiError } from \"./errors\";\nimport type {\n\tCreateCalendarCouponInput,\n\tGetEventGuestOptions,\n\tListCalendarEventsOptions,\n\tListCalendarPeopleOptions,\n\tListEventGuestsOptions,\n\tLumaCalendarEntry,\n\tLumaCoupon,\n\tLumaCouponEntry,\n\tLumaEvent,\n\tLumaGuest,\n\tLumaGuestEntry,\n\tLumaPaginatedResponse,\n\tLumaPerson,\n\tUpdateGuestStatusOptions,\n} from \"./types\";\n\nexport interface LumaClientOptions {\n\t/** Luma API key. Found under Calendar Settings → API on a Luma Plus plan. */\n\tapiKey: string;\n\t/** Override the API base URL. Defaults to {@link LUMA_API_BASE_URL}. */\n\tbaseUrl?: string;\n\t/**\n\t * Fetch implementation to use. Defaults to the global `fetch`. Pass a\n\t * custom one to inject Next.js cache hints, retries, or a test double.\n\t */\n\tfetch?: typeof fetch;\n}\n\n/** Per-request options for {@link LumaClient.request}. */\nexport interface LumaRequestInit {\n\tmethod?: string;\n\t/** Query parameters. `undefined`/`null` values are dropped; `Date`s are\n\t * serialised as ISO strings. */\n\tquery?: Record<string, unknown>;\n\t/** JSON request body. Sets `content-type: application/json`. */\n\tbody?: unknown;\n\tsignal?: AbortSignal;\n\t/** Extra `fetch` init merged last — e.g. Next.js `{ next: { revalidate } }`. */\n\tfetchInit?: RequestInit;\n}\n\nconst serializeQueryValue = (value: unknown): string =>\n\tvalue instanceof Date ? value.toISOString() : String(value);\n\nconst toIso = (value: Date | string): string =>\n\tvalue instanceof Date ? value.toISOString() : value;\n\n/** Drain an async generator into an array. */\nexport const collect = async <T>(source: AsyncIterable<T>): Promise<T[]> => {\n\tconst out: T[] = [];\n\tfor await (const item of source) {\n\t\tout.push(item);\n\t}\n\treturn out;\n};\n\n/**\n * Typed client for the Luma (lu.ma) public API.\n *\n * The typed resource methods cover the endpoints this client is built around;\n * for anything not modelled here, {@link LumaClient.request} and\n * {@link LumaClient.paginate} are public escape hatches that work against any\n * endpoint.\n */\nexport class LumaClient {\n\treadonly baseUrl: string;\n\tprivate readonly apiKey: string;\n\tprivate readonly fetchImpl: typeof fetch;\n\n\tconstructor(options: LumaClientOptions) {\n\t\tif (!options.apiKey) {\n\t\t\tthrow new Error(\"LumaClient requires an `apiKey`\");\n\t\t}\n\t\tthis.apiKey = options.apiKey;\n\t\tthis.baseUrl = options.baseUrl ?? LUMA_API_BASE_URL;\n\t\tconst fetchImpl = options.fetch ?? globalThis.fetch;\n\t\tif (typeof fetchImpl !== \"function\") {\n\t\t\tthrow new Error(\n\t\t\t\t\"No global `fetch` available; pass one via LumaClientOptions.fetch\",\n\t\t\t);\n\t\t}\n\t\tthis.fetchImpl = fetchImpl;\n\t}\n\n\t/**\n\t * Construct a client from environment variables. Reads `LUMA_API_KEY` and,\n\t * optionally, `LUMA_API_BASE_URL`.\n\t */\n\tstatic fromEnv(\n\t\tenv: Record<string, string | undefined> = typeof process !== \"undefined\"\n\t\t\t? process.env\n\t\t\t: {},\n\t): LumaClient {\n\t\tconst apiKey = env.LUMA_API_KEY;\n\t\tif (!apiKey) {\n\t\t\tthrow new Error(\"LUMA_API_KEY is not set\");\n\t\t}\n\t\treturn new LumaClient({ apiKey, baseUrl: env.LUMA_API_BASE_URL });\n\t}\n\n\t/**\n\t * Low-level request against any Luma endpoint. `path` is taken relative to\n\t * the base URL, e.g. `/v1/event/get`.\n\t */\n\tasync request<T>(path: string, init: LumaRequestInit = {}): Promise<T> {\n\t\tconst url = new URL(path.startsWith(\"/\") ? path : `/${path}`, this.baseUrl);\n\t\tif (init.query) {\n\t\t\tfor (const [key, value] of Object.entries(init.query)) {\n\t\t\t\tif (value !== undefined && value !== null) {\n\t\t\t\t\turl.searchParams.set(key, serializeQueryValue(value));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst headers: Record<string, string> = {\n\t\t\t[LUMA_API_KEY_HEADER]: this.apiKey,\n\t\t\taccept: \"application/json\",\n\t\t};\n\t\tlet body: string | undefined;\n\t\tif (init.body !== undefined) {\n\t\t\theaders[\"content-type\"] = \"application/json\";\n\t\t\tbody = JSON.stringify(init.body);\n\t\t}\n\n\t\tlet response: Response;\n\t\ttry {\n\t\t\tresponse = await this.fetchImpl(url, {\n\t\t\t\tmethod: init.method ?? \"GET\",\n\t\t\t\theaders,\n\t\t\t\tbody,\n\t\t\t\tsignal: init.signal,\n\t\t\t\t...init.fetchInit,\n\t\t\t});\n\t\t} catch (cause) {\n\t\t\tthrow new LumaApiError({\n\t\t\t\tmessage: `Luma API request to ${path} failed: ${\n\t\t\t\t\tcause instanceof Error ? cause.message : String(cause)\n\t\t\t\t}`,\n\t\t\t\tstatus: 0,\n\t\t\t\tbody: \"\",\n\t\t\t\tpath,\n\t\t\t});\n\t\t}\n\n\t\tconst text = await response.text();\n\t\tif (!response.ok) {\n\t\t\tthrow new LumaApiError({\n\t\t\t\tmessage: `Luma API responded ${response.status} for ${path}`,\n\t\t\t\tstatus: response.status,\n\t\t\t\tbody: text,\n\t\t\t\tpath,\n\t\t\t});\n\t\t}\n\t\tif (text.length === 0) {\n\t\t\treturn undefined as T;\n\t\t}\n\t\ttry {\n\t\t\treturn JSON.parse(text) as T;\n\t\t} catch {\n\t\t\tthrow new LumaApiError({\n\t\t\t\tmessage: `Luma API returned a non-JSON body for ${path}`,\n\t\t\t\tstatus: response.status,\n\t\t\t\tbody: text,\n\t\t\t\tpath,\n\t\t\t});\n\t\t}\n\t}\n\n\t/**\n\t * Iterate a cursor-paginated endpoint, yielding each entry. Follows\n\t * `next_cursor` while `has_more` is true.\n\t */\n\tasync *paginate<T>(\n\t\tpath: string,\n\t\tquery: Record<string, unknown> = {},\n\t): AsyncGenerator<T> {\n\t\tlet cursor: string | null | undefined;\n\t\t// Hard stop so a misbehaving endpoint cannot loop forever.\n\t\tfor (let page = 0; page < 10_000; page += 1) {\n\t\t\tconst pageQuery = { ...query };\n\t\t\tif (cursor) {\n\t\t\t\tpageQuery.pagination_cursor = cursor;\n\t\t\t}\n\t\t\tconst result = await this.request<LumaPaginatedResponse<T>>(path, {\n\t\t\t\tquery: pageQuery,\n\t\t\t});\n\t\t\tfor (const entry of result.entries ?? []) {\n\t\t\t\tyield entry;\n\t\t\t}\n\t\t\tif (!result.has_more || !result.next_cursor) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tcursor = result.next_cursor;\n\t\t}\n\t}\n\n\t// ─── calendar ────────────────────────────────────────────────────────\n\n\treadonly calendar = {\n\t\t/** Iterate every event on a calendar. */\n\t\tlistEvents: (options: ListCalendarEventsOptions) =>\n\t\t\tthis.paginate<LumaCalendarEntry>(\"/v1/calendar/list-events\", {\n\t\t\t\tcalendar_api_id: options.calendarApiId,\n\t\t\t\tafter: options.after,\n\t\t\t\tbefore: options.before,\n\t\t\t\tpagination_limit: options.paginationLimit,\n\t\t\t\tpagination_cursor: options.paginationCursor,\n\t\t\t}),\n\n\t\t/** Collect every event on a calendar into an array. */\n\t\tlistAllEvents: (options: ListCalendarEventsOptions) =>\n\t\t\tcollect(this.calendar.listEvents(options)),\n\n\t\t/** Iterate every person who has interacted with a calendar. */\n\t\tlistPeople: (options: ListCalendarPeopleOptions) =>\n\t\t\tthis.paginate<LumaPerson>(\"/v1/calendar/list-people\", {\n\t\t\t\tcalendar_api_id: options.calendarApiId,\n\t\t\t\tpagination_limit: options.paginationLimit,\n\t\t\t\tpagination_cursor: options.paginationCursor,\n\t\t\t}),\n\n\t\t/** Collect every person on a calendar into an array. */\n\t\tlistAllPeople: (options: ListCalendarPeopleOptions) =>\n\t\t\tcollect(this.calendar.listPeople(options)),\n\n\t\t/** Iterate every coupon on the calendar tied to the API key. */\n\t\tlistCoupons: async function* (this: LumaClient): AsyncGenerator<LumaCoupon> {\n\t\t\tfor await (const entry of this.paginate<LumaCouponEntry>(\n\t\t\t\t\"/v1/calendar/coupons\",\n\t\t\t)) {\n\t\t\t\tyield normalizeCoupon(entry);\n\t\t\t}\n\t\t}.bind(this),\n\n\t\t/** Find a calendar coupon by its code, or `null` if none matches. */\n\t\tfindCouponByCode: async (code: string): Promise<LumaCoupon | null> => {\n\t\t\tconst target = code.toLowerCase();\n\t\t\tfor await (const coupon of this.calendar.listCoupons()) {\n\t\t\t\tif (coupon.code.toLowerCase() === target) {\n\t\t\t\t\treturn coupon;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn null;\n\t\t},\n\n\t\t/** Create a calendar coupon. */\n\t\tcreateCoupon: async (input: CreateCalendarCouponInput): Promise<LumaCoupon> => {\n\t\t\tconst discount =\n\t\t\t\tinput.discount.type === \"percent\"\n\t\t\t\t\t? {\n\t\t\t\t\t\t\tdiscount_type: \"percent\" as const,\n\t\t\t\t\t\t\tpercent_off: input.discount.percentOff,\n\t\t\t\t\t\t}\n\t\t\t\t\t: {\n\t\t\t\t\t\t\tdiscount_type: \"amount\" as const,\n\t\t\t\t\t\t\tcents_off: input.discount.centsOff,\n\t\t\t\t\t\t\tcurrency: input.discount.currency.toLowerCase(),\n\t\t\t\t\t\t};\n\t\t\tconst response = await this.request<{ coupon?: LumaCouponEntry }>(\n\t\t\t\t\"/v1/calendar/coupons/create\",\n\t\t\t\t{\n\t\t\t\t\tmethod: \"POST\",\n\t\t\t\t\tbody: {\n\t\t\t\t\t\tcode: input.code,\n\t\t\t\t\t\tremaining_count: input.remainingCount,\n\t\t\t\t\t\tvalid_start_at: input.validStartAt\n\t\t\t\t\t\t\t? toIso(input.validStartAt)\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\t\tvalid_end_at: input.validEndAt\n\t\t\t\t\t\t\t? toIso(input.validEndAt)\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\t\tdiscount,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t);\n\t\t\treturn normalizeCoupon(response.coupon ?? {}, input.code);\n\t\t},\n\t};\n\n\t// ─── events ──────────────────────────────────────────────────────────\n\n\treadonly events = {\n\t\t/** Fetch a single event by its `api_id`. */\n\t\tget: async (eventApiId: string): Promise<LumaEvent> => {\n\t\t\tconst response = await this.request<{ event?: LumaEvent } & LumaEvent>(\n\t\t\t\t\"/v1/event/get\",\n\t\t\t\t{ query: { api_id: eventApiId } },\n\t\t\t);\n\t\t\treturn response.event ?? response;\n\t\t},\n\n\t\t/** Iterate every guest of an event. */\n\t\tlistGuests: (options: ListEventGuestsOptions) =>\n\t\t\tasync function* (this: LumaClient): AsyncGenerator<LumaGuest> {\n\t\t\t\tfor await (const entry of this.paginate<LumaGuestEntry>(\n\t\t\t\t\t\"/v1/event/get-guests\",\n\t\t\t\t\t{\n\t\t\t\t\t\tevent_api_id: options.eventApiId,\n\t\t\t\t\t\tapproval_status: options.approvalStatus,\n\t\t\t\t\t\tpagination_limit: options.paginationLimit,\n\t\t\t\t\t\tpagination_cursor: options.paginationCursor,\n\t\t\t\t\t},\n\t\t\t\t)) {\n\t\t\t\t\tconst guest = entry.guest ?? (entry as unknown as LumaGuest);\n\t\t\t\t\tyield guest;\n\t\t\t\t}\n\t\t\t}.call(this),\n\n\t\t/** Collect every guest of an event into an array. */\n\t\tlistAllGuests: (options: ListEventGuestsOptions) =>\n\t\t\tcollect(this.events.listGuests(options)),\n\n\t\t/** Fetch a single guest, by guest `api_id` or by registration email. */\n\t\tgetGuest: async (options: GetEventGuestOptions): Promise<LumaGuest> => {\n\t\t\tif (!options.guestApiId && !options.email) {\n\t\t\t\tthrow new Error(\"getGuest requires either `guestApiId` or `email`\");\n\t\t\t}\n\t\t\tconst response = await this.request<{ guest?: LumaGuest } & LumaGuest>(\n\t\t\t\t\"/v1/event/get-guest\",\n\t\t\t\t{\n\t\t\t\t\tquery: {\n\t\t\t\t\t\tevent_api_id: options.eventApiId,\n\t\t\t\t\t\tapi_id: options.guestApiId,\n\t\t\t\t\t\temail: options.email,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t);\n\t\t\treturn response.guest ?? response;\n\t\t},\n\n\t\t/** Update a guest's approval status (approve, decline, waitlist…). */\n\t\tupdateGuestStatus: async (options: UpdateGuestStatusOptions): Promise<void> => {\n\t\t\tawait this.request(\"/v1/event/update-guest-status\", {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: {\n\t\t\t\t\tevent_api_id: options.eventApiId,\n\t\t\t\t\tguest_api_id: options.guestApiId,\n\t\t\t\t\tstatus: options.status,\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\t};\n}\n\nconst normalizeCoupon = (\n\tentry: LumaCouponEntry,\n\tfallbackCode?: string,\n): LumaCoupon => ({\n\t...entry,\n\tapi_id: entry.api_id ?? entry.id ?? \"\",\n\tcode: entry.code ?? fallbackCode ?? \"\",\n});\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@ingram-tech/luma",
3
+ "version": "0.1.0",
4
+ "description": "TypeScript client for the Luma (lu.ma) public API.",
5
+ "license": "MIT",
6
+ "author": "Ingram Technologies",
7
+ "keywords": [
8
+ "luma",
9
+ "lu.ma",
10
+ "events",
11
+ "api",
12
+ "client",
13
+ "sdk"
14
+ ],
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/ingram-technologies/luma.git"
18
+ },
19
+ "bugs": "https://github.com/ingram-technologies/luma/issues",
20
+ "homepage": "https://github.com/ingram-technologies/luma#readme",
21
+ "type": "module",
22
+ "exports": {
23
+ ".": {
24
+ "import": "./dist/index.js",
25
+ "types": "./dist/index.d.ts"
26
+ }
27
+ },
28
+ "main": "./dist/index.js",
29
+ "types": "./dist/index.d.ts",
30
+ "files": [
31
+ "dist"
32
+ ],
33
+ "publishConfig": {
34
+ "access": "public"
35
+ },
36
+ "scripts": {
37
+ "build": "tsup",
38
+ "prepack": "bun run build",
39
+ "lint": "biome check .",
40
+ "format": "biome format --write .",
41
+ "type-check": "tsc --noEmit",
42
+ "test": "vitest",
43
+ "test:run": "vitest --run",
44
+ "ci": "bun run type-check && bun run lint && bun run test:run && bun run build"
45
+ },
46
+ "engines": {
47
+ "node": ">=18"
48
+ },
49
+ "devDependencies": {
50
+ "@biomejs/biome": "^2.4.13",
51
+ "@types/node": "^22.0.0",
52
+ "tsup": "^8.0.0",
53
+ "typescript": "^5.9.3",
54
+ "vitest": "^4.0.18"
55
+ }
56
+ }