@ingram-tech/luma 0.2.0 → 0.3.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/dist/index.js CHANGED
@@ -79,7 +79,7 @@ var LumaClient = class _LumaClient {
79
79
  }
80
80
  /**
81
81
  * Low-level request against any Luma endpoint. `path` is taken relative to
82
- * the base URL, e.g. `/v1/event/get`.
82
+ * the base URL, e.g. `/v1/events/get`.
83
83
  */
84
84
  async request(path, init = {}) {
85
85
  const url = new URL(path.startsWith("/") ? path : `/${path}`, this.baseUrl);
@@ -163,33 +163,33 @@ var LumaClient = class _LumaClient {
163
163
  }
164
164
  }
165
165
  // ─── calendar ────────────────────────────────────────────────────────
166
+ //
167
+ // The calendar is inferred from the API key, so these take no calendar id.
166
168
  calendar = {
167
- /** Iterate every event on a calendar. */
168
- listEvents: (options) => this.paginate("/v1/calendar/list-events", {
169
- calendar_api_id: options.calendarApiId,
169
+ /** Iterate every event the calendar manages. */
170
+ listEvents: (options = {}) => this.paginate("/v1/calendars/events/list", {
170
171
  after: options.after,
171
172
  before: options.before,
173
+ status: options.status,
174
+ access: options.access,
175
+ platforms: options.platforms,
176
+ sort_direction: options.sortDirection,
172
177
  pagination_limit: options.paginationLimit,
173
178
  pagination_cursor: options.paginationCursor
174
179
  }),
175
- /** Collect every event on a calendar into an array. */
176
- listAllEvents: (options) => collect(this.calendar.listEvents(options)),
177
- /** Iterate every person who has interacted with a calendar. */
178
- listPeople: (options) => this.paginate("/v1/calendar/list-people", {
179
- calendar_api_id: options.calendarApiId,
180
+ /** Collect every event the calendar manages into an array. */
181
+ listAllEvents: (options = {}) => collect(this.calendar.listEvents(options)),
182
+ /** Iterate every contact on the calendar. */
183
+ listContacts: (options = {}) => this.paginate("/v1/calendars/contacts/list", {
184
+ query: options.query,
185
+ sort_direction: options.sortDirection,
180
186
  pagination_limit: options.paginationLimit,
181
187
  pagination_cursor: options.paginationCursor
182
188
  }),
183
- /** Collect every person on a calendar into an array. */
184
- listAllPeople: (options) => collect(this.calendar.listPeople(options)),
189
+ /** Collect every contact on the calendar into an array. */
190
+ listAllContacts: (options = {}) => collect(this.calendar.listContacts(options)),
185
191
  /** Iterate every coupon on the calendar tied to the API key. */
186
- listCoupons: async function* () {
187
- for await (const entry of this.paginate(
188
- "/v1/calendar/coupons"
189
- )) {
190
- yield normalizeCoupon(entry);
191
- }
192
- }.bind(this),
192
+ listCoupons: () => this.paginate("/v1/calendars/coupons/list"),
193
193
  /** Find a calendar coupon by its code, or `null` if none matches. */
194
194
  findCouponByCode: async (code) => {
195
195
  const target = code.toLowerCase();
@@ -200,84 +200,56 @@ var LumaClient = class _LumaClient {
200
200
  }
201
201
  return null;
202
202
  },
203
- /** Create a calendar coupon. */
204
- createCoupon: async (input) => {
205
- const discount = input.discount.type === "percent" ? {
206
- discount_type: "percent",
207
- percent_off: input.discount.percentOff
208
- } : {
209
- discount_type: "amount",
210
- cents_off: input.discount.centsOff,
211
- currency: input.discount.currency.toLowerCase()
212
- };
213
- const response = await this.request(
214
- "/v1/calendar/coupons/create",
215
- {
216
- method: "POST",
217
- body: {
218
- code: input.code,
219
- remaining_count: input.remainingCount,
220
- valid_start_at: input.validStartAt ? toIso(input.validStartAt) : void 0,
221
- valid_end_at: input.validEndAt ? toIso(input.validEndAt) : void 0,
222
- discount
223
- }
203
+ /** Create a calendar coupon (applies to any event the calendar manages). */
204
+ createCoupon: (input) => this.request("/v1/calendars/coupons/create", {
205
+ method: "POST",
206
+ body: {
207
+ code: input.code,
208
+ remaining_count: input.remainingCount,
209
+ valid_start_at: toIsoNullable(input.validStartAt),
210
+ valid_end_at: toIsoNullable(input.validEndAt),
211
+ discount: input.discount.type === "percent" ? {
212
+ discount_type: "percent",
213
+ percent_off: input.discount.percentOff
214
+ } : {
215
+ discount_type: "amount",
216
+ cents_off: input.discount.centsOff,
217
+ currency: input.discount.currency.toLowerCase()
224
218
  }
225
- );
226
- return normalizeCoupon(response.coupon ?? {}, input.code);
227
- }
219
+ }
220
+ })
228
221
  };
229
222
  // ─── events ──────────────────────────────────────────────────────────
230
223
  events = {
231
- /** Fetch a single event by its `api_id`. */
232
- get: async (eventApiId) => {
233
- const response = await this.request(
234
- "/v1/event/get",
235
- { query: { api_id: eventApiId } }
236
- );
237
- return response.event ?? response;
238
- },
224
+ /** Fetch a single event by its id (`evt-…`). */
225
+ get: (eventId) => this.request("/v1/events/get", {
226
+ query: { event_id: eventId }
227
+ }),
239
228
  /** Iterate every guest of an event. */
240
- listGuests: (options) => async function* () {
241
- for await (const entry of this.paginate(
242
- "/v1/event/get-guests",
243
- {
244
- event_api_id: options.eventApiId,
245
- approval_status: options.approvalStatus,
246
- pagination_limit: options.paginationLimit,
247
- pagination_cursor: options.paginationCursor
248
- }
249
- )) {
250
- const guest = entry.guest ?? entry;
251
- yield guest;
252
- }
253
- }.call(this),
229
+ listGuests: (options) => this.paginate("/v1/events/guests/list", {
230
+ event_id: options.eventId,
231
+ approval_status: options.approvalStatus,
232
+ sort_direction: options.sortDirection,
233
+ pagination_limit: options.paginationLimit,
234
+ pagination_cursor: options.paginationCursor
235
+ }),
254
236
  /** Collect every guest of an event into an array. */
255
237
  listAllGuests: (options) => collect(this.events.listGuests(options)),
256
- /** Fetch a single guest, by guest `api_id` or by registration email. */
257
- getGuest: async (options) => {
258
- if (!options.guestApiId && !options.email) {
259
- throw new Error("getGuest requires either `guestApiId` or `email`");
260
- }
261
- const response = await this.request(
262
- "/v1/event/get-guest",
263
- {
264
- query: {
265
- event_api_id: options.eventApiId,
266
- api_id: options.guestApiId,
267
- email: options.email
268
- }
269
- }
270
- );
271
- return response.guest ?? response;
272
- },
238
+ /** Fetch a single guest (with order detail) by its guest id. */
239
+ getGuest: (options) => this.request("/v1/events/guests/get", {
240
+ query: { event_id: options.eventId, id: options.guestId }
241
+ }),
273
242
  /** Update a guest's approval status (approve, decline, waitlist…). */
274
243
  updateGuestStatus: async (options) => {
275
- await this.request("/v1/event/update-guest-status", {
244
+ await this.request("/v1/events/guests/update-status", {
276
245
  method: "POST",
277
246
  body: {
278
- event_api_id: options.eventApiId,
279
- guest_api_id: options.guestApiId,
280
- status: options.status
247
+ event_id: options.eventId,
248
+ guest_id: options.guestId,
249
+ status: options.status,
250
+ should_refund: options.shouldRefund,
251
+ send_email: options.sendEmail,
252
+ message: options.message
281
253
  }
282
254
  });
283
255
  },
@@ -285,19 +257,19 @@ var LumaClient = class _LumaClient {
285
257
  * Add guests to an event (host-side). Registers people directly — this
286
258
  * does NOT take payment; Luma owns checkout/payment on its hosted flow.
287
259
  * By default guests are added as approved ("Going") and emailed. Pass a
288
- * `ticketTypeApiId` to assign each guest a ticket of that type.
260
+ * `ticketTypeId` to assign each guest a ticket of that type.
289
261
  */
290
262
  addGuests: async (options) => {
291
263
  await this.request("/v1/events/guests/add", {
292
264
  method: "POST",
293
265
  body: {
294
- event_id: options.eventApiId,
266
+ event_id: options.eventId,
295
267
  guests: options.guests.map((guest) => ({
296
268
  email: guest.email,
297
269
  name: guest.name,
298
270
  registration_answers: guest.registrationAnswers
299
271
  })),
300
- ticket: options.ticketTypeApiId ? { event_ticket_type_id: options.ticketTypeApiId } : void 0,
272
+ ticket: options.ticketTypeId ? { event_ticket_type_id: options.ticketTypeId } : void 0,
301
273
  approval_status: options.approvalStatus,
302
274
  send_email: options.sendEmail
303
275
  }
@@ -312,7 +284,7 @@ var LumaClient = class _LumaClient {
312
284
  "/v1/events/ticket-types/list",
313
285
  {
314
286
  query: {
315
- event_id: options.eventApiId,
287
+ event_id: options.eventId,
316
288
  include_hidden: options.includeHidden
317
289
  }
318
290
  }
@@ -320,39 +292,30 @@ var LumaClient = class _LumaClient {
320
292
  return response.entries ?? [];
321
293
  },
322
294
  /** Fetch a single ticket type by its id. */
323
- getTicketType: async (ticketTypeApiId) => {
324
- const response = await this.request("/v1/events/ticket-types/get", {
325
- query: { event_ticket_type_id: ticketTypeApiId }
326
- });
327
- return response.ticket_type ?? response;
328
- },
295
+ getTicketType: (ticketTypeId) => this.request("/v1/events/ticket-types/get", {
296
+ query: { event_ticket_type_id: ticketTypeId }
297
+ }),
329
298
  /** Create a ticket type on an event. */
330
- createTicketType: async (input) => {
331
- const response = await this.request("/v1/events/ticket-types/create", {
332
- method: "POST",
333
- body: ticketTypeBody(
334
- { event_id: input.eventApiId, type: input.type },
335
- input
336
- )
337
- });
338
- return response.ticket_type ?? response;
339
- },
299
+ createTicketType: (input) => this.request("/v1/events/ticket-types/create", {
300
+ method: "POST",
301
+ body: ticketTypeBody(
302
+ { event_id: input.eventId, type: input.type },
303
+ input
304
+ )
305
+ }),
340
306
  /** Update an existing ticket type. */
341
- updateTicketType: async (input) => {
342
- const response = await this.request("/v1/events/ticket-types/update", {
343
- method: "POST",
344
- body: ticketTypeBody(
345
- { event_ticket_type_id: input.ticketTypeApiId, type: input.type },
346
- input
347
- )
348
- });
349
- return response.ticket_type ?? response;
350
- },
307
+ updateTicketType: (input) => this.request("/v1/events/ticket-types/update", {
308
+ method: "POST",
309
+ body: ticketTypeBody(
310
+ { event_ticket_type_id: input.ticketTypeId, type: input.type },
311
+ input
312
+ )
313
+ }),
351
314
  /** Delete a ticket type by its id. */
352
- deleteTicketType: async (ticketTypeApiId) => {
315
+ deleteTicketType: async (ticketTypeId) => {
353
316
  await this.request("/v1/events/ticket-types/delete", {
354
317
  method: "POST",
355
- body: { event_ticket_type_id: ticketTypeApiId }
318
+ body: { event_ticket_type_id: ticketTypeId }
356
319
  });
357
320
  }
358
321
  };
@@ -371,11 +334,6 @@ var ticketTypeBody = (base, fields) => ({
371
334
  valid_start_at: toIsoNullable(fields.validStartAt),
372
335
  valid_end_at: toIsoNullable(fields.validEndAt)
373
336
  });
374
- var normalizeCoupon = (entry, fallbackCode) => ({
375
- ...entry,
376
- api_id: entry.api_id ?? entry.id ?? "",
377
- code: entry.code ?? fallbackCode ?? ""
378
- });
379
337
  export {
380
338
  LUMA_API_BASE_URL,
381
339
  LUMA_API_KEY_HEADER,
package/dist/index.js.map CHANGED
@@ -1 +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\tAddGuestsOptions,\n\tCreateCalendarCouponInput,\n\tCreateTicketTypeInput,\n\tGetEventGuestOptions,\n\tListCalendarEventsOptions,\n\tListCalendarPeopleOptions,\n\tListEventGuestsOptions,\n\tListTicketTypesOptions,\n\tLumaCalendarEntry,\n\tLumaCoupon,\n\tLumaCouponEntry,\n\tLumaEvent,\n\tLumaGuest,\n\tLumaGuestEntry,\n\tLumaPaginatedResponse,\n\tLumaPerson,\n\tLumaTicketType,\n\tUpdateGuestStatusOptions,\n\tUpdateTicketTypeInput,\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/** Like {@link toIso} but passes `null`/`undefined` through untouched, so a\n * caller can clear a field (`null`) or leave it unset (`undefined`). */\nconst toIsoNullable = (\n\tvalue: Date | string | null | undefined,\n): string | null | undefined => (value == null ? value : toIso(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\n\t\t/**\n\t\t * Add guests to an event (host-side). Registers people directly — this\n\t\t * does NOT take payment; Luma owns checkout/payment on its hosted flow.\n\t\t * By default guests are added as approved (\"Going\") and emailed. Pass a\n\t\t * `ticketTypeApiId` to assign each guest a ticket of that type.\n\t\t */\n\t\taddGuests: async (options: AddGuestsOptions): Promise<void> => {\n\t\t\tawait this.request(\"/v1/events/guests/add\", {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: {\n\t\t\t\t\tevent_id: options.eventApiId,\n\t\t\t\t\tguests: options.guests.map((guest) => ({\n\t\t\t\t\t\temail: guest.email,\n\t\t\t\t\t\tname: guest.name,\n\t\t\t\t\t\tregistration_answers: guest.registrationAnswers,\n\t\t\t\t\t})),\n\t\t\t\t\tticket: options.ticketTypeApiId\n\t\t\t\t\t\t? { event_ticket_type_id: options.ticketTypeApiId }\n\t\t\t\t\t\t: undefined,\n\t\t\t\t\tapproval_status: options.approvalStatus,\n\t\t\t\t\tsend_email: options.sendEmail,\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\n\t\t/**\n\t\t * List an event's ticket types (tiers), including prices. Pass\n\t\t * `includeHidden` to include ticket types not shown on the public page.\n\t\t */\n\t\tlistTicketTypes: async (\n\t\t\toptions: ListTicketTypesOptions,\n\t\t): Promise<LumaTicketType[]> => {\n\t\t\tconst response = await this.request<{ entries?: LumaTicketType[] }>(\n\t\t\t\t\"/v1/events/ticket-types/list\",\n\t\t\t\t{\n\t\t\t\t\tquery: {\n\t\t\t\t\t\tevent_id: options.eventApiId,\n\t\t\t\t\t\tinclude_hidden: options.includeHidden,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t);\n\t\t\treturn response.entries ?? [];\n\t\t},\n\n\t\t/** Fetch a single ticket type by its id. */\n\t\tgetTicketType: async (ticketTypeApiId: string): Promise<LumaTicketType> => {\n\t\t\tconst response = await this.request<\n\t\t\t\t{ ticket_type?: LumaTicketType } & LumaTicketType\n\t\t\t>(\"/v1/events/ticket-types/get\", {\n\t\t\t\tquery: { event_ticket_type_id: ticketTypeApiId },\n\t\t\t});\n\t\t\treturn response.ticket_type ?? response;\n\t\t},\n\n\t\t/** Create a ticket type on an event. */\n\t\tcreateTicketType: async (\n\t\t\tinput: CreateTicketTypeInput,\n\t\t): Promise<LumaTicketType> => {\n\t\t\tconst response = await this.request<\n\t\t\t\t{ ticket_type?: LumaTicketType } & LumaTicketType\n\t\t\t>(\"/v1/events/ticket-types/create\", {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: ticketTypeBody(\n\t\t\t\t\t{ event_id: input.eventApiId, type: input.type },\n\t\t\t\t\tinput,\n\t\t\t\t),\n\t\t\t});\n\t\t\treturn response.ticket_type ?? response;\n\t\t},\n\n\t\t/** Update an existing ticket type. */\n\t\tupdateTicketType: async (\n\t\t\tinput: UpdateTicketTypeInput,\n\t\t): Promise<LumaTicketType> => {\n\t\t\tconst response = await this.request<\n\t\t\t\t{ ticket_type?: LumaTicketType } & LumaTicketType\n\t\t\t>(\"/v1/events/ticket-types/update\", {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: ticketTypeBody(\n\t\t\t\t\t{ event_ticket_type_id: input.ticketTypeApiId, type: input.type },\n\t\t\t\t\tinput,\n\t\t\t\t),\n\t\t\t});\n\t\t\treturn response.ticket_type ?? response;\n\t\t},\n\n\t\t/** Delete a ticket type by its id. */\n\t\tdeleteTicketType: async (ticketTypeApiId: string): Promise<void> => {\n\t\t\tawait this.request(\"/v1/events/ticket-types/delete\", {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: { event_ticket_type_id: ticketTypeApiId },\n\t\t\t});\n\t\t},\n\t};\n}\n\n/** Build the snake_case body shared by ticket-type create and update. Keys\n * left `undefined` are dropped by `JSON.stringify`; explicit `null` is sent. */\nconst ticketTypeBody = (\n\tbase: Record<string, unknown>,\n\tfields: {\n\t\tname?: string;\n\t\tcents?: number | null;\n\t\tcurrency?: string | null;\n\t\trequireApproval?: boolean;\n\t\tisHidden?: boolean;\n\t\tdescription?: string | null;\n\t\tisFlexible?: boolean;\n\t\tminCents?: number | null;\n\t\tmaxCapacity?: number | null;\n\t\tvalidStartAt?: Date | string | null;\n\t\tvalidEndAt?: Date | string | null;\n\t},\n): Record<string, unknown> => ({\n\t...base,\n\tname: fields.name,\n\tcents: fields.cents,\n\tcurrency: fields.currency == null ? fields.currency : fields.currency.toLowerCase(),\n\trequire_approval: fields.requireApproval,\n\tis_hidden: fields.isHidden,\n\tdescription: fields.description,\n\tis_flexible: fields.isFlexible,\n\tmin_cents: fields.minCents,\n\tmax_capacity: fields.maxCapacity,\n\tvalid_start_at: toIsoNullable(fields.validStartAt),\n\tvalid_end_at: toIsoNullable(fields.validEndAt),\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":[]}
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\tAddGuestsOptions,\n\tCreateCalendarCouponInput,\n\tCreateTicketTypeInput,\n\tGetEventGuestOptions,\n\tListCalendarEventsOptions,\n\tListContactsOptions,\n\tListEventGuestsOptions,\n\tListTicketTypesOptions,\n\tLumaCalendarEvent,\n\tLumaContact,\n\tLumaCoupon,\n\tLumaEvent,\n\tLumaGuest,\n\tLumaGuestDetail,\n\tLumaPaginatedResponse,\n\tLumaTicketType,\n\tUpdateGuestStatusOptions,\n\tUpdateTicketTypeInput,\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/** Like {@link toIso} but passes `null`/`undefined` through untouched, so a\n * caller can clear a field (`null`) or leave it unset (`undefined`). */\nconst toIsoNullable = (\n\tvalue: Date | string | null | undefined,\n): string | null | undefined => (value == null ? value : toIso(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/events/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\t//\n\t// The calendar is inferred from the API key, so these take no calendar id.\n\n\treadonly calendar = {\n\t\t/** Iterate every event the calendar manages. */\n\t\tlistEvents: (options: ListCalendarEventsOptions = {}) =>\n\t\t\tthis.paginate<LumaCalendarEvent>(\"/v1/calendars/events/list\", {\n\t\t\t\tafter: options.after,\n\t\t\t\tbefore: options.before,\n\t\t\t\tstatus: options.status,\n\t\t\t\taccess: options.access,\n\t\t\t\tplatforms: options.platforms,\n\t\t\t\tsort_direction: options.sortDirection,\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 the calendar manages into an array. */\n\t\tlistAllEvents: (options: ListCalendarEventsOptions = {}) =>\n\t\t\tcollect(this.calendar.listEvents(options)),\n\n\t\t/** Iterate every contact on the calendar. */\n\t\tlistContacts: (options: ListContactsOptions = {}) =>\n\t\t\tthis.paginate<LumaContact>(\"/v1/calendars/contacts/list\", {\n\t\t\t\tquery: options.query,\n\t\t\t\tsort_direction: options.sortDirection,\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 contact on the calendar into an array. */\n\t\tlistAllContacts: (options: ListContactsOptions = {}) =>\n\t\t\tcollect(this.calendar.listContacts(options)),\n\n\t\t/** Iterate every coupon on the calendar tied to the API key. */\n\t\tlistCoupons: (): AsyncGenerator<LumaCoupon> =>\n\t\t\tthis.paginate<LumaCoupon>(\"/v1/calendars/coupons/list\"),\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 (applies to any event the calendar manages). */\n\t\tcreateCoupon: (input: CreateCalendarCouponInput): Promise<LumaCoupon> =>\n\t\t\tthis.request<LumaCoupon>(\"/v1/calendars/coupons/create\", {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: {\n\t\t\t\t\tcode: input.code,\n\t\t\t\t\tremaining_count: input.remainingCount,\n\t\t\t\t\tvalid_start_at: toIsoNullable(input.validStartAt),\n\t\t\t\t\tvalid_end_at: toIsoNullable(input.validEndAt),\n\t\t\t\t\tdiscount:\n\t\t\t\t\t\tinput.discount.type === \"percent\"\n\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\tdiscount_type: \"percent\",\n\t\t\t\t\t\t\t\t\tpercent_off: input.discount.percentOff,\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t: {\n\t\t\t\t\t\t\t\t\tdiscount_type: \"amount\",\n\t\t\t\t\t\t\t\t\tcents_off: input.discount.centsOff,\n\t\t\t\t\t\t\t\t\tcurrency: input.discount.currency.toLowerCase(),\n\t\t\t\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}),\n\t};\n\n\t// ─── events ──────────────────────────────────────────────────────────\n\n\treadonly events = {\n\t\t/** Fetch a single event by its id (`evt-…`). */\n\t\tget: (eventId: string): Promise<LumaEvent> =>\n\t\t\tthis.request<LumaEvent>(\"/v1/events/get\", {\n\t\t\t\tquery: { event_id: eventId },\n\t\t\t}),\n\n\t\t/** Iterate every guest of an event. */\n\t\tlistGuests: (options: ListEventGuestsOptions) =>\n\t\t\tthis.paginate<LumaGuest>(\"/v1/events/guests/list\", {\n\t\t\t\tevent_id: options.eventId,\n\t\t\t\tapproval_status: options.approvalStatus,\n\t\t\t\tsort_direction: options.sortDirection,\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 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 (with order detail) by its guest id. */\n\t\tgetGuest: (options: GetEventGuestOptions): Promise<LumaGuestDetail> =>\n\t\t\tthis.request<LumaGuestDetail>(\"/v1/events/guests/get\", {\n\t\t\t\tquery: { event_id: options.eventId, id: options.guestId },\n\t\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/events/guests/update-status\", {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: {\n\t\t\t\t\tevent_id: options.eventId,\n\t\t\t\t\tguest_id: options.guestId,\n\t\t\t\t\tstatus: options.status,\n\t\t\t\t\tshould_refund: options.shouldRefund,\n\t\t\t\t\tsend_email: options.sendEmail,\n\t\t\t\t\tmessage: options.message,\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\n\t\t/**\n\t\t * Add guests to an event (host-side). Registers people directly — this\n\t\t * does NOT take payment; Luma owns checkout/payment on its hosted flow.\n\t\t * By default guests are added as approved (\"Going\") and emailed. Pass a\n\t\t * `ticketTypeId` to assign each guest a ticket of that type.\n\t\t */\n\t\taddGuests: async (options: AddGuestsOptions): Promise<void> => {\n\t\t\tawait this.request(\"/v1/events/guests/add\", {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: {\n\t\t\t\t\tevent_id: options.eventId,\n\t\t\t\t\tguests: options.guests.map((guest) => ({\n\t\t\t\t\t\temail: guest.email,\n\t\t\t\t\t\tname: guest.name,\n\t\t\t\t\t\tregistration_answers: guest.registrationAnswers,\n\t\t\t\t\t})),\n\t\t\t\t\tticket: options.ticketTypeId\n\t\t\t\t\t\t? { event_ticket_type_id: options.ticketTypeId }\n\t\t\t\t\t\t: undefined,\n\t\t\t\t\tapproval_status: options.approvalStatus,\n\t\t\t\t\tsend_email: options.sendEmail,\n\t\t\t\t},\n\t\t\t});\n\t\t},\n\n\t\t/**\n\t\t * List an event's ticket types (tiers), including prices. Pass\n\t\t * `includeHidden` to include ticket types not shown on the public page.\n\t\t */\n\t\tlistTicketTypes: async (\n\t\t\toptions: ListTicketTypesOptions,\n\t\t): Promise<LumaTicketType[]> => {\n\t\t\tconst response = await this.request<{ entries?: LumaTicketType[] }>(\n\t\t\t\t\"/v1/events/ticket-types/list\",\n\t\t\t\t{\n\t\t\t\t\tquery: {\n\t\t\t\t\t\tevent_id: options.eventId,\n\t\t\t\t\t\tinclude_hidden: options.includeHidden,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t);\n\t\t\treturn response.entries ?? [];\n\t\t},\n\n\t\t/** Fetch a single ticket type by its id. */\n\t\tgetTicketType: (ticketTypeId: string): Promise<LumaTicketType> =>\n\t\t\tthis.request<LumaTicketType>(\"/v1/events/ticket-types/get\", {\n\t\t\t\tquery: { event_ticket_type_id: ticketTypeId },\n\t\t\t}),\n\n\t\t/** Create a ticket type on an event. */\n\t\tcreateTicketType: (input: CreateTicketTypeInput): Promise<LumaTicketType> =>\n\t\t\tthis.request<LumaTicketType>(\"/v1/events/ticket-types/create\", {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: ticketTypeBody(\n\t\t\t\t\t{ event_id: input.eventId, type: input.type },\n\t\t\t\t\tinput,\n\t\t\t\t),\n\t\t\t}),\n\n\t\t/** Update an existing ticket type. */\n\t\tupdateTicketType: (input: UpdateTicketTypeInput): Promise<LumaTicketType> =>\n\t\t\tthis.request<LumaTicketType>(\"/v1/events/ticket-types/update\", {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: ticketTypeBody(\n\t\t\t\t\t{ event_ticket_type_id: input.ticketTypeId, type: input.type },\n\t\t\t\t\tinput,\n\t\t\t\t),\n\t\t\t}),\n\n\t\t/** Delete a ticket type by its id. */\n\t\tdeleteTicketType: async (ticketTypeId: string): Promise<void> => {\n\t\t\tawait this.request(\"/v1/events/ticket-types/delete\", {\n\t\t\t\tmethod: \"POST\",\n\t\t\t\tbody: { event_ticket_type_id: ticketTypeId },\n\t\t\t});\n\t\t},\n\t};\n}\n\n/** Build the snake_case body shared by ticket-type create and update. Keys\n * left `undefined` are dropped by `JSON.stringify`; explicit `null` is sent. */\nconst ticketTypeBody = (\n\tbase: Record<string, unknown>,\n\tfields: {\n\t\tname?: string;\n\t\tcents?: number | null;\n\t\tcurrency?: string | null;\n\t\trequireApproval?: boolean;\n\t\tisHidden?: boolean;\n\t\tdescription?: string | null;\n\t\tisFlexible?: boolean;\n\t\tminCents?: number | null;\n\t\tmaxCapacity?: number | null;\n\t\tvalidStartAt?: Date | string | null;\n\t\tvalidEndAt?: Date | string | null;\n\t},\n): Record<string, unknown> => ({\n\t...base,\n\tname: fields.name,\n\tcents: fields.cents,\n\tcurrency: fields.currency == null ? fields.currency : fields.currency.toLowerCase(),\n\trequire_approval: fields.requireApproval,\n\tis_hidden: fields.isHidden,\n\tdescription: fields.description,\n\tis_flexible: fields.isFlexible,\n\tmin_cents: fields.minCents,\n\tmax_capacity: fields.maxCapacity,\n\tvalid_start_at: toIsoNullable(fields.validStartAt),\n\tvalid_end_at: toIsoNullable(fields.validEndAt),\n});\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ingram-tech/luma",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "TypeScript client for the Luma (lu.ma) public API.",
5
5
  "license": "MIT",
6
6
  "author": "Ingram Technologies",
@@ -36,6 +36,7 @@
36
36
  "scripts": {
37
37
  "build": "tsup",
38
38
  "prepack": "bun run build",
39
+ "generate": "curl -sSf https://public-api.luma.com/openapi.json -o openapi.json && openapi-typescript openapi.json -o src/generated/openapi.ts && biome format --write src/generated/openapi.ts",
39
40
  "lint": "biome check .",
40
41
  "format": "biome format --write .",
41
42
  "type-check": "tsc --noEmit",
@@ -49,6 +50,7 @@
49
50
  "devDependencies": {
50
51
  "@biomejs/biome": "^2.5.1",
51
52
  "@types/node": "^26.0.1",
53
+ "openapi-typescript": "^7.13.0",
52
54
  "tsup": "^8.5.1",
53
55
  "typescript": "^5.9.3",
54
56
  "vitest": "^4.1.9"