@ophelio/sdk 0.3.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -146,30 +146,35 @@ To page manually — e.g. to surface `total_size` or drive numbered pages — fe
146
146
  `page_size` defaults to 50 and is capped at 250. Because pagination is
147
147
  offset-based, a row can be seen twice or skipped across a page boundary if the
148
148
  collection is written to while you iterate. Paginated methods are marked
149
- `(params?)` in the table below; the bounded lists (config resources,
150
- `listMembers`) return a plain array.
149
+ `(params?)` in the table below; every other list — the bounded ones such as
150
+ member roles, pricing groups, sales channels, entitlements, plans and
151
+ `listMembers` — returns a plain array.
151
152
 
152
153
  ## API surface
153
154
 
154
155
  Everything reachable with an API key is covered:
155
156
 
156
- | Namespace | Methods |
157
- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
158
- | `admit` | `check({ card })` |
159
- | `members` | `sync({ since? })`, `get(id)`, `listEntitlements(id, { type? })`, `update(id, ...)`, `remove(id)`, `setPrimary(id)`, `reissueCard(id, { note? })` |
160
- | `memberships` | `list(params?)`, `get(id)`, `create(...)`, `update(id, ...)`, `cancel(id, ...)`, `uncancel(id)`, `renew(id, ...)`, `upgrade(id, ...)`, `downgrade(id, ...)`, `listMembers(id)`, `addMember(id, ...)`, `listScheduledChanges(id, params?)`, `cancelScheduledChange(id, changeId)`, `listTransactions(id, params?)`, `listTermTransactions(id, termId, params?)`, `createTransaction(id, ...)` |
161
- | `customers` | `list(params?)`, `get(id, params?)`, `create(...)`, `update(id, ...)`, `delete(id)`, `listMemberships(id, params?)` — `list`/`get` accept `view: 'full'` to include `membership_summary` |
162
- | `entitlements` | `redeem(...)`, `recordUsage(...)`, `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)` |
163
- | `plans` | `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)`, `listBillingOptions(id)`, `createBillingOption(id, ...)`, `listEntitlements(id)`, `createEntitlement(id, ...)`, `listPlanLinks(id)`, `createPlanLink(id, ...)`, `listPricingGroupMappings(id)`, `createPricingGroupMapping(id, ...)` |
164
- | `billingOptions` | `list()`, `get(id)`, `update(id, ...)`, `delete(id)`, `listSalesChannels(id)`, `createSalesChannelLink(id, ...)` |
165
- | `memberRoles` | `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)` |
166
- | `pricingGroups` | `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)` |
167
- | `salesChannels` | `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)` |
168
- | `planEntitlements` | `get(id)`, `update(id, ...)`, `delete(id)` |
169
- | `planLinks` | `delete(id)` |
170
- | `planPricingGroupMappings` | `delete(id)` |
171
- | `billingOptionSalesChannels` | `delete(id)` |
172
- | `webhookSubscriptions` | `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)`, `listDeliveries(id, params?)` |
157
+ | Namespace | Methods |
158
+ | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
159
+ | `admit` | `check({ card })` |
160
+ | `members` | `sync({ since? })`, `get(id)`, `listEntitlements(id, { type? })`, `update(id, ...)`, `remove(id)`, `setPrimary(id)`, `reissueCard(id, { note? })` |
161
+ | `memberships` | `list(params?)`, `get(id)`, `create(...)`, `update(id, ...)`, `cancel(id, ...)`, `uncancel(id)`, `renew(id, ...)`, `upgrade(id, ...)`, `downgrade(id, ...)`, `listMembers(id)`, `addMember(id, ...)`, `listScheduledChanges(id, params?)`, `cancelScheduledChange(id, changeId)`, `listTransactions(id, params?)`, `listTermTransactions(id, termId, params?)`, `createTransaction(id, ...)` |
162
+ | `customers` | `list(params?)`, `get(id, params?)`, `create(...)`, `update(id, ...)`, `delete(id)`, `listMemberships(id, params?)` — `list`/`get` accept `view: 'full'` to include `membership_summary` |
163
+ | `entitlements` | `redeem(...)`, `recordUsage(...)`, `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)` |
164
+ | `plans` | `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)`, `listBillingOptions(id)`, `createBillingOption(id, ...)`, `listEntitlements(id)`, `createEntitlement(id, ...)`, `listPlanLinks(id)`, `createPlanLink(id, ...)`, `listPricingGroupMappings(id)`, `createPricingGroupMapping(id, ...)` |
165
+ | `billingOptions` | `list()`, `get(id)`, `update(id, ...)`, `delete(id)`, `listSalesChannels(id)`, `createSalesChannelLink(id, ...)` |
166
+ | `memberRoles` | `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)` |
167
+ | `pricingGroups` | `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)` |
168
+ | `salesChannels` | `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)` |
169
+ | `targetCodes` | `list(params?)`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)` — `update` takes `display_name` / `description` only; `code` is immutable |
170
+ | `calendars` | `list(params?)`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)`, `listRules(id, params?)`, `createRule(id, ...)` |
171
+ | `calendarRules` | `get(id)`, `update(id, ...)`, `delete(id)` |
172
+ | `planEntitlements` | `get(id)`, `update(id, ...)`, `delete(id)`, `listAvailabilities(id, params?)`, `createAvailability(id, ...)` |
173
+ | `planEntitlementAvailabilities` | `get(id)`, `update(id, ...)`, `delete(id)` |
174
+ | `planLinks` | `delete(id)` |
175
+ | `planPricingGroupMappings` | `delete(id)` |
176
+ | `billingOptionSalesChannels` | `delete(id)` |
177
+ | `webhookSubscriptions` | `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)`, `listDeliveries(id, params?)` |
173
178
 
