ts-typed-api 0.1.0 → 0.1.2

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 (38) hide show
  1. package/README.md +58 -1
  2. package/dist/definition.d.ts +70 -0
  3. package/dist/definition.js +44 -1
  4. package/dist/handler.js +159 -0
  5. package/dist/index.d.ts +1 -0
  6. package/dist/router.d.ts +7 -1
  7. package/examples/file-upload-example.ts +158 -0
  8. package/package.json +3 -1
  9. package/src/definition.ts +97 -0
  10. package/src/handler.ts +159 -1
  11. package/src/index.ts +1 -0
  12. package/src/router.ts +8 -1
  13. package/dist/apiClient.d.ts +0 -147
  14. package/dist/apiClient.js +0 -206
  15. package/dist/example-server/client.d.ts +0 -2
  16. package/dist/example-server/client.js +0 -19
  17. package/dist/example-server/definitions.d.ts +0 -157
  18. package/dist/example-server/definitions.js +0 -35
  19. package/dist/example-server/server.d.ts +0 -1
  20. package/dist/example-server/server.js +0 -66
  21. package/dist/openapiGenerator.d.ts +0 -2
  22. package/dist/openapiGenerator.js +0 -119
  23. package/dist/router/definition.d.ts +0 -118
  24. package/dist/router/definition.js +0 -80
  25. package/dist/router/router.d.ts +0 -29
  26. package/dist/router/router.js +0 -168
  27. package/dist/src/router/client.d.ts +0 -151
  28. package/dist/src/router/client.js +0 -215
  29. package/dist/src/router/definition.d.ts +0 -121
  30. package/dist/src/router/definition.js +0 -79
  31. package/dist/src/router/handler.d.ts +0 -16
  32. package/dist/src/router/handler.js +0 -170
  33. package/dist/src/router/index.d.ts +0 -5
  34. package/dist/src/router/index.js +0 -22
  35. package/dist/src/router/object-handlers.d.ts +0 -16
  36. package/dist/src/router/object-handlers.js +0 -39
  37. package/dist/src/router/router.d.ts +0 -18
  38. package/dist/src/router/router.js +0 -20
