Overview
The validateConfig() function validates a complete Revstack billing configuration to ensure all business logic rules are satisfied. It collects all validation errors before throwing, allowing developers to fix multiple issues at once.
Function Signature
function validateConfig ( config : RevstackConfig ) : void
Parameters
The billing configuration to validate.
Throws
Thrown when any validation rules are violated. Always "RevstackValidationError".
Summary of all validation failures.
Array of all individual validation error messages.
Validation Rules
The validator checks the following invariants:
1. Default Plan
Exactly one plan must have is_default: true.
// ❌ Error: No default plan
plans : {
pro : { is_default : false , /* ... */ },
business : { is_default : false , /* ... */ },
}
// ❌ Error: Multiple default plans
plans : {
free : { is_default : true , /* ... */ },
pro : { is_default : true , /* ... */ },
}
// ✅ Valid: Exactly one default
plans : {
free : { is_default : true , /* ... */ },
pro : { is_default : false , /* ... */ },
}
2. Feature References
Plans and add-ons can only reference features defined in config.features.
// ❌ Error: Plan references undefined feature
features : {
seats : { /* ... */ },
},
plans : {
pro : {
features : {
seats : { value_limit : 10 },
storage : { value_limit : 1000 }, // ❌ 'storage' not defined!
},
},
}
// ✅ Valid: All referenced features exist
features : {
seats : { /* ... */ },
storage : { /* ... */ },
},
plans : {
pro : {
features : {
seats : { value_limit : 10 },
storage : { value_limit : 1000 },
},
},
}
3. Overage Configuration
Overage can only be configured for metered features .
// ❌ Error: Overage for non-metered feature
features : {
seats : { type : "static" , /* ... */ },
},
plans : {
pro : {
prices : [{
overage_configuration: {
seats: { /* ... */ }, // ❌ 'seats' is not metered!
},
}],
},
}
// ✅ Valid: Overage for metered feature
features : {
api_calls : { type : "metered" , /* ... */ },
},
plans : {
pro : {
prices : [{
overage_configuration: {
api_calls: {
overage_amount: 10 ,
overage_unit: 1000 ,
},
},
}],
},
}
4. Add-on Billing Intervals
Recurring add-ons must match the plan’s billing interval.
// ❌ Error: Interval mismatch
plans : {
pro : {
prices : [{
billing_interval: "monthly" ,
available_addons: [ "extra_seats" ],
}],
},
},
addons : {
extra_seats : {
type : "recurring" ,
billing_interval : "yearly" , // ❌ Mismatch!
},
}
// ✅ Valid: Intervals match
plans : {
pro : {
prices : [{
billing_interval: "monthly" ,
available_addons: [ "extra_seats" ],
}],
},
},
addons : {
extra_seats : {
type : "recurring" ,
billing_interval : "monthly" , // ✅ Match!
},
}
5. Add-on References
Prices can only reference add-ons that exist in config.addons.
// ❌ Error: References undefined add-on
plans : {
pro : {
prices : [{
available_addons: [ "extra_seats" ], // ❌ Not defined!
}],
},
},
addons : {}
// ✅ Valid: Add-on exists
plans : {
pro : {
prices : [{
available_addons: [ "extra_seats" ],
}],
},
},
addons : {
extra_seats : { /* ... */ },
}
Usage
Basic Validation
import { validateConfig , defineConfig } from "@revstack/core" ;
import config from "./revstack.config" ;
try {
validateConfig ( config );
console . log ( "✅ Configuration is valid" );
} catch ( error ) {
if ( error instanceof RevstackValidationError ) {
console . error ( "❌ Validation failed:" );
error . errors . forEach (( err ) => console . error ( ` - ${ err } ` ));
}
}
In Build Scripts
// scripts/validate-config.ts
import { validateConfig } from "@revstack/core" ;
import config from "../revstack.config" ;
try {
validateConfig ( config );
console . log ( "✅ Billing configuration is valid" );
process . exit ( 0 );
} catch ( error ) {
if ( error instanceof RevstackValidationError ) {
console . error ( " \n ❌ Billing configuration validation failed: \n " );
error . errors . forEach (( err , i ) => {
console . error ( ` ${ i + 1 } . ${ err } ` );
});
console . error ( "" );
process . exit ( 1 );
}
throw error ;
}
// package.json
{
"scripts" : {
"validate:config" : "tsx scripts/validate-config.ts" ,
"build" : "npm run validate:config && next build"
}
}
In Tests
import { describe , it , expect } from "vitest" ;
import { validateConfig , RevstackValidationError } from "@revstack/core" ;
import config from "../revstack.config" ;
describe ( "Billing Configuration" , () => {
it ( "should be valid" , () => {
expect (() => validateConfig ( config )). not . toThrow ();
});
it ( "should reject missing default plan" , () => {
const invalidConfig = {
features: {},
plans: {
pro: { is_default: false , /* ... */ },
},
};
expect (() => validateConfig ( invalidConfig )). toThrow (
RevstackValidationError
);
});
it ( "should reject undefined feature references" , () => {
const invalidConfig = {
features: {
seats: { /* ... */ },
},
plans: {
free: {
is_default: true ,
features: {
storage: { /* ... */ }, // Undefined!
},
},
},
};
expect (() => validateConfig ( invalidConfig )). toThrow (
/references undefined feature "storage"/
);
});
});
Runtime Validation
import { validateConfig , RevstackValidationError } from "@revstack/core" ;
import { NextResponse } from "next/server" ;
export async function POST ( request : Request ) {
const config = await request . json ();
try {
validateConfig ( config );
} catch ( error ) {
if ( error instanceof RevstackValidationError ) {
return NextResponse . json (
{
error: "Invalid configuration" ,
details: error . errors ,
},
{ status: 400 }
);
}
throw error ;
}
// Save valid configuration
await saveConfig ( config );
return NextResponse . json ({ success: true });
}
CI/CD Pipeline
# .github/workflows/validate.yml
name : Validate Billing Config
on : [ push , pull_request ]
jobs :
validate :
runs-on : ubuntu-latest
steps :
- uses : actions/checkout@v4
- uses : actions/setup-node@v4
with :
node-version : 20
- run : npm install
- run : npm run validate:config
Error Messages
The validator provides clear, actionable error messages:
// Single error
RevstackValidationError : Revstack config validation failed :
No default plan found . Every project must have exactly one plan with is_default : true .
// Multiple errors
RevstackValidationError : Revstack config validation failed with 3 errors :
- No default plan found . Every project must have exactly one plan with is_default : true .
- Plan "pro" references undefined feature "storage" .
- Plan "business" overage_configuration references undefined feature "api_calls" .
RevstackValidationError
class RevstackValidationError extends Error {
name : "RevstackValidationError" ;
errors : string [];
}
Properties
Always "RevstackValidationError".
Human-readable summary of all errors.
Array of individual error messages.
Example
try {
validateConfig ( config );
} catch ( error ) {
if ( error instanceof RevstackValidationError ) {
console . log ( error . name ); // "RevstackValidationError"
console . log ( error . errors ); // ["No default plan found.", ...]
console . log ( error . message ); // Full summary
}
}
Best Practices
1. Validate Early
Run validation in your build process to catch errors before deployment:
{
"scripts" : {
"build" : "npm run validate:config && next build"
}
}
2. Type-Safe Configuration
Use defineConfig() with TypeScript for compile-time safety:
import { defineConfig } from "@revstack/core" ;
export default defineConfig ({
// TypeScript catches many errors before runtime
}) ;
3. Test Coverage
Write tests for your billing configuration:
it ( "should have valid billing config" , () => {
expect (() => validateConfig ( config )). not . toThrow ();
});
4. Continuous Validation
Validate configuration in CI/CD to prevent invalid configs from being merged.
Source
Location : packages/core/src/validator.ts:153-189
Error Class : packages/core/src/validator.ts:13-27