@revstackhq/core 0.0.0-dev-20260226062415 → 0.0.0-dev-20260227093823

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,437 @@
1
- export * from "./types";
2
- export * from "./define";
3
- export * from "./engine";
4
- export * from "./validator";
5
- //# sourceMappingURL=index.d.ts.map
1
+ /**
2
+ * @file types.ts
3
+ * @description Core type definitions for Revstack's Billing Engine.
4
+ * These types map directly to the PostgreSQL database schema and
5
+ * serve as the contract for "Billing as Code".
6
+ */
7
+ /**
8
+ * The type of a feature/entitlement.
9
+ * - 'boolean': On/Off flag (e.g., SSO Access).
10
+ * - 'static': Fixed numeric limit included in the plan (e.g., 5 Seats).
11
+ * - 'metered': Usage-based, tracked over time (e.g., AI Tokens).
12
+ */
13
+ type FeatureType = "boolean" | "static" | "metered";
14
+ /**
15
+ * The unit of measurement for a feature.
16
+ * Used for display, analytics, and billing calculations.
17
+ */
18
+ type UnitType = "count" | "bytes" | "seconds" | "tokens" | "requests" | "custom";
19
+ /**
20
+ * How often a feature's usage counter resets.
21
+ */
22
+ type ResetPeriod = "monthly" | "yearly" | "never";
23
+ /**
24
+ * Billing interval for a plan's price.
25
+ */
26
+ type BillingInterval = "monthly" | "quarterly" | "yearly" | "one_time";
27
+ /**
28
+ * The commercial classification of a plan.
29
+ * - 'free': No payment required (e.g., Default Guest Plan, Starter).
30
+ * - 'paid': Requires active payment method.
31
+ * - 'custom': Enterprise / negotiated pricing.
32
+ */
33
+ type PlanType = "paid" | "free" | "custom";
34
+ /**
35
+ * The lifecycle status of a plan.
36
+ * - 'draft': Not yet visible or purchasable.
37
+ * - 'active': Live and available for subscription.
38
+ * - 'archived': No longer available for new subscriptions, existing ones honored.
39
+ */
40
+ type PlanStatus = "draft" | "active" | "archived";
41
+ /**
42
+ * The lifecycle state of a customer's subscription.
43
+ * Used by the EntitlementEngine to gate access based on payment status.
44
+ */
45
+ type SubscriptionStatus = "active" | "trialing" | "past_due" | "canceled" | "paused";
46
+ /**
47
+ * Definition of a Feature available in the system.
48
+ * Maps to the `entitlements` table in the database.
49
+ *
50
+ * The `slug` field is the primary identifier and matches the dictionary
51
+ * key in `RevstackConfig.features`.
52
+ */
53
+ interface FeatureDef {
54
+ /** Unique slug/identifier (matches dictionary key in config). */
55
+ slug: string;
56
+ /** Human-readable display name. */
57
+ name: string;
58
+ /** Optional description for documentation and dashboard. */
59
+ description?: string;
60
+ /** The data type of the feature. */
61
+ type: FeatureType;
62
+ /** The unit of measurement. */
63
+ unit_type: UnitType;
64
+ }
65
+ /**
66
+ * Input type for `defineFeature()`.
67
+ * The `slug` is omitted because it is inferred from the dictionary key.
68
+ */
69
+ type FeatureDefInput = Omit<FeatureDef, "slug">;
70
+ /**
71
+ * Configures how a feature behaves inside a specific Plan.
72
+ * Maps to the `plan_entitlements` table in the database.
73
+ *
74
+ * Each field is optional — only set the fields relevant to the feature type:
75
+ * - Boolean features: use `value_bool`.
76
+ * - Static features: use `value_limit` + `is_hard_limit`.
77
+ * - Metered features: use `value_limit` + `reset_period`.
78
+ */
79
+ interface PlanFeatureValue {
80
+ /** Numeric limit (e.g., 5 seats, 10000 API calls). */
81
+ value_limit?: number;
82
+ /** Boolean toggle (e.g., SSO enabled/disabled). */
83
+ value_bool?: boolean;
84
+ /** Text value for display or metadata. */
85
+ value_text?: string;
86
+ /** If true, usage is blocked when limit is reached. */
87
+ is_hard_limit?: boolean;
88
+ /** How often usage resets. */
89
+ reset_period?: ResetPeriod;
90
+ }
91
+ /**
92
+ * Configures how a feature behaves inside a specific Addon.
93
+ * An addon can either incrementally increase a plan's limit, set an absolute new limit,
94
+ * or grant boolean access.
95
+ */
96
+ interface AddonFeatureValue {
97
+ /** Numeric limit to add or set. */
98
+ value_limit?: number;
99
+ /** How the limit is applied. 'increment' adds to the base plan, 'set' overrides it. */
100
+ type?: "increment" | "set";
101
+ /** Boolean toggle (e.g. granting access). */
102
+ has_access?: boolean;
103
+ /** If false, allows overage (relaxes the limit to a soft limit). */
104
+ is_hard_limit?: boolean;
105
+ }
106
+ /**
107
+ * Defines the pricing for a plan.
108
+ * Maps to the `prices` table in the database.
109
+ */
110
+ interface PriceDef {
111
+ /** Price amount in the smallest currency unit (e.g., cents). */
112
+ amount: number;
113
+ /** ISO 4217 currency code (e.g., "USD", "EUR"). */
114
+ currency: string;
115
+ /** How often the customer is billed. */
116
+ billing_interval: BillingInterval;
117
+ /** Number of days for a free trial before billing starts. */
118
+ trial_period_days?: number;
119
+ /** Whether this price is currently active. */
120
+ is_active?: boolean;
121
+ }
122
+ /**
123
+ * Full plan definition. Maps to the `plans` table in the database.
124
+ *
125
+ * The `slug` is the primary identifier and matches the dictionary
126
+ * key in `RevstackConfig.plans`.
127
+ */
128
+ interface PlanDef {
129
+ /** Unique slug/identifier (matches dictionary key in config). */
130
+ slug: string;
131
+ /** Human-readable display name. */
132
+ name: string;
133
+ /** Optional description. */
134
+ description?: string;
135
+ /** Whether this is the default guest plan. */
136
+ is_default: boolean;
137
+ /** Whether this plan is visible on the pricing page. */
138
+ is_public: boolean;
139
+ /** Commercial classification. */
140
+ type: PlanType;
141
+ /** Lifecycle status. */
142
+ status: PlanStatus;
143
+ /** Optional pricing tiers (1:N). Free/default plans have no prices. */
144
+ prices?: PriceDef[];
145
+ /** Feature entitlements included in this plan. */
146
+ features: Record<string, PlanFeatureValue>;
147
+ /** Slugs of addons that can be attached to this plan. */
148
+ available_addons?: string[];
149
+ }
150
+ /**
151
+ * Input type for `definePlan()`.
152
+ * - `slug` is omitted (inferred from dictionary key).
153
+ * - `status` is optional (defaults to `'active'`).
154
+ */
155
+ type PlanDefInput = Omit<PlanDef, "slug" | "status" | "features"> & {
156
+ status?: PlanStatus;
157
+ features: Record<string, PlanFeatureValue>;
158
+ available_addons?: string[];
159
+ };
160
+ /**
161
+ * An add-on is a product purchased on top of a subscription.
162
+ * Maps to the `addons` table in the database.
163
+ */
164
+ interface AddonDef {
165
+ /** Unique slug/identifier. */
166
+ slug: string;
167
+ /** Human-readable display name. */
168
+ name: string;
169
+ /** Optional description. */
170
+ description?: string;
171
+ /** Billing type. */
172
+ type: "recurring" | "one_time";
173
+ /** Add-on pricing configurations (1:N). */
174
+ prices?: PriceDef[];
175
+ /** Feature entitlements this add-on modifies or grants. */
176
+ features: Record<string, AddonFeatureValue>;
177
+ }
178
+ /**
179
+ * Input type for `defineAddon()`.
180
+ * The `slug` is omitted (inferred from dictionary key).
181
+ */
182
+ type AddonDefInput = Omit<AddonDef, "slug">;
183
+ type DiscountType = "percent" | "amount";
184
+ type DiscountDuration = "once" | "forever" | "repeating";
185
+ interface DiscountDef {
186
+ /** The code the user enters at checkout (e.g., 'BLACKFRIDAY_24'). */
187
+ code: string;
188
+ /** Friendly name for invoices. */
189
+ name?: string;
190
+ /** 'percent' (0–100) or 'amount' (smallest currency unit). */
191
+ type: DiscountType;
192
+ /** The discount value. */
193
+ value: number;
194
+ /** How long the discount lasts. */
195
+ duration: DiscountDuration;
196
+ /** If duration is 'repeating', how many months. */
197
+ duration_in_months?: number;
198
+ /** Restrict to specific plan slugs. Empty = all. */
199
+ applies_to_plans?: string[];
200
+ /** Maximum number of redemptions globally. */
201
+ max_redemptions?: number;
202
+ /** Expiration date (ISO 8601). */
203
+ expires_at?: string;
204
+ }
205
+ /**
206
+ * The output of the Entitlement Engine.
207
+ * Answers: "Can the user do this?"
208
+ */
209
+ interface CheckResult {
210
+ /** Is the action allowed? */
211
+ allowed: boolean;
212
+ /** Why was it allowed or denied? */
213
+ reason?: "feature_missing" | "limit_reached" | "past_due" | "included" | "overage_allowed";
214
+ /** How many units remain before hitting the limit. Infinity if unlimited. */
215
+ remaining?: number;
216
+ /** Estimated overage cost in the smallest currency unit. */
217
+ cost_estimate?: number;
218
+ /** Which sources granted access (plan and/or addon slugs). */
219
+ granted_by?: string[];
220
+ }
221
+ /**
222
+ * The structure of the `revstack.config.ts` file.
223
+ *
224
+ * Features and plans are dictionaries keyed by slug.
225
+ * The define helpers (`defineFeature`, `definePlan`) return input types
226
+ * without `slug` — it is inferred from the dictionary key.
227
+ */
228
+ interface RevstackConfig {
229
+ /** Dictionary of all available features, keyed by slug. */
230
+ features: Record<string, FeatureDefInput>;
231
+ /** Dictionary of all plans, keyed by slug. */
232
+ plans: Record<string, PlanDefInput>;
233
+ /** Dictionary of available add-ons, keyed by slug. */
234
+ addons?: Record<string, AddonDefInput>;
235
+ /** Array of available coupons/discounts. */
236
+ coupons?: DiscountDef[];
237
+ }
238
+
239
+ /**
240
+ * @file define.ts
241
+ * @description Identity helpers for "Billing as Code" config authoring.
242
+ *
243
+ * These functions return their input unchanged — their sole purpose is to
244
+ * provide autocompletion, type narrowing, and compile-time validation
245
+ * when developers write their `revstack.config.ts`.
246
+ *
247
+ * @example
248
+ * ```typescript
249
+ * import { defineConfig, defineFeature, definePlan } from "@revstackhq/core";
250
+ *
251
+ * const features = {
252
+ * seats: defineFeature({ name: "Seats", type: "static", unit_type: "count" }),
253
+ * sso: defineFeature({ name: "SSO", type: "boolean", unit_type: "count" }),
254
+ * };
255
+ *
256
+ * export default defineConfig({
257
+ * features,
258
+ * plans: {
259
+ * default: definePlan<typeof features>({
260
+ * name: "Default",
261
+ * is_default: true,
262
+ * is_public: false,
263
+ * type: "free",
264
+ * features: {},
265
+ * }),
266
+ * pro: definePlan<typeof features>({
267
+ * name: "Pro",
268
+ * is_default: false,
269
+ * is_public: true,
270
+ * type: "paid",
271
+ * prices: [{ amount: 2900, currency: "USD", billing_interval: "monthly" }],
272
+ * features: {
273
+ * seats: { value_limit: 5, is_hard_limit: true },
274
+ * sso: { value_bool: true },
275
+ * },
276
+ * }),
277
+ * },
278
+ * });
279
+ * ```
280
+ */
281
+
282
+ /**
283
+ * Define a feature with full type inference.
284
+ * Identity function — returns the input as-is.
285
+ */
286
+ declare function defineFeature<T extends FeatureDefInput>(config: T): T;
287
+ /**
288
+ * Define a Plan with optional compile-time feature key restriction.
289
+ *
290
+ * When called with a generic `F` (your feature dictionary type),
291
+ * the `features` object only accepts keys that exist in `F`.
292
+ *
293
+ * @typeParam F - Feature dictionary type. Pass `typeof yourFeatures` for strict keys.
294
+ *
295
+ * @example
296
+ * ```typescript
297
+ * // Strict mode — typos cause compile errors:
298
+ * definePlan<typeof features>({ ..., features: { seats: { value_limit: 5 } } });
299
+ *
300
+ * // Loose mode — any string key accepted (backwards compatible):
301
+ * definePlan({ ..., features: { anything: { value_bool: true } } });
302
+ * ```
303
+ */
304
+ declare function definePlan<F extends Record<string, FeatureDefInput> = Record<string, FeatureDefInput>>(config: Omit<PlanDefInput, "features"> & {
305
+ features: F extends Record<string, FeatureDefInput> ? Partial<Record<keyof F, PlanFeatureValue>> : Record<string, PlanFeatureValue>;
306
+ }): PlanDefInput;
307
+ /**
308
+ * Define an Add-on with optional compile-time feature key restriction.
309
+ * Same generic pattern as `definePlan`.
310
+ *
311
+ * @typeParam F - Feature dictionary type for key restriction.
312
+ */
313
+ declare function defineAddon<F extends Record<string, FeatureDefInput> = Record<string, FeatureDefInput>>(config: Omit<AddonDefInput, "features"> & {
314
+ features: F extends Record<string, FeatureDefInput> ? Partial<Record<keyof F, AddonFeatureValue>> : Record<string, AddonFeatureValue>;
315
+ }): AddonDefInput;
316
+ /**
317
+ * Define a discount/coupon with full type inference.
318
+ * Identity function — returns the input as-is.
319
+ */
320
+ declare function defineDiscount<T extends DiscountDef>(config: T): T;
321
+ /**
322
+ * Define the root billing configuration.
323
+ * Wraps the entire `revstack.config.ts` export for type inference.
324
+ */
325
+ declare function defineConfig<T extends RevstackConfig>(config: T): T;
326
+
327
+ /**
328
+ * @file engine.ts
329
+ * @description The Entitlement Engine — the logic core of Revstack.
330
+ *
331
+ * Determines if a user can access a feature based on their active Plan,
332
+ * purchased Add-ons, and subscription payment status. All decisions are
333
+ * pure, stateless computations with no side effects.
334
+ *
335
+ * @example
336
+ * ```typescript
337
+ * import { EntitlementEngine } from "@revstackhq/core";
338
+ *
339
+ * const engine = new EntitlementEngine(plan, addons, "active");
340
+ *
341
+ * // Single check
342
+ * const result = engine.check("seats", 4);
343
+ *
344
+ * // Batch check
345
+ * const results = engine.checkBatch({ seats: 4, ai_tokens: 12000 });
346
+ * ```
347
+ */
348
+
349
+ /**
350
+ * The Entitlement Engine — evaluates feature access for a single customer.
351
+ *
352
+ * Instantiate with the customer's active plan, purchased add-ons, and
353
+ * current subscription status. Then call `check()` or `checkBatch()`
354
+ * to evaluate access.
355
+ *
356
+ * **Design decisions:**
357
+ * - Stateless: no mutation, no side effects. Safe to call from any context.
358
+ * - Add-on limits are *summed* with the base plan (e.g., plan gives 5 seats +
359
+ * addon gives 3 = 8 total).
360
+ * - If ANY source sets `is_hard_limit: false`, the entire feature becomes soft-limited.
361
+ */
362
+ declare class EntitlementEngine {
363
+ private plan;
364
+ private addons;
365
+ private subscriptionStatus;
366
+ /**
367
+ * @param plan - The customer's active base plan.
368
+ * @param addons - Active add-ons the customer has purchased.
369
+ * @param subscriptionStatus - Current payment/lifecycle state of the subscription.
370
+ */
371
+ constructor(plan: PlanDef, addons?: AddonDef[], subscriptionStatus?: SubscriptionStatus);
372
+ /**
373
+ * Verify if the customer has access to a specific feature.
374
+ *
375
+ * Aggregates limits from the base plan AND any active add-ons,
376
+ * then evaluates the customer's current usage against those limits.
377
+ *
378
+ * @param featureId - The feature slug to check (e.g., `"seats"`).
379
+ * @param currentUsage - Current consumption count (default: 0).
380
+ * @returns A `CheckResult` with the access decision and metadata.
381
+ */
382
+ check(featureId: string, currentUsage?: number): CheckResult;
383
+ /**
384
+ * Evaluate multiple features in a single pass.
385
+ *
386
+ * @param usages - Map of `featureSlug → currentUsage` to evaluate.
387
+ * @returns Map of `featureSlug → CheckResult`.
388
+ */
389
+ checkBatch(usages: Record<string, number>): Record<string, CheckResult>;
390
+ }
391
+
392
+ /**
393
+ * @file validator.ts
394
+ * @description Runtime validation for Revstack billing configurations.
395
+ *
396
+ * Validates the business logic invariants of a `RevstackConfig` object
397
+ * before it is synced to the backend. Catches misconfigurations early
398
+ * that TypeScript's type system cannot enforce (e.g., referencing
399
+ * undefined features, negative prices, duplicate slugs).
400
+ *
401
+ * @example
402
+ * ```typescript
403
+ * import { validateConfig, defineConfig } from "@revstackhq/core";
404
+ *
405
+ * const config = defineConfig({ features: {}, plans: {} });
406
+ * validateConfig(config); // throws RevstackValidationError if invalid
407
+ * ```
408
+ */
409
+
410
+ /**
411
+ * Thrown when `validateConfig()` detects one or more invalid business
412
+ * logic rules in a billing configuration.
413
+ *
414
+ * The `errors` array contains all violations found — the validator
415
+ * collects every issue before throwing, so developers can fix them
416
+ * all at once instead of playing whack-a-mole.
417
+ */
418
+ declare class RevstackValidationError extends Error {
419
+ /** All validation violations found in the config. */
420
+ readonly errors: string[];
421
+ constructor(errors: string[]);
422
+ }
423
+ /**
424
+ * Validates a complete Revstack billing configuration.
425
+ *
426
+ * Checks the following invariants:
427
+ * 1. **Default plan** — Exactly one plan has `is_default: true`.
428
+ * 2. **Feature references** — Plans/addons only reference features defined in `config.features`.
429
+ * 3. **Non-negative pricing** — All price amounts and limits are ≥ 0.
430
+ * 4. **Discount bounds** — Percentage discounts have values in [0, 100].
431
+ *
432
+ * @param config - The billing configuration to validate.
433
+ * @throws {RevstackValidationError} If any violations are found.
434
+ */
435
+ declare function validateConfig(config: RevstackConfig): void;
436
+
437
+ export { type AddonDef, type AddonDefInput, type AddonFeatureValue, type BillingInterval, type CheckResult, type DiscountDef, type DiscountDuration, type DiscountType, EntitlementEngine, type FeatureDef, type FeatureDefInput, type FeatureType, type PlanDef, type PlanDefInput, type PlanFeatureValue, type PlanStatus, type PlanType, type PriceDef, type ResetPeriod, type RevstackConfig, RevstackValidationError, type SubscriptionStatus, type UnitType, defineAddon, defineConfig, defineDiscount, defineFeature, definePlan, validateConfig };