{"_id":"@abapify/speci","_rev":"6-5747897acfccb2e7ee3ba46ad49414ec","name":"@abapify/speci","dist-tags":{"latest":"0.4.1"},"versions":{"0.0.0":{"name":"@abapify/speci","version":"0.0.0","_id":"@abapify/speci@0.0.0","maintainers":[{"name":"theplenkov-npm","email":"petr.plenkov@gmail.com"}],"dist":{"shasum":"6a0c69aa7ea3701f8eba2dd88effe8072df56b5d","tarball":"https://registry.npmjs.org/@abapify/speci/-/speci-0.0.0.tgz","fileCount":2,"integrity":"sha512-DFJMwkKqAS0bKcFEYCTFjOsA+WPS1VOg4QIO/3O+n3n79v5KIySFEgIMbmwEsjvgeX0uPSKciR0NnwtteYKEHw==","signatures":[{"sig":"MEQCIBlmOp/ELSHbVulbNxt+8ZLqZuI+r/4STCPmk4sEw6gPAiBiW7ujT88IAQUSSnPJoTLDPtyINfmCcQsTO8dr45c46A==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":218},"_npmUser":{"name":"theplenkov-npm","email":"petr.plenkov@gmail.com"},"_npmVersion":"11.12.1","description":"Placeholder — real package is published via CI/CD (trusted publishing).","directories":{},"_nodeVersion":"24.10.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/speci_0.0.0_1776719446587_0.2397533551359654","host":"s3://npm-registry-packages-npm-production"}},"0.3.4":{"name":"@abapify/speci","version":"0.3.4","keywords":["api","contract","specification","typescript","client-generation","openapi"],"license":"MIT","_id":"@abapify/speci@0.3.4","maintainers":[{"name":"theplenkov-npm","email":"petr.plenkov@gmail.com"}],"homepage":"https://github.com/abapify/adt-cli/tree/main/packages/speci#readme","bugs":{"url":"https://github.com/abapify/adt-cli/issues"},"dist":{"shasum":"31335e049027b6db43871e09ab58294caebffabf","tarball":"https://registry.npmjs.org/@abapify/speci/-/speci-0.3.4.tgz","fileCount":2,"integrity":"sha512-20zmNFRwMxJYe00BxIRxslsFLP8WvizBF97TEd6pVzsKXCgq3m7y3E8yiUT3Mfv/YL7l6O9upEHkJ5YratgQHA==","signatures":[{"sig":"MEYCIQC95lGcMyCcPDSYKO38N9cBiTBvNfld5xeSJ09QBvMmVAIhALCjjMSGBqyMzUNBFayGWcDGcDSdGx/YEZiVL61o6lLL","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@abapify%2fspeci@0.3.4","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":14182},"type":"module","types":"./dist/index.d.mts","exports":{".":"./dist/index.mjs","./rest":"./dist/rest/index.mjs","./package.json":"./package.json"},"gitHead":"6a630228129c616054aad8dfafe17c42f389986e","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:94bf79fa-174f-4e17-a082-1d95dc767f1f"}},"repository":{"url":"git+https://github.com/abapify/adt-cli.git","type":"git","directory":"packages/speci"},"_npmVersion":"11.12.1","description":"Minimal arrow-function-based contract specification system for TypeScript","directories":{},"_nodeVersion":"24.14.1","dependencies":{},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/speci_0.3.4_1776722404108_0.2813319348299421","host":"s3://npm-registry-packages-npm-production"}},"0.3.5":{"name":"@abapify/speci","version":"0.3.5","keywords":["api","contract","specification","typescript","client-generation","openapi"],"license":"MIT","_id":"@abapify/speci@0.3.5","maintainers":[{"name":"theplenkov-npm","email":"petr.plenkov@gmail.com"}],"homepage":"https://github.com/abapify/adt-cli/tree/main/packages/speci#readme","bugs":{"url":"https://github.com/abapify/adt-cli/issues"},"dist":{"shasum":"2c1b672c47571937f7e25be28a9a003a29eaf4a2","tarball":"https://registry.npmjs.org/@abapify/speci/-/speci-0.3.5.tgz","fileCount":2,"integrity":"sha512-KzOqaOQfbjeSoJPM8pG0od3Kjkuyj2bm8hFZks30nFPt1qSFYOINsrMviYANOEF4Eg2JXqZGzZ4EliBipolTpA==","signatures":[{"sig":"MEYCIQDv0iPNt+IzlOtLZLjuwETy8itCDtkSD8Dbo7tDCM3aYQIhAPml5QGFvRmr2BpnvdOAjm9dYHmi1DpBLRO0guISMwZj","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@abapify%2fspeci@0.3.5","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":14182},"type":"module","types":"./dist/index.d.mts","exports":{".":"./dist/index.mjs","./rest":"./dist/rest/index.mjs","./package.json":"./package.json"},"gitHead":"185666fe6ccb304e7b08529eb376ec2493131a7a","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:94bf79fa-174f-4e17-a082-1d95dc767f1f"}},"repository":{"url":"git+https://github.com/abapify/adt-cli.git","type":"git","directory":"packages/speci"},"_npmVersion":"11.12.1","description":"Minimal arrow-function-based contract specification system for TypeScript","directories":{},"_nodeVersion":"24.14.1","dependencies":{},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/speci_0.3.5_1776723645281_0.09849246343162998","host":"s3://npm-registry-packages-npm-production"}},"0.3.6":{"name":"@abapify/speci","version":"0.3.6","keywords":["api","contract","specification","typescript","client-generation","openapi"],"license":"MIT","_id":"@abapify/speci@0.3.6","maintainers":[{"name":"theplenkov-npm","email":"petr.plenkov@gmail.com"}],"homepage":"https://github.com/abapify/adt-cli/tree/main/packages/speci#readme","bugs":{"url":"https://github.com/abapify/adt-cli/issues"},"dist":{"shasum":"e91fa764a88b25b1d0c485c76ff468a65bf5df57","tarball":"https://registry.npmjs.org/@abapify/speci/-/speci-0.3.6.tgz","fileCount":2,"integrity":"sha512-8unAOtYGJtIxSMHQKUhnwEFNnMRRlTtB8R6QqFuKS00XWIswx3OKZiQEBeEiNrpD73arVPx0EijEn7acRvIawA==","signatures":[{"sig":"MEQCIFj3ufRezOu4YVoKwGjJB2qfXTpkB0DWo29U7hR2hk8FAiAH/1+VSFrUZbdh7nUlnmRQds0Ks2fbtV9RYL6yAYzYPw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@abapify%2fspeci@0.3.6","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":14182},"type":"module","types":"./dist/index.d.mts","exports":{".":"./dist/index.mjs","./rest":"./dist/rest/index.mjs","./package.json":"./package.json"},"gitHead":"489b2b4ad7c2ea972f323d4c356997ac5962ba9e","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:94bf79fa-174f-4e17-a082-1d95dc767f1f"}},"repository":{"url":"git+https://github.com/abapify/adt-cli.git","type":"git","directory":"packages/speci"},"_npmVersion":"11.11.0","description":"Minimal arrow-function-based contract specification system for TypeScript","directories":{},"_nodeVersion":"24.14.1","dependencies":{},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/speci_0.3.6_1776744792233_0.5092799742077847","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@abapify/speci","version":"0.4.0","keywords":["api","contract","specification","typescript","client-generation","openapi"],"license":"MIT","_id":"@abapify/speci@0.4.0","maintainers":[{"name":"theplenkov-npm","email":"petr.plenkov@gmail.com"}],"homepage":"https://github.com/abapify/adt-cli/tree/main/packages/speci#readme","bugs":{"url":"https://github.com/abapify/adt-cli/issues"},"dist":{"shasum":"af620464ce58c5cae4437bdc99b73c2c78ea5c0f","tarball":"https://registry.npmjs.org/@abapify/speci/-/speci-0.4.0.tgz","fileCount":2,"integrity":"sha512-Ds48DLgXhrRA1ZUsrEEE0Uk6mNSHaVnECp5ptzNDcNX/oG6Z4uY2cuDhTVIC9XHi+ZTUu6oh1nY9kKm14uWmzg==","signatures":[{"sig":"MEUCIQDpWCLkembF5l7ZdL43UHodc2iauGUUEL8E2ksq6CtixQIgEe87oSDlyznMJEoezfMGN3N42Daws90/n0Le8GRlv3w=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@abapify%2fspeci@0.4.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":14182},"type":"module","types":"./dist/index.d.mts","exports":{".":"./dist/index.mjs","./rest":"./dist/rest/index.mjs","./package.json":"./package.json"},"gitHead":"81dc9eea47fe419e41d246f0ec212fa78570a489","_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:94bf79fa-174f-4e17-a082-1d95dc767f1f"}},"repository":{"url":"git+https://github.com/abapify/adt-cli.git","type":"git","directory":"packages/speci"},"_npmVersion":"11.13.0","description":"Minimal arrow-function-based contract specification system for TypeScript","directories":{},"_nodeVersion":"24.16.0","dependencies":{},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/speci_0.4.0_1780072632638_0.2739678009911444","host":"s3://npm-registry-packages-npm-production"}},"0.4.1":{"name":"@abapify/speci","version":"0.4.1","description":"Minimal arrow-function-based contract specification system for TypeScript","license":"MIT","publishConfig":{"access":"public"},"type":"module","types":"./dist/index.d.mts","exports":{".":"./dist/index.mjs","./rest":"./dist/rest/index.mjs","./package.json":"./package.json"},"keywords":["api","contract","specification","typescript","client-generation","openapi"],"dependencies":{},"repository":{"type":"git","url":"git+https://github.com/abapify/adt-cli.git","directory":"packages/speci"},"homepage":"https://github.com/abapify/adt-cli/tree/main/packages/speci#readme","bugs":{"url":"https://github.com/abapify/adt-cli/issues"},"gitHead":"9d3b996a5c760a8db67e28c4df3c30098bd29e4a","_id":"@abapify/speci@0.4.1","_nodeVersion":"24.16.0","_npmVersion":"11.13.0","dist":{"integrity":"sha512-CXRWsFMlhuAotjAD/NFzhxD1Q2DK0FqkdNqK7mHKSdqIiEDHgJbr7lvUtGTkR9VwdSVwtJG8fM5Ym9RxOpnN0A==","shasum":"65c5eb4506740188f1fb6ba60d04ed210a457197","tarball":"https://registry.npmjs.org/@abapify/speci/-/speci-0.4.1.tgz","fileCount":10,"unpackedSize":71358,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@abapify%2fspeci@0.4.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDxfr7F3QsBeeZ/qSbXcRmbcnzZXpAz1UqdtlkPEz2iTAIhAKshdcmFL+kOfM1TFjMW6pqrPMpZUnpuPw7vrObF6ws5"}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:94bf79fa-174f-4e17-a082-1d95dc767f1f"}},"directories":{},"maintainers":[{"name":"theplenkov-npm","email":"petr.plenkov@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/speci_0.4.1_1780074914674_0.10434854928980308"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-20T21:10:46.340Z","modified":"2026-05-29T17:15:15.120Z","0.0.0":"2026-04-20T21:10:46.731Z","0.3.4":"2026-04-20T22:00:04.236Z","0.3.5":"2026-04-20T22:20:45.439Z","0.3.6":"2026-04-21T04:13:12.357Z","0.4.0":"2026-05-29T16:37:12.796Z","0.4.1":"2026-05-29T17:15:14.825Z"},"bugs":{"url":"https://github.com/abapify/adt-cli/issues"},"license":"MIT","homepage":"https://github.com/abapify/adt-cli/tree/main/packages/speci#readme","keywords":["api","contract","specification","typescript","client-generation","openapi"],"repository":{"type":"git","url":"git+https://github.com/abapify/adt-cli.git","directory":"packages/speci"},"description":"Minimal arrow-function-based contract specification system for TypeScript","maintainers":[{"name":"theplenkov-npm","email":"petr.plenkov@gmail.com"}],"readme":"# speci\n\n[![version](https://img.shields.io/github/package-json/v/abapify/adt-cli?filename=packages/speci/package.json)](https://github.com/abapify/adt-cli/pkgs/npm/%40abapify%2Fspeci)\n\n**Minimal arrow-function-based contract specification system for TypeScript**\n\nZero decorators. Zero DSL. Zero dependencies. Just TypeScript arrow functions.\n\nPart of the **ADT Toolkit** foundation - see [main README](../../README.md) for architecture overview.\n\n## Philosophy\n\nAn endpoint = an arrow function whose parameters define the contract and whose return value defines the operation.\n\n```typescript\nimport { http } from '@abapify/speci/rest';\n\n// Shortcut syntax - super clean!\nconst updateUser = (id: string, user: UserInput) =>\n  http.put<User>(`/users/${id}`, user);\n```\n\nThis is extremely expressive while staying minimal and TypeScript-native. Choose between shortcut syntax for simplicity or full syntax for control.\n\n## Developed with ts-xsd\n\nSpeci was developed in tight combination with [ts-xsd](../ts-xsd) to support **XML-based contracts**. While most REST libraries assume JSON, Speci's `Serializable<T>` interface enables seamless integration with XML schemas:\n\n```typescript\nimport { parse, build, type XsdSchema, type InferXsd } from 'ts-xsd';\nimport { http } from '@abapify/speci/rest';\n\n// ts-xsd schema with parse/build\nconst TransportSchema = {\n  /* ... */\n} as const satisfies XsdSchema;\ntype Transport = InferXsd<typeof TransportSchema>;\n\nconst transportApi = {\n  // Speci infers types from ts-xsd's Serializable interface\n  create: (transport: Transport) =>\n    http.post<Transport>('/transports', transport),\n};\n```\n\nThis makes Speci ideal for enterprise APIs that use XML (SAP ADT, SOAP services, etc.) while remaining fully compatible with JSON-based schemas like Zod.\n\n## Modular Architecture\n\nSpeci is organized into protocol-specific modules:\n\n- **`speci`** - Core types and utilities (protocol-agnostic)\n- **`speci/rest`** - REST API (helpers, types, client generation)\n- **`speci/openapi`** - OpenAPI generation (planned)\n- **`speci/cli`** - CLI generation (planned)\n- **`speci/graphql`** - GraphQL (planned)\n- **`speci/grpc`** - gRPC (planned)\n\nEach protocol module is self-contained with its own types, helpers, and client generation.\n\n## Installation\n\n```bash\nnpm install speci\n# or\nbun add speci\n```\n\n## Quick Start\n\n### 1. Define Your Contract\n\n```typescript\nimport { http, type RestContract } from '@abapify/speci/rest';\n\n// Define schemas (use any schema library: Zod, JSON Schema, etc.)\ninterface User {\n  id: string;\n  name: string;\n  email: string;\n}\n\ninterface CreateUserInput {\n  name: string;\n  email: string;\n}\n\ninterface ErrorResponse {\n  error: string;\n  message: string;\n}\n\n// Define your API contract - choose your style!\nexport const api = {\n  users: {\n    // Shortcut syntax - just specify the success type\n    list: () => http.get<User[]>('/users'),\n\n    // Full syntax - explicit control over all responses\n    get: (id: string) =>\n      http.get(`/users/${id}`, {\n        responses: {\n          200: undefined as unknown as User,\n          404: undefined as unknown as ErrorResponse,\n        },\n      }),\n\n    // Shortcut - pass body directly\n    create: (user: CreateUserInput) => http.post<User>('/users', user),\n\n    // Full syntax with body and responses\n    update: (id: string, user: Partial<User>) =>\n      http.put(`/users/${id}`, {\n        body: user,\n        responses: {\n          200: undefined as unknown as User,\n          404: undefined as unknown as ErrorResponse,\n        },\n      }),\n\n    // Shortcut - defaults to 204 response\n    delete: (id: string) => http.delete(`/users/${id}`),\n  },\n} satisfies RestContract;\n```\n\n### 2. Generate a Typed Client\n\n```typescript\nimport {\n  createClient,\n  createFetchAdapter,\n  HttpError,\n} from '@abapify/speci/rest';\n\nconst client = createClient(api, {\n  baseUrl: 'https://api.example.com',\n  adapter: createFetchAdapter(),\n});\n\n// Use it with full type safety - returns only success types\nconst users = await client.users.list(); // Type: User[]\nconst user = await client.users.get('123'); // Type: User\n\n// Errors are thrown as HttpError with typed payloads\ntry {\n  const user = await client.users.get('999');\n} catch (error) {\n  if (client.users.get.isError(error)) {\n    // error.payload is typed as ErrorResponse!\n    console.error(error.status, error.payload.error);\n  }\n}\n```\n\n## Core Concepts\n\n### Endpoint Descriptors\n\nEvery endpoint is defined by an arrow function that returns a descriptor:\n\n```typescript\nconst endpoint = (...params) => ({\n  method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS',\n  path: '/path/with/${params}',\n  body: any, // Request body schema\n  query: any, // Query parameters schema\n  headers: any, // Headers schema\n  responses: {\n    // Response schemas by status code\n    200: SuccessSchema,\n    400: ErrorSchema,\n  },\n  metadata: {\n    // Optional metadata\n    description: 'Endpoint description',\n    tags: ['user', 'admin'],\n    deprecated: false,\n  },\n});\n```\n\n### HTTP Helper Object\n\nThe `@abapify/speci/rest` module provides an `http` object with all HTTP methods:\n\n```typescript\nimport { http } from '@abapify/speci/rest';\n\n// Shortcut syntax - super clean!\nhttp.get<SuccessType>(path); // GET with 200 response\nhttp.post<SuccessType>(path, body); // POST with 201 response\nhttp.put<SuccessType>(path, body); // PUT with 200 response\nhttp.patch<SuccessType>(path, body); // PATCH with 200 response\nhttp.delete(path); // DELETE with 204 response\n\n// Full syntax - explicit control\nhttp.get(path, { responses: { 200: Type, 404: Error } });\nhttp.post(path, { body, responses: { 201: Type, 400: Error } });\n\n// Examples\nconst getUser = (id: string) => http.get<User>(`/users/${id}`);\nconst createUser = (user: CreateUserInput) => http.post<User>('/users', user);\n```\n\n**Why `http` object?** The `delete` keyword is reserved in JavaScript, so we use `http.delete` instead of a standalone `delete` function.\n\n### Schema Support\n\nSpeci supports **any schema library** - Zod, JSON Schema, custom schemas, etc. Use the `schema()` helper for clean syntax:\n\n```typescript\nimport { z } from 'zod';\nimport { schema } from '@abapify/speci/rest';\n\n// Zod schemas\nconst UserSchema = z.object({\n  id: z.string(),\n  name: z.string(),\n  email: z.string().email(),\n});\n\ntype User = z.infer<typeof UserSchema>;\n\nconst api = {\n  getUser: (id: string) =>\n    http.get(`/users/${id}`, {\n      responses: { 200: schema(UserSchema, {} as User) }, // ✅ Schema + type\n    }),\n};\n\n// Custom schemas (e.g., XML schemas for ADT)\nconst ClassSchema = {\n  element: 'class',\n  attributes: ['name', 'type'],\n  // ... your schema definition\n};\n\ninterface ClassXml {\n  name: string;\n  type: string;\n  // ... type definition\n}\n\nconst adtApi = {\n  getClass: (name: string) =>\n    http.get(`/classes/${name}`, {\n      responses: { 200: schema(ClassSchema, {} as ClassXml) }, // ✅ Schema + type\n    }),\n};\n```\n\n**How it works:**\n\n- `schema(schemaObject, {} as Type)` provides both runtime schema and compile-time type\n- Your **adapter** sees the schema object and uses it to parse/validate\n- TypeScript sees the type for full type safety\n- Type-only assertions still work: `undefined as unknown as Type`\n\nThis makes Speci **schema-agnostic** - use whatever validation library you prefer!\n\n### Global Error Responses\n\nAvoid repeating error types across all endpoints:\n\n```typescript\nimport { createHttp } from '@abapify/speci/rest';\n\n// 1. Define global error responses\nconst globalErrors = {\n  400: undefined as unknown as ApiError,\n  401: undefined as unknown as ApiError,\n  403: undefined as unknown as ApiError,\n  404: undefined as unknown as ApiError,\n  500: undefined as unknown as ApiError,\n} as const;\n\n// 2. Create http instance with global errors\nconst api = createHttp(globalErrors);\n\n// 3. Now only specify success responses!\nconst userApi = {\n  list: () => api.get<User[]>('/users'),\n  // 400, 401, 403, 404, 500 automatically added!\n\n  get: (id: string) => api.get<User>(`/users/${id}`),\n  // Global errors merged automatically\n\n  create: (user: CreateUserInput) => api.post<User>('/users', user),\n  // All endpoints get global errors\n};\n```\n\n### Path Parameters\n\nPath parameters are automatically extracted from template literals:\n\n```typescript\nconst getPost = (userId: string, postId: string) =>\n  http.get<Post>(`/users/${userId}/posts/${postId}`);\n\n// Speci automatically maps:\n// - First param (userId) → ${userId} in path\n// - Second param (postId) → ${postId} in path\n```\n\n### Error Handling\n\nErrors are thrown as `HttpError` with typed payloads:\n\n```typescript\nimport { HttpError } from '@abapify/speci/rest';\n\ntry {\n  const user = await client.users.get('123');\n  // user is typed as User (not User | ErrorResponse)\n} catch (error) {\n  // Option 1: Use endpoint-specific type guard\n  if (client.users.get.isError(error)) {\n    // error.payload is typed as ErrorResponse\n    console.error(`HTTP ${error.status}:`, error.payload.error);\n  }\n\n  // Option 2: Generic HttpError check\n  else if (error instanceof HttpError) {\n    console.error(`HTTP ${error.status}:`, error.payload);\n  }\n\n  // Option 3: Network or other errors\n  else {\n    console.error('Network error:', error);\n  }\n}\n```\n\n### Custom HTTP Adapters\n\nSpeci is adapter-agnostic. Bring your own HTTP client:\n\n```typescript\nimport type { HttpAdapter } from '@abapify/speci/rest';\n\nconst myAdapter: HttpAdapter = {\n  async request({ method, url, body, query, headers }) {\n    // Use any HTTP client: axios, got, ky, wretch, etc.\n    return await yourHttpClient.request({ method, url, body, query, headers });\n  },\n};\n\nconst client = createClient(api, {\n  baseUrl: 'https://api.example.com',\n  adapter: myAdapter,\n});\n```\n\n### Interceptors\n\nAdd request/response/error interceptors:\n\n```typescript\nconst client = createClient(api, {\n  baseUrl: 'https://api.example.com',\n  adapter: createFetchAdapter(),\n\n  // Add auth token to all requests\n  onRequest: async (options) => ({\n    ...options,\n    headers: {\n      ...options.headers,\n      Authorization: `Bearer ${getToken()}`,\n    },\n  }),\n\n  // Transform responses\n  onResponse: async (response) => {\n    console.log('Response:', response);\n    return response;\n  },\n\n  // Handle errors\n  onError: async (error) => {\n    console.error('Error:', error);\n    throw error;\n  },\n});\n```\n\n## What Speci Can Generate\n\nFrom your arrow-function contracts, Speci can generate:\n\n- ✅ **Typed clients** (implemented)\n- 🚧 **Server routing** (planned)\n- 🚧 **OpenAPI specs** (planned)\n- 🚧 **Mock servers** (planned)\n- 🚧 **Test fixtures** (planned)\n- 🚧 **CLI tools** (planned)\n- 🚧 **GraphQL schemas** (planned)\n- 🚧 **gRPC definitions** (planned)\n\n## Why Arrow Functions?\n\n✅ **No TypeScript AST parsing** - TS gives you function types natively  \n✅ **No decorators** - They're optional syntactic sugar  \n✅ **No template-literal parsing** - Simple variable extraction  \n✅ **Perfectly readable** - Looks like ordinary domain code  \n✅ **Fully expressible** - All REST/HATEOAS/gRPC concepts can be wrapped  \n✅ **Zero framework coupling** - Pure TypeScript\n\n## Coming from ts-rest?\n\nSpeci uses the same contract-first philosophy, but with arrow functions:\n\n```typescript\n// ts-rest\nconst contract = c.router({\n  getUser: {\n    method: 'GET',\n    path: '/users/:id',\n    responses: { 200: UserSchema },\n  },\n});\n\n// Speci\nconst contract = {\n  getUser: (id: string) => http.get<User>(`/users/${id}`),\n};\n```\n\nSame type safety, less boilerplate. Choose ts-rest for maturity, Speci for simplicity and XML support.\n\n## Comparison with ts-rest\n\n[ts-rest](https://ts-rest.com/) is an excellent, mature library for type-safe REST contracts and we recommend it for most use cases. Speci covers specific scenarios where you need: **dynamic routing as functions** (endpoints defined as arrow functions with parameters), **schema flexibility** (any schema library via adapters, not just Zod), and a **flexible plugin system** for custom extensions. If ts-rest's contract-first approach works for you, use it—it's battle-tested and production-ready.\n\n## Comparison with Other Tools\n\n| Feature            | Speci           | ts-rest     | tRPC       | OpenAPI    |\n| ------------------ | --------------- | ----------- | ---------- | ---------- |\n| **Syntax**         | Arrow functions | Builder API | Procedures | YAML/JSON  |\n| **Type Safety**    | ✅ Full         | ✅ Full     | ✅ Full    | ⚠️ Codegen |\n| **Dependencies**   | 0               | Few         | Many       | Many       |\n| **Learning Curve** | Minimal         | Low         | Medium     | High       |\n| **Client**         | ✅ Yes          | ✅ Yes      | ✅ Yes     | ✅ Yes     |\n| **Server**         | 🚧 Planned      | ✅ Yes      | ✅ Yes     | ⚠️ Partial |\n| **Validation**     | 🚧 Planned      | ✅ Zod      | ✅ Zod     | ⚠️ Varies  |\n\n## Documentation\n\n- [Body Parameter Inference](./docs/body-inference.md) - Automatic body type inference from schemas\n\n## License\n\nMIT\n\n## Contributing\n\nContributions welcome! This is v0.1 - the minimal viable core. Future versions will add:\n\n- Server adapters (Express, Fastify, Hono, etc.)\n- OpenAPI generation\n- Mock server generation\n- Schema validation (Zod, JSON Schema, etc.)\n- And more!\n","readmeFilename":"README.md"}