@@ -1,168 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.registerRouteHandlers = exports.makeRouteHandlerCreator = exports.createRouteHandler = void 0;
4
- const zod_1 = require("zod");
5
- // Type-safe route handler creation function, now generic over TDef
6
- // This function is called within a context where TDef is known (e.g. specific handlers file)
7
- function createRouteHandler(domain, routeKey, handler) {
8
- // The returned object includes enough type information (domain, routeKey)
9
- // for registerRouteHandlers to correctly associate it with TDef.
10
- // The phantom type TDef is carried by the function's generic signature.
11
- return { domain, routeKey, handler };
12
- }
13
- exports.createRouteHandler = createRouteHandler;
14
- // Factory function to create a route handler creator for a specific API definition
15
- function makeRouteHandlerCreator() {
16
- return function createHandler(domain, routeKey, handler) {
17
- // We call the original createRouteHandler, but TDef is fixed here by the factory's closure.
18
- // TDomain and TRouteKey are inferred from the arguments to createHandler.
19
- return createRouteHandler(domain, routeKey, handler);
20
- };
21
- }
22
- exports.makeRouteHandlerCreator = makeRouteHandlerCreator;
23
- // Register route handlers with Express, now generic over TDef
24
- function registerRouteHandlers(app, apiDefinition, // Pass the actual API definition object
25
- routeHandlers // Use the generic handler type
26
- ) {
27
- routeHandlers.forEach((specificHandlerIterationItem) => {
28
- const { domain, routeKey, handler } = specificHandlerIterationItem; // Use 'as any' for simplicity in destructuring union
29
- const currentDomain = domain;
30
- const currentRouteKey = routeKey;
31
- // Use the passed apiDefinition object
32
- const routeDefinition = apiDefinition[currentDomain][currentRouteKey];
33
- if (!routeDefinition) {
34
- console.error(`Route definition not found for domain "${String(currentDomain)}" and routeKey "${String(currentRouteKey)}"`);
35
- return;
36
- }
37
- const { path, method } = routeDefinition;
38
- const expressMiddleware = async (expressReq, expressRes) => {
39
- try {
40
- // Ensure TDef is correctly used for type inference if this section needs it.
41
- // Currently, parsedParams,Query,Body are based on runtime routeDefinition.
42
- const parsedParams = ('params' in routeDefinition && routeDefinition.params)
43
- ? routeDefinition.params.parse(expressReq.params)
44
- : expressReq.params;
45
- const parsedQuery = ('query' in routeDefinition && routeDefinition.query)
46
- ? routeDefinition.query.parse(expressReq.query)
47
- : expressReq.query;
48
- const parsedBody = (method === 'POST' || method === 'PUT') && ('body' in routeDefinition && routeDefinition.body)
49
- ? routeDefinition.body.parse(expressReq.body)
50
- : expressReq.body;
51
- // Construct TypedRequest using TDef, currentDomain, currentRouteKey
52
- const finalTypedReq = {
53
- ...expressReq,
54
- params: parsedParams,
55
- query: parsedQuery,
56
- body: parsedBody,
57
- };
58
- // Augment expressRes with the .respond method, using TDef
59
- const typedExpressRes = expressRes;
60
- typedExpressRes.respond = (status, dataForResponse) => {
61
- // Use the passed apiDefinition object
62
- const routeSchemaForHandler = apiDefinition[currentDomain][currentRouteKey];
63
- const responseSchemaForStatus = routeSchemaForHandler.responses[status];
64
- if (!responseSchemaForStatus) {
65
- console.error(`No response schema defined for status ${status} in route ${String(currentDomain)}/${String(currentRouteKey)}`);
66
- typedExpressRes.status(500).json({
67
- // data: null, // data field might not be part of error schema for 500 if not using unified
68
- error: [{ field: "general", type: "general", message: "Internal server error: Undefined response schema for status." }]
69
- });
70
- return;
71
- }
72
- let responseBodyToValidate; // This will be the object { data: ..., error: ... }
73
- if (status === 422) {
74
- // For 422, dataForResponse is expected to be the UnifiedError array or null
75
- // The schema for 422 is errorUnifiedResponseSchema, expecting { error: UnifiedError }
76
- responseBodyToValidate = {
77
- data: null,
78
- error: dataForResponse // dataForResponse should be UnifiedError for 422
79
- };
80
- }
81
- else {
82
- // For other statuses, dataForResponse is the actual data payload for the 'data' field
83
- // The schema is createSuccessUnifiedResponseSchema, expecting { data: ActualData }
84
- responseBodyToValidate = {
85
- data: dataForResponse,
86
- error: null // error is null for success
87
- };
88
- }
89
- // Validate the constructed responseBodyToValidate against the full schema for that status
90
- const validationResult = responseSchemaForStatus.safeParse(responseBodyToValidate);
91
- if (validationResult.success) {
92
- typedExpressRes.status(status).json(validationResult.data);
93
- }
94
- else {
95
- 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);
96
- console.error("Response body was:", responseBodyToValidate);
97
- typedExpressRes.status(500).json({
98
- // data: null,
99
- error: [{ field: "general", type: "general", message: "Internal server error: Constructed response failed validation." }]
100
- });
101
- }
102
- };
103
- const specificHandlerFn = handler;
104
- await specificHandlerFn(finalTypedReq, typedExpressRes);
105
- }
106
- catch (error) {
107
- if (error instanceof zod_1.z.ZodError) {
108
- const mappedErrors = error.errors.map(err => {
109
- let errorType = 'general';
110
- const pathZero = String(err.path[0]); // Ensure pathZero is a string
111
- if (pathZero === 'params')
112
- errorType = 'param'; // Corrected: 'params' from path maps to 'param' type
113
- else if (pathZero === 'query')
114
- errorType = 'query';
115
- else if (pathZero === 'body')
116
- errorType = 'body';
117
- return {
118
- field: err.path.join('.') || 'request',
119
- message: err.message,
120
- type: errorType,
121
- };
122
- });
123
- const errorResponseBody = { data: null, error: mappedErrors };
124
- const schema422 = routeDefinition.responses[422];
125
- if (schema422) {
126
- const validationResult = schema422.safeParse(errorResponseBody);
127
- if (validationResult.success) {
128
- expressRes.status(422).json(validationResult.data);
129
- }
130
- else {
131
- console.error("FATAL: Constructed 422 error response failed its own schema validation.", validationResult.error.errors);
132
- expressRes.status(500).json({ error: [{ field: "general", type: "general", message: "Internal server error constructing validation error response." }] });
133
- }
134
- }
135
- else {
136
- console.error("Error: 422 schema not found for route, sending raw Zod errors.");
137
- expressRes.status(422).json({ error: mappedErrors }); // Fallback
138
- }
139
- }
140
- else if (error instanceof Error) {
141
- console.error(`Error in ${method} ${path}:`, error.message, error.stack);
142
- expressRes.status(500).json({ error: [{ field: "general", type: "general", message: 'Internal server error' }] });
143
- }
144
- else {
145
- console.error(`Unknown error in ${method} ${path}:`, error);
146
- expressRes.status(500).json({ error: [{ field: "general", type: "general", message: 'An unknown error occurred' }] });
147
- }
148
- }
149
- };
150
- switch (method.toUpperCase()) {
151
- case 'GET':
152
- app.get(path, expressMiddleware);
153
- break;
154
- case 'POST':
155
- app.post(path, expressMiddleware);
156
- break;
157
- case 'PUT':
158
- app.put(path, expressMiddleware);
159
- break;
160
- case 'DELETE':
161
- app.delete(path, expressMiddleware);
162
- break;
163
- default:
164
- console.warn(`Unsupported HTTP method: ${method} for path ${path}`);
165
- }
166
- });
167
- }
168
- exports.registerRouteHandlers = registerRouteHandlers;
@@ -1,151 +0,0 @@
1
- import { type ApiDefinitionSchema as BaseApiDefinitionSchema, type ApiClientParams, type ApiClientQuery, type ApiClientBody, type RouteSchema, type UnifiedError, type InferDataFromUnifiedResponse } from "./definition";
2
- import { type ZodTypeAny } from 'zod';
3
- /**
4
- * Options for an HTTP request made by an adapter.
5
- */
6
- export interface HttpRequestOptions {
7
- method: RouteSchema['method'];
8
- headers?: Record<string, string>;
9
- body?: string | FormData;
10
- }
11
- /**
12
- * Represents an HTTP response from an adapter.
13
- * @template T The expected type of the JSON body.
14
- */
15
- export interface HttpResponse<T = any> {
16
- status: number;
17
- headers: Headers;
18
- json(): Promise<T>;
19
- text(): Promise<string>;
20
- /**
21
- * Gets the underlying raw response object from the adapter (e.g., Fetch API's Response object).
22
- * The type of this object depends on the adapter implementation.
23
- */
24
- getRawResponse(): any;
25
- }
26
- /**
27
- * Interface for an HTTP client adapter.
28
- * Allows swapping out the underlying HTTP request mechanism (e.g., fetch, axios).
29
- */
30
- export interface HttpClientAdapter {
31
- /**
32
- * Makes an HTTP request.
33
- * @template T The expected type of the JSON response body.
34
- * @param url The URL to request.
35
- * @param options The request options.
36
- * @returns A promise that resolves to an HttpResponse.
37
- */
38
- request<T = any>(url: string, options: HttpRequestOptions): Promise<HttpResponse<T>>;
39
- }
40
- /**
41
- * An HttpClientAdapter implementation that uses the native Fetch API.
42
- */
43
- export declare class FetchHttpClientAdapter implements HttpClientAdapter {
44
- request<T = any>(url: string, options: HttpRequestOptions): Promise<HttpResponse<T>>;
45
- }
46
- type GetRoute<TActualDef extends BaseApiDefinitionSchema, TDomain extends keyof TActualDef['endpoints'], K extends keyof TActualDef['endpoints'][TDomain]> = TActualDef['endpoints'][TDomain][K] extends infer Rte ? Rte extends RouteSchema ? Rte : never : never;
47
- type GetResponses<Rte extends RouteSchema> = Rte['responses'];
48
- /**
49
- * Discriminated union type for the result of callApi.
50
- * @template TDef The specific ApiDefinition structure being used.
51
- * @template TDomain The domain (controller) of the API.
52
- * @template TRouteKey The key of the route within the domain.
53
- */
54
- type ApiCallResultPayload<S_STATUS_NUM extends number, ActualSchema extends ZodTypeAny> = S_STATUS_NUM extends 422 ? {
55
- status: S_STATUS_NUM;
56
- error: UnifiedError;
57
- rawResponse: any;
58
- data?: undefined;
59
- } : S_STATUS_NUM extends 204 ? {
60
- status: S_STATUS_NUM;
61
- data: void;
62
- rawResponse: any;
63
- error?: undefined;
64
- } : {
65
- status: S_STATUS_NUM;
66
- data: InferDataFromUnifiedResponse<ActualSchema>;
67
- rawResponse: any;
68
- error?: undefined;
69
- };
70
- export type ApiCallResult<TActualDef extends BaseApiDefinitionSchema, TDomain extends keyof TActualDef['endpoints'], TRouteKey extends keyof TActualDef['endpoints'][TDomain], CurrentRoute extends RouteSchema = GetRoute<TActualDef, TDomain, TRouteKey>, ResponsesMap = GetResponses<CurrentRoute>> = ResponsesMap extends Record<any, ZodTypeAny> ? {
71
- [S_STATUS_NUM in keyof ResponsesMap]: S_STATUS_NUM extends number ? ResponsesMap[S_STATUS_NUM] extends infer ActualSchema extends ZodTypeAny ? ApiCallResultPayload<S_STATUS_NUM, ActualSchema> : never : never;
72
- }[keyof ResponsesMap] : never;
73
- /**
74
- * Options for the callApi method.
75
- * @template TDef The specific ApiDefinition structure being used.
76
- * @template TDomainParam The domain (controller) of the API.
77
- * @template TRouteKeyParam The key of the route within the domain.
78
- */
79
- export type CallApiOptions<TActualDef extends BaseApiDefinitionSchema, // Made generic over TActualDef
80
- TDomainParam extends keyof TActualDef['endpoints'], TRouteKeyParam extends keyof TActualDef['endpoints'][TDomainParam]> = {
81
- params?: ApiClientParams<TActualDef, TDomainParam, TRouteKeyParam>;
82
- query?: ApiClientQuery<TActualDef, TDomainParam, TRouteKeyParam>;
83
- body?: ApiClientBody<TActualDef, TDomainParam, TRouteKeyParam>;
84
- headers?: Record<string, string>;
85
- };
86
- /**
87
- * A client for making API calls defined by an ApiDefinition.
88
- * It uses an HttpClientAdapter for making actual HTTP requests and supports persistent headers.
89
- */
90
- export declare class ApiClient<TActualDef extends BaseApiDefinitionSchema> {
91
- private baseUrl;
92
- private apiDefinitionObject;
93
- private adapter;
94
- private persistentHeaders;
95
- /**
96
- * Creates an instance of ApiClient.
97
- * @param baseUrl The base URL for all API calls (e.g., 'http://localhost:3001').
98
- * @param apiDefinitionObject The API definition object.
99
- * @param adapter An instance of HttpClientAdapter to use for requests.
100
- */
101
- constructor(baseUrl: string, apiDefinitionObject: TActualDef, // Parameter uses TActualDef
102
- adapter: HttpClientAdapter);
103
- /**
104
- * Sets a persistent header that will be included in all subsequent API calls.
105
- * If the header already exists, its value will be updated.
106
- * @param name The name of the header.
107
- * @param value The value of the header.
108
- */
109
- setHeader(name: string, value: string): void;
110
- /**
111
- * Gets the value of a persistent header.
112
- * @param name The name of the header.
113
- * @returns The value of the header, or undefined if not set.
114
- */
115
- getHeader(name: string): string | undefined;
116
- /**
117
- * Removes a persistent header.
118
- * @param name The name of the header to remove.
119
- */
120
- removeHeader(name: string): void;
121
- /**
122
- * Clears all persistent headers.
123
- */
124
- clearHeaders(): void;
125
- /**
126
- * Gets the full base URL including any prefix from the API definition.
127
- * @returns The base URL with prefix applied.
128
- */
129
- private getBaseUrlWithPrefix;
130
- /**
131
- * Makes an API call to a specified domain and route.
132
- * @template TDomain The domain (controller) of the API.
133
- * @template TRouteKey The key of the route within the domain.
134
- * @template TInferredHandlers A type inferred from the handlers object, ensuring all defined statuses are handled.
135
- * @param domain The API domain (e.g., 'user').
136
- * @param routeKey The API route key (e.g., 'getUsers').
137
- * @param callData Optional parameters, query, body, and headers for the request.
138
- * @param handlers An object where keys are status codes and values are handler functions for those statuses.
139
- * @returns A promise that resolves to the return value of the executed handler.
140
- * @throws Error if the route configuration is invalid, a network error occurs, an unhandled status code is received, or JSON parsing fails.
141
- */
142
- callApi<TDomain extends keyof TActualDef['endpoints'], TRouteKey extends keyof TActualDef['endpoints'][TDomain], TInferredHandlers extends {
143
- [KStatus in ApiCallResult<TActualDef, TDomain, TRouteKey>['status']]: (payload: Extract<ApiCallResult<TActualDef, TDomain, TRouteKey>, {
144
- status: KStatus;
145
- }>) => any;
146
- }>(domain: TDomain, routeKey: TRouteKey, callData: CallApiOptions<TActualDef, TDomain, TRouteKey> | undefined, // Uses TActualDef
147
- handlers: TInferredHandlers): Promise<{
148
- [SKey in keyof TInferredHandlers]: TInferredHandlers[SKey] extends (...args: any[]) => infer R ? R : never;
149
- }[keyof TInferredHandlers]>;
150
- }
151
- export {};
@@ -1,215 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ApiClient = exports.FetchHttpClientAdapter = void 0;
4
- // --- Fetch Implementation of the Adapter ---
5
- /**
6
- * An HttpClientAdapter implementation that uses the native Fetch API.
7
- */
8
- class FetchHttpClientAdapter {
9
- async request(url, options) {
10
- const fetchOptions = {
11
- method: options.method,
12
- headers: options.headers,
13
- // Note: `credentials` (e.g., 'include' for cookies) is not set by default.
14
- // It can be configured by extending this adapter or by managing cookies via the 'Cookie' header.
15
- };
16
- if (options.body !== undefined) {
17
- fetchOptions.body = options.body;
18
- }
19
- const nativeFetchResponse = await fetch(url, fetchOptions);
20
- return {
21
- status: nativeFetchResponse.status,
22
- headers: nativeFetchResponse.headers,
23
- json: () => nativeFetchResponse.json(),
24
- text: () => nativeFetchResponse.text(),
25
- getRawResponse: () => nativeFetchResponse,
26
- };
27
- }
28
- }
29
- exports.FetchHttpClientAdapter = FetchHttpClientAdapter;
30
- // --- API Client Class ---
31
- /**
32
- * A client for making API calls defined by an ApiDefinition.
33
- * It uses an HttpClientAdapter for making actual HTTP requests and supports persistent headers.
34
- */
35
- class ApiClient {
36
- /**
37
- * Creates an instance of ApiClient.
38
- * @param baseUrl The base URL for all API calls (e.g., 'http://localhost:3001').
39
- * @param apiDefinitionObject The API definition object.
40
- * @param adapter An instance of HttpClientAdapter to use for requests.
41
- */
42
- constructor(baseUrl, apiDefinitionObject, // Parameter uses TActualDef
43
- adapter) {
44
- this.persistentHeaders = {};
45
- this.baseUrl = baseUrl.replace(/\/$/, ''); // Remove trailing slash
46
- this.apiDefinitionObject = apiDefinitionObject;
47
- this.adapter = adapter;
48
- }
49
- /**
50
- * Sets a persistent header that will be included in all subsequent API calls.
51
- * If the header already exists, its value will be updated.
52
- * @param name The name of the header.
53
- * @param value The value of the header.
54
- */
55
- setHeader(name, value) {
56
- this.persistentHeaders[name] = value;
57
- }
58
- /**
59
- * Gets the value of a persistent header.
60
- * @param name The name of the header.
61
- * @returns The value of the header, or undefined if not set.
62
- */
63
- getHeader(name) {
64
- return this.persistentHeaders[name];
65
- }
66
- /**
67
- * Removes a persistent header.
68
- * @param name The name of the header to remove.
69
- */
70
- removeHeader(name) {
71
- delete this.persistentHeaders[name];
72
- }
73
- /**
74
- * Clears all persistent headers.
75
- */
76
- clearHeaders() {
77
- this.persistentHeaders = {};
78
- }
79
- /**
80
- * Gets the full base URL including any prefix from the API definition.
81
- * @returns The base URL with prefix applied.
82
- */
83
- getBaseUrlWithPrefix() {
84
- const prefix = this.apiDefinitionObject.prefix;
85
- if (prefix) {
86
- const cleanPrefix = prefix.startsWith('/') ? prefix : `/${prefix}`;
87
- return this.baseUrl + cleanPrefix.replace(/\/$/, '');
88
- }
89
- return this.baseUrl;
90
- }
91
- /**
92
- * Makes an API call to a specified domain and route.
93
- * @template TDomain The domain (controller) of the API.
94
- * @template TRouteKey The key of the route within the domain.
95
- * @template TInferredHandlers A type inferred from the handlers object, ensuring all defined statuses are handled.
96
- * @param domain The API domain (e.g., 'user').
97
- * @param routeKey The API route key (e.g., 'getUsers').
98
- * @param callData Optional parameters, query, body, and headers for the request.
99
- * @param handlers An object where keys are status codes and values are handler functions for those statuses.
100
- * @returns A promise that resolves to the return value of the executed handler.
101
- * @throws Error if the route configuration is invalid, a network error occurs, an unhandled status code is received, or JSON parsing fails.
102
- */
103
- async callApi(domain, routeKey, callData, // Uses TActualDef
104
- handlers) {
105
- const routeInfo = this.apiDefinitionObject.endpoints[domain][routeKey]; // Accessing from TActualDef instance
106
- if (!routeInfo || typeof routeInfo.path !== 'string') {
107
- throw new Error(`API route configuration ${String(domain)}.${String(routeKey)} not found or invalid.`);
108
- }
109
- let urlPath = routeInfo.path;
110
- if (callData?.params) {
111
- const params = callData.params;
112
- for (const key in params) {
113
- if (Object.prototype.hasOwnProperty.call(params, key) && params[key] !== undefined) {
114
- urlPath = urlPath.replace(`:${key}`, String(params[key]));
115
- }
116
- }
117
- }
118
- const url = new URL(this.getBaseUrlWithPrefix() + urlPath);
119
- if (callData?.query) {
120
- const queryParams = callData.query;
121
- for (const key in queryParams) {
122
- if (Object.prototype.hasOwnProperty.call(queryParams, key) && queryParams[key] !== undefined) {
123
- url.searchParams.append(key, String(queryParams[key]));
124
- }
125
- }
126
- }
127
- const requestHeaders = {
128
- ...this.persistentHeaders,
129
- 'Content-Type': 'application/json', // Default, can be overridden by callData.headers or persistentHeaders
130
- ...(callData?.headers || {}),
131
- };
132
- const adapterRequestOptions = {
133
- method: routeInfo.method,
134
- headers: requestHeaders,
135
- };
136
- if (routeInfo.method !== 'GET' && routeInfo.method !== 'HEAD' && callData?.body !== undefined) {
137
- adapterRequestOptions.body = JSON.stringify(callData.body);
138
- }
139
- let adapterResponse;
140
- try {
141
- adapterResponse = await this.adapter.request(url.toString(), adapterRequestOptions);
142
- }
143
- catch (networkError) {
144
- const errorMessage = networkError instanceof Error ? networkError.message : `Unknown network error calling API ${String(domain)}.${String(routeKey)}`;
145
- console.error(`Network error for ${String(domain)}.${String(routeKey)}:`, networkError);
146
- throw new Error(`Network error: ${errorMessage}`);
147
- }
148
- const runtimeStatus = adapterResponse.status;
149
- const definedStatusCodes = Object.keys(routeInfo.responses).map(Number);
150
- if (!definedStatusCodes.includes(runtimeStatus)) {
151
- const responseText = await adapterResponse.text().catch(() => "Could not read response text.");
152
- const errorMsg = `API ${String(domain)}.${String(routeKey)}: Received unhandled status code ${runtimeStatus}. Expected one of: ${definedStatusCodes.join(', ')}. Response: ${responseText}`;
153
- console.error(errorMsg);
154
- throw new Error(errorMsg);
155
- }
156
- const currentStatusLiteral = runtimeStatus;
157
- // apiResultPayload now uses ApiCallResult with TActualDef
158
- let apiResultPayload;
159
- if (currentStatusLiteral === 204) {
160
- apiResultPayload = {
161
- status: 204,
162
- data: undefined, // data is undefined for 204
163
- rawResponse: adapterResponse.getRawResponse(),
164
- };
165
- }
166
- else {
167
- let responseBodyJson;
168
- const contentType = adapterResponse.headers.get("content-type");
169
- if (contentType && contentType.includes("application/json")) {
170
- try {
171
- responseBodyJson = await adapterResponse.json();
172
- }
173
- catch (e) {
174
- const parseErrorMsg = `API ${String(domain)}.${String(routeKey)}: Failed to parse JSON for status ${currentStatusLiteral}. Error: ${e instanceof Error ? e.message : String(e)}`;
175
- console.error(parseErrorMsg, adapterResponse.getRawResponse());
176
- throw new Error(parseErrorMsg);
177
- }
178
- }
179
- else if (runtimeStatus >= 400) { // Handle non-JSON error responses
180
- const responseText = await adapterResponse.text().catch(() => "Could not read response text.");
181
- if (currentStatusLiteral === 422) { // Try to conform to UnifiedError for 422
182
- responseBodyJson = {
183
- error: [{ field: 'general', type: 'general', message: `Non-JSON error response for 422: ${responseText}` }] // Adjusted: type to 'general'
184
- };
185
- }
186
- else { // For other non-JSON errors, data will likely be undefined. Log a warning.
187
- console.warn(`API ${String(domain)}.${String(routeKey)}: Received non-JSON response for status ${currentStatusLiteral}. Response: ${responseText}`);
188
- // responseBodyJson remains undefined or as is, data extraction below will handle it.
189
- }
190
- }
191
- if (currentStatusLiteral === 422) {
192
- const errorData = responseBodyJson?.error || [{ field: 'general', type: 'general', message: `HTTP error 422: ${await adapterResponse.text().catch(() => 'Unknown error text')}` }];
193
- // Assign directly to apiResultPayload, relying on its type ApiCallResult<TActualDef, TDomain, TRouteKey>
194
- // to correctly match the 422 variant.
195
- apiResultPayload = {
196
- status: 422,
197
- error: errorData,
198
- rawResponse: adapterResponse.getRawResponse(),
199
- }; // Force cast via unknown
200
- }
201
- else {
202
- // Assuming responseBodyJson is an object like { data: <actual_payload> }
203
- // as per backend contract for non-204/non-422 responses.
204
- apiResultPayload = {
205
- status: currentStatusLiteral,
206
- data: responseBodyJson.data, // Extract the actual data from the wrapped response
207
- rawResponse: adapterResponse.getRawResponse(),
208
- };
209
- }
210
- }
211
- const handler = handlers[apiResultPayload.status];
212
- return handler(apiResultPayload); // Reverting to `as any` as TS struggles with direct narrowing here
213
- }
214
- }
215
- exports.ApiClient = ApiClient;
@@ -1,121 +0,0 @@
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: "body" | "query" | "param" | "general";
16
- message: string;
17
- }, {
18
- field: string;
19
- type: "body" | "query" | "param" | "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: "body" | "query" | "param" | "general";
31
- message: string;
32
- }, {
33
- field: string;
34
- type: "body" | "query" | "param" | "general";
35
- message: string;
36
- }>, "many">>, {
37
- field: string;
38
- type: "body" | "query" | "param" | "general";
39
- message: string;
40
- }[], {
41
- field: string;
42
- type: "body" | "query" | "param" | "general";
43
- message: string;
44
- }[] | null>;
45
- }, "strip", z.ZodTypeAny, {
46
- error: {
47
- field: string;
48
- type: "body" | "query" | "param" | "general";
49
- message: string;
50
- }[];
51
- }, {
52
- error: {
53
- field: string;
54
- type: "body" | "query" | "param" | "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 = {
85
- prefix?: string;
86
- endpoints: Record<string, Record<string, RouteSchema>>;
87
- };
88
- export declare function createApiDefinition<T extends ApiDefinitionSchema>(definition: T): T;
89
- export type ApiRouteKey<TDef extends ApiDefinitionSchema, TDomain extends keyof TDef['endpoints']> = keyof TDef['endpoints'][TDomain];
90
- export type ApiRoute<TDef extends ApiDefinitionSchema, TDomain extends keyof TDef['endpoints'], TRouteName extends ApiRouteKey<TDef, TDomain>> = TDef['endpoints'][TDomain][TRouteName];
91
- export type InferDataFromUnifiedResponse<S extends ZodTypeAny> = S extends z.ZodVoid ? void : z.infer<S> extends {
92
- data: infer D;
93
- } ? D extends null ? null : D extends (infer ActualD | null) ? ActualD extends void ? void : ActualD : D : never;
94
- export type ApiResponse<TDef extends ApiDefinitionSchema, TDomain extends keyof TDef['endpoints'], TRouteName extends ApiRouteKey<TDef, TDomain>> = TDef['endpoints'][TDomain][TRouteName] extends {
95
- responses: infer R;
96
- } ? R extends {
97
- 200: infer R200 extends ZodTypeAny;
98
- } ? InferDataFromUnifiedResponse<R200> : R extends {
99
- 201: infer R201 extends ZodTypeAny;
100
- } ? InferDataFromUnifiedResponse<R201> : R extends {
101
- 204: infer R204 extends ZodTypeAny;
102
- } ? InferDataFromUnifiedResponse<R204> : any : any;
103
- export type ApiBody<TDef extends ApiDefinitionSchema, TDomain extends keyof TDef['endpoints'], TRouteName extends ApiRouteKey<TDef, TDomain>> = TDef['endpoints'][TDomain][TRouteName] extends {
104
- body: infer B extends z.ZodTypeAny;
105
- } ? z.infer<B> : Record<string, any>;
106
- export type ApiParams<TDef extends ApiDefinitionSchema, TDomain extends keyof TDef['endpoints'], TRouteName extends ApiRouteKey<TDef, TDomain>> = TDef['endpoints'][TDomain][TRouteName] extends {
107
- params: infer P extends z.ZodTypeAny;
108
- } ? z.infer<P> : Record<string, any>;
109
- export type ApiQuery<TDef extends ApiDefinitionSchema, TDomain extends keyof TDef['endpoints'], TRouteName extends ApiRouteKey<TDef, TDomain>> = TDef['endpoints'][TDomain][TRouteName] extends {
110
- query: infer Q extends z.ZodTypeAny;
111
- } ? z.infer<Q> : Record<string, any>;
112
- export type ApiClientBody<TDef extends ApiDefinitionSchema, TDomain extends keyof TDef['endpoints'], TRouteName extends ApiRouteKey<TDef, TDomain>> = TDef['endpoints'][TDomain][TRouteName] extends {
113
- body: infer B extends ZodTypeAny;
114
- } ? z.input<B> : undefined;
115
- export type ApiClientParams<TDef extends ApiDefinitionSchema, TDomain extends keyof TDef['endpoints'], TRouteName extends ApiRouteKey<TDef, TDomain>> = TDef['endpoints'][TDomain][TRouteName] extends {
116
- params: infer P extends ZodTypeAny;
117
- } ? z.input<P> : undefined;
118
- export type ApiClientQuery<TDef extends ApiDefinitionSchema, TDomain extends keyof TDef['endpoints'], TRouteName extends ApiRouteKey<TDef, TDomain>> = TDef['endpoints'][TDomain][TRouteName] extends {
119
- query: infer Q extends ZodTypeAny;
120
- } ? z.input<Q> : undefined;
121
- export {};