ts-typed-api 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/.eslintrc.json +16 -0
  2. package/LICENSE +201 -0
  3. package/README.md +181 -0
  4. package/dist/apiClient.d.ts +147 -0
  5. package/dist/apiClient.js +206 -0
  6. package/dist/client.d.ts +152 -0
  7. package/dist/client.js +215 -0
  8. package/dist/definition.d.ts +121 -0
  9. package/dist/definition.js +79 -0
  10. package/dist/example-server/client.d.ts +2 -0
  11. package/dist/example-server/client.js +19 -0
  12. package/dist/example-server/definitions.d.ts +157 -0
  13. package/dist/example-server/definitions.js +35 -0
  14. package/dist/example-server/server.d.ts +1 -0
  15. package/dist/example-server/server.js +66 -0
  16. package/dist/handler.d.ts +16 -0
  17. package/dist/handler.js +185 -0
  18. package/dist/index.d.ts +4 -0
  19. package/dist/index.js +13 -0
  20. package/dist/object-handlers.d.ts +16 -0
  21. package/dist/object-handlers.js +39 -0
  22. package/dist/openapiGenerator.d.ts +2 -0
  23. package/dist/openapiGenerator.js +119 -0
  24. package/dist/router/definition.d.ts +118 -0
  25. package/dist/router/definition.js +80 -0
  26. package/dist/router/router.d.ts +29 -0
  27. package/dist/router/router.js +168 -0
  28. package/dist/router.d.ts +18 -0
  29. package/dist/router.js +20 -0
  30. package/dist/src/router/client.d.ts +151 -0
  31. package/dist/src/router/client.js +215 -0
  32. package/dist/src/router/definition.d.ts +121 -0
  33. package/dist/src/router/definition.js +79 -0
  34. package/dist/src/router/handler.d.ts +16 -0
  35. package/dist/src/router/handler.js +170 -0
  36. package/dist/src/router/index.d.ts +5 -0
  37. package/dist/src/router/index.js +22 -0
  38. package/dist/src/router/object-handlers.d.ts +16 -0
  39. package/dist/src/router/object-handlers.js +39 -0
  40. package/dist/src/router/router.d.ts +18 -0
  41. package/dist/src/router/router.js +20 -0
  42. package/examples/advanced/client.ts +226 -0
  43. package/examples/advanced/definitions.ts +156 -0
  44. package/examples/advanced/server.ts +298 -0
  45. package/examples/simple/client.ts +24 -0
  46. package/examples/simple/definitions.ts +57 -0
  47. package/examples/simple/server.ts +67 -0
  48. package/package.json +57 -0
  49. package/src/client.ts +368 -0
  50. package/src/definition.ts +231 -0
  51. package/src/handler.ts +220 -0
  52. package/src/index.ts +4 -0
  53. package/src/object-handlers.ts +84 -0
  54. package/src/router.ts +93 -0
  55. package/tsconfig.json +17 -0
