owostack 0.2.0 → 0.3.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/dist/index.d.ts +11 -2
- package/dist/index.js +220 -34
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { CheckResult, PlanFeatureEntry, ResetInterval, CreditSystemDefinition, AddEntityResult, RemoveEntityResult, ListEntitiesResult, MeteredFeatureConfig, TrackResult, CatalogEntry, SyncPayload, Currency, CreditPackDefinition, PlanInterval, PlanDefinition, OwostackConfig, BillingUsageParams, BillingUsageResult, InvoiceParams, InvoiceResult, InvoicesParams, InvoicesResult, PayInvoiceParams, PayInvoiceResult, WalletResult, WalletSetupResult, WalletRemoveResult, SyncResult, AttachParams, AttachResult, CheckParams, TrackParams, AddonParams, AddonResult, CustomerParams, CustomerResult, AddEntityParams, RemoveEntityParams, ListEntitiesParams, PlansParams, PlansResult, PublicPlan } from '@owostack/types';
|
|
2
|
-
export { AddEntityParams, AddEntityResult, AddonParams, AddonResult, AttachParams, AttachResult, BillingFeatureUsage, BillingUsageParams, BillingUsageResult, BooleanFeatureConfig, CardInfo, CatalogEntry, CheckCode, CheckParams, CheckResult, CustomerData, CustomerParams, CustomerResult, Invoice, InvoiceLineItem, InvoiceParams, InvoiceResult, InvoicesParams, InvoicesResult, ListEntitiesParams, ListEntitiesResult, MeteredFeatureConfig, OverageDetails, OwostackConfig, PayInvoiceParams, PayInvoiceResult, PaymentMethodInfo, PlanCredits, PlanDefinition, PlanFeatureEntry, PlansParams, PlansResult, PublicPlan, PublicPlanFeature, RemoveEntityParams, RemoveEntityResult, ResponseDetails, SyncChanges, SyncPayload, SyncResult, TrackCode, TrackParams, TrackResult, WalletRemoveResult, WalletResult, WalletSetupResult } from '@owostack/types';
|
|
1
|
+
import { CheckResult, PlanFeatureEntry, ResetInterval, CreditSystemDefinition, AddEntityResult, RemoveEntityResult, ListEntitiesResult, MeteredFeatureConfig, TrackResult, PricingTier, CatalogEntry, SyncPayload, Currency, CreditPackDefinition, PlanInterval, PlanDefinition, OwostackConfig, BillingUsageParams, BillingUsageResult, InvoiceParams, InvoiceResult, InvoicesParams, InvoicesResult, PayInvoiceParams, PayInvoiceResult, WalletResult, WalletSetupResult, WalletRemoveResult, SyncResult, AttachParams, AttachResult, CheckParams, TrackParams, AddonParams, AddonResult, CustomerParams, CustomerResult, AddEntityParams, RemoveEntityParams, ListEntitiesParams, PlansParams, PlansResult, PublicPlan } from '@owostack/types';
|
|
2
|
+
export { AddEntityParams, AddEntityResult, AddonParams, AddonResult, AttachParams, AttachResult, BillingFeatureUsage, BillingTierBreakdown, BillingUsageParams, BillingUsageResult, BooleanFeatureConfig, CardInfo, CatalogEntry, CheckCode, CheckParams, CheckResult, CurrentPricingTier, CustomerData, CustomerParams, CustomerResult, Invoice, InvoiceLineItem, InvoiceParams, InvoiceResult, InvoicesParams, InvoicesResult, ListEntitiesParams, ListEntitiesResult, MeteredFeatureConfig, OverageDetails, OwostackConfig, PayInvoiceParams, PayInvoiceResult, PaymentMethodInfo, PlanCredits, PlanDefinition, PlanFeatureEntry, PlansParams, PlansResult, PricingDetails, PricingTier, PublicPlan, PublicPlanFeature, RatingModel, RemoveEntityParams, RemoveEntityResult, ResponseDetails, SyncChanges, SyncPayload, SyncResult, TrackCode, TrackParams, TrackResult, WalletRemoveResult, WalletResult, WalletSetupResult } from '@owostack/types';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* BooleanHandle — returned by boolean().
|
|
@@ -37,6 +37,15 @@ interface MeteredHandle {
|
|
|
37
37
|
limit(value: number, config?: Omit<MeteredFeatureConfig, "limit">): PlanFeatureEntry;
|
|
38
38
|
included(value: number, config?: Omit<MeteredFeatureConfig, "limit">): PlanFeatureEntry;
|
|
39
39
|
unlimited(config?: Omit<MeteredFeatureConfig, "limit">): PlanFeatureEntry;
|
|
40
|
+
perUnit(unitPrice: number, config?: {
|
|
41
|
+
reset?: ResetInterval;
|
|
42
|
+
}): PlanFeatureEntry;
|
|
43
|
+
graduated(tiers: PricingTier[], config?: {
|
|
44
|
+
reset?: ResetInterval;
|
|
45
|
+
}): PlanFeatureEntry;
|
|
46
|
+
volume(tiers: PricingTier[], config?: {
|
|
47
|
+
reset?: ResetInterval;
|
|
48
|
+
}): PlanFeatureEntry;
|
|
40
49
|
config(opts: MeteredFeatureConfig): PlanFeatureEntry;
|
|
41
50
|
(creditCost: number): {
|
|
42
51
|
feature: string;
|
package/dist/index.js
CHANGED
|
@@ -45,6 +45,74 @@ var BooleanHandle = class {
|
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
47
|
};
|
|
48
|
+
function validateUnitPrice(unitPrice, methodName) {
|
|
49
|
+
if (!Number.isFinite(unitPrice) || unitPrice < 0) {
|
|
50
|
+
throw new Error(`${methodName}() requires a non-negative unit price.`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function validatePricingTiers(tiers, methodName) {
|
|
54
|
+
if (!Array.isArray(tiers) || tiers.length === 0) {
|
|
55
|
+
throw new Error(`${methodName}() requires at least one pricing tier.`);
|
|
56
|
+
}
|
|
57
|
+
let previousUpTo = 0;
|
|
58
|
+
for (let index = 0; index < tiers.length; index += 1) {
|
|
59
|
+
const tier = tiers[index];
|
|
60
|
+
const hasUnitPrice = tier.unitPrice !== void 0;
|
|
61
|
+
const hasFlatFee = tier.flatFee !== void 0;
|
|
62
|
+
if (!hasUnitPrice && !hasFlatFee) {
|
|
63
|
+
throw new Error(
|
|
64
|
+
`${methodName}() tier ${index + 1} must define unitPrice, flatFee, or both.`
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
if (hasUnitPrice && (!Number.isFinite(tier.unitPrice) || tier.unitPrice < 0)) {
|
|
68
|
+
throw new Error(
|
|
69
|
+
`${methodName}() tier ${index + 1} must have a non-negative unitPrice.`
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
if (tier.flatFee !== void 0 && (!Number.isFinite(tier.flatFee) || tier.flatFee < 0)) {
|
|
73
|
+
throw new Error(
|
|
74
|
+
`${methodName}() tier ${index + 1} must have a non-negative flatFee.`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
if (tier.upTo === null) {
|
|
78
|
+
if (index !== tiers.length - 1) {
|
|
79
|
+
throw new Error(
|
|
80
|
+
`${methodName}() only allows the last tier to use upTo: null.`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (!Number.isFinite(tier.upTo) || tier.upTo <= previousUpTo) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
`${methodName}() tiers must be in ascending order by upTo.`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
previousUpTo = tier.upTo;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function validateMeteredFeaturePricing(config, featureSlug) {
|
|
94
|
+
if (!config) return;
|
|
95
|
+
if (config.pricePerUnit !== void 0) {
|
|
96
|
+
validateUnitPrice(config.pricePerUnit, `${featureSlug} pricePerUnit`);
|
|
97
|
+
}
|
|
98
|
+
const ratingModel = config.ratingModel || "package";
|
|
99
|
+
if (ratingModel === "graduated" || ratingModel === "volume") {
|
|
100
|
+
validatePricingTiers(config.tiers || [], ratingModel);
|
|
101
|
+
} else if (config.tiers && config.tiers.length > 0) {
|
|
102
|
+
throw new Error(
|
|
103
|
+
`Feature '${featureSlug}' cannot define tiers with ratingModel "package".`
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
if (config.usageModel === "usage_based" && ratingModel === "package" && config.pricePerUnit === void 0) {
|
|
107
|
+
throw new Error(
|
|
108
|
+
`Feature '${featureSlug}' must define pricePerUnit for usage-based package pricing.`
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
function normalizeUsageBasedOverage(usageModel, overage) {
|
|
113
|
+
if (usageModel === "usage_based") return "charge";
|
|
114
|
+
return overage;
|
|
115
|
+
}
|
|
48
116
|
function metered(slug, opts) {
|
|
49
117
|
const callable = (creditCost) => ({ feature: slug, creditCost });
|
|
50
118
|
const handleProps = {
|
|
@@ -92,6 +160,61 @@ function metered(slug, opts) {
|
|
|
92
160
|
config: { limit: null, reset: "monthly", overage: "block", ...config }
|
|
93
161
|
};
|
|
94
162
|
},
|
|
163
|
+
perUnit(unitPrice, config) {
|
|
164
|
+
validateUnitPrice(unitPrice, "perUnit");
|
|
165
|
+
return {
|
|
166
|
+
_type: "plan_feature",
|
|
167
|
+
slug,
|
|
168
|
+
featureType: "metered",
|
|
169
|
+
name: opts?.name,
|
|
170
|
+
enabled: true,
|
|
171
|
+
config: {
|
|
172
|
+
limit: null,
|
|
173
|
+
reset: config?.reset || "monthly",
|
|
174
|
+
usageModel: "usage_based",
|
|
175
|
+
ratingModel: "package",
|
|
176
|
+
pricePerUnit: unitPrice,
|
|
177
|
+
billingUnits: 1,
|
|
178
|
+
overage: "charge"
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
},
|
|
182
|
+
graduated(tiers, config) {
|
|
183
|
+
validatePricingTiers(tiers, "graduated");
|
|
184
|
+
return {
|
|
185
|
+
_type: "plan_feature",
|
|
186
|
+
slug,
|
|
187
|
+
featureType: "metered",
|
|
188
|
+
name: opts?.name,
|
|
189
|
+
enabled: true,
|
|
190
|
+
config: {
|
|
191
|
+
limit: null,
|
|
192
|
+
reset: config?.reset || "monthly",
|
|
193
|
+
usageModel: "usage_based",
|
|
194
|
+
ratingModel: "graduated",
|
|
195
|
+
tiers: tiers.map((tier) => ({ ...tier })),
|
|
196
|
+
overage: "charge"
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
},
|
|
200
|
+
volume(tiers, config) {
|
|
201
|
+
validatePricingTiers(tiers, "volume");
|
|
202
|
+
return {
|
|
203
|
+
_type: "plan_feature",
|
|
204
|
+
slug,
|
|
205
|
+
featureType: "metered",
|
|
206
|
+
name: opts?.name,
|
|
207
|
+
enabled: true,
|
|
208
|
+
config: {
|
|
209
|
+
limit: null,
|
|
210
|
+
reset: config?.reset || "monthly",
|
|
211
|
+
usageModel: "usage_based",
|
|
212
|
+
ratingModel: "volume",
|
|
213
|
+
tiers: tiers.map((tier) => ({ ...tier })),
|
|
214
|
+
overage: "charge"
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
},
|
|
95
218
|
config(configOpts) {
|
|
96
219
|
const isEnabled = configOpts.enabled !== false;
|
|
97
220
|
return {
|
|
@@ -321,29 +444,48 @@ function buildSyncPayload(catalog, defaultProvider) {
|
|
|
321
444
|
metadata: p.metadata ?? void 0,
|
|
322
445
|
autoEnable: p.autoEnable ?? void 0,
|
|
323
446
|
isAddon: p.isAddon ?? void 0,
|
|
324
|
-
features: p.features.map((f) =>
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
// Boolean features have no limit concept (null), metered features use limit from config
|
|
328
|
-
limit: f.featureType === "boolean" ? null : f.config?.limit ?? null,
|
|
329
|
-
// Boolean features have no reset interval
|
|
330
|
-
...f.featureType !== "boolean" && {
|
|
331
|
-
reset: f.config?.reset || "monthly"
|
|
332
|
-
},
|
|
333
|
-
...f.config?.overage && { overage: f.config.overage },
|
|
334
|
-
...f.config?.overagePrice !== void 0 && {
|
|
335
|
-
overagePrice: f.config.overagePrice
|
|
336
|
-
},
|
|
337
|
-
...f.config?.maxOverageUnits !== void 0 && {
|
|
338
|
-
maxOverageUnits: f.config.maxOverageUnits
|
|
339
|
-
},
|
|
340
|
-
...f.config?.billingUnits !== void 0 && {
|
|
341
|
-
billingUnits: f.config.billingUnits
|
|
342
|
-
},
|
|
343
|
-
...f.config?.creditCost !== void 0 && {
|
|
344
|
-
creditCost: f.config.creditCost
|
|
447
|
+
features: p.features.map((f) => {
|
|
448
|
+
if (f.featureType !== "boolean") {
|
|
449
|
+
validateMeteredFeaturePricing(f.config, f.slug);
|
|
345
450
|
}
|
|
346
|
-
|
|
451
|
+
return {
|
|
452
|
+
slug: f.slug,
|
|
453
|
+
enabled: f.enabled,
|
|
454
|
+
// Boolean features have no limit concept (null), metered features use limit from config
|
|
455
|
+
limit: f.featureType === "boolean" ? null : f.config?.limit ?? null,
|
|
456
|
+
// Boolean features have no reset interval
|
|
457
|
+
...f.featureType !== "boolean" && {
|
|
458
|
+
reset: f.config?.reset || "monthly"
|
|
459
|
+
},
|
|
460
|
+
...f.config?.usageModel && { usageModel: f.config.usageModel },
|
|
461
|
+
...f.config?.pricePerUnit !== void 0 && {
|
|
462
|
+
pricePerUnit: f.config.pricePerUnit
|
|
463
|
+
},
|
|
464
|
+
...f.config?.ratingModel && { ratingModel: f.config.ratingModel },
|
|
465
|
+
...f.config?.tiers && { tiers: f.config.tiers },
|
|
466
|
+
...normalizeUsageBasedOverage(
|
|
467
|
+
f.config?.usageModel,
|
|
468
|
+
f.config?.overage
|
|
469
|
+
) && {
|
|
470
|
+
overage: normalizeUsageBasedOverage(
|
|
471
|
+
f.config?.usageModel,
|
|
472
|
+
f.config?.overage
|
|
473
|
+
)
|
|
474
|
+
},
|
|
475
|
+
...f.config?.overagePrice !== void 0 && {
|
|
476
|
+
overagePrice: f.config.overagePrice
|
|
477
|
+
},
|
|
478
|
+
...f.config?.maxOverageUnits !== void 0 && {
|
|
479
|
+
maxOverageUnits: f.config.maxOverageUnits
|
|
480
|
+
},
|
|
481
|
+
...f.config?.billingUnits !== void 0 && {
|
|
482
|
+
billingUnits: f.config.billingUnits
|
|
483
|
+
},
|
|
484
|
+
...f.config?.creditCost !== void 0 && {
|
|
485
|
+
creditCost: f.config.creditCost
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
})
|
|
347
489
|
}));
|
|
348
490
|
const creditPacks = catalog.filter((e) => e._type === "credit_pack").map((cp) => ({
|
|
349
491
|
slug: cp.slug,
|
|
@@ -674,12 +816,8 @@ var Owostack = class {
|
|
|
674
816
|
body: JSON.stringify(body)
|
|
675
817
|
});
|
|
676
818
|
if (!response.ok) {
|
|
677
|
-
const
|
|
678
|
-
|
|
679
|
-
throw new OwostackError(
|
|
680
|
-
errorData.code || "unknown_error",
|
|
681
|
-
errorData.message || errorData.error || "Request failed"
|
|
682
|
-
);
|
|
819
|
+
const errorData = extractErrorDetails(await readErrorResponse(response));
|
|
820
|
+
throw new OwostackError(errorData.code, errorData.message);
|
|
683
821
|
}
|
|
684
822
|
return response.json();
|
|
685
823
|
}
|
|
@@ -702,16 +840,64 @@ var Owostack = class {
|
|
|
702
840
|
}
|
|
703
841
|
});
|
|
704
842
|
if (!response.ok) {
|
|
705
|
-
const
|
|
706
|
-
|
|
707
|
-
throw new OwostackError(
|
|
708
|
-
errorData.code || "unknown_error",
|
|
709
|
-
errorData.message || errorData.error || "Request failed"
|
|
710
|
-
);
|
|
843
|
+
const errorData = extractErrorDetails(await readErrorResponse(response));
|
|
844
|
+
throw new OwostackError(errorData.code, errorData.message);
|
|
711
845
|
}
|
|
712
846
|
return response.json();
|
|
713
847
|
}
|
|
714
848
|
};
|
|
849
|
+
function isRecord(value) {
|
|
850
|
+
return typeof value === "object" && value !== null;
|
|
851
|
+
}
|
|
852
|
+
function asNonEmptyString(value) {
|
|
853
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
854
|
+
}
|
|
855
|
+
async function readErrorResponse(response) {
|
|
856
|
+
const raw = await response.text().catch(() => "");
|
|
857
|
+
if (!raw) return null;
|
|
858
|
+
try {
|
|
859
|
+
return JSON.parse(raw);
|
|
860
|
+
} catch {
|
|
861
|
+
return raw;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
function extractErrorDetails(payload) {
|
|
865
|
+
if (typeof payload === "string") {
|
|
866
|
+
return {
|
|
867
|
+
code: "unknown_error",
|
|
868
|
+
message: payload
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
if (!isRecord(payload)) {
|
|
872
|
+
return {
|
|
873
|
+
code: "unknown_error",
|
|
874
|
+
message: "Request failed"
|
|
875
|
+
};
|
|
876
|
+
}
|
|
877
|
+
const directCode = asNonEmptyString(payload.code);
|
|
878
|
+
const directMessage = asNonEmptyString(payload.message);
|
|
879
|
+
const directError = payload.error;
|
|
880
|
+
if (typeof directError === "string") {
|
|
881
|
+
return {
|
|
882
|
+
code: directCode || "unknown_error",
|
|
883
|
+
message: directError
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
if (isRecord(directError)) {
|
|
887
|
+
const nestedCode = asNonEmptyString(directError.code);
|
|
888
|
+
const nestedMessage = asNonEmptyString(directError.message) || asNonEmptyString(directError.error);
|
|
889
|
+
if (nestedMessage) {
|
|
890
|
+
return {
|
|
891
|
+
code: nestedCode || directCode || "unknown_error",
|
|
892
|
+
message: nestedMessage
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
return {
|
|
897
|
+
code: directCode || "unknown_error",
|
|
898
|
+
message: directMessage || "Request failed"
|
|
899
|
+
};
|
|
900
|
+
}
|
|
715
901
|
function buildPlansFn(client) {
|
|
716
902
|
const fn = ((params) => {
|
|
717
903
|
const query = {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "owostack",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Core SDK for Owostack billing infrastructure",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"payments"
|
|
31
31
|
],
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@owostack/types": "0.
|
|
33
|
+
"@owostack/types": "0.3.1"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"tsup": "^8.3.6",
|