mplementing a production-grade product bundling system requires decoupling bundle definition from application deployment, enforcing strict schema validation, and integrating usage telemetry for dynamic resolution. The following implementation uses TypeScript, Zod for validation, and a cache-backed resolver pattern suitable for high-traffic digital asset platforms.
Step 1: Define the Bundle Schema
Bundles must be treated as versioned data contracts. Define interfaces that separate metadata, entitlements, and pricing constraints.
import { z } from 'zod';
export const BundleEntitlementSchema = z.object({
feature: z.string(),
quota: z.number().int().min(0),
unit: z.enum(['requests', 'gb', 'users', 'assets']),
overage_allowed: z.boolean().default(false),
overage_rate: z.number().optional(),
});
export const BundleSchema = z.object({
id: z.string().uuid(),
version: z.string().regex(/^\d+\.\d+\.\d+$/),
name: z.string(),
tier: z.enum(['free', 'starter', 'pro', 'enterprise']),
entitlements: z.array(BundleEntitlementSchema),
pricing: z.object({
base_amount: z.number(),
currency: z.string().length(3),
billing_cycle: z.enum(['monthly', 'yearly', 'usage_based']),
}),
activation_window: z.object({
start: z.string().datetime(),
end: z.string().datetime().optional(),
}),
metadata: z.record(z.unknown()).optional(),
});
export type Bundle = z.infer<typeof BundleSchema>;
export type BundleEntitlement = z.infer<typeof BundleEntitlementSchema>;
Step 2: Implement the Bundle Resolver Service
The resolver fetches, validates, caches, and returns bundles based on tenant context, usage patterns, and feature flags.
import { createClient } from 'redis';
import { BundleSchema, Bundle } from './bundle.schema';
const redis = createClient({ url: process.env.REDIS_URL });
await redis.connect();
export class BundleResolver {
private readonly cacheTTL = 3600; // 1 hour
private readonly fallbackBundle: Bundle;
constructor(fallbackConfig: Bundle) {
this.fallbackBundle = fallbackConfig;
}
async resolve(tenantId: string, usageMetrics: UsageMetrics): Promise<Bundle> {
const cacheKey = `bundle:${tenantId}`;
const cached = await redis.get(cacheKey);
if (cached) {
return BundleSchema.parse(JSON.parse(cached));
}
try {
const rawConfig = await this.fetchFromConfigStore(tenantId);
const validated = BundleSchema.parse(rawConfig);
// Apply usage-adaptive overrides if thresholds are met
const adaptive = this.applyUsageOverrides(validated, usageMetrics);
await redis.setEx(cacheKey, this.cacheTTL, JSON.stringify(adaptive));
return adaptive;
} catch (error) {
// Log to observability pipeline, return fallback
console.error(`Bundle resolution failed for ${tenantId}`, error);
return this.fallbackBundle;
}
}
private async fetchFromConfigStore(tenantId: string): Promise<unknown> {
// Replace with actual config store (CDN, DB, or feature flag service)
const response = await fetch(`${process.env.CONFIG_API}/bundles/${tenantId}`);
return response.json();
}
private applyUsageOverrides(bundle: Bundle, metrics: UsageMetrics): Bundle {
const upgraded = { ...bundle };
const threshold = bundle.metadata?.upgrade_threshold as number | undefined;
if (threshold && metrics.current_usage >= threshold) {
upgraded.tier = this.mapToHigherTier(bundle.tier);
upgraded.entitlements = upgraded.entitlements.map(ent => ({
...ent,
quota: Math.floor(ent.quota * 1.5),
}));
}
return upgraded;
}
private mapToHigherTier(current: string): string {
const ladder = ['free', 'starter', 'pro', 'enterprise'];
const idx = ladder.indexOf(current);
return ladder[Math.min(idx + 1, ladder.length - 1)] ?? current;
}
}
interface UsageMetrics {
current_usage: number;
period: 'daily' | 'monthly';
overage_events: number;
}
Step 3: Architecture Decisions & Rationale
- Decoupled Config Store: Bundle definitions live outside the application binary. This enables hot-reloads, A/B testing, and rollback without deployment cycles.
- Schema Validation at Boundary: Zod enforces strict contracts before any business logic executes. Invalid bundles fail fast, preventing quota mismatches or pricing errors in production.
- Cache-First Resolution: Redis reduces config store latency and prevents thundering herd scenarios during traffic spikes. TTL balances freshness with performance.
- Usage-Adaptive Layer: Telemetry-driven overrides allow bundles to self-optimize based on actual consumption patterns, reducing churn and overage friction.
- Versioned Contracts: Bundle schemas include semantic versioning. Migration scripts can safely transform legacy bundles without breaking existing tenants.
Pitfall Guide
-
Coupling bundle definitions to deployment pipelines
Bundles treated as code require CI/CD execution for every pricing change. This creates release contention and slows commercial iteration. Keep bundle data in a separate config store or CDN-backed JSON registry.
-
Ignoring backward compatibility in bundle versioning
Schema changes that drop fields or alter types break existing tenant resolutions. Always maintain forward-compatible schemas and implement migration pipelines that transform legacy configurations before validation.
-
Over-relying on real-time computation without caching
Bundle resolution runs on every authenticated request. Without caching, config store latency compounds, increasing p99 response times. Implement cache-aside patterns with TTLs aligned to update frequency.
-
Missing usage-to-bundle alignment telemetry
Bundles that don't reflect actual consumption patterns cause overage penalties and support volume. Instrument usage metrics at the edge, aggregate in a time-series store, and feed them into the adaptive resolver.
-
No audit trail for bundle changes
Pricing and quota modifications require compliance tracking. Log every bundle mutation with tenant ID, timestamp, previous state, new state, and actor. Store in an append-only ledger for reconciliation.
-
Treating discounts as first-class bundle logic
Discounts belong in a pricing engine, not the bundle definition layer. Mixing promotional logic with entitlements creates validation complexity and makes rollback difficult. Keep bundles focused on quotas and features; apply discounts at checkout.
-
Neglecting cross-bundle conflict resolution
Tenants with legacy bundles, trial conversions, and enterprise overrides often trigger overlapping entitlements. Implement a deterministic resolution order: explicit overrides > usage-adaptive > base tier > fallback.
Best practices from production:
- Use immutable bundle versions; never mutate in-place
- Validate against schema before caching or applying
- Feature-flag bundle rollouts to 1%, 10%, 100% cohorts
- Monitor bundle resolution latency and error rates in observability dashboards
- Run quota reconciliation jobs nightly to catch drift between resolved bundles and actual consumption
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Early-stage SaaS with static pricing | Rule-Based Engine | Simple thresholds reduce implementation overhead while enabling hot updates | Low infrastructure, moderate dev time |
| High-volume digital asset platform | Usage-Adaptive | Real-time telemetry prevents overage friction and aligns quotas with consumption | Higher cache/telemetry costs, lower support volume |
| Enterprise contracts with custom quotas | Explicit Override Layer | Manual overrides bypass adaptive logic, ensuring contractual compliance | Minimal infra cost, high ops overhead |
| A/B testing promotional bundles | Feature-Flagged Static | Controlled rollout isolates conversion impact without telemetry complexity | Low infra, moderate monitoring cost |
| Legacy migration with schema drift | Versioned Resolver + Migration Pipeline | Transform old configs before validation prevents runtime failures | Moderate dev time, low ongoing cost |
Configuration Template
{
"id": "bndl_8f3a9c2e-1d4b-4f6a-9c8e-2b7d5f1a3c9e",
"version": "1.2.0",
"name": "Pro Asset Bundle",
"tier": "pro",
"entitlements": [
{
"feature": "api_requests",
"quota": 100000,
"unit": "requests",
"overage_allowed": true,
"overage_rate": 0.002
},
{
"feature": "storage",
"quota": 50,
"unit": "gb",
"overage_allowed": false
},
{
"feature": "concurrent_uploads",
"quota": 5,
"unit": "users",
"overage_allowed": false
}
],
"pricing": {
"base_amount": 49.00,
"currency": "USD",
"billing_cycle": "monthly"
},
"activation_window": {
"start": "2024-01-01T00:00:00Z",
"end": "2025-12-31T23:59:59Z"
},
"metadata": {
"upgrade_threshold": 85000,
"bundle_family": "asset_processing",
"deprecated": false
}
}
Quick Start Guide
- Initialize the resolver: Install
zod and redis, create the schema file, and instantiate BundleResolver with a fallback configuration.
- Load configuration: Point the resolver to a JSON config store (CDN, S3, or database). Ensure the endpoint returns valid bundle JSON matching the schema.
- Wire telemetry: Expose a lightweight usage tracking middleware that increments counters per tenant. Feed aggregated metrics into
applyUsageOverrides.
- Deploy & validate: Run the resolver against a staging tenant. Verify cache hits, schema validation, and fallback behavior. Monitor p99 resolution latency and error rates before production rollout.