@abapify/speci 0.4.0 → 0.4.1

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.
@@ -0,0 +1,2 @@
1
+ import { _ as ExtractDescriptor, a as Inferrable, b as OperationFunction, g as Contract, p as Serializable, v as ExtractParams, x as Schema, y as OperationDescriptor } from "./types-wn9lAKzN.mjs";
2
+ export { Contract, ExtractDescriptor, ExtractParams, type Inferrable, OperationDescriptor, OperationFunction, Schema, type Serializable };
package/dist/index.mjs ADDED
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,188 @@
1
+ import { _ as ExtractDescriptor, a as Inferrable, b as OperationFunction, c as RestEndpointDescriptor, d as RestOperationFunction, f as SchemaLike, h as schema, i as InferSuccessResponse, l as RestMetadata, m as createInferrable, n as InferErrorResponse, o as ResponseMap, p as Serializable, r as InferSchema, s as RestContract, t as ExtractResponse, u as RestMethod, v as ExtractParams, x as Schema } from "../types-wn9lAKzN.mjs";
2
+
3
+ //#region src/rest/helpers.d.ts
4
+ /**
5
+ * Options for REST endpoint helpers
6
+ */
7
+ interface RestEndpointOptions<TBodySchema = unknown, TResponses extends ResponseMap = ResponseMap> {
8
+ /** Request body schema - can be Inferrable schema or plain type */
9
+ body?: TBodySchema;
10
+ /** Query parameters schema */
11
+ query?: Schema;
12
+ /** Headers schema */
13
+ headers?: Schema;
14
+ /** Response schemas by status code (optional when using shortcut syntax) */
15
+ responses?: TResponses;
16
+ /** Optional REST metadata */
17
+ metadata?: RestMetadata;
18
+ }
19
+ /**
20
+ * HTTP helper object with optional global responses
21
+ */
22
+ type Http<TGlobalResponses extends ResponseMap = {}> = {
23
+ get: {
24
+ <TSuccess = unknown>(path: string): RestEndpointDescriptor<'GET', string, never, {
25
+ 200: TSuccess;
26
+ } & TGlobalResponses>;
27
+ <TPath extends string, TResponses extends ResponseMap>(path: TPath, options: Omit<RestEndpointOptions<never, TResponses>, 'body'>): RestEndpointDescriptor<'GET', TPath, never, TResponses & TGlobalResponses>;
28
+ };
29
+ post: <TPath extends string, TBody = unknown, TResponses extends ResponseMap = ResponseMap>(path: TPath, options: RestEndpointOptions<TBody, TResponses>) => RestEndpointDescriptor<'POST', TPath, TBody, TResponses & TGlobalResponses>;
30
+ put: <TPath extends string, TBody = unknown, TResponses extends ResponseMap = ResponseMap>(path: TPath, options: RestEndpointOptions<TBody, TResponses>) => RestEndpointDescriptor<'PUT', TPath, TBody, TResponses & TGlobalResponses>;
31
+ patch: <TPath extends string, TBody = unknown, TResponses extends ResponseMap = ResponseMap>(path: TPath, options: RestEndpointOptions<TBody, TResponses>) => RestEndpointDescriptor<'PATCH', TPath, TBody, TResponses & TGlobalResponses>;
32
+ delete: {
33
+ (path: string): RestEndpointDescriptor<'DELETE', string, never, {
34
+ 204: undefined;
35
+ } & TGlobalResponses>;
36
+ <TPath extends string, TResponses extends ResponseMap>(path: TPath, options: Omit<RestEndpointOptions<never, TResponses>, 'body'>): RestEndpointDescriptor<'DELETE', TPath, never, TResponses & TGlobalResponses>;
37
+ };
38
+ head: <TPath extends string, TResponses extends ResponseMap>(path: TPath, options: Omit<RestEndpointOptions<never, TResponses>, 'body'>) => RestEndpointDescriptor<'HEAD', TPath, never, TResponses & TGlobalResponses>;
39
+ options: <TPath extends string, TResponses extends ResponseMap>(path: TPath, options: Omit<RestEndpointOptions<never, TResponses>, 'body'>) => RestEndpointDescriptor<'OPTIONS', TPath, never, TResponses & TGlobalResponses>;
40
+ };
41
+ /**
42
+ * Create HTTP helpers for defining REST endpoints
43
+ *
44
+ * @param globalResponses - Optional: Global response map to merge with all endpoint responses
45
+ *
46
+ * @example
47
+ * // Without global responses
48
+ * const http = createHttp();
49
+ * http.get('/users', {
50
+ * responses: { 200: User[], 404: NotFoundError }
51
+ * })
52
+ *
53
+ * @example
54
+ * // With global error responses
55
+ * const globalErrors = {
56
+ * 400: {} as ApiError,
57
+ * 401: {} as ApiError,
58
+ * 500: {} as ApiError
59
+ * };
60
+ *
61
+ * const api = createHttp(globalErrors);
62
+ * api.get('/users', {
63
+ * responses: { 200: [] as User[] }
64
+ * // 400, 401, 500 are automatically added
65
+ * })
66
+ */
67
+ declare function createHttp<TGlobalResponses extends ResponseMap = {}>(globalResponses?: TGlobalResponses): Http<TGlobalResponses>;
68
+ /**
69
+ * Default HTTP helper object
70
+ *
71
+ * @example
72
+ * import { http } from '@abapify/speci/rest'
73
+ *
74
+ * http.get('/users', {
75
+ * responses: { 200: User[], 404: NotFoundError }
76
+ * })
77
+ *
78
+ * http.delete('/users/123', {
79
+ * responses: { 204: undefined, 404: NotFoundError }
80
+ * })
81
+ */
82
+ declare const http: Http<{}>;
83
+ //#endregion
84
+ //#region src/rest/client/types.d.ts
85
+ /**
86
+ * HTTP error with typed payload
87
+ */
88
+ declare class HttpError<TPayload = unknown> extends Error {
89
+ readonly status: number;
90
+ readonly payload: TPayload;
91
+ constructor(status: number, payload: TPayload, message?: string);
92
+ }
93
+ /**
94
+ * HTTP request options
95
+ */
96
+ interface HttpRequestOptions {
97
+ method: string;
98
+ url: string;
99
+ body?: unknown;
100
+ query?: Record<string, any>;
101
+ headers?: Record<string, string>;
102
+ bodySchema?: unknown;
103
+ responses?: Record<number, unknown>;
104
+ }
105
+ /**
106
+ * HTTP adapter interface - consumer provides their own implementation
107
+ *
108
+ * @template TDefaultResponse - Default response type for all requests (useful for testing)
109
+ */
110
+ interface HttpAdapter<TDefaultResponse = unknown> {
111
+ /**
112
+ * Execute an HTTP request
113
+ */
114
+ request<TResponse = TDefaultResponse>(options?: HttpRequestOptions): Promise<TResponse>;
115
+ }
116
+ /**
117
+ * Client configuration
118
+ */
119
+ interface ClientConfig {
120
+ /** Base URL for all requests */
121
+ baseUrl: string;
122
+ /** HTTP adapter implementation */
123
+ adapter: HttpAdapter;
124
+ /** Default headers for all requests */
125
+ headers?: Record<string, string>;
126
+ /** Request interceptor */
127
+ onRequest?: (options: any) => any | Promise<any>;
128
+ /** Response interceptor */
129
+ onResponse?: (response: any) => any | Promise<any>;
130
+ /** Error interceptor */
131
+ onError?: (error: any) => any | Promise<any>;
132
+ }
133
+ /**
134
+ * Check if body is an Inferrable schema (has _infer property)
135
+ */
136
+ type IsInferrableBody<TBody> = TBody extends {
137
+ _infer?: any;
138
+ } ? true : false;
139
+ /**
140
+ * Check if method is PUT or PATCH (update operations that typically send partial data)
141
+ */
142
+ type IsUpdateMethod<TMethod> = TMethod extends 'PUT' | 'PATCH' ? true : false;
143
+ /**
144
+ * Extract body type from a REST endpoint descriptor
145
+ * Only extracts if the body is an Inferrable schema
146
+ * For PUT/PATCH methods, returns Partial<T> since updates typically send partial data
147
+ */
148
+ type ExtractBodyType<TDescriptor> = TDescriptor extends RestEndpointDescriptor<infer TMethod, any, infer TBody, any> ? IsInferrableBody<TBody> extends true ? TBody extends {
149
+ _infer?: infer U;
150
+ } ? IsUpdateMethod<TMethod> extends true ? Partial<U> : U : never : never : never;
151
+ /**
152
+ * Build parameter list for a REST client method
153
+ * - If descriptor has a body with Inferrable schema, append the inferred type as a parameter
154
+ * - Otherwise, use the function's declared parameters
155
+ */
156
+ type BuildParams<T extends OperationFunction> = ExtractDescriptor<T> extends RestEndpointDescriptor ? ExtractBodyType<ExtractDescriptor<T>> extends never ? ExtractParams<T> : ExtractParams<T> extends [] ? [ExtractBodyType<ExtractDescriptor<T>>] : [...ExtractParams<T>, ExtractBodyType<ExtractDescriptor<T>>] : ExtractParams<T>;
157
+ /**
158
+ * Convert a REST operation function to a client method
159
+ * Only returns success response types (2xx) - errors are thrown as HttpError
160
+ * Includes typed error property for error response payloads
161
+ *
162
+ * Automatically infers parameter types from body schema if present
163
+ */
164
+ type RestClientMethod<T extends OperationFunction> = {
165
+ (...params: BuildParams<T>): Promise<ExtractDescriptor<T> extends RestEndpointDescriptor ? InferSuccessResponse<ExtractDescriptor<T>> : never>; /** Typed error response payload - use with HttpError */
166
+ error: ExtractDescriptor<T> extends RestEndpointDescriptor ? InferErrorResponse<ExtractDescriptor<T>> : never; /** Check if error is from this endpoint */
167
+ isError(error: unknown): error is HttpError<ExtractDescriptor<T> extends RestEndpointDescriptor ? InferErrorResponse<ExtractDescriptor<T>> : never>;
168
+ };
169
+ /**
170
+ * Convert a REST contract to a typed client.
171
+ * Uses Record<string, any> instead of Contract to allow concrete types without index signature.
172
+ */
173
+ type RestClient<T extends Record<string, any>> = { [K in keyof T]: T[K] extends OperationFunction ? RestClientMethod<T[K]> : T[K] extends Record<string, any> ? RestClient<T[K]> : never };
174
+ //#endregion
175
+ //#region src/rest/client/create-client.d.ts
176
+ /**
177
+ * Create a typed REST client from a contract
178
+ */
179
+ declare function createClient<T extends Record<string, any>>(contract: T, config: ClientConfig): RestClient<T>;
180
+ //#endregion
181
+ //#region src/rest/client/fetch-adapter.d.ts
182
+ /**
183
+ * Create a fetch-based HTTP adapter
184
+ */
185
+ declare function createFetchAdapter(options?: RequestInit): HttpAdapter;
186
+ //#endregion
187
+ export { ClientConfig, type ExtractResponse, HttpAdapter, HttpError, HttpRequestOptions, type InferSchema, type InferSuccessResponse, type Inferrable, type ResponseMap, RestClient, RestClientMethod, type RestContract, type RestEndpointDescriptor, type RestEndpointOptions, type RestMetadata, type RestMethod, type RestOperationFunction, type SchemaLike, type Serializable, createClient, createFetchAdapter, createHttp, createInferrable, http, schema };
188
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/rest/helpers.ts","../../src/rest/client/types.ts","../../src/rest/client/create-client.ts","../../src/rest/client/fetch-adapter.ts"],"mappings":"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"}
@@ -0,0 +1,277 @@
1
+ //#region src/rest/types.ts
2
+ /**
3
+ * Create an Inferrable schema with automatic type inference
4
+ * Cleaner than the manual _infer pattern
5
+ */
6
+ function createInferrable() {
7
+ return { _infer: void 0 };
8
+ }
9
+ /**
10
+ * Helper to wrap a schema with type information
11
+ * Use this if your schema doesn't implement Inferrable
12
+ *
13
+ * @example
14
+ * responses: { 200: schema(UserSchema, {} as User) }
15
+ */
16
+ function schema(schemaObject, _type) {
17
+ return schemaObject;
18
+ }
19
+
20
+ //#endregion
21
+ //#region src/rest/helpers.ts
22
+ /**
23
+ * Create HTTP helpers for defining REST endpoints
24
+ *
25
+ * @param globalResponses - Optional: Global response map to merge with all endpoint responses
26
+ *
27
+ * @example
28
+ * // Without global responses
29
+ * const http = createHttp();
30
+ * http.get('/users', {
31
+ * responses: { 200: User[], 404: NotFoundError }
32
+ * })
33
+ *
34
+ * @example
35
+ * // With global error responses
36
+ * const globalErrors = {
37
+ * 400: {} as ApiError,
38
+ * 401: {} as ApiError,
39
+ * 500: {} as ApiError
40
+ * };
41
+ *
42
+ * const api = createHttp(globalErrors);
43
+ * api.get('/users', {
44
+ * responses: { 200: [] as User[] }
45
+ * // 400, 401, 500 are automatically added
46
+ * })
47
+ */
48
+ function createHttp(globalResponses) {
49
+ const mergeResponses = (responses) => {
50
+ if (!globalResponses) return responses;
51
+ return {
52
+ ...globalResponses,
53
+ ...responses
54
+ };
55
+ };
56
+ return {
57
+ get: (path, options) => {
58
+ if (!options) return {
59
+ method: "GET",
60
+ path,
61
+ responses: mergeResponses({ 200: void 0 })
62
+ };
63
+ return {
64
+ method: "GET",
65
+ path,
66
+ ...options,
67
+ responses: mergeResponses(options.responses || { 200: void 0 })
68
+ };
69
+ },
70
+ post: (path, options) => {
71
+ const { body, responses, ...rest } = options;
72
+ return {
73
+ method: "POST",
74
+ path,
75
+ body,
76
+ ...rest,
77
+ responses: mergeResponses(responses)
78
+ };
79
+ },
80
+ put: (path, options) => {
81
+ const { body, responses, ...rest } = options;
82
+ return {
83
+ method: "PUT",
84
+ path,
85
+ body,
86
+ ...rest,
87
+ responses: mergeResponses(responses)
88
+ };
89
+ },
90
+ patch: (path, options) => {
91
+ const { body, responses, ...rest } = options;
92
+ return {
93
+ method: "PATCH",
94
+ path,
95
+ body,
96
+ ...rest,
97
+ responses: mergeResponses(responses)
98
+ };
99
+ },
100
+ delete: (path, options) => {
101
+ if (!options) return {
102
+ method: "DELETE",
103
+ path,
104
+ responses: mergeResponses({ 204: void 0 })
105
+ };
106
+ return {
107
+ method: "DELETE",
108
+ path,
109
+ ...options,
110
+ responses: mergeResponses(options.responses || { 204: void 0 })
111
+ };
112
+ },
113
+ head: (path, options) => ({
114
+ method: "HEAD",
115
+ path,
116
+ ...options,
117
+ responses: mergeResponses(options.responses)
118
+ }),
119
+ options: (path, options) => ({
120
+ method: "OPTIONS",
121
+ path,
122
+ ...options,
123
+ responses: mergeResponses(options.responses)
124
+ })
125
+ };
126
+ }
127
+ /**
128
+ * Default HTTP helper object
129
+ *
130
+ * @example
131
+ * import { http } from '@abapify/speci/rest'
132
+ *
133
+ * http.get('/users', {
134
+ * responses: { 200: User[], 404: NotFoundError }
135
+ * })
136
+ *
137
+ * http.delete('/users/123', {
138
+ * responses: { 204: undefined, 404: NotFoundError }
139
+ * })
140
+ */
141
+ const http = createHttp();
142
+
143
+ //#endregion
144
+ //#region src/rest/client/types.ts
145
+ /**
146
+ * HTTP error with typed payload
147
+ */
148
+ var HttpError = class extends Error {
149
+ constructor(status, payload, message) {
150
+ super(message || `HTTP ${status}`);
151
+ this.status = status;
152
+ this.payload = payload;
153
+ this.name = "HttpError";
154
+ }
155
+ };
156
+
157
+ //#endregion
158
+ //#region src/rest/client/create-client.ts
159
+ /**
160
+ * Extract path parameters from a path template
161
+ * Example: "/users/${id}/posts/${postId}" -> ["id", "postId"]
162
+ */
163
+ function extractPathParams(path) {
164
+ const matches = path.matchAll(/\$\{(\w+)\}/g);
165
+ return Array.from(matches, (m) => m[1]);
166
+ }
167
+ /**
168
+ * Replace path parameters with actual values
169
+ * Example: "/users/${id}" with {id: "123"} -> "/users/123"
170
+ */
171
+ function replacePath(path, params) {
172
+ return path.replaceAll(/\$\{(\w+)\}/g, (_, key) => {
173
+ const value = params[key];
174
+ if (value === void 0) throw new Error(`Missing path parameter: ${key}`);
175
+ return encodeURIComponent(String(value));
176
+ });
177
+ }
178
+ /**
179
+ * Check if a value is an Inferrable schema (has _infer property)
180
+ */
181
+ function isInferrableSchema(value) {
182
+ return value && typeof value === "object" && "_infer" in value;
183
+ }
184
+ /**
185
+ * Create a client method from an operation function
186
+ */
187
+ function createMethod(config, operationFn) {
188
+ const method = async (...args) => {
189
+ const descriptor = operationFn(...args);
190
+ const pathParamNames = descriptor.path ? extractPathParams(descriptor.path) : [];
191
+ const pathParams = {};
192
+ const firstArg = args[0];
193
+ if (firstArg && typeof firstArg === "object" && !Array.isArray(firstArg)) pathParamNames.forEach((name) => {
194
+ if (firstArg[name] !== void 0) pathParams[name] = firstArg[name];
195
+ });
196
+ else pathParamNames.forEach((name, index) => {
197
+ if (args[index] !== void 0) pathParams[name] = args[index];
198
+ });
199
+ let bodyData = descriptor.body;
200
+ let bodySchema = void 0;
201
+ const declaredParamCount = operationFn.length;
202
+ if (isInferrableSchema(descriptor.body)) {
203
+ bodySchema = descriptor.body;
204
+ bodyData = args[declaredParamCount];
205
+ }
206
+ const url = descriptor.path ? config.baseUrl + replacePath(descriptor.path, pathParams) : config.baseUrl;
207
+ let requestOptions = {
208
+ method: descriptor.method || "GET",
209
+ url,
210
+ body: bodyData,
211
+ bodySchema,
212
+ query: descriptor.query,
213
+ headers: {
214
+ ...config.headers,
215
+ ...descriptor.headers
216
+ },
217
+ responses: descriptor.responses
218
+ };
219
+ if (config.onRequest) requestOptions = await config.onRequest(requestOptions);
220
+ try {
221
+ let response = await config.adapter.request(requestOptions);
222
+ if (config.onResponse) response = await config.onResponse(response);
223
+ return response;
224
+ } catch (error) {
225
+ if (config.onError) return await config.onError(error);
226
+ throw error;
227
+ }
228
+ };
229
+ method.error = void 0;
230
+ method.isError = (error) => {
231
+ return error instanceof Error && error.name === "HttpError";
232
+ };
233
+ return method;
234
+ }
235
+ /**
236
+ * Create a typed REST client from a contract
237
+ */
238
+ function createClient(contract, config) {
239
+ const client = {};
240
+ for (const [key, value] of Object.entries(contract)) if (typeof value === "function") client[key] = createMethod(config, value);
241
+ else if (typeof value === "object" && value !== null) client[key] = createClient(value, config);
242
+ return client;
243
+ }
244
+
245
+ //#endregion
246
+ //#region src/rest/client/fetch-adapter.ts
247
+ /**
248
+ * Create a fetch-based HTTP adapter
249
+ */
250
+ function createFetchAdapter(options) {
251
+ return { async request(opts) {
252
+ const { method, url, body, query, headers } = opts;
253
+ const fullUrl = url + (query ? "?" + new URLSearchParams(query).toString() : "");
254
+ const response = await fetch(fullUrl, {
255
+ ...options,
256
+ method,
257
+ headers: {
258
+ "Content-Type": "application/json",
259
+ ...options?.headers,
260
+ ...headers
261
+ },
262
+ body: body ? JSON.stringify(body) : void 0
263
+ });
264
+ if (!response.ok) {
265
+ const errorData = await response.json().catch(() => ({
266
+ error: "HTTP Error",
267
+ message: response.statusText
268
+ }));
269
+ throw new HttpError(response.status, errorData);
270
+ }
271
+ return await response.json();
272
+ } };
273
+ }
274
+
275
+ //#endregion
276
+ export { HttpError, createClient, createFetchAdapter, createHttp, createInferrable, http, schema };
277
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/rest/types.ts","../../src/rest/helpers.ts","../../src/rest/client/types.ts","../../src/rest/client/create-client.ts","../../src/rest/client/fetch-adapter.ts"],"sourcesContent":["/**\n * Speci REST - REST-specific types\n */\n\nimport type { OperationDescriptor, Schema } from '../core/types';\n\n/**\n * HTTP methods for REST APIs\n */\nexport type RestMethod =\n | 'GET'\n | 'POST'\n | 'PUT'\n | 'PATCH'\n | 'DELETE'\n | 'HEAD'\n | 'OPTIONS';\n\n/**\n * Inferrable schema interface (explicit _infer property)\n *\n * Schemas that implement this interface will have their types automatically inferred.\n *\n * @example\n * const UserSchema = {\n * ...yourSchemaDefinition,\n * _infer: undefined as unknown as User\n * } as const;\n *\n * responses: { 200: UserSchema } // Type is User\n */\nexport interface Inferrable<T = unknown> {\n /** Type inference marker - never accessed at runtime */\n _infer?: T;\n}\n\n/**\n * Serializable schema interface (parse/build methods + Inferrable)\n *\n * Extends Inferrable so schemas with parse() automatically get _infer marker.\n * This is the preferred pattern for schema libraries like ts-xsd.\n *\n * @example\n * const UserSchema = {\n * parse: (raw: string): User => JSON.parse(raw),\n * build: (data: User): string => JSON.stringify(data),\n * };\n *\n * responses: { 200: UserSchema } // Type is User (inferred via _infer from Inferrable)\n */\nexport interface Serializable<T = unknown> extends Inferrable<T> {\n /** Parse raw string to typed object */\n parse(raw: string): T;\n /** Build typed object to string */\n build?(data: T): string;\n}\n\n/**\n * Create an Inferrable schema with automatic type inference\n * Cleaner than the manual _infer pattern\n */\nexport function createInferrable<T>(): Inferrable<T> {\n return { _infer: undefined as unknown as T };\n}\n\n/**\n * Infer type from a schema\n *\n * Uses _infer property from Inferrable<T> (which Serializable extends).\n * Falls back to original type if no _infer property.\n *\n * @example\n * type A = InferSchema<{ _infer?: User }> // User\n * type B = InferSchema<string> // string (fallback)\n */\nexport type InferSchema<T> = T extends { _infer?: infer U }\n ? NonNullable<U>\n : T;\n\n/**\n * Schema-like object - can be any schema library (Zod, JSON Schema, custom, etc.)\n * Adapters interpret these based on their capabilities.\n */\nexport type SchemaLike = unknown;\n\n/**\n * Helper to wrap a schema with type information\n * Use this if your schema doesn't implement Inferrable\n *\n * @example\n * responses: { 200: schema(UserSchema, {} as User) }\n */\nexport function schema<T>(schemaObject: SchemaLike, _type: T): Inferrable<T> {\n return schemaObject as Inferrable<T>;\n}\n\n/**\n * Response map - maps status codes to response types or schemas\n *\n * Values can be:\n * - Type assertions: `undefined as unknown as MyType`\n * - Schema objects: `MyZodSchema`, `MyElementSchema`, etc.\n * - Schema with type: `MySchema as SchemaWithType<MyType>`\n * - The adapter will interpret them appropriately\n */\nexport type ResponseMap = Record<number, SchemaLike>;\n\n/**\n * REST endpoint descriptor\n * Extends the protocol-agnostic OperationDescriptor with REST-specific fields\n */\nexport interface RestEndpointDescriptor<\n TMethod extends RestMethod = RestMethod,\n TPath extends string = string,\n TBodySchema = unknown,\n TResponses extends ResponseMap = ResponseMap,\n> extends OperationDescriptor {\n /** HTTP method */\n method: TMethod;\n\n /** URL path with optional template variables */\n path: TPath;\n\n /** Request body schema - type will be inferred from Inferrable schemas */\n body?: InferSchema<TBodySchema>;\n\n /** Query parameters schema */\n query?: Schema;\n\n /** Headers schema */\n headers?: Schema;\n\n /** Response schemas by HTTP status code */\n responses: TResponses;\n\n /** REST-specific metadata */\n metadata?: RestMetadata;\n}\n\n/**\n * REST-specific metadata\n */\nexport interface RestMetadata {\n /** Endpoint description */\n description?: string;\n\n /** Tags for grouping */\n tags?: string[];\n\n /** Deprecation notice */\n deprecated?: boolean;\n\n /** Rate limit information */\n rateLimit?: {\n requests: number;\n window: string;\n };\n\n /** Cache control */\n cache?: {\n maxAge?: number;\n private?: boolean;\n };\n\n /** Any additional metadata */\n [key: string]: any;\n}\n\n/**\n * REST operation function type\n */\nexport type RestOperationFunction<\n TParams extends any[] = any[],\n TDescriptor extends RestEndpointDescriptor = RestEndpointDescriptor,\n> = (...params: TParams) => TDescriptor;\n\n/**\n * REST Contract - enforces that all properties are either:\n * - RestOperationFunction (endpoint)\n * - Nested RestContract (namespace)\n */\nexport type RestContract = {\n [key: string]: RestOperationFunction | RestContract;\n};\n\n/**\n * Extract response type for a specific HTTP status code\n * Automatically infers types from Inferrable schemas\n */\nexport type ExtractResponse<\n T extends RestEndpointDescriptor,\n Status extends keyof T['responses'],\n> = InferSchema<T['responses'][Status]>;\n\n/**\n * Infer the success response type (2xx status codes only)\n * Filters out error responses (4xx, 5xx) and only returns 2xx response types\n * Automatically infers types from Inferrable schemas\n */\nexport type InferSuccessResponse<T extends RestEndpointDescriptor> = Exclude<\n {\n [K in keyof T['responses']]: K extends\n | 200\n | 201\n | 202\n | 203\n | 204\n | 205\n | 206\n | 207\n | 208\n | 226\n ? InferSchema<T['responses'][K]>\n : never;\n }[keyof T['responses']],\n never | undefined\n>;\n\n/**\n * Infer the error response type (non-2xx status codes)\n * Captures all error responses (4xx, 5xx) as a union type\n */\nexport type InferErrorResponse<T extends RestEndpointDescriptor> = Exclude<\n {\n [K in keyof T['responses']]: K extends\n | 200\n | 201\n | 202\n | 203\n | 204\n | 205\n | 206\n | 207\n | 208\n | 226\n ? never\n : T['responses'][K];\n }[keyof T['responses']],\n never | undefined\n>;\n","/**\n * Speci REST - Helper functions for REST endpoints\n */\n\nimport type { Schema } from '../core/types';\nimport type {\n RestEndpointDescriptor,\n RestMetadata,\n ResponseMap,\n} from './types';\n\n/**\n * Options for REST endpoint helpers\n */\nexport interface RestEndpointOptions<\n TBodySchema = unknown,\n TResponses extends ResponseMap = ResponseMap,\n> {\n /** Request body schema - can be Inferrable schema or plain type */\n body?: TBodySchema;\n\n /** Query parameters schema */\n query?: Schema;\n\n /** Headers schema */\n headers?: Schema;\n\n /** Response schemas by status code (optional when using shortcut syntax) */\n responses?: TResponses;\n\n /** Optional REST metadata */\n metadata?: RestMetadata;\n}\n\n/**\n * HTTP helper object with optional global responses\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\ntype Http<TGlobalResponses extends ResponseMap = {}> = {\n // GET - with shortcut syntax\n get: {\n <TSuccess = unknown>(\n path: string,\n ): RestEndpointDescriptor<\n 'GET',\n string,\n never,\n { 200: TSuccess } & TGlobalResponses\n >;\n\n <TPath extends string, TResponses extends ResponseMap>(\n path: TPath,\n options: Omit<RestEndpointOptions<never, TResponses>, 'body'>,\n ): RestEndpointDescriptor<\n 'GET',\n TPath,\n never,\n TResponses & TGlobalResponses\n >;\n };\n\n // POST - always use options (no shortcut)\n post: <\n TPath extends string,\n TBody = unknown,\n TResponses extends ResponseMap = ResponseMap,\n >(\n path: TPath,\n options: RestEndpointOptions<TBody, TResponses>,\n ) => RestEndpointDescriptor<\n 'POST',\n TPath,\n TBody,\n TResponses & TGlobalResponses\n >;\n\n // PUT - always use options (no shortcut)\n put: <\n TPath extends string,\n TBody = unknown,\n TResponses extends ResponseMap = ResponseMap,\n >(\n path: TPath,\n options: RestEndpointOptions<TBody, TResponses>,\n ) => RestEndpointDescriptor<\n 'PUT',\n TPath,\n TBody,\n TResponses & TGlobalResponses\n >;\n\n // PATCH - always use options (no shortcut)\n patch: <\n TPath extends string,\n TBody = unknown,\n TResponses extends ResponseMap = ResponseMap,\n >(\n path: TPath,\n options: RestEndpointOptions<TBody, TResponses>,\n ) => RestEndpointDescriptor<\n 'PATCH',\n TPath,\n TBody,\n TResponses & TGlobalResponses\n >;\n\n // DELETE - with shortcut syntax\n delete: {\n (\n path: string,\n ): RestEndpointDescriptor<\n 'DELETE',\n string,\n never,\n { 204: undefined } & TGlobalResponses\n >;\n\n <TPath extends string, TResponses extends ResponseMap>(\n path: TPath,\n options: Omit<RestEndpointOptions<never, TResponses>, 'body'>,\n ): RestEndpointDescriptor<\n 'DELETE',\n TPath,\n never,\n TResponses & TGlobalResponses\n >;\n };\n\n head: <TPath extends string, TResponses extends ResponseMap>(\n path: TPath,\n options: Omit<RestEndpointOptions<never, TResponses>, 'body'>,\n ) => RestEndpointDescriptor<\n 'HEAD',\n TPath,\n never,\n TResponses & TGlobalResponses\n >;\n\n options: <TPath extends string, TResponses extends ResponseMap>(\n path: TPath,\n options: Omit<RestEndpointOptions<never, TResponses>, 'body'>,\n ) => RestEndpointDescriptor<\n 'OPTIONS',\n TPath,\n never,\n TResponses & TGlobalResponses\n >;\n};\n\n/**\n * Create HTTP helpers for defining REST endpoints\n *\n * @param globalResponses - Optional: Global response map to merge with all endpoint responses\n *\n * @example\n * // Without global responses\n * const http = createHttp();\n * http.get('/users', {\n * responses: { 200: User[], 404: NotFoundError }\n * })\n *\n * @example\n * // With global error responses\n * const globalErrors = {\n * 400: {} as ApiError,\n * 401: {} as ApiError,\n * 500: {} as ApiError\n * };\n *\n * const api = createHttp(globalErrors);\n * api.get('/users', {\n * responses: { 200: [] as User[] }\n * // 400, 401, 500 are automatically added\n * })\n */\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport function createHttp<TGlobalResponses extends ResponseMap = {}>(\n globalResponses?: TGlobalResponses,\n): Http<TGlobalResponses> {\n const mergeResponses = (responses: any) => {\n if (!globalResponses) return responses;\n return { ...globalResponses, ...responses };\n };\n\n return {\n get: (path: string, options?: any) => {\n // Shortcut: api.get<User[]>('/path')\n if (!options) {\n return {\n method: 'GET' as const,\n path,\n responses: mergeResponses({ 200: undefined }),\n };\n }\n // Full: api.get('/path', { responses: {...} })\n return {\n method: 'GET' as const,\n path,\n ...options,\n responses: mergeResponses(options.responses || { 200: undefined }),\n };\n },\n post: (path, options) => {\n const { body, responses, ...rest } = options;\n return {\n method: 'POST' as const,\n path,\n body,\n ...rest,\n responses: mergeResponses(responses),\n };\n },\n\n put: (path, options) => {\n const { body, responses, ...rest } = options;\n return {\n method: 'PUT' as const,\n path,\n body,\n ...rest,\n responses: mergeResponses(responses),\n };\n },\n\n patch: (path, options) => {\n const { body, responses, ...rest } = options;\n return {\n method: 'PATCH' as const,\n path,\n body,\n ...rest,\n responses: mergeResponses(responses),\n };\n },\n delete: (path: string, options?: any) => {\n // Shortcut: api.delete('/path')\n if (!options) {\n return {\n method: 'DELETE' as const,\n path,\n responses: mergeResponses({ 204: undefined }),\n };\n }\n // Full: api.delete('/path', { responses: {...} })\n return {\n method: 'DELETE' as const,\n path,\n ...options,\n responses: mergeResponses(options.responses || { 204: undefined }),\n };\n },\n head: (path: string, options: any) => ({\n method: 'HEAD' as const,\n path,\n ...options,\n responses: mergeResponses(options.responses),\n }),\n options: (path: string, options: any) => ({\n method: 'OPTIONS' as const,\n path,\n ...options,\n responses: mergeResponses(options.responses),\n }),\n } as Http<TGlobalResponses>;\n}\n\n/**\n * Default HTTP helper object\n *\n * @example\n * import { http } from '@abapify/speci/rest'\n *\n * http.get('/users', {\n * responses: { 200: User[], 404: NotFoundError }\n * })\n *\n * http.delete('/users/123', {\n * responses: { 204: undefined, 404: NotFoundError }\n * })\n */\nexport const http = createHttp();\n","/**\n * Speci REST - Client Types\n *\n * REST-specific client interface types.\n */\n\nimport type {\n OperationFunction,\n ExtractParams,\n ExtractDescriptor,\n} from '../../core/types';\nimport type {\n RestEndpointDescriptor,\n InferSuccessResponse,\n InferErrorResponse,\n} from '../types';\n\n/**\n * HTTP error with typed payload\n */\nexport class HttpError<TPayload = unknown> extends Error {\n constructor(\n public readonly status: number,\n public readonly payload: TPayload,\n message?: string,\n ) {\n super(message || `HTTP ${status}`);\n this.name = 'HttpError';\n }\n}\n\n/**\n * HTTP request options\n */\nexport interface HttpRequestOptions {\n method: string;\n url: string;\n body?: unknown;\n query?: Record<string, any>;\n headers?: Record<string, string>;\n bodySchema?: unknown; // Schema for serializing body (passed when body is Inferrable)\n responses?: Record<number, unknown>; // Response schemas by status code\n}\n\n/**\n * HTTP adapter interface - consumer provides their own implementation\n *\n * @template TDefaultResponse - Default response type for all requests (useful for testing)\n */\nexport interface HttpAdapter<TDefaultResponse = unknown> {\n /**\n * Execute an HTTP request\n */\n request<TResponse = TDefaultResponse>(\n options?: HttpRequestOptions,\n ): Promise<TResponse>;\n}\n\n/**\n * Client configuration\n */\nexport interface ClientConfig {\n /** Base URL for all requests */\n baseUrl: string;\n\n /** HTTP adapter implementation */\n adapter: HttpAdapter;\n\n /** Default headers for all requests */\n headers?: Record<string, string>;\n\n /** Request interceptor */\n onRequest?: (options: any) => any | Promise<any>;\n\n /** Response interceptor */\n onResponse?: (response: any) => any | Promise<any>;\n\n /** Error interceptor */\n onError?: (error: any) => any | Promise<any>;\n}\n\n/**\n * Check if body is an Inferrable schema (has _infer property)\n */\ntype IsInferrableBody<TBody> = TBody extends { _infer?: any } ? true : false;\n\n/**\n * Check if method is PUT or PATCH (update operations that typically send partial data)\n */\ntype IsUpdateMethod<TMethod> = TMethod extends 'PUT' | 'PATCH' ? true : false;\n\n/**\n * Extract body type from a REST endpoint descriptor\n * Only extracts if the body is an Inferrable schema\n * For PUT/PATCH methods, returns Partial<T> since updates typically send partial data\n */\ntype ExtractBodyType<TDescriptor> =\n TDescriptor extends RestEndpointDescriptor<\n infer TMethod,\n any,\n infer TBody,\n any\n >\n ? IsInferrableBody<TBody> extends true\n ? TBody extends { _infer?: infer U }\n ? IsUpdateMethod<TMethod> extends true\n ? Partial<U> // PUT/PATCH: allow partial body\n : U // POST: require full body\n : never\n : never\n : never;\n\n/**\n * Build parameter list for a REST client method\n * - If descriptor has a body with Inferrable schema, append the inferred type as a parameter\n * - Otherwise, use the function's declared parameters\n */\ntype BuildParams<T extends OperationFunction> =\n ExtractDescriptor<T> extends RestEndpointDescriptor\n ? ExtractBodyType<ExtractDescriptor<T>> extends never\n ? ExtractParams<T> // No body - use declared params\n : ExtractParams<T> extends []\n ? [ExtractBodyType<ExtractDescriptor<T>>] // Empty params + body - add body param\n : [...ExtractParams<T>, ExtractBodyType<ExtractDescriptor<T>>] // Has params + body - append body\n : ExtractParams<T>;\n\n/**\n * Convert a REST operation function to a client method\n * Only returns success response types (2xx) - errors are thrown as HttpError\n * Includes typed error property for error response payloads\n *\n * Automatically infers parameter types from body schema if present\n */\nexport type RestClientMethod<T extends OperationFunction> = {\n (\n ...params: BuildParams<T>\n ): Promise<\n ExtractDescriptor<T> extends RestEndpointDescriptor\n ? InferSuccessResponse<ExtractDescriptor<T>>\n : never\n >;\n /** Typed error response payload - use with HttpError */\n error: ExtractDescriptor<T> extends RestEndpointDescriptor\n ? InferErrorResponse<ExtractDescriptor<T>>\n : never;\n /** Check if error is from this endpoint */\n isError(\n error: unknown,\n ): error is HttpError<\n ExtractDescriptor<T> extends RestEndpointDescriptor\n ? InferErrorResponse<ExtractDescriptor<T>>\n : never\n >;\n};\n\n/**\n * Convert a REST contract to a typed client.\n * Uses Record<string, any> instead of Contract to allow concrete types without index signature.\n */\nexport type RestClient<T extends Record<string, any>> = {\n [K in keyof T]: T[K] extends OperationFunction\n ? RestClientMethod<T[K]>\n : T[K] extends Record<string, any>\n ? RestClient<T[K]>\n : never;\n};\n","/**\n * Speci v0.1 - Client Generator\n *\n * Creates a typed client from a contract specification.\n */\n\nimport type { OperationFunction } from '../../core/types';\nimport type { RestClient, ClientConfig } from './types';\n\n/**\n * Extract path parameters from a path template\n * Example: \"/users/${id}/posts/${postId}\" -> [\"id\", \"postId\"]\n */\nfunction extractPathParams(path: string): string[] {\n const matches = path.matchAll(/\\$\\{(\\w+)\\}/g);\n return Array.from(matches, (m) => m[1]);\n}\n\n/**\n * Replace path parameters with actual values\n * Example: \"/users/${id}\" with {id: \"123\"} -> \"/users/123\"\n */\nfunction replacePath(path: string, params: Record<string, any>): string {\n return path.replaceAll(/\\$\\{(\\w+)\\}/g, (_, key) => {\n const value = params[key];\n if (value === undefined) {\n throw new Error(`Missing path parameter: ${key}`);\n }\n return encodeURIComponent(String(value));\n });\n}\n\n/**\n * Check if a value is an Inferrable schema (has _infer property)\n */\nfunction isInferrableSchema(value: any): boolean {\n return value && typeof value === 'object' && '_infer' in value;\n}\n\n/**\n * Create a client method from an operation function\n */\nfunction createMethod(\n config: ClientConfig,\n operationFn: OperationFunction,\n): any {\n const method = async (...args: any[]) => {\n // Execute the operation function with the provided args\n // Contract functions receive params (object or individual), we pass them through\n const descriptor: any = operationFn(...args);\n\n // For REST endpoints, handle path parameters\n const pathParamNames = descriptor.path\n ? extractPathParams(descriptor.path)\n : [];\n\n // Build path params object\n // If first arg is an object with path param keys, use it; otherwise use positional args\n const pathParams: Record<string, any> = {};\n const firstArg = args[0];\n\n if (firstArg && typeof firstArg === 'object' && !Array.isArray(firstArg)) {\n // Object-based parameters - extract from first arg\n pathParamNames.forEach((name) => {\n if (firstArg[name] !== undefined) {\n pathParams[name] = firstArg[name];\n }\n });\n } else {\n // Positional parameters - use arg indices\n pathParamNames.forEach((name, index) => {\n if (args[index] !== undefined) {\n pathParams[name] = args[index];\n }\n });\n }\n\n // Handle body parameter\n // If descriptor.body is an Inferrable schema, body data comes after path params\n let bodyData = descriptor.body;\n let bodySchema = undefined;\n\n // Use operationFn.length to determine how many path params the function declared\n // This works because template strings consume the params before we see them\n const declaredParamCount = operationFn.length;\n\n if (isInferrableSchema(descriptor.body)) {\n // Body is a schema - body data comes after all declared parameters\n bodySchema = descriptor.body;\n // Body is at position: number of declared params (path params consumed by template)\n bodyData = args[declaredParamCount];\n }\n\n // Replace path parameters if path exists\n const url = descriptor.path\n ? config.baseUrl + replacePath(descriptor.path, pathParams)\n : config.baseUrl;\n\n // Prepare request options (REST-specific, but works generically)\n let requestOptions = {\n method: descriptor.method || 'GET',\n url,\n body: bodyData, // Actual data (not schema)\n bodySchema, // Schema for adapter to use\n query: descriptor.query,\n headers: {\n ...config.headers,\n ...descriptor.headers,\n },\n responses: descriptor.responses, // Pass responses for adapter\n };\n\n // Apply request interceptor\n if (config.onRequest) {\n requestOptions = await config.onRequest(requestOptions);\n }\n\n try {\n // Execute request\n let response = await config.adapter.request(requestOptions);\n\n // Apply response interceptor\n if (config.onResponse) {\n response = await config.onResponse(response);\n }\n\n return response;\n } catch (error) {\n // Apply error interceptor\n if (config.onError) {\n return await config.onError(error);\n }\n throw error;\n }\n };\n\n // Add error type property and isError method for type checking\n (method as any).error = undefined;\n (method as any).isError = (error: unknown): error is any => {\n return error instanceof Error && error.name === 'HttpError';\n };\n\n return method;\n}\n\n/**\n * Create a typed REST client from a contract\n */\nexport function createClient<T extends Record<string, any>>(\n contract: T,\n config: ClientConfig,\n): RestClient<T> {\n const client: any = {};\n\n for (const [key, value] of Object.entries(contract)) {\n if (typeof value === 'function') {\n // It's an operation function\n client[key] = createMethod(config, value as OperationFunction);\n } else if (typeof value === 'object' && value !== null) {\n // It's a nested contract\n client[key] = createClient(value as Record<string, any>, config);\n }\n }\n\n return client as RestClient<T>;\n}\n","/**\n * Speci v0.1 - Fetch Adapter\n *\n * Native fetch API adapter for Speci clients.\n */\n\nimport type { HttpAdapter } from './types';\nimport { HttpError } from './types';\n\n/**\n * Create a fetch-based HTTP adapter\n */\nexport function createFetchAdapter(options?: RequestInit): HttpAdapter {\n return {\n async request<TResponse = unknown>(opts: {\n method: string;\n url: string;\n body?: unknown;\n query?: Record<string, any>;\n headers?: Record<string, string>;\n }): Promise<TResponse> {\n const { method, url, body, query, headers } = opts;\n // Build query string\n const queryString = query\n ? '?' + new URLSearchParams(query).toString()\n : '';\n\n const fullUrl = url + queryString;\n\n // Execute fetch\n const response = await fetch(fullUrl, {\n ...options,\n method,\n headers: {\n 'Content-Type': 'application/json',\n ...options?.headers,\n ...headers,\n },\n body: body ? JSON.stringify(body) : undefined,\n });\n\n // Check if response is successful (2xx)\n if (!response.ok) {\n // Parse error response\n const errorData = await response.json().catch(() => ({\n error: 'HTTP Error',\n message: response.statusText,\n }));\n throw new HttpError(response.status, errorData);\n }\n\n // Parse successful response\n const data = await response.json();\n return data as TResponse;\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,203 @@
1
+ //#region src/core/types.d.ts
2
+ /**
3
+ * Speci v0.1 - Core Types (Protocol-Agnostic)
4
+ *
5
+ * These types work across all protocols: REST, GraphQL, gRPC, WebSocket, etc.
6
+ * Protocol-specific types are in their respective modules.
7
+ */
8
+ /**
9
+ * Schema type - can be any type that describes data structure
10
+ * Works with: Zod, JSON Schema, TypeScript types, Protobuf, GraphQL types, etc.
11
+ */
12
+ type Schema<T = any> = T;
13
+ /**
14
+ * Generic operation descriptor
15
+ * Protocol-specific descriptors extend this base interface
16
+ */
17
+ interface OperationDescriptor {
18
+ /** Optional metadata */
19
+ metadata?: {
20
+ /** Operation description */description?: string; /** Tags for grouping */
21
+ tags?: string[]; /** Deprecation notice */
22
+ deprecated?: boolean; /** Any additional metadata */
23
+ [key: string]: any;
24
+ };
25
+ }
26
+ /**
27
+ * Arrow function that defines an operation contract
28
+ * Generic across all protocols
29
+ */
30
+ type OperationFunction<TParams extends any[] = any[], TDescriptor extends OperationDescriptor = OperationDescriptor> = (...params: TParams) => TDescriptor;
31
+ /**
32
+ * Contract - a collection of operation functions
33
+ * Works for REST, GraphQL, gRPC, etc.
34
+ */
35
+ type Contract = {
36
+ [key: string]: OperationFunction | Contract;
37
+ };
38
+ /**
39
+ * Extract parameter types from an operation function
40
+ */
41
+ type ExtractParams<T> = T extends ((...args: infer P) => any) ? P : never;
42
+ /**
43
+ * Extract descriptor from an operation function
44
+ */
45
+ type ExtractDescriptor<T> = T extends ((...args: any[]) => infer D) ? D : never;
46
+ //#endregion
47
+ //#region src/rest/types.d.ts
48
+ /**
49
+ * HTTP methods for REST APIs
50
+ */
51
+ type RestMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
52
+ /**
53
+ * Inferrable schema interface (explicit _infer property)
54
+ *
55
+ * Schemas that implement this interface will have their types automatically inferred.
56
+ *
57
+ * @example
58
+ * const UserSchema = {
59
+ * ...yourSchemaDefinition,
60
+ * _infer: undefined as unknown as User
61
+ * } as const;
62
+ *
63
+ * responses: { 200: UserSchema } // Type is User
64
+ */
65
+ interface Inferrable<T = unknown> {
66
+ /** Type inference marker - never accessed at runtime */
67
+ _infer?: T;
68
+ }
69
+ /**
70
+ * Serializable schema interface (parse/build methods + Inferrable)
71
+ *
72
+ * Extends Inferrable so schemas with parse() automatically get _infer marker.
73
+ * This is the preferred pattern for schema libraries like ts-xsd.
74
+ *
75
+ * @example
76
+ * const UserSchema = {
77
+ * parse: (raw: string): User => JSON.parse(raw),
78
+ * build: (data: User): string => JSON.stringify(data),
79
+ * };
80
+ *
81
+ * responses: { 200: UserSchema } // Type is User (inferred via _infer from Inferrable)
82
+ */
83
+ interface Serializable<T = unknown> extends Inferrable<T> {
84
+ /** Parse raw string to typed object */
85
+ parse(raw: string): T;
86
+ /** Build typed object to string */
87
+ build?(data: T): string;
88
+ }
89
+ /**
90
+ * Create an Inferrable schema with automatic type inference
91
+ * Cleaner than the manual _infer pattern
92
+ */
93
+ declare function createInferrable<T>(): Inferrable<T>;
94
+ /**
95
+ * Infer type from a schema
96
+ *
97
+ * Uses _infer property from Inferrable<T> (which Serializable extends).
98
+ * Falls back to original type if no _infer property.
99
+ *
100
+ * @example
101
+ * type A = InferSchema<{ _infer?: User }> // User
102
+ * type B = InferSchema<string> // string (fallback)
103
+ */
104
+ type InferSchema<T> = T extends {
105
+ _infer?: infer U;
106
+ } ? NonNullable<U> : T;
107
+ /**
108
+ * Schema-like object - can be any schema library (Zod, JSON Schema, custom, etc.)
109
+ * Adapters interpret these based on their capabilities.
110
+ */
111
+ type SchemaLike = unknown;
112
+ /**
113
+ * Helper to wrap a schema with type information
114
+ * Use this if your schema doesn't implement Inferrable
115
+ *
116
+ * @example
117
+ * responses: { 200: schema(UserSchema, {} as User) }
118
+ */
119
+ declare function schema<T>(schemaObject: SchemaLike, _type: T): Inferrable<T>;
120
+ /**
121
+ * Response map - maps status codes to response types or schemas
122
+ *
123
+ * Values can be:
124
+ * - Type assertions: `undefined as unknown as MyType`
125
+ * - Schema objects: `MyZodSchema`, `MyElementSchema`, etc.
126
+ * - Schema with type: `MySchema as SchemaWithType<MyType>`
127
+ * - The adapter will interpret them appropriately
128
+ */
129
+ type ResponseMap = Record<number, SchemaLike>;
130
+ /**
131
+ * REST endpoint descriptor
132
+ * Extends the protocol-agnostic OperationDescriptor with REST-specific fields
133
+ */
134
+ interface RestEndpointDescriptor<TMethod extends RestMethod = RestMethod, TPath extends string = string, TBodySchema = unknown, TResponses extends ResponseMap = ResponseMap> extends OperationDescriptor {
135
+ /** HTTP method */
136
+ method: TMethod;
137
+ /** URL path with optional template variables */
138
+ path: TPath;
139
+ /** Request body schema - type will be inferred from Inferrable schemas */
140
+ body?: InferSchema<TBodySchema>;
141
+ /** Query parameters schema */
142
+ query?: Schema;
143
+ /** Headers schema */
144
+ headers?: Schema;
145
+ /** Response schemas by HTTP status code */
146
+ responses: TResponses;
147
+ /** REST-specific metadata */
148
+ metadata?: RestMetadata;
149
+ }
150
+ /**
151
+ * REST-specific metadata
152
+ */
153
+ interface RestMetadata {
154
+ /** Endpoint description */
155
+ description?: string;
156
+ /** Tags for grouping */
157
+ tags?: string[];
158
+ /** Deprecation notice */
159
+ deprecated?: boolean;
160
+ /** Rate limit information */
161
+ rateLimit?: {
162
+ requests: number;
163
+ window: string;
164
+ };
165
+ /** Cache control */
166
+ cache?: {
167
+ maxAge?: number;
168
+ private?: boolean;
169
+ };
170
+ /** Any additional metadata */
171
+ [key: string]: any;
172
+ }
173
+ /**
174
+ * REST operation function type
175
+ */
176
+ type RestOperationFunction<TParams extends any[] = any[], TDescriptor extends RestEndpointDescriptor = RestEndpointDescriptor> = (...params: TParams) => TDescriptor;
177
+ /**
178
+ * REST Contract - enforces that all properties are either:
179
+ * - RestOperationFunction (endpoint)
180
+ * - Nested RestContract (namespace)
181
+ */
182
+ type RestContract = {
183
+ [key: string]: RestOperationFunction | RestContract;
184
+ };
185
+ /**
186
+ * Extract response type for a specific HTTP status code
187
+ * Automatically infers types from Inferrable schemas
188
+ */
189
+ type ExtractResponse<T extends RestEndpointDescriptor, Status extends keyof T['responses']> = InferSchema<T['responses'][Status]>;
190
+ /**
191
+ * Infer the success response type (2xx status codes only)
192
+ * Filters out error responses (4xx, 5xx) and only returns 2xx response types
193
+ * Automatically infers types from Inferrable schemas
194
+ */
195
+ type InferSuccessResponse<T extends RestEndpointDescriptor> = Exclude<{ [K in keyof T['responses']]: K extends 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 226 ? InferSchema<T['responses'][K]> : never }[keyof T['responses']], never | undefined>;
196
+ /**
197
+ * Infer the error response type (non-2xx status codes)
198
+ * Captures all error responses (4xx, 5xx) as a union type
199
+ */
200
+ type InferErrorResponse<T extends RestEndpointDescriptor> = Exclude<{ [K in keyof T['responses']]: K extends 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 226 ? never : T['responses'][K] }[keyof T['responses']], never | undefined>;
201
+ //#endregion
202
+ export { ExtractDescriptor as _, Inferrable as a, OperationFunction as b, RestEndpointDescriptor as c, RestOperationFunction as d, SchemaLike as f, Contract as g, schema as h, InferSuccessResponse as i, RestMetadata as l, createInferrable as m, InferErrorResponse as n, ResponseMap as o, Serializable as p, InferSchema as r, RestContract as s, ExtractResponse as t, RestMethod as u, ExtractParams as v, Schema as x, OperationDescriptor as y };
203
+ //# sourceMappingURL=types-wn9lAKzN.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-wn9lAKzN.d.mts","names":[],"sources":["../src/core/types.ts","../src/rest/types.ts"],"mappings":";;AAWA;;;;;AAMA;;;;AAAA,KANY,MAAA,YAAkB,CAAA;;;;;UAMb,mBAAA;EAaD;EAXd,QAAA;IAmB2B,4BAjBzB,WAAA,WAmBkB;IAhBlB,IAAA,aAiBY;IAdZ,UAAA,YAcmC;IAAA,CAXlC,GAAA;EAAA;AAAA;;;;;KAQO,iBAAA,oDAEU,mBAAA,GAAsB,mBAAA,QACpC,MAAA,EAAQ,OAAA,KAAY,WAAA;;;AAM5B;;KAAY,QAAA;EAAA,CACT,GAAA,WAAc,iBAAA,GAAoB,QAAA;AAAA;;;;KAMzB,aAAA,MAAmB,CAAA,cAAc,IAAA,qBAAwB,CAAA;AAArE;;;AAAA,KAKY,iBAAA,MAAuB,CAAA,cAAc,IAAA,uBAC7C,CAAA;;;;;AA3CJ;KCRY,UAAA;;;;;;;;;;AD6BZ;;;;UCPiB,UAAA;EDUD;ECRd,MAAA,GAAS,CAAA;AAAA;;;;;;;;;;;ADcX;;;;UCGiB,YAAA,sBAAkC,UAAA,CAAW,CAAA;EDF7C;ECIf,KAAA,CAAM,GAAA,WAAc,CAAA;EDJuB;ECM3C,KAAA,EAAO,IAAA,EAAM,CAAA;AAAA;;;;;iBAOC,gBAAA,GAAA,CAAA,GAAuB,UAAA,CAAW,CAAA;;;;;;ADFlD;;;;;KCgBY,WAAA,MAAiB,CAAA;EAAY,MAAA;AAAA,IACrC,WAAA,CAAY,CAAA,IACZ,CAAA;;;;;KAMQ,UAAA;;AA1EZ;;;;;AAsBA;iBA6DgB,MAAA,GAAA,CAAU,YAAA,EAAc,UAAA,EAAY,KAAA,EAAO,CAAA,GAAI,UAAA,CAAW,CAAA;;;;;;;;AA1C1E;;KAuDY,WAAA,GAAc,MAAA,SAAe,UAAA;;;;;UAMxB,sBAAA,iBACC,UAAA,GAAa,UAAA,2EAGV,WAAA,GAAc,WAAA,UACzB,mBAAA;EAlEmD;EAoE3D,MAAA,EAAQ,OAAA;EApEyC;EAuEjD,IAAA,EAAM,KAAA;EArEN;EAwEA,IAAA,GAAO,WAAA,CAAY,WAAA;EAxEC;EA2EpB,KAAA,GAAQ,MAAA;EAzEK;EA4Eb,OAAA,GAAU,MAAA;EA5EI;EA+Ed,SAAA,EAAW,UAAA;EAxEG;EA2Ed,QAAA,GAAW,YAAA;AAAA;;;;UAMI,YAAA;EAjFkC;EAmFjD,WAAA;EArEU;EAwEV,IAAA;EAxEqB;EA2ErB,UAAA;EA1Ec;EA6Ed,SAAA;IACE,QAAA;IACA,MAAA;EAAA;EAhFoB;EAoFtB,KAAA;IACE,MAAA;IACA,OAAA;EAAA;EArFY;EAAA,CAyFb,GAAA;AAAA;;AAlFH;;KAwFY,qBAAA,oDAEU,sBAAA,GAAyB,sBAAA,QACvC,MAAA,EAAQ,OAAA,KAAY,WAAA;;;AAlF5B;;;KAyFY,YAAA;EAAA,CACT,GAAA,WAAc,qBAAA,GAAwB,YAAA;AAAA;;;;;KAO7B,eAAA,WACA,sBAAA,uBACW,CAAA,iBACnB,WAAA,CAAY,CAAA,cAAe,MAAA;;;;;;KAOnB,oBAAA,WAA+B,sBAAA,IAA0B,OAAA,eAErD,CAAA,gBAAiB,CAAA,qEAWzB,WAAA,CAAY,CAAA,cAAe,CAAA,mBAEzB,CAAA;;;;;KAQE,kBAAA,WAA6B,sBAAA,IAA0B,OAAA,eAEnD,CAAA,gBAAiB,CAAA,6EAYzB,CAAA,cAAe,CAAA,UACb,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abapify/speci",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Minimal arrow-function-based contract specification system for TypeScript",
5
5
  "license": "MIT",
6
6
  "publishConfig": {