@ophelio/sdk 0.1.0 → 0.2.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
@@ -5,9 +5,7 @@ membership & entitlement API. Works on Node 20+, Cloudflare Workers, Deno and
5
5
  Bun — zero runtime dependencies, ESM + CJS, fully typed from the platform's
6
6
  OpenAPI spec.
7
7
 
8
- > **Status:** pre-1.0. Minor versions may contain breaking changes (most
9
- > notably, list-method return shapes will change when the API gains
10
- > pagination).
8
+ > **Status:** pre-1.0. Minor versions may contain breaking changes.
11
9
 
12
10
  ## Install
13
11
 
@@ -42,17 +40,6 @@ Pass your project API key (`o_live_…`); the project is implied by the key:
42
40
  new Ophelio({ apiKey: 'o_live_…' })
43
41
  ```
44
42
 
45
- For first-party / server-side session use, pass auth headers instead and
46
- optionally your own `fetch`:
47
-
48
- ```ts
49
- new Ophelio({
50
- baseUrl: 'https://api.your-deployment.example',
51
- headers: { cookie: cookieHeader },
52
- fetch: event.fetch, // e.g. SvelteKit
53
- })
54
- ```
55
-
56
43
  ## Errors
57
44
 
58
45
  Non-2xx responses throw a typed subclass of `OphelioError` mapped from the
@@ -115,44 +102,75 @@ switch (event.type) {
115
102
  Unknown event types still verify and parse, so new platform events don't
116
103
  break older SDK versions.
117
104
 
105
+ ## Pagination
106
+
107
+ The larger list endpoints are paginated. Their `list*` methods accept an
108
+ optional `{ page_size?, page_token? }` and resolve to a `Page<T>`:
109
+
110
+ ```ts
111
+ interface Page<T> {
112
+ items: T[]
113
+ next_page_token: string // '' on the last page
114
+ total_size: number // exact count across all pages
115
+ }
116
+ ```
117
+
118
+ Most of the time, let `paginate` walk the pages for you — pass a closure that
119
+ forwards the pagination params, capturing any ids, filters and options in it:
120
+
121
+ ```ts
122
+ import { collect, paginate } from '@ophelio/sdk'
123
+
124
+ for await (const membership of paginate((page) =>
125
+ ophelio.memberships.list(page),
126
+ )) {
127
+ // …
128
+ }
129
+
130
+ // nested + filtered:
131
+ for await (const change of paginate((page) =>
132
+ ophelio.memberships.listScheduledChanges(id, { ...page, status: 'all' }),
133
+ )) {
134
+ // …
135
+ }
136
+
137
+ // or drain everything into an array (loads the whole collection into memory):
138
+ const customers = await collect((page) => ophelio.customers.list(page))
139
+ ```
140
+
141
+ To page manually — e.g. to surface `total_size` or drive numbered pages — feed
142
+ `next_page_token` back in as `page_token` until it comes back empty.
143
+
144
+ `page_size` defaults to 50 and is capped at 250. Because pagination is
145
+ offset-based, a row can be seen twice or skipped across a page boundary if the
146
+ collection is written to while you iterate. Paginated methods are marked
147
+ `(params?)` in the table below; the bounded lists (config resources,
148
+ `listMembers`) return a plain array.
149
+
118
150
  ## API surface
119
151
 
120
152
  Everything reachable with an API key is covered:
121
153
 
122
- | Namespace | Methods |
123
- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
124
- | `admit` | `check({ card })` |
125
- | `members` | `sync({ since? })`, `get(id)`, `listEntitlements(id, { type? })`, `reissueCard(id, { note? })` |
126
- | `memberships` | `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `cancel(id, ...)`, `uncancel(id)`, `renew(id, ...)`, `upgrade(id, ...)`, `downgrade(id, ...)`, `listMembers(id)`, `listScheduledChanges(id, { status? })`, `cancelScheduledChange(id, changeId)`, `listTransactions(id)`, `listTermTransactions(id, termId)`, `createTransaction(id, ...)` |
127
- | `customers` | `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)`, `listMemberships(id)` |
128
- | `entitlements` | `redeem(...)`, `recordUsage(...)`, `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)` |
129
- | `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, ...)` |
130
- | `billingOptions` | `list()`, `get(id)`, `update(id, ...)`, `delete(id)`, `listSalesChannels(id)`, `createSalesChannelLink(id, ...)` |
131
- | `memberRoles` | `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)` |
132
- | `pricingGroups` | `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)` |
133
- | `salesChannels` | `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)` |
134
- | `planEntitlements` | `get(id)`, `update(id, ...)`, `delete(id)` |
135
- | `planLinks` | `delete(id)` |
136
- | `planPricingGroupMappings` | `delete(id)` |
137
- | `billingOptionSalesChannels` | `delete(id)` |
138
- | `webhookSubscriptions` | `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)`, `listDeliveries(id)` |
154
+ | Namespace | Methods |
155
+ | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
156
+ | `admit` | `check({ card })` |
157
+ | `members` | `sync({ since? })`, `get(id)`, `listEntitlements(id, { type? })`, `reissueCard(id, { note? })` |
158
+ | `memberships` | `list(params?)`, `get(id)`, `create(...)`, `update(id, ...)`, `cancel(id, ...)`, `uncancel(id)`, `renew(id, ...)`, `upgrade(id, ...)`, `downgrade(id, ...)`, `listMembers(id)`, `listScheduledChanges(id, params?)`, `cancelScheduledChange(id, changeId)`, `listTransactions(id, params?)`, `listTermTransactions(id, termId, params?)`, `createTransaction(id, ...)` |
159
+ | `customers` | `list(params?)`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)`, `listMemberships(id, params?)` |
160
+ | `entitlements` | `redeem(...)`, `recordUsage(...)`, `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)` |
161
+ | `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, ...)` |
162
+ | `billingOptions` | `list()`, `get(id)`, `update(id, ...)`, `delete(id)`, `listSalesChannels(id)`, `createSalesChannelLink(id, ...)` |
163
+ | `memberRoles` | `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)` |
164
+ | `pricingGroups` | `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)` |
165
+ | `salesChannels` | `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)` |
166
+ | `planEntitlements` | `get(id)`, `update(id, ...)`, `delete(id)` |
167
+ | `planLinks` | `delete(id)` |
168
+ | `planPricingGroupMappings` | `delete(id)` |
169
+ | `billingOptionSalesChannels` | `delete(id)` |
170
+ | `webhookSubscriptions` | `list()`, `get(id)`, `create(...)`, `update(id, ...)`, `delete(id)`, `listDeliveries(id, params?)` |
139
171
 
140
172
  Every method accepts a trailing `options` argument: `{ signal?, headers? }`
141
173
  (plus `idempotencyKey?` on `redeem` / `recordUsage`).
142
174
 
143
175
  Endpoints requiring a user session (API key management, organisation and
144
176
  project admin) are intentionally not in the SDK — API keys cannot call them.
145
-
146
- ## Development (monorepo)
147
-
148
- Types are generated from `packages/ophelio-api/openapi.json`:
149
-
150
- ```sh
151
- npm run generate:sdk # repo root: regenerate spec + SDK types
152
- npm test -w packages/ophelio-sdk
153
- ```
154
-
155
- `src/generated/schema.d.ts` is a build artifact (git-ignored), regenerated
156
- from the committed spec on every `build`, `typecheck`, and `test`. The
157
- committed source of truth is `packages/ophelio-api/openapi.json`; CI fails
158
- if it drifts from the API controllers.
package/dist/index.cjs CHANGED
@@ -216,6 +216,13 @@ var APIResource = class {
216
216
  this.transport = transport;
217
217
  }
218
218
  };
219
+ function pageQuery(params) {
220
+ if (!params) return void 0;
221
+ const query = {};
222
+ if (params.page_size !== void 0) query.page_size = String(params.page_size);
223
+ if (params.page_token !== void 0) query.page_token = params.page_token;
224
+ return query;
225
+ }
219
226
  //#endregion
220
227
  //#region src/resources/admit.ts
221
228
  var AdmitResource = class extends APIResource {
@@ -300,13 +307,19 @@ var BillingOptionsResource = class extends APIResource {
300
307
  //#endregion
301
308
  //#region src/resources/customers.ts
302
309
  var CustomersResource = class extends APIResource {
303
- async list(options) {
304
- return (await this.transport.request({
310
+ async list(params, options) {
311
+ const response = await this.transport.request({
305
312
  method: "GET",
306
313
  path: "/api/v1/customers",
314
+ query: pageQuery(params),
307
315
  retryable: true,
308
316
  options
309
- })).customers;
317
+ });
318
+ return {
319
+ items: response.customers,
320
+ next_page_token: response.next_page_token,
321
+ total_size: response.total_size
322
+ };
310
323
  }
311
324
  get(id, options) {
312
325
  return this.transport.request({
@@ -342,13 +355,19 @@ var CustomersResource = class extends APIResource {
342
355
  options
343
356
  });
344
357
  }
345
- async listMemberships(id, options) {
346
- return (await this.transport.request({
358
+ async listMemberships(id, params, options) {
359
+ const response = await this.transport.request({
347
360
  method: "GET",
348
361
  path: `/api/v1/customers/${encodeURIComponent(id)}/memberships`,
362
+ query: pageQuery(params),
349
363
  retryable: true,
350
364
  options
351
- })).memberships;
365
+ });
366
+ return {
367
+ items: response.memberships,
368
+ next_page_token: response.next_page_token,
369
+ total_size: response.total_size
370
+ };
352
371
  }
353
372
  };
354
373
  //#endregion
@@ -521,13 +540,19 @@ var MembersResource = class extends APIResource {
521
540
  //#endregion
522
541
  //#region src/resources/memberships.ts
523
542
  var MembershipsResource = class extends APIResource {
524
- async list(options) {
525
- return (await this.transport.request({
543
+ async list(params, options) {
544
+ const response = await this.transport.request({
526
545
  method: "GET",
527
546
  path: "/api/v1/memberships",
547
+ query: pageQuery(params),
528
548
  retryable: true,
529
549
  options
530
- })).memberships;
550
+ });
551
+ return {
552
+ items: response.memberships,
553
+ next_page_token: response.next_page_token,
554
+ total_size: response.total_size
555
+ };
531
556
  }
532
557
  get(id, options) {
533
558
  return this.transport.request({
@@ -573,13 +598,19 @@ var MembershipsResource = class extends APIResource {
573
598
  options
574
599
  });
575
600
  }
576
- async listTransactions(id, options) {
577
- return (await this.transport.request({
601
+ async listTransactions(id, params, options) {
602
+ const response = await this.transport.request({
578
603
  method: "GET",
579
604
  path: `/api/v1/memberships/${encodeURIComponent(id)}/transactions`,
605
+ query: pageQuery(params),
580
606
  retryable: true,
581
607
  options
582
- })).transactions;
608
+ });
609
+ return {
610
+ items: response.transactions,
611
+ next_page_token: response.next_page_token,
612
+ total_size: response.total_size
613
+ };
583
614
  }
584
615
  /**
585
616
  * Report a payment outcome. Ophel.io never processes payments — your
@@ -636,13 +667,21 @@ var MembershipsResource = class extends APIResource {
636
667
  });
637
668
  }
638
669
  async listScheduledChanges(id, params, options) {
639
- return (await this.transport.request({
670
+ const response = await this.transport.request({
640
671
  method: "GET",
641
672
  path: `/api/v1/memberships/${encodeURIComponent(id)}/scheduled-changes`,
642
- query: params?.status ? { status: params.status } : void 0,
673
+ query: {
674
+ ...pageQuery(params),
675
+ ...params?.status ? { status: params.status } : {}
676
+ },
643
677
  retryable: true,
644
678
  options
645
- })).scheduled_changes;
679
+ });
680
+ return {
681
+ items: response.scheduled_changes,
682
+ next_page_token: response.next_page_token,
683
+ total_size: response.total_size
684
+ };
646
685
  }
647
686
  async cancelScheduledChange(id, changeId, options) {
648
687
  await this.transport.request({
@@ -652,13 +691,19 @@ var MembershipsResource = class extends APIResource {
652
691
  options
653
692
  });
654
693
  }
655
- async listTermTransactions(id, termId, options) {
656
- return (await this.transport.request({
694
+ async listTermTransactions(id, termId, params, options) {
695
+ const response = await this.transport.request({
657
696
  method: "GET",
658
697
  path: `/api/v1/memberships/${encodeURIComponent(id)}/terms/${encodeURIComponent(termId)}/transactions`,
698
+ query: pageQuery(params),
659
699
  retryable: true,
660
700
  options
661
- })).transactions;
701
+ });
702
+ return {
703
+ items: response.transactions,
704
+ next_page_token: response.next_page_token,
705
+ total_size: response.total_size
706
+ };
662
707
  }
663
708
  };
664
709
  //#endregion
@@ -942,13 +987,19 @@ var WebhookSubscriptionsResource = class extends APIResource {
942
987
  options
943
988
  });
944
989
  }
945
- async listDeliveries(id, options) {
946
- return (await this.transport.request({
990
+ async listDeliveries(id, params, options) {
991
+ const response = await this.transport.request({
947
992
  method: "GET",
948
993
  path: `/api/v1/webhook-subscriptions/${encodeURIComponent(id)}/deliveries`,
994
+ query: pageQuery(params),
949
995
  retryable: true,
950
996
  options
951
- })).webhook_deliveries;
997
+ });
998
+ return {
999
+ items: response.webhook_deliveries,
1000
+ next_page_token: response.next_page_token,
1001
+ total_size: response.total_size
1002
+ };
952
1003
  }
953
1004
  /** The HMAC signing secret is only returned on creation — store it then. */
954
1005
  create(params, options) {
@@ -1072,6 +1123,52 @@ var Ophelio = class Ophelio {
1072
1123
  this.webhookSubscriptions = new WebhookSubscriptionsResource(transport);
1073
1124
  }
1074
1125
  };
1126
+ //#endregion
1127
+ //#region src/core/pagination.ts
1128
+ /**
1129
+ * Streams every item across all pages of a paginated `list*` method, fetching
1130
+ * the next page lazily as you iterate:
1131
+ *
1132
+ * ```ts
1133
+ * for await (const membership of paginate((page) => ophelio.memberships.list(page))) {
1134
+ * // …
1135
+ * }
1136
+ *
1137
+ * // nested + filtered + abortable:
1138
+ * for await (const change of paginate((page) =>
1139
+ * ophelio.memberships.listScheduledChanges(id, { ...page, status: 'all' }, { signal }))) {
1140
+ * // …
1141
+ * }
1142
+ * ```
1143
+ *
1144
+ * Because this walks offset pages, a row may be seen twice or skipped across a
1145
+ * page boundary if the collection is written to concurrently.
1146
+ */
1147
+ async function* paginate(fetchPage, params = {}) {
1148
+ let pageToken = params.page_token;
1149
+ do {
1150
+ const page = await fetchPage({
1151
+ ...params,
1152
+ page_token: pageToken
1153
+ });
1154
+ yield* page.items;
1155
+ pageToken = page.next_page_token || void 0;
1156
+ } while (pageToken);
1157
+ }
1158
+ /**
1159
+ * Drains every page into a single array — the "give me everything" shortcut
1160
+ * over `paginate`. Loads the whole collection into memory, so prefer
1161
+ * `paginate` for large or unbounded lists.
1162
+ *
1163
+ * ```ts
1164
+ * const customers = await collect((page) => ophelio.customers.list(page))
1165
+ * ```
1166
+ */
1167
+ async function collect(fetchPage, params = {}) {
1168
+ const items = [];
1169
+ for await (const item of paginate(fetchPage, params)) items.push(item);
1170
+ return items;
1171
+ }
1075
1172
  /**
1076
1173
  * Canonical webhook event codes for the Ophel.io platform.
1077
1174
  *
@@ -1112,5 +1209,7 @@ exports.UnavailableError = UnavailableError;
1112
1209
  exports.UnimplementedError = UnimplementedError;
1113
1210
  exports.WEBHOOK_EVENT_CODES = WEBHOOK_EVENT_CODES;
1114
1211
  exports.WebhookSignatureVerificationError = WebhookSignatureVerificationError;
1212
+ exports.collect = collect;
1115
1213
  exports.constructEvent = constructEvent;
1214
+ exports.paginate = paginate;
1116
1215
  exports.verifyWebhookSignature = verifyWebhookSignature;