owostack 0.1.4 → 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, 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;
@@ -133,7 +142,23 @@ declare function plan(slug: string, config: {
133
142
  trialDays?: number;
134
143
  provider?: string;
135
144
  metadata?: Record<string, unknown>;
145
+ autoEnable?: boolean;
146
+ isAddon?: boolean;
136
147
  }): PlanDefinition;
148
+ /**
149
+ * Create a credit pack definition.
150
+ * Credit packs are one-time purchases that add credits to a customer's balance.
151
+ */
152
+ declare function creditPack(slug: string, config: {
153
+ name: string;
154
+ description?: string;
155
+ credits: number;
156
+ price: number;
157
+ currency: Currency;
158
+ creditSystem: string;
159
+ provider?: string;
160
+ metadata?: Record<string, unknown>;
161
+ }): CreditPackDefinition;
137
162
  /**
138
163
  * Extract SyncPayload from catalog.
139
164
  */
@@ -446,4 +471,4 @@ declare class OwostackError extends Error {
446
471
  constructor(code: string, message: string);
447
472
  }
448
473
 
449
- export { BooleanHandle, CreditSystemHandle, EntityHandle, type MeteredHandle, Owostack, OwostackError, boolean, buildSyncPayload, creditSystem, entity, metered, plan };
474
+ export { BooleanHandle, CreditSystemHandle, EntityHandle, type MeteredHandle, Owostack, OwostackError, boolean, buildSyncPayload, creditPack, creditSystem, entity, metered, plan };
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 {
@@ -258,6 +381,13 @@ function plan(slug, config) {
258
381
  ...config
259
382
  };
260
383
  }
384
+ function creditPack(slug, config) {
385
+ return {
386
+ _type: "credit_pack",
387
+ slug,
388
+ ...config
389
+ };
390
+ }
261
391
  function slugToName(slug) {
262
392
  return slug.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
263
393
  }
@@ -273,7 +403,9 @@ function buildSyncPayload(catalog, defaultProvider) {
273
403
  slug: f.slug,
274
404
  type: f.featureType,
275
405
  name: f.name || slugToName(f.slug),
276
- ...handle instanceof EntityHandle && { meterType: "non_consumable" }
406
+ ...handle instanceof EntityHandle && {
407
+ meterType: "non_consumable"
408
+ }
277
409
  });
278
410
  }
279
411
  }
@@ -310,34 +442,67 @@ function buildSyncPayload(catalog, defaultProvider) {
310
442
  trialDays: p.trialDays ?? void 0,
311
443
  provider: p.provider ?? void 0,
312
444
  metadata: p.metadata ?? void 0,
313
- features: p.features.map((f) => ({
314
- slug: f.slug,
315
- enabled: f.enabled,
316
- // Boolean features have no limit concept (null), metered features use limit from config
317
- limit: f.featureType === "boolean" ? null : f.config?.limit ?? null,
318
- // Boolean features have no reset interval
319
- ...f.featureType !== "boolean" && {
320
- reset: f.config?.reset || "monthly"
321
- },
322
- ...f.config?.overage && { overage: f.config.overage },
323
- ...f.config?.overagePrice !== void 0 && {
324
- overagePrice: f.config.overagePrice
325
- },
326
- ...f.config?.maxOverageUnits !== void 0 && {
327
- maxOverageUnits: f.config.maxOverageUnits
328
- },
329
- ...f.config?.billingUnits !== void 0 && {
330
- billingUnits: f.config.billingUnits
331
- },
332
- ...f.config?.creditCost !== void 0 && {
333
- creditCost: f.config.creditCost
445
+ autoEnable: p.autoEnable ?? void 0,
446
+ isAddon: p.isAddon ?? void 0,
447
+ features: p.features.map((f) => {
448
+ if (f.featureType !== "boolean") {
449
+ validateMeteredFeaturePricing(f.config, f.slug);
334
450
  }
335
- }))
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
+ })
489
+ }));
490
+ const creditPacks = catalog.filter((e) => e._type === "credit_pack").map((cp) => ({
491
+ slug: cp.slug,
492
+ name: cp.name,
493
+ description: cp.description ?? void 0,
494
+ credits: cp.credits,
495
+ price: cp.price,
496
+ currency: cp.currency,
497
+ creditSystem: cp.creditSystem,
498
+ provider: cp.provider ?? void 0,
499
+ metadata: cp.metadata ?? void 0
336
500
  }));
337
501
  return {
338
502
  defaultProvider,
339
503
  features: Array.from(featureMap.values()),
340
504
  creditSystems,
505
+ creditPacks,
341
506
  plans
342
507
  };
343
508
  }
@@ -428,6 +593,7 @@ var Owostack = class {
428
593
  success: true,
429
594
  features: { created: [], updated: [], unchanged: [] },
430
595
  creditSystems: { created: [], updated: [], unchanged: [] },
596
+ creditPacks: { created: [], updated: [], unchanged: [] },
431
597
  plans: { created: [], updated: [], unchanged: [] },
432
598
  warnings: ["No catalog entries to sync."]
433
599
  };
@@ -809,6 +975,7 @@ export {
809
975
  OwostackError,
810
976
  boolean,
811
977
  buildSyncPayload,
978
+ creditPack,
812
979
  creditSystem,
813
980
  entity,
814
981
  metered,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "owostack",
3
- "version": "0.1.4",
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.1.4"
33
+ "@owostack/types": "0.3.0"
34
34
  },
35
35
  "devDependencies": {
36
36
  "tsup": "^8.3.6",