@ophelio/sdk 0.1.1 → 0.2.1
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 +71 -50
- package/dist/index.cjs +162 -33
- package/dist/index.d.cts +226 -34
- package/dist/index.d.mts +226 -34
- package/dist/index.mjs +161 -34
- package/package.json +5 -2
package/dist/index.mjs
CHANGED
|
@@ -116,6 +116,15 @@ function sleep(ms) {
|
|
|
116
116
|
const DEFAULT_BASE_URL = "https://api.ophel.io";
|
|
117
117
|
const DEFAULT_TIMEOUT_MS = 3e4;
|
|
118
118
|
const DEFAULT_MAX_RETRIES = 2;
|
|
119
|
+
/**
|
|
120
|
+
* Resolves the caller-supplied idempotency key to the value sent on the wire.
|
|
121
|
+
* Trims it and falls back to a generated key when it is absent, empty, or
|
|
122
|
+
* whitespace-only — a blank key must never leave an idempotency-keyed mutation
|
|
123
|
+
* retryable but unkeyed, or a retried write could duplicate.
|
|
124
|
+
*/
|
|
125
|
+
function resolveIdempotencyKey(idempotencyKey) {
|
|
126
|
+
return idempotencyKey?.trim() || crypto.randomUUID();
|
|
127
|
+
}
|
|
119
128
|
var Transport = class {
|
|
120
129
|
apiKey;
|
|
121
130
|
baseUrl;
|
|
@@ -215,6 +224,13 @@ var APIResource = class {
|
|
|
215
224
|
this.transport = transport;
|
|
216
225
|
}
|
|
217
226
|
};
|
|
227
|
+
function pageQuery(params) {
|
|
228
|
+
if (!params) return void 0;
|
|
229
|
+
const query = {};
|
|
230
|
+
if (params.page_size !== void 0) query.page_size = String(params.page_size);
|
|
231
|
+
if (params.page_token !== void 0) query.page_token = params.page_token;
|
|
232
|
+
return query;
|
|
233
|
+
}
|
|
218
234
|
//#endregion
|
|
219
235
|
//#region src/resources/admit.ts
|
|
220
236
|
var AdmitResource = class extends APIResource {
|
|
@@ -299,13 +315,19 @@ var BillingOptionsResource = class extends APIResource {
|
|
|
299
315
|
//#endregion
|
|
300
316
|
//#region src/resources/customers.ts
|
|
301
317
|
var CustomersResource = class extends APIResource {
|
|
302
|
-
async list(options) {
|
|
303
|
-
|
|
318
|
+
async list(params, options) {
|
|
319
|
+
const response = await this.transport.request({
|
|
304
320
|
method: "GET",
|
|
305
321
|
path: "/api/v1/customers",
|
|
322
|
+
query: pageQuery(params),
|
|
306
323
|
retryable: true,
|
|
307
324
|
options
|
|
308
|
-
})
|
|
325
|
+
});
|
|
326
|
+
return {
|
|
327
|
+
items: response.customers,
|
|
328
|
+
next_page_token: response.next_page_token,
|
|
329
|
+
total_size: response.total_size
|
|
330
|
+
};
|
|
309
331
|
}
|
|
310
332
|
get(id, options) {
|
|
311
333
|
return this.transport.request({
|
|
@@ -341,13 +363,19 @@ var CustomersResource = class extends APIResource {
|
|
|
341
363
|
options
|
|
342
364
|
});
|
|
343
365
|
}
|
|
344
|
-
async listMemberships(id, options) {
|
|
345
|
-
|
|
366
|
+
async listMemberships(id, params, options) {
|
|
367
|
+
const response = await this.transport.request({
|
|
346
368
|
method: "GET",
|
|
347
369
|
path: `/api/v1/customers/${encodeURIComponent(id)}/memberships`,
|
|
370
|
+
query: pageQuery(params),
|
|
348
371
|
retryable: true,
|
|
349
372
|
options
|
|
350
|
-
})
|
|
373
|
+
});
|
|
374
|
+
return {
|
|
375
|
+
items: response.memberships,
|
|
376
|
+
next_page_token: response.next_page_token,
|
|
377
|
+
total_size: response.total_size
|
|
378
|
+
};
|
|
351
379
|
}
|
|
352
380
|
};
|
|
353
381
|
//#endregion
|
|
@@ -364,7 +392,7 @@ var EntitlementsResource = class extends APIResource {
|
|
|
364
392
|
method: "POST",
|
|
365
393
|
path: "/api/v1/entitlements/redeem",
|
|
366
394
|
body: params,
|
|
367
|
-
idempotencyKey: idempotencyKey
|
|
395
|
+
idempotencyKey: resolveIdempotencyKey(idempotencyKey),
|
|
368
396
|
retryable: true,
|
|
369
397
|
options: requestOptions
|
|
370
398
|
});
|
|
@@ -379,7 +407,7 @@ var EntitlementsResource = class extends APIResource {
|
|
|
379
407
|
method: "POST",
|
|
380
408
|
path: "/api/v1/entitlements/usage",
|
|
381
409
|
body: params,
|
|
382
|
-
idempotencyKey: idempotencyKey
|
|
410
|
+
idempotencyKey: resolveIdempotencyKey(idempotencyKey),
|
|
383
411
|
retryable: true,
|
|
384
412
|
options: requestOptions
|
|
385
413
|
})).usage;
|
|
@@ -520,13 +548,19 @@ var MembersResource = class extends APIResource {
|
|
|
520
548
|
//#endregion
|
|
521
549
|
//#region src/resources/memberships.ts
|
|
522
550
|
var MembershipsResource = class extends APIResource {
|
|
523
|
-
async list(options) {
|
|
524
|
-
|
|
551
|
+
async list(params, options) {
|
|
552
|
+
const response = await this.transport.request({
|
|
525
553
|
method: "GET",
|
|
526
554
|
path: "/api/v1/memberships",
|
|
555
|
+
query: pageQuery(params),
|
|
527
556
|
retryable: true,
|
|
528
557
|
options
|
|
529
|
-
})
|
|
558
|
+
});
|
|
559
|
+
return {
|
|
560
|
+
items: response.memberships,
|
|
561
|
+
next_page_token: response.next_page_token,
|
|
562
|
+
total_size: response.total_size
|
|
563
|
+
};
|
|
530
564
|
}
|
|
531
565
|
get(id, options) {
|
|
532
566
|
return this.transport.request({
|
|
@@ -536,13 +570,22 @@ var MembershipsResource = class extends APIResource {
|
|
|
536
570
|
options
|
|
537
571
|
});
|
|
538
572
|
}
|
|
539
|
-
|
|
573
|
+
/**
|
|
574
|
+
* Create a membership atomically (record, members, first term, optional
|
|
575
|
+
* payment). Idempotent: the Idempotency-Key is generated once per call (and
|
|
576
|
+
* reused across the SDK's internal retries), so a retried create returns the
|
|
577
|
+
* original membership instead of creating a duplicate. Supply your own key to
|
|
578
|
+
* dedupe application-level retries across processes.
|
|
579
|
+
*/
|
|
580
|
+
create(params, options = {}) {
|
|
581
|
+
const { idempotencyKey, ...requestOptions } = options;
|
|
540
582
|
return this.transport.request({
|
|
541
583
|
method: "POST",
|
|
542
584
|
path: "/api/v1/memberships",
|
|
543
585
|
body: params,
|
|
544
|
-
|
|
545
|
-
|
|
586
|
+
idempotencyKey: resolveIdempotencyKey(idempotencyKey),
|
|
587
|
+
retryable: true,
|
|
588
|
+
options: requestOptions
|
|
546
589
|
});
|
|
547
590
|
}
|
|
548
591
|
update(id, params, options) {
|
|
@@ -572,25 +615,36 @@ var MembershipsResource = class extends APIResource {
|
|
|
572
615
|
options
|
|
573
616
|
});
|
|
574
617
|
}
|
|
575
|
-
async listTransactions(id, options) {
|
|
576
|
-
|
|
618
|
+
async listTransactions(id, params, options) {
|
|
619
|
+
const response = await this.transport.request({
|
|
577
620
|
method: "GET",
|
|
578
621
|
path: `/api/v1/memberships/${encodeURIComponent(id)}/transactions`,
|
|
622
|
+
query: pageQuery(params),
|
|
579
623
|
retryable: true,
|
|
580
624
|
options
|
|
581
|
-
})
|
|
625
|
+
});
|
|
626
|
+
return {
|
|
627
|
+
items: response.transactions,
|
|
628
|
+
next_page_token: response.next_page_token,
|
|
629
|
+
total_size: response.total_size
|
|
630
|
+
};
|
|
582
631
|
}
|
|
583
632
|
/**
|
|
584
633
|
* Report a payment outcome. Ophel.io never processes payments — your
|
|
585
634
|
* payment provider does; this records the result and drives dunning.
|
|
635
|
+
* Idempotent: the Idempotency-Key is generated once per call (and reused
|
|
636
|
+
* across the SDK's internal retries), so a retried report records the
|
|
637
|
+
* transaction once and does not advance dunning twice.
|
|
586
638
|
*/
|
|
587
|
-
createTransaction(id, params, options) {
|
|
639
|
+
createTransaction(id, params, options = {}) {
|
|
640
|
+
const { idempotencyKey, ...requestOptions } = options;
|
|
588
641
|
return this.transport.request({
|
|
589
642
|
method: "POST",
|
|
590
643
|
path: `/api/v1/memberships/${encodeURIComponent(id)}/transactions`,
|
|
591
644
|
body: params,
|
|
592
|
-
|
|
593
|
-
|
|
645
|
+
idempotencyKey: resolveIdempotencyKey(idempotencyKey),
|
|
646
|
+
retryable: true,
|
|
647
|
+
options: requestOptions
|
|
594
648
|
});
|
|
595
649
|
}
|
|
596
650
|
async listMembers(id, options) {
|
|
@@ -601,14 +655,21 @@ var MembershipsResource = class extends APIResource {
|
|
|
601
655
|
options
|
|
602
656
|
})).members;
|
|
603
657
|
}
|
|
604
|
-
/**
|
|
605
|
-
|
|
658
|
+
/**
|
|
659
|
+
* Open the next term. Rejected (409) while cancelled or cancellation-pending.
|
|
660
|
+
* Idempotent: the Idempotency-Key is generated once per call (and reused
|
|
661
|
+
* across the SDK's internal retries), so a retried renewal returns the
|
|
662
|
+
* original result instead of opening a second term.
|
|
663
|
+
*/
|
|
664
|
+
renew(id, params, options = {}) {
|
|
665
|
+
const { idempotencyKey, ...requestOptions } = options;
|
|
606
666
|
return this.transport.request({
|
|
607
667
|
method: "POST",
|
|
608
668
|
path: `/api/v1/memberships/${encodeURIComponent(id)}/renew`,
|
|
609
669
|
body: params,
|
|
610
|
-
|
|
611
|
-
|
|
670
|
+
idempotencyKey: resolveIdempotencyKey(idempotencyKey),
|
|
671
|
+
retryable: true,
|
|
672
|
+
options: requestOptions
|
|
612
673
|
});
|
|
613
674
|
}
|
|
614
675
|
/**
|
|
@@ -635,13 +696,21 @@ var MembershipsResource = class extends APIResource {
|
|
|
635
696
|
});
|
|
636
697
|
}
|
|
637
698
|
async listScheduledChanges(id, params, options) {
|
|
638
|
-
|
|
699
|
+
const response = await this.transport.request({
|
|
639
700
|
method: "GET",
|
|
640
701
|
path: `/api/v1/memberships/${encodeURIComponent(id)}/scheduled-changes`,
|
|
641
|
-
query:
|
|
702
|
+
query: {
|
|
703
|
+
...pageQuery(params),
|
|
704
|
+
...params?.status ? { status: params.status } : {}
|
|
705
|
+
},
|
|
642
706
|
retryable: true,
|
|
643
707
|
options
|
|
644
|
-
})
|
|
708
|
+
});
|
|
709
|
+
return {
|
|
710
|
+
items: response.scheduled_changes,
|
|
711
|
+
next_page_token: response.next_page_token,
|
|
712
|
+
total_size: response.total_size
|
|
713
|
+
};
|
|
645
714
|
}
|
|
646
715
|
async cancelScheduledChange(id, changeId, options) {
|
|
647
716
|
await this.transport.request({
|
|
@@ -651,13 +720,19 @@ var MembershipsResource = class extends APIResource {
|
|
|
651
720
|
options
|
|
652
721
|
});
|
|
653
722
|
}
|
|
654
|
-
async listTermTransactions(id, termId, options) {
|
|
655
|
-
|
|
723
|
+
async listTermTransactions(id, termId, params, options) {
|
|
724
|
+
const response = await this.transport.request({
|
|
656
725
|
method: "GET",
|
|
657
726
|
path: `/api/v1/memberships/${encodeURIComponent(id)}/terms/${encodeURIComponent(termId)}/transactions`,
|
|
727
|
+
query: pageQuery(params),
|
|
658
728
|
retryable: true,
|
|
659
729
|
options
|
|
660
|
-
})
|
|
730
|
+
});
|
|
731
|
+
return {
|
|
732
|
+
items: response.transactions,
|
|
733
|
+
next_page_token: response.next_page_token,
|
|
734
|
+
total_size: response.total_size
|
|
735
|
+
};
|
|
661
736
|
}
|
|
662
737
|
};
|
|
663
738
|
//#endregion
|
|
@@ -941,13 +1016,19 @@ var WebhookSubscriptionsResource = class extends APIResource {
|
|
|
941
1016
|
options
|
|
942
1017
|
});
|
|
943
1018
|
}
|
|
944
|
-
async listDeliveries(id, options) {
|
|
945
|
-
|
|
1019
|
+
async listDeliveries(id, params, options) {
|
|
1020
|
+
const response = await this.transport.request({
|
|
946
1021
|
method: "GET",
|
|
947
1022
|
path: `/api/v1/webhook-subscriptions/${encodeURIComponent(id)}/deliveries`,
|
|
1023
|
+
query: pageQuery(params),
|
|
948
1024
|
retryable: true,
|
|
949
1025
|
options
|
|
950
|
-
})
|
|
1026
|
+
});
|
|
1027
|
+
return {
|
|
1028
|
+
items: response.webhook_deliveries,
|
|
1029
|
+
next_page_token: response.next_page_token,
|
|
1030
|
+
total_size: response.total_size
|
|
1031
|
+
};
|
|
951
1032
|
}
|
|
952
1033
|
/** The HMAC signing secret is only returned on creation — store it then. */
|
|
953
1034
|
create(params, options) {
|
|
@@ -1071,6 +1152,52 @@ var Ophelio = class Ophelio {
|
|
|
1071
1152
|
this.webhookSubscriptions = new WebhookSubscriptionsResource(transport);
|
|
1072
1153
|
}
|
|
1073
1154
|
};
|
|
1155
|
+
//#endregion
|
|
1156
|
+
//#region src/core/pagination.ts
|
|
1157
|
+
/**
|
|
1158
|
+
* Streams every item across all pages of a paginated `list*` method, fetching
|
|
1159
|
+
* the next page lazily as you iterate:
|
|
1160
|
+
*
|
|
1161
|
+
* ```ts
|
|
1162
|
+
* for await (const membership of paginate((page) => ophelio.memberships.list(page))) {
|
|
1163
|
+
* // …
|
|
1164
|
+
* }
|
|
1165
|
+
*
|
|
1166
|
+
* // nested + filtered + abortable:
|
|
1167
|
+
* for await (const change of paginate((page) =>
|
|
1168
|
+
* ophelio.memberships.listScheduledChanges(id, { ...page, status: 'all' }, { signal }))) {
|
|
1169
|
+
* // …
|
|
1170
|
+
* }
|
|
1171
|
+
* ```
|
|
1172
|
+
*
|
|
1173
|
+
* Because this walks offset pages, a row may be seen twice or skipped across a
|
|
1174
|
+
* page boundary if the collection is written to concurrently.
|
|
1175
|
+
*/
|
|
1176
|
+
async function* paginate(fetchPage, params = {}) {
|
|
1177
|
+
let pageToken = params.page_token;
|
|
1178
|
+
do {
|
|
1179
|
+
const page = await fetchPage({
|
|
1180
|
+
...params,
|
|
1181
|
+
page_token: pageToken
|
|
1182
|
+
});
|
|
1183
|
+
yield* page.items;
|
|
1184
|
+
pageToken = page.next_page_token || void 0;
|
|
1185
|
+
} while (pageToken);
|
|
1186
|
+
}
|
|
1187
|
+
/**
|
|
1188
|
+
* Drains every page into a single array — the "give me everything" shortcut
|
|
1189
|
+
* over `paginate`. Loads the whole collection into memory, so prefer
|
|
1190
|
+
* `paginate` for large or unbounded lists.
|
|
1191
|
+
*
|
|
1192
|
+
* ```ts
|
|
1193
|
+
* const customers = await collect((page) => ophelio.customers.list(page))
|
|
1194
|
+
* ```
|
|
1195
|
+
*/
|
|
1196
|
+
async function collect(fetchPage, params = {}) {
|
|
1197
|
+
const items = [];
|
|
1198
|
+
for await (const item of paginate(fetchPage, params)) items.push(item);
|
|
1199
|
+
return items;
|
|
1200
|
+
}
|
|
1074
1201
|
/**
|
|
1075
1202
|
* Canonical webhook event codes for the Ophel.io platform.
|
|
1076
1203
|
*
|
|
@@ -1095,4 +1222,4 @@ const WEBHOOK_EVENT_CODES = [
|
|
|
1095
1222
|
"plan.updated"
|
|
1096
1223
|
];
|
|
1097
1224
|
//#endregion
|
|
1098
|
-
export { AlreadyExistsError, DEFAULT_BASE_URL, InternalError, InvalidArgumentError, NotFoundError, Ophelio, OphelioConnectionError, OphelioError, OphelioTimeoutError, PermissionDeniedError, ResourceExhaustedError, UnauthenticatedError, UnavailableError, UnimplementedError, WEBHOOK_EVENT_CODES, WebhookSignatureVerificationError, constructEvent, verifyWebhookSignature };
|
|
1225
|
+
export { AlreadyExistsError, DEFAULT_BASE_URL, InternalError, InvalidArgumentError, NotFoundError, Ophelio, OphelioConnectionError, OphelioError, OphelioTimeoutError, PermissionDeniedError, ResourceExhaustedError, UnauthenticatedError, UnavailableError, UnimplementedError, WEBHOOK_EVENT_CODES, WebhookSignatureVerificationError, collect, constructEvent, paginate, verifyWebhookSignature };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ophelio/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Official JavaScript / TypeScript SDK for the Ophel.io membership & entitlement API.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ophel.io",
|
|
@@ -37,11 +37,14 @@
|
|
|
37
37
|
"pretest": "npm run generate:types",
|
|
38
38
|
"test": "vitest run",
|
|
39
39
|
"pretypecheck": "npm run generate:types",
|
|
40
|
-
"typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.test.json"
|
|
40
|
+
"typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.test.json",
|
|
41
|
+
"check:package": "npm run build && publint --strict && attw --pack"
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|
|
44
|
+
"@arethetypeswrong/cli": "^0.18.0",
|
|
43
45
|
"@types/node": "^25.6.0",
|
|
44
46
|
"ophelio-utils": "*",
|
|
47
|
+
"publint": "^0.3.21",
|
|
45
48
|
"tsdown": "^0.22.0",
|
|
46
49
|
"typescript": "^6.0.3",
|
|
47
50
|
"vitest": "^4.0.18"
|