|
1 | | -import { ContractDefinition } from './types'; |
| 1 | +import { IRequest } from 'itty-router'; |
| 2 | +import { |
| 3 | + ContractDefinition, |
| 4 | + ContractOperation, |
| 5 | + ContractOperationHandler, |
| 6 | + HandlersForContract, |
| 7 | +} from './types'; |
2 | 8 |
|
3 | 9 | /** |
4 | 10 | * Creates a contract from a contract definition |
@@ -54,3 +60,73 @@ import { ContractDefinition } from './types'; |
54 | 60 | export function createContract<T extends ContractDefinition>(definition: T): T { |
55 | 61 | return definition; |
56 | 62 | } |
| 63 | + |
| 64 | +/** |
| 65 | + * Define handlers for a contract with type safety |
| 66 | + * This function validates that handlers match the contract and can be used |
| 67 | + * to define handlers in separate files that will be combined later |
| 68 | + * |
| 69 | + * The function accepts handlers that may use extended request types (e.g., AuthenticatedRequest) |
| 70 | + * as long as they are compatible with the contract's ContractRequest type. |
| 71 | + * |
| 72 | + * @typeParam TContract - The contract definition type |
| 73 | + * @typeParam Args - Additional arguments passed to handlers (defaults to any[]) |
| 74 | + * |
| 75 | + * @param contract - The contract definition to validate handlers against |
| 76 | + * @param handlers - Handlers object that must match all operations in the contract. |
| 77 | + * Handlers can use extended request types (e.g., AuthenticatedRequest) |
| 78 | + * as long as they extend IRequest and are compatible with ContractRequest. |
| 79 | + * @returns The handlers object with full type safety, ready to be combined with other handlers |
| 80 | + * |
| 81 | + * @example |
| 82 | + * ```typescript |
| 83 | + * // handlers/users.handlers.ts |
| 84 | + * import { usersContract } from '../contracts/users.contract'; |
| 85 | + * import { defineHandlers } from 'itty-spec'; |
| 86 | + * import type { AuthenticatedRequest } from '../middleware/auth.middleware'; |
| 87 | + * |
| 88 | + * export const userHandlers = defineHandlers(usersContract, { |
| 89 | + * getUsers: async (request: AuthenticatedRequest) => { |
| 90 | + * // request is fully typed based on usersContract.getUsers |
| 91 | + * // and also has userId, userRole from AuthenticatedRequest |
| 92 | + * const { page, limit } = request.validatedQuery; |
| 93 | + * return request.respond({ |
| 94 | + * status: 200, |
| 95 | + * contentType: 'application/json', |
| 96 | + * body: { users: [] }, |
| 97 | + * }); |
| 98 | + * }, |
| 99 | + * getUserById: async (request: AuthenticatedRequest) => { |
| 100 | + * // implementation |
| 101 | + * }, |
| 102 | + * // TypeScript will ensure all contract operations have handlers |
| 103 | + * }); |
| 104 | + * ``` |
| 105 | + * |
| 106 | + * @example |
| 107 | + * ```typescript |
| 108 | + * // index.ts - combine handlers later |
| 109 | + * import { userHandlers } from './handlers/users.handlers'; |
| 110 | + * import { productHandlers } from './handlers/products.handlers'; |
| 111 | + * import { contract } from './contracts'; |
| 112 | + * |
| 113 | + * const router = createRouter({ |
| 114 | + * contract, |
| 115 | + * handlers: { |
| 116 | + * ...userHandlers, |
| 117 | + * ...productHandlers, |
| 118 | + * }, |
| 119 | + * }); |
| 120 | + * ``` |
| 121 | + */ |
| 122 | +export function defineHandlers<TContract extends ContractDefinition, Args extends any[] = any[]>( |
| 123 | + contract: TContract, |
| 124 | + handlers: { |
| 125 | + [K in keyof TContract]: ( |
| 126 | + request: IRequest & Parameters<ContractOperationHandler<TContract[K], Args>>[0], |
| 127 | + ...args: Args |
| 128 | + ) => ReturnType<ContractOperationHandler<TContract[K], Args>>; |
| 129 | + } |
| 130 | +): HandlersForContract<TContract, Args> { |
| 131 | + return handlers as HandlersForContract<TContract, Args>; |
| 132 | +} |
0 commit comments