Files
cfw-autumn/shared/api/customers/cusFeatures/apiCusFeature.ts
John Yeo 1847470403 feat: implement resource-based API versioning system with strong typing
## Summary
Implemented a comprehensive API versioning system with resource-level version changes,
strong typing using Zod schemas, and a composable getApi* pattern.

## Key Changes

### Versioning Infrastructure
- **VersionChange base class**: Now strongly typed with Zod schemas (TNewSchema, TOldSchema, TDataSchema)
- **Bidirectional transforms**: `transformRequest` (old→new) and `transformResponse` (new→old)
- **Input + Data pattern**: `input` for version-specific data, `data` for additional context
- **applyVersionChanges**: Fixed TypeScript errors and added biome-ignore comments for necessary `any` types

### Resource Organization
- **Folder structure**: Each resource gets its own `changes/` folder
  - `shared/api/customers/changes/` - Customer-level changes
  - `shared/api/customers/cusProducts/changes/` - Product-level changes
  - `shared/api/customers/cusFeatures/changes/` - Feature-level changes

### Version Changes (Strongly Typed)
- **V0_2_ProductItems**: Products gained items field (V0_2+ → V0_1)
- **V1_2_FeaturesArrayToObject**: Features object ↔ array (V1_2 → V1_1)
- **V1_1_MergedResponse**: Merged customer response ↔ split (V1_1+ → V1_0)
- **V1_1_LegacyExpandInvoices**: Side-effect only (invoices auto-expand in V1_0)

### getApi* Pattern (server/src/internal/customers/cusUtils/apiCusUtils/)
- **getApiCusProduct**: Builds product in latest format, applies V0_2_ProductItems transform
- **getApiCusFeature**: Transforms balances (used→usage), applies V1_2_FeaturesArrayToObject
- **getApiCustomer**: Orchestrates products/features, merges, applies V1_1_MergedResponse

### V2 Handler Demo
- **handleGetCustomerV2**: Demonstrates zero version branching in handler
- Calls `getApiCustomer` which handles all versioning internally
- Side effects handled explicitly (expand invoices for V1_0)

## Type Safety
- All version changes use Zod schemas for input/output types
- Runtime validation with `parse()` ensures data integrity
- Compile-time type checking catches transformation errors
- Context data support via optional `data` parameter

## Benefits
 No scattered if-else version checks
 Composable resource-based architecture
 Strong typing with Zod schemas
 Self-documenting version changes
 Easy to add new versions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-06 19:25:21 +01:00

59 lines
1.7 KiB
TypeScript

import { ApiFeatureType } from "@api/features/apiFeature.js";
import { EntInterval } from "@models/productModels/entModels/entEnums.js";
import { z } from "zod/v4";
export const ApiCusRolloverSchema = z.object({
balance: z.number(),
expires_at: z.number(),
});
// Version 3 of cus feature response
export const ApiCusFeatureBreakdownSchema = z.object({
interval: z.enum(EntInterval),
interval_count: z.number().nullish(),
balance: z.number().nullish(),
usage: z.number().nullish(),
included_usage: z.number().nullish(),
next_reset_at: z.number().nullish(),
usage_limit: z.number().nullish(),
rollovers: z.array(ApiCusRolloverSchema).nullish(),
});
export const CoreCusFeatureSchema = z.object({
interval: z.enum(EntInterval).or(z.literal("multiple")).nullish(),
interval_count: z.number().nullish(),
unlimited: z.boolean().nullish(),
balance: z.number().nullish(),
usage: z.number().nullish(),
included_usage: z.number().nullish(),
next_reset_at: z.number().nullish(),
overage_allowed: z.boolean().nullish(),
breakdown: z.array(ApiCusFeatureBreakdownSchema).nullish(),
credit_schema: z
.array(
z.object({
feature_id: z.string(),
credit_amount: z.number(),
}),
)
.nullish(),
usage_limit: z.number().nullish(),
rollovers: z.array(ApiCusRolloverSchema).nullish(),
});
export const ApiCusFeatureSchema = z
.object({
id: z.string(),
type: z.enum(ApiFeatureType),
name: z.string().nullish(),
})
.extend(CoreCusFeatureSchema.shape);
export type ApiCusFeature = z.infer<typeof ApiCusFeatureSchema>;
export type ApiCusRollover = z.infer<typeof ApiCusRolloverSchema>;
export type ApiCusFeatureBreakdown = z.infer<
typeof ApiCusFeatureBreakdownSchema
>;