owostack 0.2.0 → 0.3.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/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
- slug: f.slug,
326
- enabled: f.enabled,
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "owostack",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
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.2.0"
33
+ "@owostack/types": "0.3.0"
34
34
  },
35
35
  "devDependencies": {
36
36
  "tsup": "^8.3.6",