@ophelio/sdk 0.3.2 → 0.4.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 CHANGED
@@ -21,7 +21,10 @@ import { Ophelio } from '@ophelio/sdk'
21
21
  const ophelio = new Ophelio({ apiKey: process.env.OPHELIO_API_KEY })
22
22
 
23
23
  // Gate admission check
24
- const result = await ophelio.admit.check({ card: 'M-123456' })
24
+ const result = await ophelio.admit.check({
25
+ card: 'M-123456',
26
+ targets: ['venue_a'], // where this gate is; optional
27
+ })
25
28
  if (result.admitted) {
26
29
  console.log(result.pricing_group) // e.g. 'member_adult'
27
30
  }
@@ -156,8 +159,8 @@ Everything reachable with an API key is covered:
156
159
 
157
160
  | Namespace | Methods |
158
161
  | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
159
- | `admit` | `check({ card })` |
160
- | `members` | `sync({ since? })`, `get(id)`, `listEntitlements(id, { type? })`, `update(id, ...)`, `remove(id)`, `setPrimary(id)`, `reissueCard(id, { note? })` |
162
+ | `admit` | `check({ card, targets? })` |
163
+ | `members` | `sync({ since? })`, `get(id)`, `listEntitlements(id, { type?, targets? })`, `update(id, ...)`, `remove(id)`, `setPrimary(id)`, `reissueCard(id, { note? })` |
161
164
  | `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
165
  | `customers` | `list(params?)`, `get(id, params?)`, `create(...)`, `update(id, ...)`, `delete(id)`, `listMemberships(id, params?)` — `list`/`get` accept `view: 'full'` to include `membership_summary` |
163
166
  | `entitlements` | `redeem(...)`, `recordUsage(...)`, `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)` |
@@ -166,8 +169,8 @@ Everything reachable with an API key is covered:
166
169
  | `memberRoles` | `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)` |
167
170
  | `pricingGroups` | `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)` |
168
171
  | `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, ...)` |
172
+ | `targetCodes` | `list(params?)`, `get(id, params?)`, `create(...)`, `update(id, ...)`, `delete(id)` — `list`/`get` accept `view: 'full'` to include `plan_entitlement_count`; `update` takes `display_name` / `description` only, as `code` is immutable |
173
+ | `calendars` | `list(params?)`, `get(id, params?)`, `create(...)`, `update(id, ...)`, `delete(id)`, `listRules(id, params?)`, `createRule(id, ...)` — `list`/`get` accept `view: 'full'` to include `plan_entitlement_count` |
171
174
  | `calendarRules` | `get(id)`, `update(id, ...)`, `delete(id)` |
172
175
  | `planEntitlements` | `get(id)`, `update(id, ...)`, `delete(id)`, `listAvailabilities(id, params?)`, `createAvailability(id, ...)` |
173
176
  | `planEntitlementAvailabilities` | `get(id)`, `update(id, ...)`, `delete(id)` |
@@ -209,8 +212,10 @@ A calendar containing no rules is valid for **all** dates, not none.
209
212
 
210
213
  ### Target codes
211
214
 
212
- The registered vocabulary of locations a gate reports (`venue_a`,
213
- `coffee_shop`). A `code` is lowercase letters and digits with single
215
+ A registered code for each venue, zone, shop or gate that accepts
216
+ memberships (`venue_a`, `coffee_shop`). Register each one once, then name it
217
+ wherever a benefit is restricted, and wherever a request says where it is
218
+ coming from. A `code` is lowercase letters and digits with single
214
219
  underscores between segments, at least one letter, 2–64 characters — and it is
215
220
  **matched exactly**. A miscased code is an unregistered code, so send values
216
221
  back verbatim from `list()` rather than constructing or case-folding them.
@@ -249,8 +254,54 @@ await ophelio.planEntitlements.createAvailability(planEntitlement.id, {
249
254
 
250
255
  Rows **OR**, and the absence of rows is judged **per plan entitlement**: a plan
251
256
  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.
257
+ Every code in `targets` must already be registered as a target code — an
258
+ unregistered one is rejected, never registered on the fly.
259
+
260
+ ### Target context
261
+
262
+ A read can say where it is being made, and the entitlements it gets back are
263
+ judged against the availability rows in that light. `admit.check` and
264
+ `members.listEntitlements` take `targets` — a set of registered target codes,
265
+ sent comma-separated on the wire — and `entitlements.recordUsage` takes a
266
+ singular `target`, because one usage event happens at one place.
267
+
268
+ ```ts
269
+ const result = await ophelio.admit.check({
270
+ card: 'M-123456',
271
+ targets: ['venue_a', 'coffee_shop'],
272
+ })
273
+
274
+ result.target_context_applied // true — the entitlements were target-filtered
275
+
276
+ for (const entitlement of result.entitlements) {
277
+ entitlement.valid_at // ['coffee_shop'] — where it is valid, when restricted
278
+ entitlement.valid_on // the date windows it matched, when restricted
279
+ }
280
+ ```
254
281
 
255
- Availability rows can be authored today but nothing reads them yet, so they do
256
- not restrict admission.
282
+ **Three states, answered differently.** Omitting `targets` is the permissive
283
+ default: every target restriction is treated as satisfied, and
284
+ `target_context_applied` comes back `false`. Passing `[]` says the caller is
285
+ naming no targets, so only benefits valid anywhere survive. Passing codes
286
+ means a benefit needs one of them. Dates are enforced in all three.
287
+
288
+ **Codes are matched exactly.** Send them back verbatim from
289
+ `targetCodes.list()`; an unregistered or miscased code is refused with a 400
290
+ rather than dropped, so a typo cannot pass for a benefit that does not apply.
291
+
292
+ **The parameter means something different on each surface.** At the gate it is
293
+ the context the decision is made in; on `members.listEntitlements` it is a
294
+ filter over the result. `members.sync` takes no target context at all — the
295
+ offline roster carries each member's entitlements as at the moment of sync,
296
+ and the gate that caches it evaluates no rules of its own.
297
+
298
+ `valid_on` and `valid_at` carry only what an entitlement **matched**, and are
299
+ omitted entirely when nothing restricts it on that axis. What it failed is
300
+ never returned: the response says where and when a benefit is valid, never
301
+ which restrictions the member did not satisfy. Because rows OR, the two read
302
+ as independent unions rather than as pairs.
303
+
304
+ A project can require target context — once every gate reports its own, it is
305
+ turned on and a read that says nothing about where it is is refused from then
306
+ on. It is a per-project setting rather than anything the client sends, so roll
307
+ out sending `targets` before expecting it to be enforced.
package/dist/index.cjs CHANGED
@@ -248,12 +248,30 @@ function pageQuery(params) {
248
248
  //#endregion
249
249
  //#region src/resources/admit.ts
250
250
  var AdmitResource = class extends APIResource {
251
- /** Gate admission check: `GET /admit?card=<cardId>`. */
251
+ /**
252
+ * Gate admission check: `GET /admit?card=<cardId>`.
253
+ *
254
+ * `targets` names the venue, zone, shop or gate the request is being made
255
+ * at, and is the context the entitlements are judged in — a benefit
256
+ * restricted to somewhere else drops out of `entitlements`. Three states,
257
+ * and they are answered differently: omit it and every target restriction
258
+ * is treated as satisfied; pass `[]` and only benefits valid anywhere
259
+ * survive; pass codes and a benefit needs one of them. Read
260
+ * `target_context_applied` on the result to tell the first apart from the
261
+ * other two.
262
+ *
263
+ * Codes are matched exactly, so send them back verbatim from
264
+ * `targetCodes.list()` — an unregistered or miscased code is refused with
265
+ * a 400 rather than quietly ignored.
266
+ */
252
267
  check(params, options) {
253
268
  return this.transport.request({
254
269
  method: "GET",
255
270
  path: "/api/v1/admit",
256
- query: { card: params.card },
271
+ query: {
272
+ card: params.card,
273
+ targets: params.targets
274
+ },
257
275
  retryable: true,
258
276
  options
259
277
  });
@@ -329,7 +347,7 @@ var BillingOptionsResource = class extends APIResource {
329
347
  //#endregion
330
348
  //#region src/resources/calendar-rules.ts
331
349
  /**
332
- * A calendar rule is one row of a calendar's date vocabulary. Conditions AND
350
+ * A calendar rule is one row of a calendar's set of dates. Conditions AND
333
351
  * within a rule and rules OR across a calendar, so "weekends in August" is a
334
352
  * single rule rather than two.
335
353
  *
@@ -367,17 +385,24 @@ var CalendarRulesResource = class extends APIResource {
367
385
  //#endregion
368
386
  //#region src/resources/calendars.ts
369
387
  /**
370
- * Calendars are named, reusable date vocabularies ("Peak 2026", "Weekends").
388
+ * Calendars are named, reusable sets of dates ("Peak 2026", "Weekends").
371
389
  * The dates themselves live in the rules attached to them.
372
390
  *
373
391
  * A calendar containing no rules is valid for **all** dates, not none.
374
392
  */
375
393
  var CalendarsResource = class extends APIResource {
394
+ /**
395
+ * Pass `view: 'full'` to include `plan_entitlement_count` on every
396
+ * calendar.
397
+ */
376
398
  async list(params, options) {
377
399
  const response = await this.transport.request({
378
400
  method: "GET",
379
401
  path: "/api/v1/calendars",
380
- query: pageQuery(params),
402
+ query: {
403
+ ...pageQuery(params),
404
+ ...params?.view ? { view: params.view } : {}
405
+ },
381
406
  retryable: true,
382
407
  options
383
408
  });
@@ -387,10 +412,14 @@ var CalendarsResource = class extends APIResource {
387
412
  total_size: response.total_size
388
413
  };
389
414
  }
390
- get(id, options) {
415
+ /**
416
+ * Pass `view: 'full'` to include `plan_entitlement_count`.
417
+ */
418
+ get(id, params = {}, options) {
391
419
  return this.transport.request({
392
420
  method: "GET",
393
421
  path: `/api/v1/calendars/${encodeURIComponent(id)}`,
422
+ query: params.view ? { view: params.view } : void 0,
394
423
  retryable: true,
395
424
  options
396
425
  });
@@ -543,6 +572,14 @@ var EntitlementsResource = class extends APIResource {
543
572
  /**
544
573
  * Fire-and-forget batch usage reporting (one row per member). Idempotent
545
574
  * per (key, member). Returns the recorded usage rows.
575
+ *
576
+ * `target` is the single registered code for the venue, zone, shop or gate
577
+ * the usage happened at — one event, one place, which is why it is
578
+ * singular here where the reads take `targets`. It is both the context the
579
+ * entitlement is judged in and a column on the row that comes back, so it
580
+ * is queryable in a way the free-form `context` is not. It is part of the
581
+ * idempotency fingerprint: a replay of the same key at a different target
582
+ * is refused rather than silently returning the original row.
546
583
  */
547
584
  async recordUsage(params, options = {}) {
548
585
  const { idempotencyKey, ...requestOptions } = options;
@@ -668,11 +705,29 @@ var MembersResource = class extends APIResource {
668
705
  options
669
706
  });
670
707
  }
708
+ /**
709
+ * The entitlements a member can use right now.
710
+ *
711
+ * `targets` filters the result down to the benefits valid at the venue,
712
+ * zone, shop or gate it names — the same codes `admit.check` takes, but
713
+ * read as a filter here rather than as the context a gate decision is made
714
+ * in. Three states: omit it and nothing is filtered by target; pass `[]`
715
+ * and only benefits valid anywhere come back; pass codes and a benefit
716
+ * needs one of them. Codes are matched exactly, so send them back verbatim
717
+ * from `targetCodes.list()` — an unregistered one is refused with a 400.
718
+ *
719
+ * Each entitlement carries `valid_on` and `valid_at` when something
720
+ * restricts it by date or by target; both list only what it matched, never
721
+ * what it failed.
722
+ */
671
723
  async listEntitlements(id, params = {}, options) {
672
724
  return (await this.transport.request({
673
725
  method: "GET",
674
726
  path: `/api/v1/members/${encodeURIComponent(id)}/entitlements`,
675
- query: { type: params.type },
727
+ query: {
728
+ type: params.type,
729
+ targets: params.targets
730
+ },
676
731
  retryable: true,
677
732
  options
678
733
  })).entitlements;
@@ -1257,16 +1312,23 @@ var SalesChannelsResource = class extends APIResource {
1257
1312
  //#endregion
1258
1313
  //#region src/resources/target-codes.ts
1259
1314
  /**
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.
1315
+ * The registered code for each venue, zone, shop or gate a request can name
1316
+ * (`venue_a`, `coffee_shop`). Codes are matched exactly — list them and send
1317
+ * values back verbatim rather than constructing or case-folding them.
1263
1318
  */
1264
1319
  var TargetCodesResource = class extends APIResource {
1320
+ /**
1321
+ * Pass `view: 'full'` to include `plan_entitlement_count` on every target
1322
+ * code.
1323
+ */
1265
1324
  async list(params, options) {
1266
1325
  const response = await this.transport.request({
1267
1326
  method: "GET",
1268
1327
  path: "/api/v1/target-codes",
1269
- query: pageQuery(params),
1328
+ query: {
1329
+ ...pageQuery(params),
1330
+ ...params?.view ? { view: params.view } : {}
1331
+ },
1270
1332
  retryable: true,
1271
1333
  options
1272
1334
  });
@@ -1276,10 +1338,14 @@ var TargetCodesResource = class extends APIResource {
1276
1338
  total_size: response.total_size
1277
1339
  };
1278
1340
  }
1279
- get(id, options) {
1341
+ /**
1342
+ * Pass `view: 'full'` to include `plan_entitlement_count`.
1343
+ */
1344
+ get(id, params = {}, options) {
1280
1345
  return this.transport.request({
1281
1346
  method: "GET",
1282
1347
  path: `/api/v1/target-codes/${encodeURIComponent(id)}`,
1348
+ query: params.view ? { view: params.view } : void 0,
1283
1349
  retryable: true,
1284
1350
  options
1285
1351
  });