174
179
  Every method accepts a trailing `options` argument: `{ signal?, headers? }`
175
180
  (plus `idempotencyKey?` on the idempotency-keyed mutations: `memberships.create`
@@ -177,3 +182,75 @@ Every method accepts a trailing `options` argument: `{ signal?, headers? }`
177
182
 
178
183
  Endpoints requiring a user session (API key management, organisation and
179
184
  project admin) are intentionally not in the SDK — API keys cannot call them.
185
+
186
+ ### Calendar rules
187
+
188
+ `date_from` and `date_until` are **timezone-free calendar dates** (`YYYY-MM-DD`),
189
+ never instants — an ISO timestamp is rejected. `date_until` is **inclusive**:
190
+ `'2026-08-31'` covers the whole of 31 August. Both are nullable, meaning
191
+ open-ended on that side.
192
+
193
+ `days_of_week` uses **ISO-8601 numbering: 1 = Monday … 7 = Sunday** (note that
194
+ JavaScript's `Date.prototype.getDay()` calls Sunday 0). An empty array means
195
+ every day.
196
+
197
+ Conditions AND within a rule and rules OR across a calendar, so "weekends in
198
+ August" is a single rule:
199
+
200
+ ```ts
201
+ await ophelio.calendars.createRule(calendar.id, {
202
+ date_from: '2026-08-01',
203
+ date_until: '2026-08-31',
204
+ days_of_week: [6, 7],
205
+ })
206
+ ```
207
+
208
+ A calendar containing no rules is valid for **all** dates, not none.
209
+
210
+ ### Target codes
211
+
212
+ The registered vocabulary of locations a gate reports (`venue_a`,
213
+ `coffee_shop`). A `code` is lowercase letters and digits with single
214
+ underscores between segments, at least one letter, 2–64 characters — and it is
215
+ **matched exactly**. A miscased code is an unregistered code, so send values
216
+ back verbatim from `list()` rather than constructing or case-folding them.
217
+
218
+ A code outside the grammar is **rejected, never transformed**: `Venue A` comes
219
+ back as a 400, not as a stored `venue_a`.
220
+
221
+ `code` is **immutable from creation**; `display_name` and `description` stay
222
+ editable forever. `UpdateTargetCodeParams` therefore has no `code` field, so a
223
+ rename fails to type-check before it can fail with a 400:
224
+
225
+ ```ts
226
+ const venue = await ophelio.targetCodes.create({
227
+ code: 'venue_a',
228
+ display_name: 'Venue A',
229
+ })
230
+
231
+ await ophelio.targetCodes.update(venue.id, { display_name: 'Venue A (west)' })
232
+ ```
233
+
234
+ To correct a genuine typo, create the right code, repoint whatever uses it, and
235
+ delete the old one.
236
+
237
+ ### Plan entitlement availabilities
238
+
239
+ An availability row says when and where one plan entitlement applies: a
240
+ `calendar_id` (null means any date) and `targets`, a set of registered target
241
+ codes (an empty array means anywhere).
242
+
243
+ ```ts
244
+ await ophelio.planEntitlements.createAvailability(planEntitlement.id, {
245
+ calendar_id: peak.id,
246
+ targets: ['venue_a'],
247
+ })
248
+ ```
249
+
250
+ Rows **OR**, and the absence of rows is judged **per plan entitlement**: a plan
251
+ entitlement with no rows at all is unrestricted, never restricted to nothing.
252
+ Every code in `targets` must already exist in the project's target code
253
+ vocabulary — an unregistered one is rejected, never registered on the fly.
254
+
255
+ Availability rows can be authored today but nothing reads them yet, so they do
256
+ not restrict admission.
package/dist/index.cjs CHANGED
@@ -126,6 +126,21 @@ const DEFAULT_MAX_RETRIES = 2;
126
126
  function resolveIdempotencyKey(idempotencyKey) {
127
127
  return idempotencyKey?.trim() || crypto.randomUUID();
128
128
  }
129
+ /**
130
+ * Serialises query parameters into a search string.
131
+ *
132
+ * `undefined` drops the parameter entirely. An empty array does not: it emits
133
+ * the bare key (`?status=`), because "filter by nothing" and "do not filter"
134
+ * are different requests and the API distinguishes them.
135
+ */
136
+ function serialiseQuery(query) {
137
+ const search = new URLSearchParams();
138
+ for (const [key, value] of Object.entries(query ?? {})) {
139
+ if (value === void 0) continue;
140
+ search.set(key, Array.isArray(value) ? value.join(",") : value);
141
+ }
142
+ return search.toString();
143
+ }
129
144
  var Transport = class {
130
145
  apiKey;
131
146
  baseUrl;
@@ -180,9 +195,7 @@ var Transport = class {
180
195
  throw lastError;
181
196
  }
182
197
  buildUrl(params) {
183
- const search = new URLSearchParams();
184
- for (const [key, value] of Object.entries(params.query ?? {})) if (value !== void 0) search.set(key, value);
185
- const queryString = search.toString();
198
+ const queryString = serialiseQuery(params.query);
186
199
  return `${this.baseUrl}${params.path}${queryString ? `?${queryString}` : ""}`;
187
200
  }
188
201
  buildHeaders(params) {
@@ -314,6 +327,125 @@ var BillingOptionsResource = class extends APIResource {
314
327
  }
315
328
  };
316
329
  //#endregion
330
+ //#region src/resources/calendar-rules.ts
331
+ /**
332
+ * A calendar rule is one row of a calendar's date vocabulary. Conditions AND
333
+ * within a rule and rules OR across a calendar, so "weekends in August" is a
334
+ * single rule rather than two.
335
+ *
336
+ * Rules are created and listed through their calendar
337
+ * (`ophelio.calendars.createRule` / `ophelio.calendars.listRules`); this
338
+ * resource addresses an individual rule.
339
+ */
340
+ var CalendarRulesResource = class extends APIResource {
341
+ get(id, options) {
342
+ return this.transport.request({
343
+ method: "GET",
344
+ path: `/api/v1/calendar-rules/${encodeURIComponent(id)}`,
345
+ retryable: true,
346
+ options
347
+ });
348
+ }
349
+ update(id, params, options) {
350
+ return this.transport.request({
351
+ method: "PATCH",
352
+ path: `/api/v1/calendar-rules/${encodeURIComponent(id)}`,
353
+ body: params,
354
+ retryable: false,
355
+ options
356
+ });
357
+ }
358
+ async delete(id, options) {
359
+ await this.transport.request({
360
+ method: "DELETE",
361
+ path: `/api/v1/calendar-rules/${encodeURIComponent(id)}`,
362
+ retryable: false,
363
+ options
364
+ });
365
+ }
366
+ };
367
+ //#endregion
368
+ //#region src/resources/calendars.ts
369
+ /**
370
+ * Calendars are named, reusable date vocabularies ("Peak 2026", "Weekends").
371
+ * The dates themselves live in the rules attached to them.
372
+ *
373
+ * A calendar containing no rules is valid for **all** dates, not none.
374
+ */
375
+ var CalendarsResource = class extends APIResource {
376
+ async list(params, options) {
377
+ const response = await this.transport.request({
378
+ method: "GET",
379
+ path: "/api/v1/calendars",
380
+ query: pageQuery(params),
381
+ retryable: true,
382
+ options
383
+ });
384
+ return {
385
+ items: response.calendars,
386
+ next_page_token: response.next_page_token,
387
+ total_size: response.total_size
388
+ };
389
+ }
390
+ get(id, options) {
391
+ return this.transport.request({
392
+ method: "GET",
393
+ path: `/api/v1/calendars/${encodeURIComponent(id)}`,
394
+ retryable: true,
395
+ options
396
+ });
397
+ }
398
+ create(params, options) {
399
+ return this.transport.request({
400
+ method: "POST",
401
+ path: "/api/v1/calendars",
402
+ body: params,
403
+ retryable: false,
404
+ options
405
+ });
406
+ }
407
+ update(id, params, options) {
408
+ return this.transport.request({
409
+ method: "PATCH",
410
+ path: `/api/v1/calendars/${encodeURIComponent(id)}`,
411
+ body: params,
412
+ retryable: false,
413
+ options
414
+ });
415
+ }
416
+ async delete(id, options) {
417
+ await this.transport.request({
418
+ method: "DELETE",
419
+ path: `/api/v1/calendars/${encodeURIComponent(id)}`,
420
+ retryable: false,
421
+ options
422
+ });
423
+ }
424
+ async listRules(id, params, options) {
425
+ const response = await this.transport.request({
426
+ method: "GET",
427
+ path: `/api/v1/calendars/${encodeURIComponent(id)}/rules`,
428
+ query: pageQuery(params),
429
+ retryable: true,
430
+ options
431
+ });
432
+ return {
433
+ items: response.rules,
434
+ next_page_token: response.next_page_token,
435
+ total_size: response.total_size
436
+ };
437
+ }
438
+ createRule(id, params, options) {
439
+ return this.transport.request({
440
+ method: "POST",
441
+ path: `/api/v1/calendars/${encodeURIComponent(id)}/rules`,
442
+ body: params,
443
+ retryable: false,
444
+ options
445
+ });
446
+ }
447
+ };
448
+ //#endregion
317
449
  //#region src/resources/customers.ts
318
450
  var CustomersResource = class extends APIResource {
319
451
  /**
@@ -799,6 +931,45 @@ var MembershipsResource = class extends APIResource {
799
931
  }
800
932
  };
801
933
  //#endregion
934
+ //#region src/resources/plan-entitlement-availabilities.ts
935
+ /**
936
+ * An availability row says when and where one plan entitlement applies: a
937
+ * calendar (null = any date) and a set of registered target codes (empty =
938
+ * anywhere). Rows OR, and a plan entitlement with no rows at all is
939
+ * unrestricted.
940
+ *
941
+ * Rows are created and listed through their plan entitlement
942
+ * (`ophelio.planEntitlements.createAvailability` / `listAvailabilities`); this
943
+ * resource addresses an individual row.
944
+ */
945
+ var PlanEntitlementAvailabilitiesResource = class extends APIResource {
946
+ get(id, options) {
947
+ return this.transport.request({
948
+ method: "GET",
949
+ path: `/api/v1/plan-entitlement-availabilities/${encodeURIComponent(id)}`,
950
+ retryable: true,
951
+ options
952
+ });
953
+ }
954
+ update(id, params, options) {
955
+ return this.transport.request({
956
+ method: "PATCH",
957
+ path: `/api/v1/plan-entitlement-availabilities/${encodeURIComponent(id)}`,
958
+ body: params,
959
+ retryable: false,
960
+ options
961
+ });
962
+ }
963
+ async delete(id, options) {
964
+ await this.transport.request({
965
+ method: "DELETE",
966
+ path: `/api/v1/plan-entitlement-availabilities/${encodeURIComponent(id)}`,
967
+ retryable: false,
968
+ options
969
+ });
970
+ }
971
+ };
972
+ //#endregion
802
973
  //#region src/resources/plan-entitlements.ts
803
974
  var PlanEntitlementsResource = class extends APIResource {
804
975
  get(id, options) {
@@ -827,6 +998,29 @@ var PlanEntitlementsResource = class extends APIResource {
827
998
  options
828
999
  });
829
1000
  }
1001
+ async listAvailabilities(id, params, options) {
1002
+ const response = await this.transport.request({
1003
+ method: "GET",
1004
+ path: `/api/v1/plan-entitlements/${encodeURIComponent(id)}/availabilities`,
1005
+ query: pageQuery(params),
1006
+ retryable: true,
1007
+ options
1008
+ });
1009
+ return {
1010
+ items: response.availabilities,
1011
+ next_page_token: response.next_page_token,
1012
+ total_size: response.total_size
1013
+ };
1014
+ }
1015
+ createAvailability(id, params, options) {
1016
+ return this.transport.request({
1017
+ method: "POST",
1018
+ path: `/api/v1/plan-entitlements/${encodeURIComponent(id)}/availabilities`,
1019
+ body: params,
1020
+ retryable: false,
1021
+ options
1022
+ });
1023
+ }
830
1024
  };
831
1025
  //#endregion
832
1026
  //#region src/resources/plan-links.ts
@@ -1061,6 +1255,74 @@ var SalesChannelsResource = class extends APIResource {
1061
1255
  }
1062
1256
  };
1063
1257
  //#endregion
1258
+ //#region src/resources/target-codes.ts
1259
+ /**
1260
+ * The registered vocabulary of locations a gate can report (`venue_a`,
1261
+ * `coffee_shop`). Codes are matched exactly — list them and send values back
1262
+ * verbatim rather than constructing or case-folding them.
1263
+ */
1264
+ var TargetCodesResource = class extends APIResource {
1265
+ async list(params, options) {
1266
+ const response = await this.transport.request({
1267
+ method: "GET",
1268
+ path: "/api/v1/target-codes",
1269
+ query: pageQuery(params),
1270
+ retryable: true,
1271
+ options
1272
+ });
1273
+ return {
1274
+ items: response.target_codes,
1275
+ next_page_token: response.next_page_token,
1276
+ total_size: response.total_size
1277
+ };
1278
+ }
1279
+ get(id, options) {
1280
+ return this.transport.request({
1281
+ method: "GET",
1282
+ path: `/api/v1/target-codes/${encodeURIComponent(id)}`,
1283
+ retryable: true,
1284
+ options
1285
+ });
1286
+ }
1287
+ /**
1288
+ * `code` is lowercase letters and digits with single underscores between
1289
+ * segments, at least one letter, 2–64 characters. Anything else is refused
1290
+ * with a 400 rather than normalised, so `Venue A` is an error, not
1291
+ * `venue_a`.
1292
+ */
1293
+ create(params, options) {
1294
+ return this.transport.request({
1295
+ method: "POST",
1296
+ path: "/api/v1/target-codes",
1297
+ body: params,
1298
+ retryable: false,
1299
+ options
1300
+ });
1301
+ }
1302
+ /**
1303
+ * `display_name` and `description` only — `code` is immutable from
1304
+ * creation and sending it is a 400. To correct a typo, create the right
1305
+ * code, repoint whatever uses it, then delete the old one.
1306
+ */
1307
+ update(id, params, options) {
1308
+ return this.transport.request({
1309
+ method: "PATCH",
1310
+ path: `/api/v1/target-codes/${encodeURIComponent(id)}`,
1311
+ body: params,
1312
+ retryable: false,
1313
+ options
1314
+ });
1315
+ }
1316
+ async delete(id, options) {
1317
+ await this.transport.request({
1318
+ method: "DELETE",
1319
+ path: `/api/v1/target-codes/${encodeURIComponent(id)}`,
1320
+ retryable: false,
1321
+ options
1322
+ });
1323
+ }
1324
+ };
1325
+ //#endregion
1064
1326
  //#region src/resources/webhook-subscriptions.ts
1065
1327
  var WebhookSubscriptionsResource = class extends APIResource {
1066
1328
  async list(options) {
@@ -1185,7 +1447,11 @@ var Ophelio = class Ophelio {
1185
1447
  memberRoles;
1186
1448
  pricingGroups;
1187
1449
  salesChannels;
1450
+ targetCodes;
1451
+ calendars;
1452
+ calendarRules;
1188
1453
  planEntitlements;
1454
+ planEntitlementAvailabilities;
1189
1455
  planLinks;
1190
1456
  planPricingGroupMappings;
1191
1457
  billingOptionSalesChannels;
@@ -1208,7 +1474,11 @@ var Ophelio = class Ophelio {
1208
1474
  this.memberRoles = new MemberRolesResource(transport);
1209
1475
  this.pricingGroups = new PricingGroupsResource(transport);
1210
1476
  this.salesChannels = new SalesChannelsResource(transport);
1477
+ this.targetCodes = new TargetCodesResource(transport);
1478
+ this.calendars = new CalendarsResource(transport);
1479
+ this.calendarRules = new CalendarRulesResource(transport);
1211
1480
  this.planEntitlements = new PlanEntitlementsResource(transport);
1481
+ this.planEntitlementAvailabilities = new PlanEntitlementAvailabilitiesResource(transport);
1212
1482
  this.planLinks = new PlanLinksResource(transport);
1213
1483
  this.planPricingGroupMappings = new PlanPricingGroupMappingsResource(transport);
1214
1484
  this.billingOptionSalesChannels = new BillingOptionSalesChannelsResource(transport);