## 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>
59 lines
1.7 KiB
TypeScript
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
|
|
>;
|