This document provides a quick reference for the three newly implemented features.
Status: Complete Files:
src/lib/pagination.test.ts(270 lines, added property tests)package.json(addedfast-check)
What to do:
- Install dependencies:
npm install - Run tests:
npm test -- src/lib/pagination.test.ts
Key Test Properties:
- Offset invariant:
offset == (page-1) * limit - Limit clamping:
1 <= limit <= MAX_LIMIT - Cursor round-trips are reversible
- Legacy offset fallback works for numeric cursors
- Error accumulation for invalid inputs
Test Coverage: 80 edge cases across 10 property-based scenarios
Status: Complete Files:
src/middleware/costMeter.ts(core middleware)src/middleware/__tests__/costMeter.test.ts(integration tests)
Key Functions:
// Deduct credits with optimistic locking
await deductCredits('org-1', 100)
// Refund on error
await refundCredits('org-1', 100)
// Configure cost weights
configureCostMeter({
defaultCostWeight: 1,
costWeights: { '/api/verify': 5, '/api/bulk/verify': 10 },
maxRetries: 3
})
// Resolve weight for a route
const weight = resolveCostWeight('/api/verify')
// Use as middleware
app.use(costMeterMiddleware)Database Schema:
CREATE TABLE org_credits (
org_id TEXT PRIMARY KEY,
balance BIGINT NOT NULL DEFAULT 10000,
version INTEGER NOT NULL DEFAULT 1,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
)Features:
- ✅ Optimistic locking for concurrent deductions
- ✅ Version-based conflict detection & retries
- ✅ Auto-refund on 5xx responses
- ✅ Graceful handling of missing orgs
- ✅ Configurable cost weights per route
Test Scenarios (20+ cases):
- Concurrent deductions maintain consistency
- Version increments on each operation
- Refunds restore balance correctly
- Credit initialization race resolved safely
- Route cost weight resolution
Status: Complete Files:
src/middleware/tenantContext.ts(core middleware)src/middleware/__tests__/tenantContext.test.ts(unit tests)src/utils/logger.ts(updated with tenantId logging)
Key Functions:
// Configure tenant resolution
configureTenantContext({
allowHeaderFallback: false,
allowDefaultTenant: false,
tenantScopedRoutes: ['/api/tenant/:id/.*', '/api/orgs/:id/.*'],
requiredOnScoped: true
})
// Use middleware
app.use(requireUserAuth)
app.use(tenantContextMiddleware)
app.use(requireTenant) // For tenant-scoped routes
// Get tenant ID from request
const tenantId = getTenantId(req)
// Validate tenant ID format
const isValid = isValidTenantId('my-org')Tenant Resolution Priority:
- Authenticated principal's tenantId (highest)
- X-Tenant-ID header (if
allowHeaderFallback: true) - Default tenant (if
allowDefaultTenant: true)
Tenant ID Format:
- Pattern:
[a-z0-9][a-z0-9-]*[a-z0-9] - Case-insensitive
- Max 255 characters
- Examples:
tenant-1,my-org,ABC123
Logging Integration:
All logs now include tenantId field from AsyncLocalStorage:
[2024-01-15T10:30:45.123Z] [INFO] [RequestID: req-123] [CorrelationID: corr-456] [TenantId: tenant-1] - User login
Features:
- ✅ Derives tenant from auth principal (no silent defaults)
- ✅ Validates tenant ID format
- ✅ Rejects missing tenant on scoped routes (401)
- ✅ Adds tenantId to all structured logs
- ✅ Proper AsyncLocalStorage cleanup
- ✅ Support for multi-tenant deployments
Test Scenarios (25+ cases):
- Principal tenant resolution
- Header fallback behavior
- Tenant ID format validation
- Scoped route enforcement
- Enterprise & SaaS flow integration
- Error handling for malformed tenants
import express from 'express'
import { requireUserAuth } from './middleware/auth.js'
import { tenantContextMiddleware, requireTenant } from './middleware/tenantContext.js'
import { costMeterMiddleware, configureCostMeter } from './middleware/costMeter.js'
const app = express()
// Configure cost meter
configureCostMeter({
defaultCostWeight: 1,
costWeights: {
'POST /api/verify': 5,
'POST /api/bulk/verify': 10,
}
})
// Middleware stack
app.use(requireUserAuth) // Authenticate & get tenantId
app.use(tenantContextMiddleware) // Resolve & validate tenant
app.use(costMeterMiddleware) // Track costs
app.use(requireTenant) // Require tenant for routes
// Route handler
app.post('/api/verify', async (req, res) => {
const tenantId = getTenantId(req)
const cost = resolveCostWeight(req.route.path)
await deductCredits(tenantId, cost)
// Handle verification
res.json({ verified: true })
})After installing dependencies (npm install):
# Run all tests
npm test
# Run specific test suite
npm test -- src/lib/pagination.test.ts
npm test -- src/middleware/__tests__/costMeter.test.ts
npm test -- src/middleware/__tests__/tenantContext.test.ts
# Run with coverage
npm run test:coverage- Uses fast-check library for property-based testing
- Generates 100+ random test cases per property by default
- No external dependencies beyond test framework
- Requires PostgreSQL database
- Uses optimistic locking pattern (suitable for low-contention workloads)
- For high-concurrency, consider adding Redis-based rate limiting
- Initial balance: 10,000 credits per org
- Integrates with existing
requireUserAuthmiddleware - Works with both Express-based auth and auth.ts mock users
- AsyncLocalStorage provides thread-safe context isolation
- tenantId automatically included in all logger output
-
Install dependencies:
npm install
-
Create database migration (optional, table created on first use):
npm run migrate:create
Then add the org_credits schema from Feature 2 section above.
-
Update app.ts to use the new middleware:
import { tenantContextMiddleware } from './middleware/tenantContext.js' import { costMeterMiddleware } from './middleware/costMeter.js' app.use(tenantContextMiddleware) app.use(costMeterMiddleware)
-
Run tests to verify:
npm test
Tests won't run (vitest not found):
- Run
npm installto ensure dependencies are installed
costMeter tests fail (Postgres error):
- Ensure testcontainers Docker daemon is running
- Check that port 5432 is available
tenantId not in logs:
- Ensure tenantContextMiddleware runs before logger usage
- Check that tracingContext is properly initialized in app startup
Tenant validation failing unexpectedly:
- Use
isValidTenantId()to check format before setting - Remember tenant IDs are case-insensitive (normalized to lowercase)
- Check max length (255 chars)
Consider these enhancements:
- Pagination: Add encoded cursor support to API routes
- Cost Meter: Add Redis-based rate limiting for high concurrency
- Tenant Context: Add tenant quotas and tier enforcement
- Logging: Add tenant ID to request correlation IDs
- Monitoring: Add metrics for tenant usage and cost breakdown
For detailed implementation information, see IMPLEMENTATION_SUMMARY.md.