@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/README.md +41 -28
- package/dist/index.d.ts +7657 -170
- package/dist/index.js +80 -122
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
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/
|
|
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
|
|
168
|
-
listEvents: (options) => this.paginate("/v1/
|
|
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
|
|
176
|
-
listAllEvents: (options) => collect(this.calendar.listEvents(options)),
|
|
177
|
-
/** Iterate every
|
|
178
|
-
|
|
179
|
-
|
|
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
|
|
184
|
-
|
|
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:
|
|
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:
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
-
|
|
227
|
-
}
|
|
219
|
+
}
|
|
220
|
+
})
|
|
228
221
|
};
|
|
229
222
|
// ─── events ──────────────────────────────────────────────────────────
|
|
230
223
|
events = {
|
|
231
|
-
/** Fetch a single event by its `
|
|
232
|
-
get:
|
|
233
|
-
|
|
234
|
-
|
|
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) =>
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
|
257
|
-
getGuest:
|
|
258
|
-
|
|
259
|
-
|
|
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/
|
|
244
|
+
await this.request("/v1/events/guests/update-status", {
|
|
276
245
|
method: "POST",
|
|
277
246
|
body: {
|
|
278
|
-
|
|
279
|
-
|
|
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
|
-
* `
|
|
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.
|
|
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.
|
|
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.
|
|
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:
|
|
324
|
-
|
|
325
|
-
|
|
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:
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
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:
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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 (
|
|
315
|
+
deleteTicketType: async (ticketTypeId) => {
|
|
353
316
|
await this.request("/v1/events/ticket-types/delete", {
|
|
354
317
|
method: "POST",
|
|
355
|
-
body: { event_ticket_type_id:
|
|
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":";AACO,IAAM,oBAAoB;AAG1B,IAAM,sBAAsB;AAG5B,IAAM,mBAAmB;;;ACHzB,IAAM,eAAN,cAA2B,MAAM;AAAA;AAAA,EAE9B;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA,EAET,YAAY,QAKT;AACF,UAAM,OAAO,OAAO;AACpB,SAAK,OAAO;AACZ,SAAK,SAAS,OAAO;AACrB,SAAK,OAAO,OAAO;AACnB,SAAK,OAAO,OAAO;AAAA,EACpB;AAAA;AAAA,EAGA,IAAI,cAAuB;AAC1B,WAAO,KAAK,WAAW,OAAO,KAAK,WAAW;AAAA,EAC/C;AAAA;AAAA,EAGA,IAAI,gBAAyB;AAC5B,WAAO,KAAK,WAAW;AAAA,EACxB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAI,wBAAiC;AACpC,UAAM,QAAQ,KAAK,KAAK,YAAY;AACpC,YACE,KAAK,WAAW,OAAO,KAAK,WAAW,QACxC,MAAM,SAAS,MAAM,MACpB,MAAM,SAAS,OAAO,KAAK,MAAM,SAAS,SAAS;AAAA,EAEtD;AACD;;;ACAA,IAAM,sBAAsB,CAAC,UAC5B,iBAAiB,OAAO,MAAM,YAAY,IAAI,OAAO,KAAK;AAE3D,IAAM,QAAQ,CAAC,UACd,iBAAiB,OAAO,MAAM,YAAY,IAAI;AAI/C,IAAM,gBAAgB,CACrB,UACgC,SAAS,OAAO,QAAQ,MAAM,KAAK;AAG7D,IAAM,UAAU,OAAU,WAA2C;AAC3E,QAAM,MAAW,CAAC;AAClB,mBAAiB,QAAQ,QAAQ;AAChC,QAAI,KAAK,IAAI;AAAA,EACd;AACA,SAAO;AACR;AAUO,IAAM,aAAN,MAAM,YAAW;AAAA,EACd;AAAA,EACQ;AAAA,EACA;AAAA,EAEjB,YAAY,SAA4B;AACvC,QAAI,CAAC,QAAQ,QAAQ;AACpB,YAAM,IAAI,MAAM,iCAAiC;AAAA,IAClD;AACA,SAAK,SAAS,QAAQ;AACtB,SAAK,UAAU,QAAQ,WAAW;AAClC,UAAM,YAAY,QAAQ,SAAS,WAAW;AAC9C,QAAI,OAAO,cAAc,YAAY;AACpC,YAAM,IAAI;AAAA,QACT;AAAA,MACD;AAAA,IACD;AACA,SAAK,YAAY;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,QACN,MAA0C,OAAO,YAAY,cAC1D,QAAQ,MACR,CAAC,GACS;AACb,UAAM,SAAS,IAAI;AACnB,QAAI,CAAC,QAAQ;AACZ,YAAM,IAAI,MAAM,yBAAyB;AAAA,IAC1C;AACA,WAAO,IAAI,YAAW,EAAE,QAAQ,SAAS,IAAI,kBAAkB,CAAC;AAAA,EACjE;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,QAAW,MAAc,OAAwB,CAAC,GAAe;AACtE,UAAM,MAAM,IAAI,IAAI,KAAK,WAAW,GAAG,IAAI,OAAO,IAAI,IAAI,IAAI,KAAK,OAAO;AAC1E,QAAI,KAAK,OAAO;AACf,iBAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,KAAK,GAAG;AACtD,YAAI,UAAU,UAAa,UAAU,MAAM;AAC1C,cAAI,aAAa,IAAI,KAAK,oBAAoB,KAAK,CAAC;AAAA,QACrD;AAAA,MACD;AAAA,IACD;AAEA,UAAM,UAAkC;AAAA,MACvC,CAAC,mBAAmB,GAAG,KAAK;AAAA,MAC5B,QAAQ;AAAA,IACT;AACA,QAAI;AACJ,QAAI,KAAK,SAAS,QAAW;AAC5B,cAAQ,cAAc,IAAI;AAC1B,aAAO,KAAK,UAAU,KAAK,IAAI;AAAA,IAChC;AAEA,QAAI;AACJ,QAAI;AACH,iBAAW,MAAM,KAAK,UAAU,KAAK;AAAA,QACpC,QAAQ,KAAK,UAAU;AAAA,QACvB;AAAA,QACA;AAAA,QACA,QAAQ,KAAK;AAAA,QACb,GAAG,KAAK;AAAA,MACT,CAAC;AAAA,IACF,SAAS,OAAO;AACf,YAAM,IAAI,aAAa;AAAA,QACtB,SAAS,uBAAuB,IAAI,YACnC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CACtD;AAAA,QACA,QAAQ;AAAA,QACR,MAAM;AAAA,QACN;AAAA,MACD,CAAC;AAAA,IACF;AAEA,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,QAAI,CAAC,SAAS,IAAI;AACjB,YAAM,IAAI,aAAa;AAAA,QACtB,SAAS,sBAAsB,SAAS,MAAM,QAAQ,IAAI;AAAA,QAC1D,QAAQ,SAAS;AAAA,QACjB,MAAM;AAAA,QACN;AAAA,MACD,CAAC;AAAA,IACF;AACA,QAAI,KAAK,WAAW,GAAG;AACtB,aAAO;AAAA,IACR;AACA,QAAI;AACH,aAAO,KAAK,MAAM,IAAI;AAAA,IACvB,QAAQ;AACP,YAAM,IAAI,aAAa;AAAA,QACtB,SAAS,yCAAyC,IAAI;AAAA,QACtD,QAAQ,SAAS;AAAA,QACjB,MAAM;AAAA,QACN;AAAA,MACD,CAAC;AAAA,IACF;AAAA,EACD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,SACN,MACA,QAAiC,CAAC,GACd;AACpB,QAAI;AAEJ,aAAS,OAAO,GAAG,OAAO,KAAQ,QAAQ,GAAG;AAC5C,YAAM,YAAY,EAAE,GAAG,MAAM;AAC7B,UAAI,QAAQ;AACX,kBAAU,oBAAoB;AAAA,MAC/B;AACA,YAAM,SAAS,MAAM,KAAK,QAAkC,MAAM;AAAA,QACjE,OAAO;AAAA,MACR,CAAC;AACD,iBAAW,SAAS,OAAO,WAAW,CAAC,GAAG;AACzC,cAAM;AAAA,MACP;AACA,UAAI,CAAC,OAAO,YAAY,CAAC,OAAO,aAAa;AAC5C;AAAA,MACD;AACA,eAAS,OAAO;AAAA,IACjB;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAMS,WAAW;AAAA;AAAA,IAEnB,YAAY,CAAC,UAAqC,CAAC,MAClD,KAAK,SAA4B,6BAA6B;AAAA,MAC7D,OAAO,QAAQ;AAAA,MACf,QAAQ,QAAQ;AAAA,MAChB,QAAQ,QAAQ;AAAA,MAChB,QAAQ,QAAQ;AAAA,MAChB,WAAW,QAAQ;AAAA,MACnB,gBAAgB,QAAQ;AAAA,MACxB,kBAAkB,QAAQ;AAAA,MAC1B,mBAAmB,QAAQ;AAAA,IAC5B,CAAC;AAAA;AAAA,IAGF,eAAe,CAAC,UAAqC,CAAC,MACrD,QAAQ,KAAK,SAAS,WAAW,OAAO,CAAC;AAAA;AAAA,IAG1C,cAAc,CAAC,UAA+B,CAAC,MAC9C,KAAK,SAAsB,+BAA+B;AAAA,MACzD,OAAO,QAAQ;AAAA,MACf,gBAAgB,QAAQ;AAAA,MACxB,kBAAkB,QAAQ;AAAA,MAC1B,mBAAmB,QAAQ;AAAA,IAC5B,CAAC;AAAA;AAAA,IAGF,iBAAiB,CAAC,UAA+B,CAAC,MACjD,QAAQ,KAAK,SAAS,aAAa,OAAO,CAAC;AAAA;AAAA,IAG5C,aAAa,MACZ,KAAK,SAAqB,4BAA4B;AAAA;AAAA,IAGvD,kBAAkB,OAAO,SAA6C;AACrE,YAAM,SAAS,KAAK,YAAY;AAChC,uBAAiB,UAAU,KAAK,SAAS,YAAY,GAAG;AACvD,YAAI,OAAO,KAAK,YAAY,MAAM,QAAQ;AACzC,iBAAO;AAAA,QACR;AAAA,MACD;AACA,aAAO;AAAA,IACR;AAAA;AAAA,IAGA,cAAc,CAAC,UACd,KAAK,QAAoB,gCAAgC;AAAA,MACxD,QAAQ;AAAA,MACR,MAAM;AAAA,QACL,MAAM,MAAM;AAAA,QACZ,iBAAiB,MAAM;AAAA,QACvB,gBAAgB,cAAc,MAAM,YAAY;AAAA,QAChD,cAAc,cAAc,MAAM,UAAU;AAAA,QAC5C,UACC,MAAM,SAAS,SAAS,YACrB;AAAA,UACA,eAAe;AAAA,UACf,aAAa,MAAM,SAAS;AAAA,QAC7B,IACC;AAAA,UACA,eAAe;AAAA,UACf,WAAW,MAAM,SAAS;AAAA,UAC1B,UAAU,MAAM,SAAS,SAAS,YAAY;AAAA,QAC/C;AAAA,MACJ;AAAA,IACD,CAAC;AAAA,EACH;AAAA;AAAA,EAIS,SAAS;AAAA;AAAA,IAEjB,KAAK,CAAC,YACL,KAAK,QAAmB,kBAAkB;AAAA,MACzC,OAAO,EAAE,UAAU,QAAQ;AAAA,IAC5B,CAAC;AAAA;AAAA,IAGF,YAAY,CAAC,YACZ,KAAK,SAAoB,0BAA0B;AAAA,MAClD,UAAU,QAAQ;AAAA,MAClB,iBAAiB,QAAQ;AAAA,MACzB,gBAAgB,QAAQ;AAAA,MACxB,kBAAkB,QAAQ;AAAA,MAC1B,mBAAmB,QAAQ;AAAA,IAC5B,CAAC;AAAA;AAAA,IAGF,eAAe,CAAC,YACf,QAAQ,KAAK,OAAO,WAAW,OAAO,CAAC;AAAA;AAAA,IAGxC,UAAU,CAAC,YACV,KAAK,QAAyB,yBAAyB;AAAA,MACtD,OAAO,EAAE,UAAU,QAAQ,SAAS,IAAI,QAAQ,QAAQ;AAAA,IACzD,CAAC;AAAA;AAAA,IAGF,mBAAmB,OAAO,YAAqD;AAC9E,YAAM,KAAK,QAAQ,mCAAmC;AAAA,QACrD,QAAQ;AAAA,QACR,MAAM;AAAA,UACL,UAAU,QAAQ;AAAA,UAClB,UAAU,QAAQ;AAAA,UAClB,QAAQ,QAAQ;AAAA,UAChB,eAAe,QAAQ;AAAA,UACvB,YAAY,QAAQ;AAAA,UACpB,SAAS,QAAQ;AAAA,QAClB;AAAA,MACD,CAAC;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAQA,WAAW,OAAO,YAA6C;AAC9D,YAAM,KAAK,QAAQ,yBAAyB;AAAA,QAC3C,QAAQ;AAAA,QACR,MAAM;AAAA,UACL,UAAU,QAAQ;AAAA,UAClB,QAAQ,QAAQ,OAAO,IAAI,CAAC,WAAW;AAAA,YACtC,OAAO,MAAM;AAAA,YACb,MAAM,MAAM;AAAA,YACZ,sBAAsB,MAAM;AAAA,UAC7B,EAAE;AAAA,UACF,QAAQ,QAAQ,eACb,EAAE,sBAAsB,QAAQ,aAAa,IAC7C;AAAA,UACH,iBAAiB,QAAQ;AAAA,UACzB,YAAY,QAAQ;AAAA,QACrB;AAAA,MACD,CAAC;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,iBAAiB,OAChB,YAC+B;AAC/B,YAAM,WAAW,MAAM,KAAK;AAAA,QAC3B;AAAA,QACA;AAAA,UACC,OAAO;AAAA,YACN,UAAU,QAAQ;AAAA,YAClB,gBAAgB,QAAQ;AAAA,UACzB;AAAA,QACD;AAAA,MACD;AACA,aAAO,SAAS,WAAW,CAAC;AAAA,IAC7B;AAAA;AAAA,IAGA,eAAe,CAAC,iBACf,KAAK,QAAwB,+BAA+B;AAAA,MAC3D,OAAO,EAAE,sBAAsB,aAAa;AAAA,IAC7C,CAAC;AAAA;AAAA,IAGF,kBAAkB,CAAC,UAClB,KAAK,QAAwB,kCAAkC;AAAA,MAC9D,QAAQ;AAAA,MACR,MAAM;AAAA,QACL,EAAE,UAAU,MAAM,SAAS,MAAM,MAAM,KAAK;AAAA,QAC5C;AAAA,MACD;AAAA,IACD,CAAC;AAAA;AAAA,IAGF,kBAAkB,CAAC,UAClB,KAAK,QAAwB,kCAAkC;AAAA,MAC9D,QAAQ;AAAA,MACR,MAAM;AAAA,QACL,EAAE,sBAAsB,MAAM,cAAc,MAAM,MAAM,KAAK;AAAA,QAC7D;AAAA,MACD;AAAA,IACD,CAAC;AAAA;AAAA,IAGF,kBAAkB,OAAO,iBAAwC;AAChE,YAAM,KAAK,QAAQ,kCAAkC;AAAA,QACpD,QAAQ;AAAA,QACR,MAAM,EAAE,sBAAsB,aAAa;AAAA,MAC5C,CAAC;AAAA,IACF;AAAA,EACD;AACD;AAIA,IAAM,iBAAiB,CACtB,MACA,YAa8B;AAAA,EAC9B,GAAG;AAAA,EACH,MAAM,OAAO;AAAA,EACb,OAAO,OAAO;AAAA,EACd,UAAU,OAAO,YAAY,OAAO,OAAO,WAAW,OAAO,SAAS,YAAY;AAAA,EAClF,kBAAkB,OAAO;AAAA,EACzB,WAAW,OAAO;AAAA,EAClB,aAAa,OAAO;AAAA,EACpB,aAAa,OAAO;AAAA,EACpB,WAAW,OAAO;AAAA,EAClB,cAAc,OAAO;AAAA,EACrB,gBAAgB,cAAc,OAAO,YAAY;AAAA,EACjD,cAAc,cAAc,OAAO,UAAU;AAC9C;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ingram-tech/luma",
|
|
3
|
-
"version": "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"
|