@@ -0,0 +1,185 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerRouteHandlers = registerRouteHandlers;
4
+ const zod_1 = require("zod");
5
+ // Register route handlers with Express, now generic over TDef
6
+ function registerRouteHandlers(app, apiDefinition, // Pass the actual API definition object
7
+ routeHandlers, // Use the generic handler type
8
+ middlewares) {
9
+ routeHandlers.forEach((specificHandlerIterationItem) => {
10
+ const { domain, routeKey, handler } = specificHandlerIterationItem; // Use 'as any' for simplicity in destructuring union
11
+ const currentDomain = domain;
12
+ const currentRouteKey = routeKey;
13
+ // Use the passed apiDefinition object
14
+ const routeDefinition = apiDefinition.endpoints[currentDomain][currentRouteKey];
15
+ if (!routeDefinition) {
16
+ console.error(`Route definition not found for domain "${String(currentDomain)}" and routeKey "${String(currentRouteKey)}"`);
17
+ return;
18
+ }
19
+ const { path, method } = routeDefinition;
20
+ // Apply prefix from API definition if it exists
21
+ const fullPath = apiDefinition.prefix
22
+ ? `${apiDefinition.prefix.startsWith('/') ? apiDefinition.prefix : `/${apiDefinition.prefix}`}${path}`.replace(/\/+/g, '/')
23
+ : path;
24
+ const expressMiddleware = async (expressReq, expressRes) => {
25
+ try {
26
+ // Ensure TDef is correctly used for type inference if this section needs it.
27
+ // Currently, parsedParams,Query,Body are based on runtime routeDefinition.
28
+ const parsedParams = ('params' in routeDefinition && routeDefinition.params)
29
+ ? routeDefinition.params.parse(expressReq.params)
30
+ : expressReq.params;
31
+ const parsedQuery = ('query' in routeDefinition && routeDefinition.query)
32
+ ? routeDefinition.query.parse(expressReq.query)
33
+ : expressReq.query;
34
+ const parsedBody = (method === 'POST' || method === 'PUT') && ('body' in routeDefinition && routeDefinition.body)
35
+ ? routeDefinition.body.parse(expressReq.body)
36
+ : expressReq.body;
37
+ // Construct TypedRequest using TDef, currentDomain, currentRouteKey
38
+ const finalTypedReq = {
39
+ ...expressReq,
40
+ params: parsedParams,
41
+ query: parsedQuery,
42
+ body: parsedBody,
43
+ headers: expressReq.headers,
44
+ cookies: expressReq.cookies,
45
+ ip: expressReq.ip,
46
+ ips: expressReq.ips,
47
+ hostname: expressReq.hostname,
48
+ protocol: expressReq.protocol,
49
+ secure: expressReq.secure,
50
+ xhr: expressReq.xhr,
51
+ fresh: expressReq.fresh,
52
+ stale: expressReq.stale,
53
+ subdomains: expressReq.subdomains,
54
+ path: expressReq.path,
55
+ originalUrl: expressReq.originalUrl,
56
+ baseUrl: expressReq.baseUrl,
57
+ url: expressReq.url,
58
+ };
59
+ // Augment expressRes with the .respond method, using TDef
60
+ const typedExpressRes = expressRes;
61
+ typedExpressRes.respond = (status, dataForResponse) => {
62
+ // Use the passed apiDefinition object
63
+ const routeSchemaForHandler = apiDefinition.endpoints[currentDomain][currentRouteKey];
64
+ const responseSchemaForStatus = routeSchemaForHandler.responses[status];
65
+ if (!responseSchemaForStatus) {
66
+ console.error(`No response schema defined for status ${status} in route ${String(currentDomain)}/${String(currentRouteKey)}`);
67
+ typedExpressRes.status(500).json({
68
+ // data: null, // data field might not be part of error schema for 500 if not using unified
69
+ error: [{ field: "general", type: "general", message: "Internal server error: Undefined response schema for status." }]
70
+ });
71
+ return;
72
+ }
73
+ let responseBodyToValidate; // This will be the object { data: ..., error: ... }
74
+ if (status === 422) {
75
+ // For 422, dataForResponse is expected to be the UnifiedError array or null
76
+ // The schema for 422 is errorUnifiedResponseSchema, expecting { error: UnifiedError }
77
+ responseBodyToValidate = {
78
+ data: null, // data is null for 422
79
+ error: dataForResponse // dataForResponse should be UnifiedError for 422
80
+ };
81
+ }
82
+ else {
83
+ // For other statuses, dataForResponse is the actual data payload for the 'data' field
84
+ // The schema is createSuccessUnifiedResponseSchema, expecting { data: ActualData }
85
+ responseBodyToValidate = {
86
+ data: dataForResponse,
87
+ error: null // error is null for success
88
+ };
89
+ }
90
+ // Validate the constructed responseBodyToValidate against the full schema for that status
91
+ const validationResult = responseSchemaForStatus.safeParse(responseBodyToValidate);
92
+ if (validationResult.success) {
93
+ typedExpressRes.status(status).json(validationResult.data);
94
+ }
95
+ else {
96
+ console.error(`FATAL: Constructed response body failed Zod validation for status ${status} in route ${String(currentDomain)}/${String(currentRouteKey)}. This indicates an issue with respond logic or schemas.`, validationResult.error.errors);
97
+ console.error("Response body was:", responseBodyToValidate);
98
+ typedExpressRes.status(500).json({
99
+ // data: null,
100
+ error: [{ field: "general", type: "general", message: "Internal server error: Constructed response failed validation." }]
101
+ });
102
+ }
103
+ };
104
+ const specificHandlerFn = handler;
105
+ await specificHandlerFn(finalTypedReq, typedExpressRes);
106
+ }
107
+ catch (error) {
108
+ if (error instanceof zod_1.z.ZodError) {
109
+ const mappedErrors = error.errors.map(err => {
110
+ let errorType = 'general';
111
+ const pathZero = String(err.path[0]); // Ensure pathZero is a string
112
+ if (pathZero === 'params')
113
+ errorType = 'param'; // Corrected: 'params' from path maps to 'param' type
114
+ else if (pathZero === 'query')
115
+ errorType = 'query';
116
+ else if (pathZero === 'body')
117
+ errorType = 'body';
118
+ return {
119
+ field: err.path.join('.') || 'request',
120
+ message: err.message,
121
+ type: errorType,
122
+ };
123
+ });
124
+ const errorResponseBody = { data: null, error: mappedErrors };
125
+ const schema422 = routeDefinition.responses[422];
126
+ if (schema422) {
127
+ const validationResult = schema422.safeParse(errorResponseBody);
128
+ if (validationResult.success) {
129
+ expressRes.status(422).json(validationResult.data);
130
+ }
131
+ else {
132
+ console.error("FATAL: Constructed 422 error response failed its own schema validation.", validationResult.error.errors);
133
+ expressRes.status(500).json({ error: [{ field: "general", type: "general", message: "Internal server error constructing validation error response." }] });
134
+ }
135
+ }
136
+ else {
137
+ console.error("Error: 422 schema not found for route, sending raw Zod errors.");
138
+ expressRes.status(422).json({ error: mappedErrors }); // Fallback
139
+ }
140
+ }
141
+ else if (error instanceof Error) {
142
+ console.error(`Error in ${method} ${path}:`, error.message, error.stack);
143
+ expressRes.status(500).json({ error: [{ field: "general", type: "general", message: 'Internal server error' }] });
144
+ }
145
+ else {
146
+ console.error(`Unknown error in ${method} ${path}:`, error);
147
+ expressRes.status(500).json({ error: [{ field: "general", type: "general", message: 'An unknown error occurred' }] });
148
+ }
149
+ }
150
+ };
151
+ // Create middleware wrappers that include endpoint information
152
+ const middlewareWrappers = [];
153
+ if (middlewares && middlewares.length > 0) {
154
+ middlewares.forEach(middleware => {
155
+ const wrappedMiddleware = async (req, res, next) => {
156
+ try {
157
+ await middleware(req, res, next, { domain: currentDomain, routeKey: currentRouteKey });
158
+ }
159
+ catch (error) {
160
+ next(error);
161
+ }
162
+ };
163
+ middlewareWrappers.push(wrappedMiddleware);
164
+ });
165
+ }
166
+ // Register route with middlewares
167
+ const allHandlers = [...middlewareWrappers, expressMiddleware];
168
+ switch (method.toUpperCase()) {
169
+ case 'GET':
170
+ app.get(fullPath, ...allHandlers);
171
+ break;
172
+ case 'POST':
173
+ app.post(fullPath, ...allHandlers);
174
+ break;
175
+ case 'PUT':
176
+ app.put(fullPath, ...allHandlers);
177
+ break;
178
+ case 'DELETE':
179
+ app.delete(fullPath, ...allHandlers);
180
+ break;
181
+ default:
182
+ console.warn(`Unsupported HTTP method: ${method} for path ${fullPath}`);
183
+ }
184
+ });
185
+ }
@@ -0,0 +1,4 @@
1
+ export { ApiClient, FetchHttpClientAdapter } from './client';
2
+ export { CreateApiDefinition, CreateResponses } from './definition';
3
+ export { RegisterHandlers, EndpointMiddleware } from './object-handlers';
4
+ export { z as ZodSchema } from 'zod';
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ZodSchema = exports.RegisterHandlers = exports.CreateResponses = exports.CreateApiDefinition = exports.FetchHttpClientAdapter = exports.ApiClient = void 0;
4
+ var client_1 = require("./client");
5
+ Object.defineProperty(exports, "ApiClient", { enumerable: true, get: function () { return client_1.ApiClient; } });
6
+ Object.defineProperty(exports, "FetchHttpClientAdapter", { enumerable: true, get: function () { return client_1.FetchHttpClientAdapter; } });
7
+ var definition_1 = require("./definition");
8
+ Object.defineProperty(exports, "CreateApiDefinition", { enumerable: true, get: function () { return definition_1.CreateApiDefinition; } });
9
+ Object.defineProperty(exports, "CreateResponses", { enumerable: true, get: function () { return definition_1.CreateResponses; } });
10
+ var object_handlers_1 = require("./object-handlers");
11
+ Object.defineProperty(exports, "RegisterHandlers", { enumerable: true, get: function () { return object_handlers_1.RegisterHandlers; } });
12
+ var zod_1 = require("zod");
13
+ Object.defineProperty(exports, "ZodSchema", { enumerable: true, get: function () { return zod_1.z; } });
@@ -0,0 +1,16 @@
1
+ import express from "express";
2
+ import { ApiDefinitionSchema } from "./definition";
3
+ import { TypedRequest, TypedResponse } from "./router";
4
+ export type EndpointMiddleware = (req: express.Request, res: express.Response, next: express.NextFunction, endpointInfo: {
5
+ domain: string;
6
+ routeKey: string;
7
+ }) => void | Promise<void>;
8
+ type HandlerFunction<TDef extends ApiDefinitionSchema, TDomain extends keyof TDef['endpoints'], TRouteKey extends keyof TDef['endpoints'][TDomain]> = (req: TypedRequest<TDef, TDomain, TRouteKey>, res: TypedResponse<TDef, TDomain, TRouteKey>) => Promise<void> | void;
9
+ export type ObjectHandlers<TDef extends ApiDefinitionSchema> = {
10
+ [TDomain in keyof TDef['endpoints']]: {
11
+ [TRouteKey in keyof TDef['endpoints'][TDomain]]: HandlerFunction<TDef, TDomain, TRouteKey>;
12
+ };
13
+ };
14
+ export declare function RegisterHandlers<TDef extends ApiDefinitionSchema>(app: express.Express, apiDefinition: TDef, objectHandlers: ObjectHandlers<TDef>, middlewares?: EndpointMiddleware[]): void;
15
+ export declare function makeObjectHandlerRegistrar<TDef extends ApiDefinitionSchema>(apiDefinition: TDef): (app: express.Express, objectHandlers: ObjectHandlers<TDef>, middlewares?: EndpointMiddleware[]) => void;
16
+ export {};
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RegisterHandlers = RegisterHandlers;
4
+ exports.makeObjectHandlerRegistrar = makeObjectHandlerRegistrar;
5
+ const handler_1 = require("./handler");
6
+ // Transform object-based handlers to array format
7
+ function transformObjectHandlersToArray(objectHandlers) {
8
+ const handlerArray = [];
9
+ // Iterate through domains
10
+ for (const domain in objectHandlers) {
11
+ if (Object.prototype.hasOwnProperty.call(objectHandlers, domain)) {
12
+ const domainHandlers = objectHandlers[domain];
13
+ // Iterate through routes in this domain
14
+ for (const routeKey in domainHandlers) {
15
+ if (Object.prototype.hasOwnProperty.call(domainHandlers, routeKey)) {
16
+ const handler = domainHandlers[routeKey];
17
+ // Create the handler object in the format expected by registerRouteHandlers
18
+ handlerArray.push({
19
+ domain,
20
+ routeKey,
21
+ handler
22
+ });
23
+ }
24
+ }
25
+ }
26
+ }
27
+ return handlerArray;
28
+ }
29
+ // Main utility function that registers object-based handlers
30
+ function RegisterHandlers(app, apiDefinition, objectHandlers, middlewares) {
31
+ const handlerArray = transformObjectHandlersToArray(objectHandlers);
32
+ (0, handler_1.registerRouteHandlers)(app, apiDefinition, handlerArray, middlewares);
33
+ }
34
+ // Factory function to create a typed handler registrar for a specific API definition
35
+ function makeObjectHandlerRegistrar(apiDefinition) {
36
+ return function (app, objectHandlers, middlewares) {
37
+ RegisterHandlers(app, apiDefinition, objectHandlers, middlewares);
38
+ };
39
+ }
@@ -0,0 +1,2 @@
1
+ import { ApiDefinitionSchema } from './router/definition';
2
+ export declare function generateOpenApiSpec(definition: ApiDefinitionSchema): import("openapi3-ts/oas30").OpenAPIObject;
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateOpenApiSpec = void 0;
4
+ const zod_to_openapi_1 = require("@asteasolutions/zod-to-openapi");
5
+ const zod_1 = require("zod");
6
+ // Extend Zod with OpenAPI capabilities
7
+ (0, zod_to_openapi_1.extendZodWithOpenApi)(zod_1.z);
8
+ function generateOpenApiSpec(definition) {
9
+ const registry = new zod_to_openapi_1.OpenAPIRegistry();
10
+ // Helper to convert Zod schema to OpenAPI schema component
11
+ function registerSchema(name, schema) {
12
+ try {
13
+ return registry.register(name, schema); // Cast to any to handle complex Zod types
14
+ }
15
+ catch (error) {
16
+ console.warn(`Could not register schema ${name}: ${error.message}`);
17
+ // Fallback or simplified schema if registration fails
18
+ return registry.register(name, zod_1.z.object({}).openapi({ description: `Schema for ${name} (fallback due to registration error)` }));
19
+ }
20
+ }
21
+ // Function to convert Zod schema to OpenAPI Parameter Object or Request Body Object
22
+ function zodSchemaToOpenApiParameter(schema, inType) {
23
+ if (!schema || !(schema instanceof zod_1.z.ZodObject))
24
+ return []; // Ensure schema is a ZodObject
25
+ const shape = schema.shape;
26
+ return Object.entries(shape).map(([key, val]) => ({
27
+ name: key,
28
+ in: inType,
29
+ required: !val.isOptional(),
30
+ schema: registerSchema(`${inType}_${key}_${Date.now()}`, val),
31
+ description: val.description,
32
+ }));
33
+ }
34
+ function zodSchemaToOpenApiRequestBody(schema) {
35
+ if (!schema)
36
+ return undefined;
37
+ return {
38
+ required: true,
39
+ content: {
40
+ 'application/json': {
41
+ schema: registerSchema(`RequestBody_${Date.now()}`, schema), // Unique name
42
+ },
43
+ },
44
+ };
45
+ }
46
+ // Iterate over the API definition to register routes
47
+ Object.keys(definition).forEach(domainNameKey => {
48
+ // domainNameKey is a string, representing the domain like 'users', 'products'
49
+ const domain = definition[domainNameKey];
50
+ Object.keys(domain).forEach(routeNameKey => {
51
+ // routeNameKey is a string, representing the route name like 'getUser', 'createProduct'
52
+ const route = domain[routeNameKey];
53
+ const pathItem = {}; // Define PathItemObject structure
54
+ const parameters = [];
55
+ if (route.params) {
56
+ parameters.push(...zodSchemaToOpenApiParameter(route.params, 'path'));
57
+ }
58
+ if (route.query) {
59
+ parameters.push(...zodSchemaToOpenApiParameter(route.query, 'query'));
60
+ }
61
+ const requestBody = zodSchemaToOpenApiRequestBody(route.body);
62
+ const responses = {};
63
+ for (const statusCode in route.responses) {
64
+ const responseSchema = route.responses[parseInt(statusCode)];
65
+ if (responseSchema) {
66
+ responses[statusCode] = {
67
+ description: `Response for status code ${statusCode}`,
68
+ content: {
69
+ 'application/json': {
70
+ schema: registerSchema(`Response_${statusCode}_${routeNameKey}_${domainNameKey}`, responseSchema),
71
+ },
72
+ },
73
+ };
74
+ }
75
+ }
76
+ // Add 422 response if not already defined, as it's a default in createResponses
77
+ // Assuming route.responses[422] would exist if it's a standard part of the definition
78
+ if (!responses['422'] && route.responses && route.responses[422]) {
79
+ responses['422'] = {
80
+ description: 'Validation Error',
81
+ content: {
82
+ 'application/json': {
83
+ schema: registerSchema(`Response_422_${routeNameKey}_${domainNameKey}`, route.responses[422]),
84
+ },
85
+ },
86
+ };
87
+ }
88
+ const operation = {
89
+ summary: `${domainNameKey} - ${routeNameKey}`,
90
+ tags: [domainNameKey],
91
+ parameters: parameters.length > 0 ? parameters : undefined,
92
+ requestBody: requestBody,
93
+ responses: responses,
94
+ };
95
+ // Register the route with the registry
96
+ // The path needs to be transformed from Express-style (:param) to OpenAPI-style ({param})
97
+ const openApiPath = route.path.replace(/:(\w+)/g, '{$1}');
98
+ registry.registerPath({
99
+ method: route.method.toLowerCase(),
100
+ path: openApiPath,
101
+ ...operation,
102
+ // Add description or other OpenAPI fields if available in RouteSchema
103
+ });
104
+ });
105
+ });
106
+ // Generate the OpenAPI document
107
+ const generator = new zod_to_openapi_1.OpenApiGeneratorV3(registry.definitions);
108
+ const openApiDocument = generator.generateDocument({
109
+ openapi: '3.0.0',
110
+ info: {
111
+ title: 'My API',
112
+ version: '1.0.0',
113
+ description: 'Automatically generated OpenAPI specification',
114
+ },
115
+ servers: [{ url: '/api' }], // Adjust as needed
116
+ });
117
+ return openApiDocument;
118
+ }
119
+ exports.generateOpenApiSpec = generateOpenApiSpec;
@@ -0,0 +1,118 @@
1
+ import { z, ZodTypeAny, ZodType } from 'zod';
2
+ export declare class TsTypeMarker<T> {
3
+ readonly _isTsTypeMarker = true;
4
+ readonly _type: T;
5
+ constructor();
6
+ }
7
+ export declare function CustomResponse<T>(): TsTypeMarker<T>;
8
+ type InputSchemaOrMarker = ZodTypeAny | TsTypeMarker<any>;
9
+ declare const unifiedErrorSchema: z.ZodNullable<z.ZodArray<z.ZodObject<{
10
+ field: z.ZodString;
11
+ type: z.ZodEnum<["body", "query", "param", "general"]>;
12
+ message: z.ZodString;
13
+ }, "strip", z.ZodTypeAny, {
14
+ field: string;
15
+ type: "param" | "body" | "query" | "general";
16
+ message: string;
17
+ }, {
18
+ field: string;
19
+ type: "param" | "body" | "query" | "general";
20
+ message: string;
21
+ }>, "many">>;
22
+ export type UnifiedError = z.infer<typeof unifiedErrorSchema>;
23
+ declare const errorUnifiedResponseSchema: z.ZodObject<{
24
+ error: z.ZodEffects<z.ZodNullable<z.ZodArray<z.ZodObject<{
25
+ field: z.ZodString;
26
+ type: z.ZodEnum<["body", "query", "param", "general"]>;
27
+ message: z.ZodString;
28
+ }, "strip", z.ZodTypeAny, {
29
+ field: string;
30
+ type: "param" | "body" | "query" | "general";
31
+ message: string;
32
+ }, {
33
+ field: string;
34
+ type: "param" | "body" | "query" | "general";
35
+ message: string;
36
+ }>, "many">>, {
37
+ field: string;
38
+ type: "param" | "body" | "query" | "general";
39
+ message: string;
40
+ }[] | null, {
41
+ field: string;
42
+ type: "param" | "body" | "query" | "general";
43
+ message: string;
44
+ }[] | null>;
45
+ }, "strip", z.ZodTypeAny, {
46
+ error: {
47
+ field: string;
48
+ type: "param" | "body" | "query" | "general";
49
+ message: string;
50
+ }[] | null;
51
+ }, {
52
+ error: {
53
+ field: string;
54
+ type: "param" | "body" | "query" | "general";
55
+ message: string;
56
+ }[] | null;
57
+ }>;
58
+ export declare const HttpSuccessCodes: readonly [200, 201, 202, 204];
59
+ export declare const HttpClientErrorCodes: readonly [400, 401, 403, 404, 409];
60
+ export declare const HttpServerErrorCodes: readonly [500];
61
+ export type HttpSuccessStatusCode = typeof HttpSuccessCodes[number];
62
+ export type HttpClientErrorStatusCode = typeof HttpClientErrorCodes[number];
63
+ export type HttpServerErrorStatusCode = typeof HttpServerErrorCodes[number];
64
+ export type AllowedInputStatusCode = HttpSuccessStatusCode | HttpClientErrorStatusCode | HttpServerErrorStatusCode;
65
+ export type AllowedResponseStatusCode = AllowedInputStatusCode | 422;
66
+ type CreateResponsesReturnType<InputSchemas extends Partial<Record<AllowedInputStatusCode, InputSchemaOrMarker>>> = {
67
+ [KStatus in keyof InputSchemas]: InputSchemas[KStatus] extends TsTypeMarker<infer T> ? z.ZodObject<{
68
+ data: ZodType<T, z.ZodTypeDef, T>;
69
+ }> : InputSchemas[KStatus] extends ZodTypeAny ? z.ZodObject<{
70
+ data: InputSchemas[KStatus];
71
+ }> : never;
72
+ } & {
73
+ 422: typeof errorUnifiedResponseSchema;
74
+ };
75
+ export declare function createResponses<TInputMap extends Partial<Record<AllowedInputStatusCode, InputSchemaOrMarker>>>(schemas: TInputMap): CreateResponsesReturnType<TInputMap>;
76
+ export interface RouteSchema {
77
+ path: string;
78
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'OPTIONS' | 'HEAD';
79
+ params?: ZodTypeAny;
80
+ query?: ZodTypeAny;
81
+ body?: ZodTypeAny;
82
+ responses: Record<number, ZodTypeAny>;
83
+ }
84
+ export type ApiDefinitionSchema = Record<string, Record<string, RouteSchema>>;
85
+ export declare function createApiDefinition<T extends ApiDefinitionSchema>(definition: T): T;
86
+ export type ApiRouteKey<TDef extends ApiDefinitionSchema, TDomain extends keyof TDef> = keyof TDef[TDomain];
87
+ export type ApiRoute<TDef extends ApiDefinitionSchema, TDomain extends keyof TDef, TRouteName extends ApiRouteKey<TDef, TDomain>> = TDef[TDomain][TRouteName];
88
+ export type InferDataFromUnifiedResponse<S extends ZodTypeAny> = S extends z.ZodVoid ? void : z.infer<S> extends {
89
+ data: infer D;
90
+ } ? D extends null ? null : D extends (infer ActualD | null) ? ActualD extends void ? void : ActualD : D : never;
91
+ export type ApiResponse<TDef extends ApiDefinitionSchema, TDomain extends keyof TDef, TRouteName extends ApiRouteKey<TDef, TDomain>> = TDef[TDomain][TRouteName] extends {
92
+ responses: infer R;
93
+ } ? R extends {
94
+ 200: infer R200 extends ZodTypeAny;
95
+ } ? InferDataFromUnifiedResponse<R200> : R extends {
96
+ 201: infer R201 extends ZodTypeAny;
97
+ } ? InferDataFromUnifiedResponse<R201> : R extends {
98
+ 204: infer R204 extends ZodTypeAny;
99
+ } ? InferDataFromUnifiedResponse<R204> : any : any;
100
+ export type ApiBody<TDef extends ApiDefinitionSchema, TDomain extends keyof TDef, TRouteName extends ApiRouteKey<TDef, TDomain>> = TDef[TDomain][TRouteName] extends {
101
+ body: infer B extends z.ZodTypeAny;
102
+ } ? z.infer<B> : Record<string, any>;
103
+ export type ApiParams<TDef extends ApiDefinitionSchema, TDomain extends keyof TDef, TRouteName extends ApiRouteKey<TDef, TDomain>> = TDef[TDomain][TRouteName] extends {
104
+ params: infer P extends z.ZodTypeAny;
105
+ } ? z.infer<P> : Record<string, any>;
106
+ export type ApiQuery<TDef extends ApiDefinitionSchema, TDomain extends keyof TDef, TRouteName extends ApiRouteKey<TDef, TDomain>> = TDef[TDomain][TRouteName] extends {
107
+ query: infer Q extends z.ZodTypeAny;
108
+ } ? z.infer<Q> : Record<string, any>;
109
+ export type ApiClientBody<TDef extends ApiDefinitionSchema, TDomain extends keyof TDef, TRouteName extends ApiRouteKey<TDef, TDomain>> = TDef[TDomain][TRouteName] extends {
110
+ body: infer B extends ZodTypeAny;
111
+ } ? z.input<B> : undefined;
112
+ export type ApiClientParams<TDef extends ApiDefinitionSchema, TDomain extends keyof TDef, TRouteName extends ApiRouteKey<TDef, TDomain>> = TDef[TDomain][TRouteName] extends {
113
+ params: infer P extends ZodTypeAny;
114
+ } ? z.input<P> : undefined;
115
+ export type ApiClientQuery<TDef extends ApiDefinitionSchema, TDomain extends keyof TDef, TRouteName extends ApiRouteKey<TDef, TDomain>> = TDef[TDomain][TRouteName] extends {
116
+ query: infer Q extends ZodTypeAny;
117
+ } ? z.input<Q> : undefined;
118
+ export {};
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createApiDefinition = exports.createResponses = exports.HttpServerErrorCodes = exports.HttpClientErrorCodes = exports.HttpSuccessCodes = exports.CustomResponse = exports.TsTypeMarker = void 0;
4
+ const zod_1 = require("zod");
5
+ // Marker class for raw TypeScript types
6
+ class TsTypeMarker {
7
+ _isTsTypeMarker = true;
8
+ _type; // Phantom type, used for inference
9
+ constructor() {
10
+ // This constructor doesn't need to do anything with T at runtime.
11
+ // T is purely a compile-time construct.
12
+ }
13
+ }
14
+ exports.TsTypeMarker = TsTypeMarker;
15
+ // Helper function to create a TsTypeMarker instance
16
+ function CustomResponse() {
17
+ return new TsTypeMarker();
18
+ }
19
+ exports.CustomResponse = CustomResponse;
20
+ // Define the structure for error details
21
+ const errorDetailSchema = zod_1.z.object({
22
+ field: zod_1.z.string(),
23
+ type: zod_1.z.enum(['body', 'query', 'param', 'general']),
24
+ message: zod_1.z.string(),
25
+ });
26
+ // Define the schema for the error list
27
+ const unifiedErrorSchema = zod_1.z.array(errorDetailSchema).nullable(); // Nullable if no errors
28
+ // Helper function to create the success-specific unified response schema
29
+ // This wraps the original data schema with a 'data' field and sets 'error' to null.
30
+ function createSuccessUnifiedResponseSchema(dataSchema) {
31
+ return zod_1.z.object({
32
+ data: dataSchema, // Data is present as per dataSchema (can be nullable if dataSchema itself is)
33
+ });
34
+ }
35
+ // Schema for error responses (e.g., 422 Validation Error)
36
+ // Ensures 'data' is null and 'error' is populated.
37
+ const errorUnifiedResponseSchema = zod_1.z.object({
38
+ error: unifiedErrorSchema.refine(val => val !== null, { message: "Error list cannot be null for errorUnifiedResponseSchema" }), // Error list is mandatory
39
+ });
40
+ // Define allowed HTTP status codes
41
+ exports.HttpSuccessCodes = [200, 201, 202, 204];
42
+ exports.HttpClientErrorCodes = [400, 401, 403, 404, 409]; // 422 is handled separately
43
+ exports.HttpServerErrorCodes = [500];
44
+ // Helper function to create response schemas with unified structure and default 422 error
45
+ // Schemas input is now constrained to use AllowedInputStatusCode as keys.
46
+ function createResponses(schemas) {
47
+ const builtResult = {}; // Using any for intermediate dynamic construction.
48
+ for (const stringStatusKey in schemas) {
49
+ if (Object.prototype.hasOwnProperty.call(schemas, stringStatusKey)) {
50
+ const numericKey = parseInt(stringStatusKey);
51
+ const schemaOrMarker = schemas[numericKey];
52
+ if (schemaOrMarker) { // Check if schemaOrMarker is defined (due to Partial)
53
+ if (schemaOrMarker instanceof TsTypeMarker) {
54
+ // For TsTypeMarker, create a ZodObject with data typed as z.any() at runtime.
55
+ // The actual type T is carried by CreateResponsesReturnType for compile-time inference.
56
+ builtResult[numericKey] = zod_1.z.object({
57
+ data: zod_1.z.any(), // Runtime placeholder
58
+ });
59
+ }
60
+ else if (schemaOrMarker instanceof zod_1.ZodType) { // It's a Zod schema
61
+ builtResult[numericKey] = createSuccessUnifiedResponseSchema(schemaOrMarker);
62
+ }
63
+ // Note: If schemaOrMarker is something else, it would be a type error
64
+ // based on InputSchemaOrMarker, or this runtime check would skip it.
65
+ }
66
+ }
67
+ }
68
+ // Always set/overwrite the 422 response to use errorUnifiedResponseSchema
69
+ builtResult[422] = errorUnifiedResponseSchema;
70
+ // Cast to the more specific return type at the end.
71
+ // This is safe if builtResult's structure matches CreateResponsesReturnType<TInputMap>.
72
+ return builtResult;
73
+ }
74
+ exports.createResponses = createResponses;
75
+ // Helper function to ensure the definition conforms to ApiDefinitionSchema
76
+ // while preserving the literal types of the passed object.
77
+ function createApiDefinition(definition) {
78
+ return definition;
79
+ }
80
+ exports.createApiDefinition = createApiDefinition;
@@ -0,0 +1,29 @@
1
+ import express from "express";
2
+ import { ApiDefinitionSchema, ApiBody, ApiParams, ApiQuery, InferDataFromUnifiedResponse } from './definition';
3
+ export interface TypedRequest<TDef extends ApiDefinitionSchema, TDomain extends keyof TDef, TRouteKey extends keyof TDef[TDomain], // Using direct keyof for simplicity here
4
+ P extends ApiParams<TDef, TDomain, TRouteKey> = ApiParams<TDef, TDomain, TRouteKey>, ReqBody extends ApiBody<TDef, TDomain, TRouteKey> = ApiBody<TDef, TDomain, TRouteKey>, Q extends ApiQuery<TDef, TDomain, TRouteKey> = ApiQuery<TDef, TDomain, TRouteKey>, L extends Record<string, any> = Record<string, any>> extends express.Request<P, any, ReqBody, Q, L> {
5
+ }
6
+ type ResponseDataForStatus<TDef extends ApiDefinitionSchema, TDomain extends keyof TDef, TRouteName extends keyof TDef[TDomain], TStatus extends keyof TDef[TDomain][TRouteName]['responses'] & number> = InferDataFromUnifiedResponse<TDef[TDomain][TRouteName]['responses'][TStatus]>;
7
+ type RespondFunction<TDef extends ApiDefinitionSchema, TDomain extends keyof TDef, TRouteName extends keyof TDef[TDomain]> = <TStatusLocal extends keyof TDef[TDomain][TRouteName]['responses'] & number>(status: TStatusLocal, data: ResponseDataForStatus<TDef, TDomain, TRouteName, TStatusLocal>) => void;
8
+ export interface TypedResponse<TDef extends ApiDefinitionSchema, TDomain extends keyof TDef, TRouteName extends keyof TDef[TDomain], L extends Record<string, any> = Record<string, any>> extends express.Response<any, L> {
9
+ respond: RespondFunction<TDef, TDomain, TRouteName>;
10
+ json: <B = any>(body: B) => this;
11
+ }
12
+ export declare function createRouteHandler<TDef extends ApiDefinitionSchema, TDomain extends keyof TDef, TRouteKey extends keyof TDef[TDomain]>(domain: TDomain, routeKey: TRouteKey, handler: (req: TypedRequest<TDef, TDomain, TRouteKey>, res: TypedResponse<TDef, TDomain, TRouteKey>) => Promise<void> | void): {
13
+ domain: TDomain;
14
+ routeKey: TRouteKey;
15
+ handler: (req: TypedRequest<TDef, TDomain, TRouteKey>, res: TypedResponse<TDef, TDomain, TRouteKey>) => Promise<void> | void;
16
+ };
17
+ export declare function makeRouteHandlerCreator<TDef extends ApiDefinitionSchema>(): <TDomain extends keyof TDef, TRouteKey extends keyof TDef[TDomain]>(domain: TDomain, routeKey: TRouteKey, handler: (req: TypedRequest<TDef, TDomain, TRouteKey, ApiParams<TDef, TDomain, TRouteKey>, ApiBody<TDef, TDomain, TRouteKey>, ApiQuery<TDef, TDomain, TRouteKey>, Record<string, any>>, res: TypedResponse<TDef, TDomain, TRouteKey, Record<string, any>>) => Promise<void> | void) => {
18
+ domain: TDomain;
19
+ routeKey: TRouteKey;
20
+ handler: (req: TypedRequest<TDef, TDomain, TRouteKey, ApiParams<TDef, TDomain, TRouteKey>, ApiBody<TDef, TDomain, TRouteKey>, ApiQuery<TDef, TDomain, TRouteKey>, Record<string, any>>, res: TypedResponse<TDef, TDomain, TRouteKey, Record<string, any>>) => void | Promise<void>;
21
+ };
22
+ export type SpecificRouteHandler<TDef extends ApiDefinitionSchema> = {
23
+ [TDomain_ in keyof TDef]: {
24
+ [TRouteKey_ in keyof TDef[TDomain_]]: ReturnType<typeof createRouteHandler<TDef, TDomain_, TRouteKey_>>;
25
+ }[keyof TDef[TDomain_]];
26
+ }[keyof TDef];
27
+ export declare function registerRouteHandlers<TDef extends ApiDefinitionSchema>(app: express.Express, apiDefinition: TDef, // Pass the actual API definition object
28
+ routeHandlers: Array<SpecificRouteHandler<TDef>>): void;
29
+ export {};