@richie-rpc/core 1.2.4 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -23,6 +23,8 @@ const contract = defineContract({
23
23
  params: z.object({ id: z.string() }),
24
24
  responses: {
25
25
  [Status.OK]: z.object({ id: z.string(), name: z.string() }),
26
+ },
27
+ errorResponses: {
26
28
  [Status.NotFound]: z.object({ error: z.string() }),
27
29
  },
28
30
  },
@@ -33,6 +35,9 @@ const contract = defineContract({
33
35
  responses: {
34
36
  [Status.Created]: z.object({ id: z.string(), name: z.string(), email: z.string() }),
35
37
  },
38
+ errorResponses: {
39
+ [Status.BadRequest]: z.object({ error: z.string() }),
40
+ },
36
41
  },
37
42
  });
38
43
  ```
@@ -48,7 +53,8 @@ Each endpoint can have:
48
53
  - `headers` (optional): Zod schema for request headers
49
54
  - `body` (optional): Zod schema for request body
50
55
  - `contentType` (optional): Request content type (`'application/json'` or `'multipart/form-data'`)
51
- - `responses` (required): Object mapping status codes to Zod schemas
56
+ - `responses` (required): Object mapping success status codes to Zod schemas
57
+ - `errorResponses` (optional): Object mapping error status codes to Zod schemas. Error responses are thrown as `ErrorResponse` by the client instead of being returned as data, enabling clean `useSuspenseQuery` patterns where `data` is always the success type.
52
58
 
53
59
  ### Streaming Endpoint
54
60
 
@@ -258,6 +264,8 @@ const contract = defineContract({
258
264
  params: z.object({ id: z.string() }),
259
265
  responses: {
260
266
  [Status.OK]: UserSchema,
267
+ },
268
+ errorResponses: {
261
269
  [Status.NotFound]: ErrorSchema,
262
270
  },
263
271
  },
@@ -311,6 +319,8 @@ The package exports several utility types for extracting types from endpoint def
311
319
  - `ExtractBody<T>`: Extract request body type
312
320
  - `ExtractResponses<T>`: Extract all response types
313
321
  - `ExtractResponse<T, Status>`: Extract specific response type by status code
322
+ - `ExtractErrorResponses<T>`: Extract all error response types
323
+ - `ExtractErrorResponse<T, Status>`: Extract specific error response type by status code
314
324
 
315
325
  ## Links
316
326
 
@@ -47,7 +47,9 @@ var __export = (target, all) => {
47
47
  // packages/core/index.ts
48
48
  var exports_core = {};
49
49
  __export(exports_core, {
50
+ validatePathPattern: () => validatePathPattern,
50
51
  parseQuery: () => parseQuery,
52
+ parsePathParamsDetailed: () => parsePathParamsDetailed,
51
53
  parsePathParams: () => parsePathParams,
52
54
  objectToFormData: () => objectToFormData,
53
55
  matchPath: () => matchPath,
@@ -81,32 +83,68 @@ var Status = {
81
83
  ServiceUnavailable: 503,
82
84
  GatewayTimeout: 504
83
85
  };
86
+ function parsePathParamsDetailed(path) {
87
+ const regular = [];
88
+ const regularMatches = path.match(/:([^/]+)/g);
89
+ if (regularMatches) {
90
+ regular.push(...regularMatches.map((match) => match.slice(1)));
91
+ }
92
+ const wildcardMatch = path.match(/\*([^/]+)$/);
93
+ return {
94
+ regular,
95
+ wildcard: wildcardMatch?.[1] ?? null
96
+ };
97
+ }
84
98
  function parsePathParams(path) {
85
- const matches = path.match(/:([^/]+)/g);
86
- if (!matches)
87
- return [];
88
- return matches.map((match) => match.slice(1));
99
+ const { regular, wildcard } = parsePathParamsDetailed(path);
100
+ return wildcard ? [...regular, wildcard] : regular;
89
101
  }
90
102
  function matchPath(pattern, path) {
91
- const paramNames = parsePathParams(pattern);
92
- const regexPattern = pattern.replace(/:[^/]+/g, "([^/]+)").replace(/\//g, "\\/");
103
+ const { regular, wildcard } = parsePathParamsDetailed(pattern);
104
+ let regexPattern = pattern.replace(/:[^/]+/g, "([^/]+)").replace(/\//g, "\\/");
105
+ if (wildcard) {
106
+ regexPattern = regexPattern.replace(/\*[^/]+$/, "(.+)");
107
+ }
93
108
  const regex = new RegExp(`^${regexPattern}$`);
94
109
  const match = path.match(regex);
95
110
  if (!match)
96
111
  return null;
97
112
  const params = {};
98
- paramNames.forEach((name, index) => {
113
+ regular.forEach((name, index) => {
99
114
  params[name] = match[index + 1] ?? "";
100
115
  });
116
+ if (wildcard) {
117
+ params[wildcard] = match[regular.length + 1] ?? "";
118
+ }
101
119
  return params;
102
120
  }
103
121
  function interpolatePath(pattern, params) {
104
122
  let result = pattern;
105
123
  for (const [key, value] of Object.entries(params)) {
106
- result = result.replace(`:${key}`, String(value));
124
+ if (result.includes(`:${key}`)) {
125
+ result = result.replace(`:${key}`, String(value));
126
+ } else if (result.includes(`*${key}`)) {
127
+ result = result.replace(`*${key}`, String(value));
128
+ }
107
129
  }
108
130
  return result;
109
131
  }
132
+ function validatePathPattern(pattern) {
133
+ const wildcardIndex = pattern.indexOf("*");
134
+ if (wildcardIndex !== -1) {
135
+ const afterWildcard = pattern.slice(wildcardIndex + 1);
136
+ if (afterWildcard.includes("/")) {
137
+ throw new Error(`Invalid path pattern "${pattern}": Wildcard parameter must be the last segment`);
138
+ }
139
+ if (!/^\*[a-zA-Z_][a-zA-Z0-9_]*$/.test(pattern.slice(wildcardIndex))) {
140
+ throw new Error(`Invalid path pattern "${pattern}": Wildcard parameter must have a valid name (e.g., *filePath)`);
141
+ }
142
+ }
143
+ const wildcardCount = (pattern.match(/\*/g) || []).length;
144
+ if (wildcardCount > 1) {
145
+ throw new Error(`Invalid path pattern "${pattern}": Only one wildcard parameter is allowed`);
146
+ }
147
+ }
110
148
  function buildUrl(baseUrl, path, query) {
111
149
  const normalizedBase = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
112
150
  const normalizedPath = path.startsWith("/") ? path : `/${path}`;
@@ -200,4 +238,4 @@ function formDataToObject(formData) {
200
238
  }
201
239
  })
202
240
 
203
- //# debugId=7092E09755013A0164756E2164756E21
241
+ //# debugId=CA0ED6C82DBBBD5564756E2164756E21
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../../index.ts"],
4
4
  "sourcesContent": [
5
- "import type { z } from 'zod';\n\n// HTTP methods supported\nexport type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';\n\n// Content types supported for request bodies\nexport type ContentType = 'application/json' | 'multipart/form-data';\n\n// HTTP status codes as const object for type-safe responses without 'as const'\nexport const Status = {\n // Success responses\n OK: 200 as const,\n Created: 201 as const,\n Accepted: 202 as const,\n NoContent: 204 as const,\n\n // Redirection\n MovedPermanently: 301 as const,\n Found: 302 as const,\n NotModified: 304 as const,\n\n // Client errors\n BadRequest: 400 as const,\n Unauthorized: 401 as const,\n Forbidden: 403 as const,\n NotFound: 404 as const,\n MethodNotAllowed: 405 as const,\n Conflict: 409 as const,\n UnprocessableEntity: 422 as const,\n TooManyRequests: 429 as const,\n\n // Server errors\n InternalServerError: 500 as const,\n NotImplemented: 501 as const,\n BadGateway: 502 as const,\n ServiceUnavailable: 503 as const,\n GatewayTimeout: 504 as const,\n} as const;\n\n// Base fields shared by all endpoint types\ninterface BaseEndpointFields {\n path: string;\n params?: z.ZodTypeAny;\n query?: z.ZodTypeAny;\n headers?: z.ZodTypeAny;\n}\n\n// Standard HTTP endpoint\nexport interface StandardEndpointDefinition extends BaseEndpointFields {\n type: 'standard';\n method: HttpMethod;\n body?: z.ZodTypeAny;\n contentType?: ContentType;\n responses: Record<number, z.ZodTypeAny>;\n}\n\n// Streaming response endpoint (NDJSON)\nexport interface StreamingEndpointDefinition extends BaseEndpointFields {\n type: 'streaming';\n method: 'POST';\n body?: z.ZodTypeAny;\n contentType?: ContentType;\n /** Schema for each NDJSON chunk (type inference only, not validated) */\n chunk: z.ZodTypeAny;\n /** Optional final response after stream ends */\n finalResponse?: z.ZodTypeAny;\n /** Error responses for non-streaming failures */\n errorResponses?: Record<number, z.ZodTypeAny>;\n}\n\n// SSE endpoint\nexport interface SSEEndpointDefinition extends BaseEndpointFields {\n type: 'sse';\n method: 'GET';\n /** Event types: key = event name, value = data schema (type inference only) */\n events: Record<string, z.ZodTypeAny>;\n /** Error responses for connection failures */\n errorResponses?: Record<number, z.ZodTypeAny>;\n}\n\n// Download endpoint (binary file response)\nexport interface DownloadEndpointDefinition extends BaseEndpointFields {\n type: 'download';\n method: 'GET'; // Downloads are GET-only\n /** Error responses (non-2xx status codes) */\n errorResponses?: Record<number, z.ZodTypeAny>;\n}\n\n// Union of all endpoint types\nexport type AnyEndpointDefinition =\n | StandardEndpointDefinition\n | StreamingEndpointDefinition\n | SSEEndpointDefinition\n | DownloadEndpointDefinition;\n\n// Alias for backwards compatibility in type utilities\nexport type EndpointDefinition = AnyEndpointDefinition;\n\n// Contract is a collection of named endpoints\nexport type Contract = Record<string, AnyEndpointDefinition>;\n\n// Extract the Zod type from a schema\nexport type InferZodType<T> = T extends z.ZodTypeAny ? z.infer<T> : never;\n\n// Extract params type from endpoint\nexport type ExtractParams<T extends EndpointDefinition> = T['params'] extends z.ZodTypeAny\n ? InferZodType<T['params']>\n : never;\n\n// Extract query type from endpoint\nexport type ExtractQuery<T extends EndpointDefinition> = T['query'] extends z.ZodTypeAny\n ? InferZodType<T['query']>\n : never;\n\n// Extract headers type from endpoint\nexport type ExtractHeaders<T extends EndpointDefinition> = T['headers'] extends z.ZodTypeAny\n ? InferZodType<T['headers']>\n : never;\n\n// Extract body type from endpoint (only standard and streaming have body)\nexport type ExtractBody<T extends EndpointDefinition> = T extends { body: z.ZodTypeAny }\n ? InferZodType<T['body']>\n : never;\n\n// Extract response types for all status codes (only standard has responses)\nexport type ExtractResponses<T extends EndpointDefinition> = T extends {\n responses: Record<number, z.ZodTypeAny>;\n}\n ? {\n [K in keyof T['responses']]: T['responses'][K] extends z.ZodTypeAny\n ? InferZodType<T['responses'][K]>\n : never;\n }\n : never;\n\n// Extract a specific response type by status code (only standard has responses)\nexport type ExtractResponse<T extends EndpointDefinition, Status extends number> = T extends {\n responses: Record<number, z.ZodTypeAny>;\n}\n ? Status extends keyof T['responses']\n ? T['responses'][Status] extends z.ZodTypeAny\n ? InferZodType<T['responses'][Status]>\n : never\n : never\n : never;\n\n// Extract chunk type from streaming endpoint\nexport type ExtractChunk<T extends StreamingEndpointDefinition> = T['chunk'] extends z.ZodTypeAny\n ? InferZodType<T['chunk']>\n : never;\n\n// Extract final response type from streaming endpoint\nexport type ExtractFinalResponse<T extends StreamingEndpointDefinition> =\n T['finalResponse'] extends z.ZodTypeAny ? InferZodType<T['finalResponse']> : undefined;\n\n// Extract SSE event union type\nexport type ExtractSSEEvents<T extends SSEEndpointDefinition> = {\n [K in keyof T['events']]: {\n event: K;\n data: T['events'][K] extends z.ZodTypeAny ? InferZodType<T['events'][K]> : never;\n id?: string;\n };\n}[keyof T['events']];\n\n// Extract specific SSE event data type\nexport type ExtractSSEEventData<\n T extends SSEEndpointDefinition,\n K extends keyof T['events'],\n> = T['events'][K] extends z.ZodTypeAny ? InferZodType<T['events'][K]> : never;\n\n// Extract error responses for download endpoint\nexport type ExtractDownloadErrorResponse<\n T extends DownloadEndpointDefinition,\n Status extends keyof T['errorResponses'],\n> =\n T['errorResponses'] extends Record<number, z.ZodTypeAny>\n ? Status extends keyof T['errorResponses']\n ? T['errorResponses'][Status] extends z.ZodTypeAny\n ? InferZodType<T['errorResponses'][Status]>\n : never\n : never\n : never;\n\n// Upload progress event\nexport interface UploadProgressEvent {\n loaded: number;\n total: number;\n progress: number; // 0-1 (percentage as decimal)\n}\n\n// Download progress event\nexport interface DownloadProgressEvent {\n loaded: number;\n total: number;\n progress: number; // 0-1 (percentage as decimal), NaN if total unknown\n}\n\n// Path parameter extraction utilities\nexport type ExtractPathParams<T extends string> =\n T extends `${infer _Start}:${infer Param}/${infer Rest}`\n ? Param | ExtractPathParams<`/${Rest}`>\n : T extends `${infer _Start}:${infer Param}`\n ? Param\n : never;\n\n// Convert path params to object type\nexport type PathParamsObject<T extends string> = {\n [K in ExtractPathParams<T>]: string;\n};\n\n/**\n * Parse path parameters from a URL path pattern\n * e.g., \"/users/:id/posts/:postId\" => [\"id\", \"postId\"]\n */\nexport function parsePathParams(path: string): string[] {\n const matches = path.match(/:([^/]+)/g);\n if (!matches) return [];\n return matches.map((match) => match.slice(1));\n}\n\n/**\n * Match a URL path against a pattern and extract parameters\n * e.g., matchPath(\"/users/:id\", \"/users/123\") => { id: \"123\" }\n */\nexport function matchPath(pattern: string, path: string): Record<string, string> | null {\n const paramNames = parsePathParams(pattern);\n\n // Convert pattern to regex\n const regexPattern = pattern.replace(/:[^/]+/g, '([^/]+)').replace(/\\//g, '\\\\/');\n\n const regex = new RegExp(`^${regexPattern}$`);\n const match = path.match(regex);\n\n if (!match) return null;\n\n const params: Record<string, string> = {};\n paramNames.forEach((name, index) => {\n params[name] = match[index + 1] ?? '';\n });\n\n return params;\n}\n\n/**\n * Interpolate path parameters into a URL pattern\n * e.g., interpolatePath(\"/users/:id\", { id: \"123\" }) => \"/users/123\"\n */\nexport function interpolatePath(pattern: string, params: Record<string, string | number>): string {\n let result = pattern;\n for (const [key, value] of Object.entries(params)) {\n result = result.replace(`:${key}`, String(value));\n }\n return result;\n}\n\n/**\n * Build a complete URL with query parameters\n */\nexport function buildUrl(\n baseUrl: string,\n path: string,\n query?: Record<string, string | number | boolean | string[]>,\n): string {\n // Normalize baseUrl - remove trailing slash\n const normalizedBase = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;\n\n // Ensure path starts with /\n const normalizedPath = path.startsWith('/') ? path : `/${path}`;\n\n // Concatenate base and path\n const fullPath = normalizedBase + normalizedPath;\n\n const url = new URL(fullPath);\n\n if (query) {\n for (const [key, value] of Object.entries(query)) {\n if (value !== undefined && value !== null) {\n if (Array.isArray(value)) {\n for (const v of value) {\n url.searchParams.append(key, String(v));\n }\n } else {\n url.searchParams.append(key, String(value));\n }\n }\n }\n }\n\n return url.toString();\n}\n\n/**\n * Parse query parameters from URLSearchParams\n */\nexport function parseQuery(searchParams: URLSearchParams): Record<string, string | string[]> {\n const result: Record<string, string | string[]> = {};\n\n for (const [key, value] of searchParams.entries()) {\n const existing = result[key];\n if (existing) {\n if (Array.isArray(existing)) {\n existing.push(value);\n } else {\n result[key] = [existing, value];\n }\n } else {\n result[key] = value;\n }\n }\n\n return result;\n}\n\n// Type helper to ensure a value is a valid contract\nexport function defineContract<T extends Contract>(contract: T): T {\n return contract;\n}\n\n/**\n * Convert an object to FormData using the hybrid JSON + Files approach.\n * Files are extracted and replaced with { __fileRef__: \"path\" } placeholders.\n * The resulting FormData contains __json__ with the serialized structure\n * and individual file entries at their path keys.\n */\nexport function objectToFormData(obj: Record<string, unknown>): FormData {\n const formData = new FormData();\n const files: Array<{ path: string; file: File }> = [];\n\n function traverse(value: unknown, path: string): unknown {\n if (value instanceof File) {\n files.push({ path, file: value });\n return { __fileRef__: path };\n }\n if (Array.isArray(value)) {\n return value.map((item, i) => traverse(item, `${path}.${i}`));\n }\n if (value && typeof value === 'object') {\n const result: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value)) {\n result[k] = traverse(v, path ? `${path}.${k}` : k);\n }\n return result;\n }\n return value;\n }\n\n const jsonWithRefs = traverse(obj, '');\n formData.append('__json__', JSON.stringify(jsonWithRefs));\n\n for (const { path, file } of files) {\n formData.append(path, file);\n }\n\n return formData;\n}\n\n/**\n * Parse FormData back to an object, reconstructing the structure with File objects.\n * Expects FormData created by objectToFormData with __json__ and file entries.\n * Falls back to simple Object.fromEntries for FormData without __json__.\n */\nexport function formDataToObject(formData: FormData): Record<string, unknown> {\n const jsonStr = formData.get('__json__');\n if (typeof jsonStr !== 'string') {\n return Object.fromEntries(formData.entries());\n }\n\n const obj = JSON.parse(jsonStr);\n\n function replaceRefs(value: unknown): unknown {\n if (value && typeof value === 'object' && '__fileRef__' in value) {\n const path = (value as { __fileRef__: string }).__fileRef__;\n return formData.get(path);\n }\n if (Array.isArray(value)) {\n return value.map(replaceRefs);\n }\n if (value && typeof value === 'object') {\n const result: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value)) {\n result[k] = replaceRefs(v);\n }\n return result;\n }\n return value;\n }\n\n return replaceRefs(obj) as Record<string, unknown>;\n}\n\n// Re-export WebSocket types\nexport * from './websocket.cjs';\n"
5
+ "import type { z } from 'zod';\n\n// HTTP methods supported\nexport type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';\n\n// Content types supported for request bodies\nexport type ContentType = 'application/json' | 'multipart/form-data';\n\n// HTTP status codes as const object for type-safe responses without 'as const'\nexport const Status = {\n // Success responses\n OK: 200 as const,\n Created: 201 as const,\n Accepted: 202 as const,\n NoContent: 204 as const,\n\n // Redirection\n MovedPermanently: 301 as const,\n Found: 302 as const,\n NotModified: 304 as const,\n\n // Client errors\n BadRequest: 400 as const,\n Unauthorized: 401 as const,\n Forbidden: 403 as const,\n NotFound: 404 as const,\n MethodNotAllowed: 405 as const,\n Conflict: 409 as const,\n UnprocessableEntity: 422 as const,\n TooManyRequests: 429 as const,\n\n // Server errors\n InternalServerError: 500 as const,\n NotImplemented: 501 as const,\n BadGateway: 502 as const,\n ServiceUnavailable: 503 as const,\n GatewayTimeout: 504 as const,\n} as const;\n\n// Base fields shared by all endpoint types\ninterface BaseEndpointFields {\n path: string;\n params?: z.ZodTypeAny;\n query?: z.ZodTypeAny;\n headers?: z.ZodTypeAny;\n}\n\n// Standard HTTP endpoint\nexport interface StandardEndpointDefinition extends BaseEndpointFields {\n type: 'standard';\n method: HttpMethod;\n body?: z.ZodTypeAny;\n contentType?: ContentType;\n responses: Record<number, z.ZodTypeAny>;\n errorResponses?: Record<number, z.ZodTypeAny>;\n}\n\n// Streaming response endpoint (NDJSON)\nexport interface StreamingEndpointDefinition extends BaseEndpointFields {\n type: 'streaming';\n method: 'POST';\n body?: z.ZodTypeAny;\n contentType?: ContentType;\n /** Schema for each NDJSON chunk (type inference only, not validated) */\n chunk: z.ZodTypeAny;\n /** Optional final response after stream ends */\n finalResponse?: z.ZodTypeAny;\n /** Error responses for non-streaming failures */\n errorResponses?: Record<number, z.ZodTypeAny>;\n}\n\n// SSE endpoint\nexport interface SSEEndpointDefinition extends BaseEndpointFields {\n type: 'sse';\n method: 'GET';\n /** Event types: key = event name, value = data schema (type inference only) */\n events: Record<string, z.ZodTypeAny>;\n /** Error responses for connection failures */\n errorResponses?: Record<number, z.ZodTypeAny>;\n}\n\n// Download endpoint (binary file response)\nexport interface DownloadEndpointDefinition extends BaseEndpointFields {\n type: 'download';\n method: 'GET'; // Downloads are GET-only\n /** Error responses (non-2xx status codes) */\n errorResponses?: Record<number, z.ZodTypeAny>;\n}\n\n// Union of all endpoint types\nexport type AnyEndpointDefinition =\n | StandardEndpointDefinition\n | StreamingEndpointDefinition\n | SSEEndpointDefinition\n | DownloadEndpointDefinition;\n\n// Alias for backwards compatibility in type utilities\nexport type EndpointDefinition = AnyEndpointDefinition;\n\n// Contract is a collection of named endpoints\nexport type Contract = Record<string, AnyEndpointDefinition>;\n\n// Extract the Zod type from a schema\nexport type InferZodType<T> = T extends z.ZodTypeAny ? z.infer<T> : never;\n\n// Extract params type from endpoint\nexport type ExtractParams<T extends EndpointDefinition> = T['params'] extends z.ZodTypeAny\n ? InferZodType<T['params']>\n : never;\n\n// Extract query type from endpoint\nexport type ExtractQuery<T extends EndpointDefinition> = T['query'] extends z.ZodTypeAny\n ? InferZodType<T['query']>\n : never;\n\n// Extract headers type from endpoint\nexport type ExtractHeaders<T extends EndpointDefinition> = T['headers'] extends z.ZodTypeAny\n ? InferZodType<T['headers']>\n : never;\n\n// Extract body type from endpoint (only standard and streaming have body)\nexport type ExtractBody<T extends EndpointDefinition> = T extends { body: z.ZodTypeAny }\n ? InferZodType<T['body']>\n : never;\n\n// Extract response types for all status codes (only standard has responses)\nexport type ExtractResponses<T extends EndpointDefinition> = T extends {\n responses: Record<number, z.ZodTypeAny>;\n}\n ? {\n [K in keyof T['responses']]: T['responses'][K] extends z.ZodTypeAny\n ? InferZodType<T['responses'][K]>\n : never;\n }\n : never;\n\n// Extract a specific response type by status code (only standard has responses)\nexport type ExtractResponse<T extends EndpointDefinition, Status extends number> = T extends {\n responses: Record<number, z.ZodTypeAny>;\n}\n ? Status extends keyof T['responses']\n ? T['responses'][Status] extends z.ZodTypeAny\n ? InferZodType<T['responses'][Status]>\n : never\n : never\n : never;\n\n// Extract error response types for all status codes\nexport type ExtractErrorResponses<T extends EndpointDefinition> = T extends {\n errorResponses: Record<number, z.ZodTypeAny>;\n} ? {\n [K in keyof T['errorResponses']]: T['errorResponses'][K] extends z.ZodTypeAny\n ? InferZodType<T['errorResponses'][K]>\n : never;\n } : never;\n\n// Extract a specific error response type by status code\nexport type ExtractErrorResponse<T extends EndpointDefinition, Status extends number> = T extends {\n errorResponses: Record<number, z.ZodTypeAny>;\n} ? Status extends keyof T['errorResponses']\n ? T['errorResponses'][Status] extends z.ZodTypeAny\n ? InferZodType<T['errorResponses'][Status]>\n : never\n : never\n : never;\n\n// Extract chunk type from streaming endpoint\nexport type ExtractChunk<T extends StreamingEndpointDefinition> = T['chunk'] extends z.ZodTypeAny\n ? InferZodType<T['chunk']>\n : never;\n\n// Extract final response type from streaming endpoint\nexport type ExtractFinalResponse<T extends StreamingEndpointDefinition> =\n T['finalResponse'] extends z.ZodTypeAny ? InferZodType<T['finalResponse']> : undefined;\n\n// Extract SSE event union type\nexport type ExtractSSEEvents<T extends SSEEndpointDefinition> = {\n [K in keyof T['events']]: {\n event: K;\n data: T['events'][K] extends z.ZodTypeAny ? InferZodType<T['events'][K]> : never;\n id?: string;\n };\n}[keyof T['events']];\n\n// Extract specific SSE event data type\nexport type ExtractSSEEventData<\n T extends SSEEndpointDefinition,\n K extends keyof T['events'],\n> = T['events'][K] extends z.ZodTypeAny ? InferZodType<T['events'][K]> : never;\n\n// Extract error responses for download endpoint\nexport type ExtractDownloadErrorResponse<\n T extends DownloadEndpointDefinition,\n Status extends keyof T['errorResponses'],\n> =\n T['errorResponses'] extends Record<number, z.ZodTypeAny>\n ? Status extends keyof T['errorResponses']\n ? T['errorResponses'][Status] extends z.ZodTypeAny\n ? InferZodType<T['errorResponses'][Status]>\n : never\n : never\n : never;\n\n// Upload progress event\nexport interface UploadProgressEvent {\n loaded: number;\n total: number;\n progress: number; // 0-1 (percentage as decimal)\n}\n\n// Download progress event\nexport interface DownloadProgressEvent {\n loaded: number;\n total: number;\n progress: number; // 0-1 (percentage as decimal), NaN if total unknown\n}\n\n// Path parameter extraction utilities\n// Extract regular path params (:param)\ntype ExtractRegularParams<T extends string> =\n T extends `${infer _Start}:${infer Param}/${infer Rest}`\n ? Param | ExtractRegularParams<`/${Rest}`>\n : T extends `${infer _Start}:${infer Param}`\n ? Param\n : never;\n\n// Extract wildcard params (*param) - must be at end of path\ntype ExtractWildcardParams<T extends string> = T extends `${infer _Start}*${infer Param}`\n ? Param\n : never;\n\n// Combined extraction of all path params\nexport type ExtractPathParams<T extends string> =\n | ExtractRegularParams<T>\n | ExtractWildcardParams<T>;\n\n// Convert path params to object type\nexport type PathParamsObject<T extends string> = {\n [K in ExtractPathParams<T>]: string;\n};\n\n/**\n * Detailed path parameter info distinguishing regular vs wildcard params\n */\nexport interface ParsedPathParams {\n regular: string[];\n wildcard: string | null;\n}\n\n/**\n * Parse path parameters with detailed type information\n * e.g., \"/users/:id/*path\" => { regular: [\"id\"], wildcard: \"path\" }\n */\nexport function parsePathParamsDetailed(path: string): ParsedPathParams {\n const regular: string[] = [];\n\n // Extract regular params (:param)\n const regularMatches = path.match(/:([^/]+)/g);\n if (regularMatches) {\n regular.push(...regularMatches.map((match) => match.slice(1)));\n }\n\n // Extract wildcard param (*param) - must be at end\n const wildcardMatch = path.match(/\\*([^/]+)$/);\n\n return {\n regular,\n wildcard: wildcardMatch?.[1] ?? null,\n };\n}\n\n/**\n * Parse path parameters from a URL path pattern\n * e.g., \"/users/:id/posts/:postId\" => [\"id\", \"postId\"]\n * e.g., \"/files/*path\" => [\"path\"]\n */\nexport function parsePathParams(path: string): string[] {\n const { regular, wildcard } = parsePathParamsDetailed(path);\n return wildcard ? [...regular, wildcard] : regular;\n}\n\n/**\n * Match a URL path against a pattern and extract parameters\n * e.g., matchPath(\"/users/:id\", \"/users/123\") => { id: \"123\" }\n * e.g., matchPath(\"/files/*path\", \"/files/a/b/c.txt\") => { path: \"a/b/c.txt\" }\n */\nexport function matchPath(pattern: string, path: string): Record<string, string> | null {\n const { regular, wildcard } = parsePathParamsDetailed(pattern);\n\n // Convert pattern to regex:\n // 1. Replace :param with capturing group for single segment\n // 2. Replace *param with capturing group for remaining path (including slashes)\n let regexPattern = pattern.replace(/:[^/]+/g, '([^/]+)').replace(/\\//g, '\\\\/');\n\n // Handle wildcard at the end - replace *paramName with (.+)\n if (wildcard) {\n regexPattern = regexPattern.replace(/\\*[^/]+$/, '(.+)');\n }\n\n const regex = new RegExp(`^${regexPattern}$`);\n const match = path.match(regex);\n\n if (!match) return null;\n\n const params: Record<string, string> = {};\n\n // Extract regular params first\n regular.forEach((name, index) => {\n params[name] = match[index + 1] ?? '';\n });\n\n // Extract wildcard param if present (always last capture group)\n if (wildcard) {\n params[wildcard] = match[regular.length + 1] ?? '';\n }\n\n return params;\n}\n\n/**\n * Interpolate path parameters into a URL pattern\n * e.g., interpolatePath(\"/users/:id\", { id: \"123\" }) => \"/users/123\"\n * e.g., interpolatePath(\"/files/*path\", { path: \"a/b/c.txt\" }) => \"/files/a/b/c.txt\"\n */\nexport function interpolatePath(pattern: string, params: Record<string, string | number>): string {\n let result = pattern;\n for (const [key, value] of Object.entries(params)) {\n // Try replacing :param first, then *param\n if (result.includes(`:${key}`)) {\n result = result.replace(`:${key}`, String(value));\n } else if (result.includes(`*${key}`)) {\n result = result.replace(`*${key}`, String(value));\n }\n }\n return result;\n}\n\n/**\n * Validate a path pattern for correct syntax\n * @throws Error if pattern is invalid\n */\nexport function validatePathPattern(pattern: string): void {\n // Check for wildcard not at the end\n const wildcardIndex = pattern.indexOf('*');\n if (wildcardIndex !== -1) {\n const afterWildcard = pattern.slice(wildcardIndex + 1);\n\n // Wildcard param name should go to end of string (no more slashes)\n if (afterWildcard.includes('/')) {\n throw new Error(\n `Invalid path pattern \"${pattern}\": Wildcard parameter must be the last segment`,\n );\n }\n\n // Ensure wildcard has a valid name (like Express 5 requires)\n if (!/^\\*[a-zA-Z_][a-zA-Z0-9_]*$/.test(pattern.slice(wildcardIndex))) {\n throw new Error(\n `Invalid path pattern \"${pattern}\": Wildcard parameter must have a valid name (e.g., *filePath)`,\n );\n }\n }\n\n // Check for multiple wildcards\n const wildcardCount = (pattern.match(/\\*/g) || []).length;\n if (wildcardCount > 1) {\n throw new Error(`Invalid path pattern \"${pattern}\": Only one wildcard parameter is allowed`);\n }\n}\n\n/**\n * Build a complete URL with query parameters\n */\nexport function buildUrl(\n baseUrl: string,\n path: string,\n query?: Record<string, string | number | boolean | string[]>,\n): string {\n // Normalize baseUrl - remove trailing slash\n const normalizedBase = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;\n\n // Ensure path starts with /\n const normalizedPath = path.startsWith('/') ? path : `/${path}`;\n\n // Concatenate base and path\n const fullPath = normalizedBase + normalizedPath;\n\n const url = new URL(fullPath);\n\n if (query) {\n for (const [key, value] of Object.entries(query)) {\n if (value !== undefined && value !== null) {\n if (Array.isArray(value)) {\n for (const v of value) {\n url.searchParams.append(key, String(v));\n }\n } else {\n url.searchParams.append(key, String(value));\n }\n }\n }\n }\n\n return url.toString();\n}\n\n/**\n * Parse query parameters from URLSearchParams\n */\nexport function parseQuery(searchParams: URLSearchParams): Record<string, string | string[]> {\n const result: Record<string, string | string[]> = {};\n\n for (const [key, value] of searchParams.entries()) {\n const existing = result[key];\n if (existing) {\n if (Array.isArray(existing)) {\n existing.push(value);\n } else {\n result[key] = [existing, value];\n }\n } else {\n result[key] = value;\n }\n }\n\n return result;\n}\n\n// Type helper to ensure a value is a valid contract\nexport function defineContract<T extends Contract>(contract: T): T {\n return contract;\n}\n\n/**\n * Convert an object to FormData using the hybrid JSON + Files approach.\n * Files are extracted and replaced with { __fileRef__: \"path\" } placeholders.\n * The resulting FormData contains __json__ with the serialized structure\n * and individual file entries at their path keys.\n */\nexport function objectToFormData(obj: Record<string, unknown>): FormData {\n const formData = new FormData();\n const files: Array<{ path: string; file: File }> = [];\n\n function traverse(value: unknown, path: string): unknown {\n if (value instanceof File) {\n files.push({ path, file: value });\n return { __fileRef__: path };\n }\n if (Array.isArray(value)) {\n return value.map((item, i) => traverse(item, `${path}.${i}`));\n }\n if (value && typeof value === 'object') {\n const result: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value)) {\n result[k] = traverse(v, path ? `${path}.${k}` : k);\n }\n return result;\n }\n return value;\n }\n\n const jsonWithRefs = traverse(obj, '');\n formData.append('__json__', JSON.stringify(jsonWithRefs));\n\n for (const { path, file } of files) {\n formData.append(path, file);\n }\n\n return formData;\n}\n\n/**\n * Parse FormData back to an object, reconstructing the structure with File objects.\n * Expects FormData created by objectToFormData with __json__ and file entries.\n * Falls back to simple Object.fromEntries for FormData without __json__.\n */\nexport function formDataToObject(formData: FormData): Record<string, unknown> {\n const jsonStr = formData.get('__json__');\n if (typeof jsonStr !== 'string') {\n return Object.fromEntries(formData.entries());\n }\n\n const obj = JSON.parse(jsonStr);\n\n function replaceRefs(value: unknown): unknown {\n if (value && typeof value === 'object' && '__fileRef__' in value) {\n const path = (value as { __fileRef__: string }).__fileRef__;\n return formData.get(path);\n }\n if (Array.isArray(value)) {\n return value.map(replaceRefs);\n }\n if (value && typeof value === 'object') {\n const result: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value)) {\n result[k] = replaceRefs(v);\n }\n return result;\n }\n return value;\n }\n\n return replaceRefs(obj) as Record<string, unknown>;\n}\n\n// Re-export WebSocket types\nexport * from './websocket.cjs';\n"
6
6
  ],
7
- "mappings": "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",
8
- "debugId": "7092E09755013A0164756E2164756E21",
7
+ "mappings": "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",
8
+ "debugId": "CA0ED6C82DBBBD5564756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@richie-rpc/core",
3
- "version": "1.2.4",
3
+ "version": "2.0.0",
4
4
  "type": "commonjs"
5
5
  }
@@ -23,32 +23,68 @@ var Status = {
23
23
  ServiceUnavailable: 503,
24
24
  GatewayTimeout: 504
25
25
  };
26
+ function parsePathParamsDetailed(path) {
27
+ const regular = [];
28
+ const regularMatches = path.match(/:([^/]+)/g);
29
+ if (regularMatches) {
30
+ regular.push(...regularMatches.map((match) => match.slice(1)));
31
+ }
32
+ const wildcardMatch = path.match(/\*([^/]+)$/);
33
+ return {
34
+ regular,
35
+ wildcard: wildcardMatch?.[1] ?? null
36
+ };
37
+ }
26
38
  function parsePathParams(path) {
27
- const matches = path.match(/:([^/]+)/g);
28
- if (!matches)
29
- return [];
30
- return matches.map((match) => match.slice(1));
39
+ const { regular, wildcard } = parsePathParamsDetailed(path);
40
+ return wildcard ? [...regular, wildcard] : regular;
31
41
  }
32
42
  function matchPath(pattern, path) {
33
- const paramNames = parsePathParams(pattern);
34
- const regexPattern = pattern.replace(/:[^/]+/g, "([^/]+)").replace(/\//g, "\\/");
43
+ const { regular, wildcard } = parsePathParamsDetailed(pattern);
44
+ let regexPattern = pattern.replace(/:[^/]+/g, "([^/]+)").replace(/\//g, "\\/");
45
+ if (wildcard) {
46
+ regexPattern = regexPattern.replace(/\*[^/]+$/, "(.+)");
47
+ }
35
48
  const regex = new RegExp(`^${regexPattern}$`);
36
49
  const match = path.match(regex);
37
50
  if (!match)
38
51
  return null;
39
52
  const params = {};
40
- paramNames.forEach((name, index) => {
53
+ regular.forEach((name, index) => {
41
54
  params[name] = match[index + 1] ?? "";
42
55
  });
56
+ if (wildcard) {
57
+ params[wildcard] = match[regular.length + 1] ?? "";
58
+ }
43
59
  return params;
44
60
  }
45
61
  function interpolatePath(pattern, params) {
46
62
  let result = pattern;
47
63
  for (const [key, value] of Object.entries(params)) {
48
- result = result.replace(`:${key}`, String(value));
64
+ if (result.includes(`:${key}`)) {
65
+ result = result.replace(`:${key}`, String(value));
66
+ } else if (result.includes(`*${key}`)) {
67
+ result = result.replace(`*${key}`, String(value));
68
+ }
49
69
  }
50
70
  return result;
51
71
  }
72
+ function validatePathPattern(pattern) {
73
+ const wildcardIndex = pattern.indexOf("*");
74
+ if (wildcardIndex !== -1) {
75
+ const afterWildcard = pattern.slice(wildcardIndex + 1);
76
+ if (afterWildcard.includes("/")) {
77
+ throw new Error(`Invalid path pattern "${pattern}": Wildcard parameter must be the last segment`);
78
+ }
79
+ if (!/^\*[a-zA-Z_][a-zA-Z0-9_]*$/.test(pattern.slice(wildcardIndex))) {
80
+ throw new Error(`Invalid path pattern "${pattern}": Wildcard parameter must have a valid name (e.g., *filePath)`);
81
+ }
82
+ }
83
+ const wildcardCount = (pattern.match(/\*/g) || []).length;
84
+ if (wildcardCount > 1) {
85
+ throw new Error(`Invalid path pattern "${pattern}": Only one wildcard parameter is allowed`);
86
+ }
87
+ }
52
88
  function buildUrl(baseUrl, path, query) {
53
89
  const normalizedBase = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
54
90
  const normalizedPath = path.startsWith("/") ? path : `/${path}`;
@@ -141,7 +177,9 @@ function formDataToObject(formData) {
141
177
  return replaceRefs(obj);
142
178
  }
143
179
  export {
180
+ validatePathPattern,
144
181
  parseQuery,
182
+ parsePathParamsDetailed,
145
183
  parsePathParams,
146
184
  objectToFormData,
147
185
  matchPath,
@@ -152,4 +190,4 @@ export {
152
190
  Status
153
191
  };
154
192
 
155
- //# debugId=D33EA4F40F08382064756E2164756E21
193
+ //# debugId=BA32EBFEE62F023664756E2164756E21
@@ -2,9 +2,9 @@
2
2
  "version": 3,
3
3
  "sources": ["../../index.ts"],
4
4
  "sourcesContent": [
5
- "import type { z } from 'zod';\n\n// HTTP methods supported\nexport type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';\n\n// Content types supported for request bodies\nexport type ContentType = 'application/json' | 'multipart/form-data';\n\n// HTTP status codes as const object for type-safe responses without 'as const'\nexport const Status = {\n // Success responses\n OK: 200 as const,\n Created: 201 as const,\n Accepted: 202 as const,\n NoContent: 204 as const,\n\n // Redirection\n MovedPermanently: 301 as const,\n Found: 302 as const,\n NotModified: 304 as const,\n\n // Client errors\n BadRequest: 400 as const,\n Unauthorized: 401 as const,\n Forbidden: 403 as const,\n NotFound: 404 as const,\n MethodNotAllowed: 405 as const,\n Conflict: 409 as const,\n UnprocessableEntity: 422 as const,\n TooManyRequests: 429 as const,\n\n // Server errors\n InternalServerError: 500 as const,\n NotImplemented: 501 as const,\n BadGateway: 502 as const,\n ServiceUnavailable: 503 as const,\n GatewayTimeout: 504 as const,\n} as const;\n\n// Base fields shared by all endpoint types\ninterface BaseEndpointFields {\n path: string;\n params?: z.ZodTypeAny;\n query?: z.ZodTypeAny;\n headers?: z.ZodTypeAny;\n}\n\n// Standard HTTP endpoint\nexport interface StandardEndpointDefinition extends BaseEndpointFields {\n type: 'standard';\n method: HttpMethod;\n body?: z.ZodTypeAny;\n contentType?: ContentType;\n responses: Record<number, z.ZodTypeAny>;\n}\n\n// Streaming response endpoint (NDJSON)\nexport interface StreamingEndpointDefinition extends BaseEndpointFields {\n type: 'streaming';\n method: 'POST';\n body?: z.ZodTypeAny;\n contentType?: ContentType;\n /** Schema for each NDJSON chunk (type inference only, not validated) */\n chunk: z.ZodTypeAny;\n /** Optional final response after stream ends */\n finalResponse?: z.ZodTypeAny;\n /** Error responses for non-streaming failures */\n errorResponses?: Record<number, z.ZodTypeAny>;\n}\n\n// SSE endpoint\nexport interface SSEEndpointDefinition extends BaseEndpointFields {\n type: 'sse';\n method: 'GET';\n /** Event types: key = event name, value = data schema (type inference only) */\n events: Record<string, z.ZodTypeAny>;\n /** Error responses for connection failures */\n errorResponses?: Record<number, z.ZodTypeAny>;\n}\n\n// Download endpoint (binary file response)\nexport interface DownloadEndpointDefinition extends BaseEndpointFields {\n type: 'download';\n method: 'GET'; // Downloads are GET-only\n /** Error responses (non-2xx status codes) */\n errorResponses?: Record<number, z.ZodTypeAny>;\n}\n\n// Union of all endpoint types\nexport type AnyEndpointDefinition =\n | StandardEndpointDefinition\n | StreamingEndpointDefinition\n | SSEEndpointDefinition\n | DownloadEndpointDefinition;\n\n// Alias for backwards compatibility in type utilities\nexport type EndpointDefinition = AnyEndpointDefinition;\n\n// Contract is a collection of named endpoints\nexport type Contract = Record<string, AnyEndpointDefinition>;\n\n// Extract the Zod type from a schema\nexport type InferZodType<T> = T extends z.ZodTypeAny ? z.infer<T> : never;\n\n// Extract params type from endpoint\nexport type ExtractParams<T extends EndpointDefinition> = T['params'] extends z.ZodTypeAny\n ? InferZodType<T['params']>\n : never;\n\n// Extract query type from endpoint\nexport type ExtractQuery<T extends EndpointDefinition> = T['query'] extends z.ZodTypeAny\n ? InferZodType<T['query']>\n : never;\n\n// Extract headers type from endpoint\nexport type ExtractHeaders<T extends EndpointDefinition> = T['headers'] extends z.ZodTypeAny\n ? InferZodType<T['headers']>\n : never;\n\n// Extract body type from endpoint (only standard and streaming have body)\nexport type ExtractBody<T extends EndpointDefinition> = T extends { body: z.ZodTypeAny }\n ? InferZodType<T['body']>\n : never;\n\n// Extract response types for all status codes (only standard has responses)\nexport type ExtractResponses<T extends EndpointDefinition> = T extends {\n responses: Record<number, z.ZodTypeAny>;\n}\n ? {\n [K in keyof T['responses']]: T['responses'][K] extends z.ZodTypeAny\n ? InferZodType<T['responses'][K]>\n : never;\n }\n : never;\n\n// Extract a specific response type by status code (only standard has responses)\nexport type ExtractResponse<T extends EndpointDefinition, Status extends number> = T extends {\n responses: Record<number, z.ZodTypeAny>;\n}\n ? Status extends keyof T['responses']\n ? T['responses'][Status] extends z.ZodTypeAny\n ? InferZodType<T['responses'][Status]>\n : never\n : never\n : never;\n\n// Extract chunk type from streaming endpoint\nexport type ExtractChunk<T extends StreamingEndpointDefinition> = T['chunk'] extends z.ZodTypeAny\n ? InferZodType<T['chunk']>\n : never;\n\n// Extract final response type from streaming endpoint\nexport type ExtractFinalResponse<T extends StreamingEndpointDefinition> =\n T['finalResponse'] extends z.ZodTypeAny ? InferZodType<T['finalResponse']> : undefined;\n\n// Extract SSE event union type\nexport type ExtractSSEEvents<T extends SSEEndpointDefinition> = {\n [K in keyof T['events']]: {\n event: K;\n data: T['events'][K] extends z.ZodTypeAny ? InferZodType<T['events'][K]> : never;\n id?: string;\n };\n}[keyof T['events']];\n\n// Extract specific SSE event data type\nexport type ExtractSSEEventData<\n T extends SSEEndpointDefinition,\n K extends keyof T['events'],\n> = T['events'][K] extends z.ZodTypeAny ? InferZodType<T['events'][K]> : never;\n\n// Extract error responses for download endpoint\nexport type ExtractDownloadErrorResponse<\n T extends DownloadEndpointDefinition,\n Status extends keyof T['errorResponses'],\n> =\n T['errorResponses'] extends Record<number, z.ZodTypeAny>\n ? Status extends keyof T['errorResponses']\n ? T['errorResponses'][Status] extends z.ZodTypeAny\n ? InferZodType<T['errorResponses'][Status]>\n : never\n : never\n : never;\n\n// Upload progress event\nexport interface UploadProgressEvent {\n loaded: number;\n total: number;\n progress: number; // 0-1 (percentage as decimal)\n}\n\n// Download progress event\nexport interface DownloadProgressEvent {\n loaded: number;\n total: number;\n progress: number; // 0-1 (percentage as decimal), NaN if total unknown\n}\n\n// Path parameter extraction utilities\nexport type ExtractPathParams<T extends string> =\n T extends `${infer _Start}:${infer Param}/${infer Rest}`\n ? Param | ExtractPathParams<`/${Rest}`>\n : T extends `${infer _Start}:${infer Param}`\n ? Param\n : never;\n\n// Convert path params to object type\nexport type PathParamsObject<T extends string> = {\n [K in ExtractPathParams<T>]: string;\n};\n\n/**\n * Parse path parameters from a URL path pattern\n * e.g., \"/users/:id/posts/:postId\" => [\"id\", \"postId\"]\n */\nexport function parsePathParams(path: string): string[] {\n const matches = path.match(/:([^/]+)/g);\n if (!matches) return [];\n return matches.map((match) => match.slice(1));\n}\n\n/**\n * Match a URL path against a pattern and extract parameters\n * e.g., matchPath(\"/users/:id\", \"/users/123\") => { id: \"123\" }\n */\nexport function matchPath(pattern: string, path: string): Record<string, string> | null {\n const paramNames = parsePathParams(pattern);\n\n // Convert pattern to regex\n const regexPattern = pattern.replace(/:[^/]+/g, '([^/]+)').replace(/\\//g, '\\\\/');\n\n const regex = new RegExp(`^${regexPattern}$`);\n const match = path.match(regex);\n\n if (!match) return null;\n\n const params: Record<string, string> = {};\n paramNames.forEach((name, index) => {\n params[name] = match[index + 1] ?? '';\n });\n\n return params;\n}\n\n/**\n * Interpolate path parameters into a URL pattern\n * e.g., interpolatePath(\"/users/:id\", { id: \"123\" }) => \"/users/123\"\n */\nexport function interpolatePath(pattern: string, params: Record<string, string | number>): string {\n let result = pattern;\n for (const [key, value] of Object.entries(params)) {\n result = result.replace(`:${key}`, String(value));\n }\n return result;\n}\n\n/**\n * Build a complete URL with query parameters\n */\nexport function buildUrl(\n baseUrl: string,\n path: string,\n query?: Record<string, string | number | boolean | string[]>,\n): string {\n // Normalize baseUrl - remove trailing slash\n const normalizedBase = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;\n\n // Ensure path starts with /\n const normalizedPath = path.startsWith('/') ? path : `/${path}`;\n\n // Concatenate base and path\n const fullPath = normalizedBase + normalizedPath;\n\n const url = new URL(fullPath);\n\n if (query) {\n for (const [key, value] of Object.entries(query)) {\n if (value !== undefined && value !== null) {\n if (Array.isArray(value)) {\n for (const v of value) {\n url.searchParams.append(key, String(v));\n }\n } else {\n url.searchParams.append(key, String(value));\n }\n }\n }\n }\n\n return url.toString();\n}\n\n/**\n * Parse query parameters from URLSearchParams\n */\nexport function parseQuery(searchParams: URLSearchParams): Record<string, string | string[]> {\n const result: Record<string, string | string[]> = {};\n\n for (const [key, value] of searchParams.entries()) {\n const existing = result[key];\n if (existing) {\n if (Array.isArray(existing)) {\n existing.push(value);\n } else {\n result[key] = [existing, value];\n }\n } else {\n result[key] = value;\n }\n }\n\n return result;\n}\n\n// Type helper to ensure a value is a valid contract\nexport function defineContract<T extends Contract>(contract: T): T {\n return contract;\n}\n\n/**\n * Convert an object to FormData using the hybrid JSON + Files approach.\n * Files are extracted and replaced with { __fileRef__: \"path\" } placeholders.\n * The resulting FormData contains __json__ with the serialized structure\n * and individual file entries at their path keys.\n */\nexport function objectToFormData(obj: Record<string, unknown>): FormData {\n const formData = new FormData();\n const files: Array<{ path: string; file: File }> = [];\n\n function traverse(value: unknown, path: string): unknown {\n if (value instanceof File) {\n files.push({ path, file: value });\n return { __fileRef__: path };\n }\n if (Array.isArray(value)) {\n return value.map((item, i) => traverse(item, `${path}.${i}`));\n }\n if (value && typeof value === 'object') {\n const result: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value)) {\n result[k] = traverse(v, path ? `${path}.${k}` : k);\n }\n return result;\n }\n return value;\n }\n\n const jsonWithRefs = traverse(obj, '');\n formData.append('__json__', JSON.stringify(jsonWithRefs));\n\n for (const { path, file } of files) {\n formData.append(path, file);\n }\n\n return formData;\n}\n\n/**\n * Parse FormData back to an object, reconstructing the structure with File objects.\n * Expects FormData created by objectToFormData with __json__ and file entries.\n * Falls back to simple Object.fromEntries for FormData without __json__.\n */\nexport function formDataToObject(formData: FormData): Record<string, unknown> {\n const jsonStr = formData.get('__json__');\n if (typeof jsonStr !== 'string') {\n return Object.fromEntries(formData.entries());\n }\n\n const obj = JSON.parse(jsonStr);\n\n function replaceRefs(value: unknown): unknown {\n if (value && typeof value === 'object' && '__fileRef__' in value) {\n const path = (value as { __fileRef__: string }).__fileRef__;\n return formData.get(path);\n }\n if (Array.isArray(value)) {\n return value.map(replaceRefs);\n }\n if (value && typeof value === 'object') {\n const result: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value)) {\n result[k] = replaceRefs(v);\n }\n return result;\n }\n return value;\n }\n\n return replaceRefs(obj) as Record<string, unknown>;\n}\n\n// Re-export WebSocket types\nexport * from './websocket.mjs';\n"
5
+ "import type { z } from 'zod';\n\n// HTTP methods supported\nexport type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';\n\n// Content types supported for request bodies\nexport type ContentType = 'application/json' | 'multipart/form-data';\n\n// HTTP status codes as const object for type-safe responses without 'as const'\nexport const Status = {\n // Success responses\n OK: 200 as const,\n Created: 201 as const,\n Accepted: 202 as const,\n NoContent: 204 as const,\n\n // Redirection\n MovedPermanently: 301 as const,\n Found: 302 as const,\n NotModified: 304 as const,\n\n // Client errors\n BadRequest: 400 as const,\n Unauthorized: 401 as const,\n Forbidden: 403 as const,\n NotFound: 404 as const,\n MethodNotAllowed: 405 as const,\n Conflict: 409 as const,\n UnprocessableEntity: 422 as const,\n TooManyRequests: 429 as const,\n\n // Server errors\n InternalServerError: 500 as const,\n NotImplemented: 501 as const,\n BadGateway: 502 as const,\n ServiceUnavailable: 503 as const,\n GatewayTimeout: 504 as const,\n} as const;\n\n// Base fields shared by all endpoint types\ninterface BaseEndpointFields {\n path: string;\n params?: z.ZodTypeAny;\n query?: z.ZodTypeAny;\n headers?: z.ZodTypeAny;\n}\n\n// Standard HTTP endpoint\nexport interface StandardEndpointDefinition extends BaseEndpointFields {\n type: 'standard';\n method: HttpMethod;\n body?: z.ZodTypeAny;\n contentType?: ContentType;\n responses: Record<number, z.ZodTypeAny>;\n errorResponses?: Record<number, z.ZodTypeAny>;\n}\n\n// Streaming response endpoint (NDJSON)\nexport interface StreamingEndpointDefinition extends BaseEndpointFields {\n type: 'streaming';\n method: 'POST';\n body?: z.ZodTypeAny;\n contentType?: ContentType;\n /** Schema for each NDJSON chunk (type inference only, not validated) */\n chunk: z.ZodTypeAny;\n /** Optional final response after stream ends */\n finalResponse?: z.ZodTypeAny;\n /** Error responses for non-streaming failures */\n errorResponses?: Record<number, z.ZodTypeAny>;\n}\n\n// SSE endpoint\nexport interface SSEEndpointDefinition extends BaseEndpointFields {\n type: 'sse';\n method: 'GET';\n /** Event types: key = event name, value = data schema (type inference only) */\n events: Record<string, z.ZodTypeAny>;\n /** Error responses for connection failures */\n errorResponses?: Record<number, z.ZodTypeAny>;\n}\n\n// Download endpoint (binary file response)\nexport interface DownloadEndpointDefinition extends BaseEndpointFields {\n type: 'download';\n method: 'GET'; // Downloads are GET-only\n /** Error responses (non-2xx status codes) */\n errorResponses?: Record<number, z.ZodTypeAny>;\n}\n\n// Union of all endpoint types\nexport type AnyEndpointDefinition =\n | StandardEndpointDefinition\n | StreamingEndpointDefinition\n | SSEEndpointDefinition\n | DownloadEndpointDefinition;\n\n// Alias for backwards compatibility in type utilities\nexport type EndpointDefinition = AnyEndpointDefinition;\n\n// Contract is a collection of named endpoints\nexport type Contract = Record<string, AnyEndpointDefinition>;\n\n// Extract the Zod type from a schema\nexport type InferZodType<T> = T extends z.ZodTypeAny ? z.infer<T> : never;\n\n// Extract params type from endpoint\nexport type ExtractParams<T extends EndpointDefinition> = T['params'] extends z.ZodTypeAny\n ? InferZodType<T['params']>\n : never;\n\n// Extract query type from endpoint\nexport type ExtractQuery<T extends EndpointDefinition> = T['query'] extends z.ZodTypeAny\n ? InferZodType<T['query']>\n : never;\n\n// Extract headers type from endpoint\nexport type ExtractHeaders<T extends EndpointDefinition> = T['headers'] extends z.ZodTypeAny\n ? InferZodType<T['headers']>\n : never;\n\n// Extract body type from endpoint (only standard and streaming have body)\nexport type ExtractBody<T extends EndpointDefinition> = T extends { body: z.ZodTypeAny }\n ? InferZodType<T['body']>\n : never;\n\n// Extract response types for all status codes (only standard has responses)\nexport type ExtractResponses<T extends EndpointDefinition> = T extends {\n responses: Record<number, z.ZodTypeAny>;\n}\n ? {\n [K in keyof T['responses']]: T['responses'][K] extends z.ZodTypeAny\n ? InferZodType<T['responses'][K]>\n : never;\n }\n : never;\n\n// Extract a specific response type by status code (only standard has responses)\nexport type ExtractResponse<T extends EndpointDefinition, Status extends number> = T extends {\n responses: Record<number, z.ZodTypeAny>;\n}\n ? Status extends keyof T['responses']\n ? T['responses'][Status] extends z.ZodTypeAny\n ? InferZodType<T['responses'][Status]>\n : never\n : never\n : never;\n\n// Extract error response types for all status codes\nexport type ExtractErrorResponses<T extends EndpointDefinition> = T extends {\n errorResponses: Record<number, z.ZodTypeAny>;\n} ? {\n [K in keyof T['errorResponses']]: T['errorResponses'][K] extends z.ZodTypeAny\n ? InferZodType<T['errorResponses'][K]>\n : never;\n } : never;\n\n// Extract a specific error response type by status code\nexport type ExtractErrorResponse<T extends EndpointDefinition, Status extends number> = T extends {\n errorResponses: Record<number, z.ZodTypeAny>;\n} ? Status extends keyof T['errorResponses']\n ? T['errorResponses'][Status] extends z.ZodTypeAny\n ? InferZodType<T['errorResponses'][Status]>\n : never\n : never\n : never;\n\n// Extract chunk type from streaming endpoint\nexport type ExtractChunk<T extends StreamingEndpointDefinition> = T['chunk'] extends z.ZodTypeAny\n ? InferZodType<T['chunk']>\n : never;\n\n// Extract final response type from streaming endpoint\nexport type ExtractFinalResponse<T extends StreamingEndpointDefinition> =\n T['finalResponse'] extends z.ZodTypeAny ? InferZodType<T['finalResponse']> : undefined;\n\n// Extract SSE event union type\nexport type ExtractSSEEvents<T extends SSEEndpointDefinition> = {\n [K in keyof T['events']]: {\n event: K;\n data: T['events'][K] extends z.ZodTypeAny ? InferZodType<T['events'][K]> : never;\n id?: string;\n };\n}[keyof T['events']];\n\n// Extract specific SSE event data type\nexport type ExtractSSEEventData<\n T extends SSEEndpointDefinition,\n K extends keyof T['events'],\n> = T['events'][K] extends z.ZodTypeAny ? InferZodType<T['events'][K]> : never;\n\n// Extract error responses for download endpoint\nexport type ExtractDownloadErrorResponse<\n T extends DownloadEndpointDefinition,\n Status extends keyof T['errorResponses'],\n> =\n T['errorResponses'] extends Record<number, z.ZodTypeAny>\n ? Status extends keyof T['errorResponses']\n ? T['errorResponses'][Status] extends z.ZodTypeAny\n ? InferZodType<T['errorResponses'][Status]>\n : never\n : never\n : never;\n\n// Upload progress event\nexport interface UploadProgressEvent {\n loaded: number;\n total: number;\n progress: number; // 0-1 (percentage as decimal)\n}\n\n// Download progress event\nexport interface DownloadProgressEvent {\n loaded: number;\n total: number;\n progress: number; // 0-1 (percentage as decimal), NaN if total unknown\n}\n\n// Path parameter extraction utilities\n// Extract regular path params (:param)\ntype ExtractRegularParams<T extends string> =\n T extends `${infer _Start}:${infer Param}/${infer Rest}`\n ? Param | ExtractRegularParams<`/${Rest}`>\n : T extends `${infer _Start}:${infer Param}`\n ? Param\n : never;\n\n// Extract wildcard params (*param) - must be at end of path\ntype ExtractWildcardParams<T extends string> = T extends `${infer _Start}*${infer Param}`\n ? Param\n : never;\n\n// Combined extraction of all path params\nexport type ExtractPathParams<T extends string> =\n | ExtractRegularParams<T>\n | ExtractWildcardParams<T>;\n\n// Convert path params to object type\nexport type PathParamsObject<T extends string> = {\n [K in ExtractPathParams<T>]: string;\n};\n\n/**\n * Detailed path parameter info distinguishing regular vs wildcard params\n */\nexport interface ParsedPathParams {\n regular: string[];\n wildcard: string | null;\n}\n\n/**\n * Parse path parameters with detailed type information\n * e.g., \"/users/:id/*path\" => { regular: [\"id\"], wildcard: \"path\" }\n */\nexport function parsePathParamsDetailed(path: string): ParsedPathParams {\n const regular: string[] = [];\n\n // Extract regular params (:param)\n const regularMatches = path.match(/:([^/]+)/g);\n if (regularMatches) {\n regular.push(...regularMatches.map((match) => match.slice(1)));\n }\n\n // Extract wildcard param (*param) - must be at end\n const wildcardMatch = path.match(/\\*([^/]+)$/);\n\n return {\n regular,\n wildcard: wildcardMatch?.[1] ?? null,\n };\n}\n\n/**\n * Parse path parameters from a URL path pattern\n * e.g., \"/users/:id/posts/:postId\" => [\"id\", \"postId\"]\n * e.g., \"/files/*path\" => [\"path\"]\n */\nexport function parsePathParams(path: string): string[] {\n const { regular, wildcard } = parsePathParamsDetailed(path);\n return wildcard ? [...regular, wildcard] : regular;\n}\n\n/**\n * Match a URL path against a pattern and extract parameters\n * e.g., matchPath(\"/users/:id\", \"/users/123\") => { id: \"123\" }\n * e.g., matchPath(\"/files/*path\", \"/files/a/b/c.txt\") => { path: \"a/b/c.txt\" }\n */\nexport function matchPath(pattern: string, path: string): Record<string, string> | null {\n const { regular, wildcard } = parsePathParamsDetailed(pattern);\n\n // Convert pattern to regex:\n // 1. Replace :param with capturing group for single segment\n // 2. Replace *param with capturing group for remaining path (including slashes)\n let regexPattern = pattern.replace(/:[^/]+/g, '([^/]+)').replace(/\\//g, '\\\\/');\n\n // Handle wildcard at the end - replace *paramName with (.+)\n if (wildcard) {\n regexPattern = regexPattern.replace(/\\*[^/]+$/, '(.+)');\n }\n\n const regex = new RegExp(`^${regexPattern}$`);\n const match = path.match(regex);\n\n if (!match) return null;\n\n const params: Record<string, string> = {};\n\n // Extract regular params first\n regular.forEach((name, index) => {\n params[name] = match[index + 1] ?? '';\n });\n\n // Extract wildcard param if present (always last capture group)\n if (wildcard) {\n params[wildcard] = match[regular.length + 1] ?? '';\n }\n\n return params;\n}\n\n/**\n * Interpolate path parameters into a URL pattern\n * e.g., interpolatePath(\"/users/:id\", { id: \"123\" }) => \"/users/123\"\n * e.g., interpolatePath(\"/files/*path\", { path: \"a/b/c.txt\" }) => \"/files/a/b/c.txt\"\n */\nexport function interpolatePath(pattern: string, params: Record<string, string | number>): string {\n let result = pattern;\n for (const [key, value] of Object.entries(params)) {\n // Try replacing :param first, then *param\n if (result.includes(`:${key}`)) {\n result = result.replace(`:${key}`, String(value));\n } else if (result.includes(`*${key}`)) {\n result = result.replace(`*${key}`, String(value));\n }\n }\n return result;\n}\n\n/**\n * Validate a path pattern for correct syntax\n * @throws Error if pattern is invalid\n */\nexport function validatePathPattern(pattern: string): void {\n // Check for wildcard not at the end\n const wildcardIndex = pattern.indexOf('*');\n if (wildcardIndex !== -1) {\n const afterWildcard = pattern.slice(wildcardIndex + 1);\n\n // Wildcard param name should go to end of string (no more slashes)\n if (afterWildcard.includes('/')) {\n throw new Error(\n `Invalid path pattern \"${pattern}\": Wildcard parameter must be the last segment`,\n );\n }\n\n // Ensure wildcard has a valid name (like Express 5 requires)\n if (!/^\\*[a-zA-Z_][a-zA-Z0-9_]*$/.test(pattern.slice(wildcardIndex))) {\n throw new Error(\n `Invalid path pattern \"${pattern}\": Wildcard parameter must have a valid name (e.g., *filePath)`,\n );\n }\n }\n\n // Check for multiple wildcards\n const wildcardCount = (pattern.match(/\\*/g) || []).length;\n if (wildcardCount > 1) {\n throw new Error(`Invalid path pattern \"${pattern}\": Only one wildcard parameter is allowed`);\n }\n}\n\n/**\n * Build a complete URL with query parameters\n */\nexport function buildUrl(\n baseUrl: string,\n path: string,\n query?: Record<string, string | number | boolean | string[]>,\n): string {\n // Normalize baseUrl - remove trailing slash\n const normalizedBase = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;\n\n // Ensure path starts with /\n const normalizedPath = path.startsWith('/') ? path : `/${path}`;\n\n // Concatenate base and path\n const fullPath = normalizedBase + normalizedPath;\n\n const url = new URL(fullPath);\n\n if (query) {\n for (const [key, value] of Object.entries(query)) {\n if (value !== undefined && value !== null) {\n if (Array.isArray(value)) {\n for (const v of value) {\n url.searchParams.append(key, String(v));\n }\n } else {\n url.searchParams.append(key, String(value));\n }\n }\n }\n }\n\n return url.toString();\n}\n\n/**\n * Parse query parameters from URLSearchParams\n */\nexport function parseQuery(searchParams: URLSearchParams): Record<string, string | string[]> {\n const result: Record<string, string | string[]> = {};\n\n for (const [key, value] of searchParams.entries()) {\n const existing = result[key];\n if (existing) {\n if (Array.isArray(existing)) {\n existing.push(value);\n } else {\n result[key] = [existing, value];\n }\n } else {\n result[key] = value;\n }\n }\n\n return result;\n}\n\n// Type helper to ensure a value is a valid contract\nexport function defineContract<T extends Contract>(contract: T): T {\n return contract;\n}\n\n/**\n * Convert an object to FormData using the hybrid JSON + Files approach.\n * Files are extracted and replaced with { __fileRef__: \"path\" } placeholders.\n * The resulting FormData contains __json__ with the serialized structure\n * and individual file entries at their path keys.\n */\nexport function objectToFormData(obj: Record<string, unknown>): FormData {\n const formData = new FormData();\n const files: Array<{ path: string; file: File }> = [];\n\n function traverse(value: unknown, path: string): unknown {\n if (value instanceof File) {\n files.push({ path, file: value });\n return { __fileRef__: path };\n }\n if (Array.isArray(value)) {\n return value.map((item, i) => traverse(item, `${path}.${i}`));\n }\n if (value && typeof value === 'object') {\n const result: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value)) {\n result[k] = traverse(v, path ? `${path}.${k}` : k);\n }\n return result;\n }\n return value;\n }\n\n const jsonWithRefs = traverse(obj, '');\n formData.append('__json__', JSON.stringify(jsonWithRefs));\n\n for (const { path, file } of files) {\n formData.append(path, file);\n }\n\n return formData;\n}\n\n/**\n * Parse FormData back to an object, reconstructing the structure with File objects.\n * Expects FormData created by objectToFormData with __json__ and file entries.\n * Falls back to simple Object.fromEntries for FormData without __json__.\n */\nexport function formDataToObject(formData: FormData): Record<string, unknown> {\n const jsonStr = formData.get('__json__');\n if (typeof jsonStr !== 'string') {\n return Object.fromEntries(formData.entries());\n }\n\n const obj = JSON.parse(jsonStr);\n\n function replaceRefs(value: unknown): unknown {\n if (value && typeof value === 'object' && '__fileRef__' in value) {\n const path = (value as { __fileRef__: string }).__fileRef__;\n return formData.get(path);\n }\n if (Array.isArray(value)) {\n return value.map(replaceRefs);\n }\n if (value && typeof value === 'object') {\n const result: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value)) {\n result[k] = replaceRefs(v);\n }\n return result;\n }\n return value;\n }\n\n return replaceRefs(obj) as Record<string, unknown>;\n}\n\n// Re-export WebSocket types\nexport * from './websocket.mjs';\n"
6
6
  ],
7
- "mappings": "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",
8
- "debugId": "D33EA4F40F08382064756E2164756E21",
7
+ "mappings": ";;AAyfA;AAhfO,IAAM,SAAS;AAAA,EAEpB,IAAI;AAAA,EACJ,SAAS;AAAA,EACT,UAAU;AAAA,EACV,WAAW;AAAA,EAGX,kBAAkB;AAAA,EAClB,OAAO;AAAA,EACP,aAAa;AAAA,EAGb,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,WAAW;AAAA,EACX,UAAU;AAAA,EACV,kBAAkB;AAAA,EAClB,UAAU;AAAA,EACV,qBAAqB;AAAA,EACrB,iBAAiB;AAAA,EAGjB,qBAAqB;AAAA,EACrB,gBAAgB;AAAA,EAChB,YAAY;AAAA,EACZ,oBAAoB;AAAA,EACpB,gBAAgB;AAClB;AAwNO,SAAS,uBAAuB,CAAC,MAAgC;AAAA,EACtE,MAAM,UAAoB,CAAC;AAAA,EAG3B,MAAM,iBAAiB,KAAK,MAAM,WAAW;AAAA,EAC7C,IAAI,gBAAgB;AAAA,IAClB,QAAQ,KAAK,GAAG,eAAe,IAAI,CAAC,UAAU,MAAM,MAAM,CAAC,CAAC,CAAC;AAAA,EAC/D;AAAA,EAGA,MAAM,gBAAgB,KAAK,MAAM,YAAY;AAAA,EAE7C,OAAO;AAAA,IACL;AAAA,IACA,UAAU,gBAAgB,MAAM;AAAA,EAClC;AAAA;AAQK,SAAS,eAAe,CAAC,MAAwB;AAAA,EACtD,QAAQ,SAAS,aAAa,wBAAwB,IAAI;AAAA,EAC1D,OAAO,WAAW,CAAC,GAAG,SAAS,QAAQ,IAAI;AAAA;AAQtC,SAAS,SAAS,CAAC,SAAiB,MAA6C;AAAA,EACtF,QAAQ,SAAS,aAAa,wBAAwB,OAAO;AAAA,EAK7D,IAAI,eAAe,QAAQ,QAAQ,WAAW,SAAS,EAAE,QAAQ,OAAO,KAAK;AAAA,EAG7E,IAAI,UAAU;AAAA,IACZ,eAAe,aAAa,QAAQ,YAAY,MAAM;AAAA,EACxD;AAAA,EAEA,MAAM,QAAQ,IAAI,OAAO,IAAI,eAAe;AAAA,EAC5C,MAAM,QAAQ,KAAK,MAAM,KAAK;AAAA,EAE9B,IAAI,CAAC;AAAA,IAAO,OAAO;AAAA,EAEnB,MAAM,SAAiC,CAAC;AAAA,EAGxC,QAAQ,QAAQ,CAAC,MAAM,UAAU;AAAA,IAC/B,OAAO,QAAQ,MAAM,QAAQ,MAAM;AAAA,GACpC;AAAA,EAGD,IAAI,UAAU;AAAA,IACZ,OAAO,YAAY,MAAM,QAAQ,SAAS,MAAM;AAAA,EAClD;AAAA,EAEA,OAAO;AAAA;AAQF,SAAS,eAAe,CAAC,SAAiB,QAAiD;AAAA,EAChG,IAAI,SAAS;AAAA,EACb,YAAY,KAAK,UAAU,OAAO,QAAQ,MAAM,GAAG;AAAA,IAEjD,IAAI,OAAO,SAAS,IAAI,KAAK,GAAG;AAAA,MAC9B,SAAS,OAAO,QAAQ,IAAI,OAAO,OAAO,KAAK,CAAC;AAAA,IAClD,EAAO,SAAI,OAAO,SAAS,IAAI,KAAK,GAAG;AAAA,MACrC,SAAS,OAAO,QAAQ,IAAI,OAAO,OAAO,KAAK,CAAC;AAAA,IAClD;AAAA,EACF;AAAA,EACA,OAAO;AAAA;AAOF,SAAS,mBAAmB,CAAC,SAAuB;AAAA,EAEzD,MAAM,gBAAgB,QAAQ,QAAQ,GAAG;AAAA,EACzC,IAAI,kBAAkB,IAAI;AAAA,IACxB,MAAM,gBAAgB,QAAQ,MAAM,gBAAgB,CAAC;AAAA,IAGrD,IAAI,cAAc,SAAS,GAAG,GAAG;AAAA,MAC/B,MAAM,IAAI,MACR,yBAAyB,uDAC3B;AAAA,IACF;AAAA,IAGA,IAAI,CAAC,6BAA6B,KAAK,QAAQ,MAAM,aAAa,CAAC,GAAG;AAAA,MACpE,MAAM,IAAI,MACR,yBAAyB,uEAC3B;AAAA,IACF;AAAA,EACF;AAAA,EAGA,MAAM,iBAAiB,QAAQ,MAAM,KAAK,KAAK,CAAC,GAAG;AAAA,EACnD,IAAI,gBAAgB,GAAG;AAAA,IACrB,MAAM,IAAI,MAAM,yBAAyB,kDAAkD;AAAA,EAC7F;AAAA;AAMK,SAAS,QAAQ,CACtB,SACA,MACA,OACQ;AAAA,EAER,MAAM,iBAAiB,QAAQ,SAAS,GAAG,IAAI,QAAQ,MAAM,GAAG,EAAE,IAAI;AAAA,EAGtE,MAAM,iBAAiB,KAAK,WAAW,GAAG,IAAI,OAAO,IAAI;AAAA,EAGzD,MAAM,WAAW,iBAAiB;AAAA,EAElC,MAAM,MAAM,IAAI,IAAI,QAAQ;AAAA,EAE5B,IAAI,OAAO;AAAA,IACT,YAAY,KAAK,UAAU,OAAO,QAAQ,KAAK,GAAG;AAAA,MAChD,IAAI,UAAU,aAAa,UAAU,MAAM;AAAA,QACzC,IAAI,MAAM,QAAQ,KAAK,GAAG;AAAA,UACxB,WAAW,KAAK,OAAO;AAAA,YACrB,IAAI,aAAa,OAAO,KAAK,OAAO,CAAC,CAAC;AAAA,UACxC;AAAA,QACF,EAAO;AAAA,UACL,IAAI,aAAa,OAAO,KAAK,OAAO,KAAK,CAAC;AAAA;AAAA,MAE9C;AAAA,IACF;AAAA,EACF;AAAA,EAEA,OAAO,IAAI,SAAS;AAAA;AAMf,SAAS,UAAU,CAAC,cAAkE;AAAA,EAC3F,MAAM,SAA4C,CAAC;AAAA,EAEnD,YAAY,KAAK,UAAU,aAAa,QAAQ,GAAG;AAAA,IACjD,MAAM,WAAW,OAAO;AAAA,IACxB,IAAI,UAAU;AAAA,MACZ,IAAI,MAAM,QAAQ,QAAQ,GAAG;AAAA,QAC3B,SAAS,KAAK,KAAK;AAAA,MACrB,EAAO;AAAA,QACL,OAAO,OAAO,CAAC,UAAU,KAAK;AAAA;AAAA,IAElC,EAAO;AAAA,MACL,OAAO,OAAO;AAAA;AAAA,EAElB;AAAA,EAEA,OAAO;AAAA;AAIF,SAAS,cAAkC,CAAC,UAAgB;AAAA,EACjE,OAAO;AAAA;AASF,SAAS,gBAAgB,CAAC,KAAwC;AAAA,EACvE,MAAM,WAAW,IAAI;AAAA,EACrB,MAAM,QAA6C,CAAC;AAAA,EAEpD,SAAS,QAAQ,CAAC,OAAgB,MAAuB;AAAA,IACvD,IAAI,iBAAiB,MAAM;AAAA,MACzB,MAAM,KAAK,EAAE,MAAM,MAAM,MAAM,CAAC;AAAA,MAChC,OAAO,EAAE,aAAa,KAAK;AAAA,IAC7B;AAAA,IACA,IAAI,MAAM,QAAQ,KAAK,GAAG;AAAA,MACxB,OAAO,MAAM,IAAI,CAAC,MAAM,MAAM,SAAS,MAAM,GAAG,QAAQ,GAAG,CAAC;AAAA,IAC9D;AAAA,IACA,IAAI,SAAS,OAAO,UAAU,UAAU;AAAA,MACtC,MAAM,SAAkC,CAAC;AAAA,MACzC,YAAY,GAAG,MAAM,OAAO,QAAQ,KAAK,GAAG;AAAA,QAC1C,OAAO,KAAK,SAAS,GAAG,OAAO,GAAG,QAAQ,MAAM,CAAC;AAAA,MACnD;AAAA,MACA,OAAO;AAAA,IACT;AAAA,IACA,OAAO;AAAA;AAAA,EAGT,MAAM,eAAe,SAAS,KAAK,EAAE;AAAA,EACrC,SAAS,OAAO,YAAY,KAAK,UAAU,YAAY,CAAC;AAAA,EAExD,aAAa,MAAM,UAAU,OAAO;AAAA,IAClC,SAAS,OAAO,MAAM,IAAI;AAAA,EAC5B;AAAA,EAEA,OAAO;AAAA;AAQF,SAAS,gBAAgB,CAAC,UAA6C;AAAA,EAC5E,MAAM,UAAU,SAAS,IAAI,UAAU;AAAA,EACvC,IAAI,OAAO,YAAY,UAAU;AAAA,IAC/B,OAAO,OAAO,YAAY,SAAS,QAAQ,CAAC;AAAA,EAC9C;AAAA,EAEA,MAAM,MAAM,KAAK,MAAM,OAAO;AAAA,EAE9B,SAAS,WAAW,CAAC,OAAyB;AAAA,IAC5C,IAAI,SAAS,OAAO,UAAU,YAAY,iBAAiB,OAAO;AAAA,MAChE,MAAM,OAAQ,MAAkC;AAAA,MAChD,OAAO,SAAS,IAAI,IAAI;AAAA,IAC1B;AAAA,IACA,IAAI,MAAM,QAAQ,KAAK,GAAG;AAAA,MACxB,OAAO,MAAM,IAAI,WAAW;AAAA,IAC9B;AAAA,IACA,IAAI,SAAS,OAAO,UAAU,UAAU;AAAA,MACtC,MAAM,SAAkC,CAAC;AAAA,MACzC,YAAY,GAAG,MAAM,OAAO,QAAQ,KAAK,GAAG;AAAA,QAC1C,OAAO,KAAK,YAAY,CAAC;AAAA,MAC3B;AAAA,MACA,OAAO;AAAA,IACT;AAAA,IACA,OAAO;AAAA;AAAA,EAGT,OAAO,YAAY,GAAG;AAAA;",
8
+ "debugId": "BA32EBFEE62F023664756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@richie-rpc/core",
3
- "version": "1.2.4",
3
+ "version": "2.0.0",
4
4
  "type": "module"
5
5
  }
@@ -35,6 +35,7 @@ export interface StandardEndpointDefinition extends BaseEndpointFields {
35
35
  body?: z.ZodTypeAny;
36
36
  contentType?: ContentType;
37
37
  responses: Record<number, z.ZodTypeAny>;
38
+ errorResponses?: Record<number, z.ZodTypeAny>;
38
39
  }
39
40
  export interface StreamingEndpointDefinition extends BaseEndpointFields {
40
41
  type: 'streaming';
@@ -80,6 +81,14 @@ export type ExtractResponses<T extends EndpointDefinition> = T extends {
80
81
  export type ExtractResponse<T extends EndpointDefinition, Status extends number> = T extends {
81
82
  responses: Record<number, z.ZodTypeAny>;
82
83
  } ? Status extends keyof T['responses'] ? T['responses'][Status] extends z.ZodTypeAny ? InferZodType<T['responses'][Status]> : never : never : never;
84
+ export type ExtractErrorResponses<T extends EndpointDefinition> = T extends {
85
+ errorResponses: Record<number, z.ZodTypeAny>;
86
+ } ? {
87
+ [K in keyof T['errorResponses']]: T['errorResponses'][K] extends z.ZodTypeAny ? InferZodType<T['errorResponses'][K]> : never;
88
+ } : never;
89
+ export type ExtractErrorResponse<T extends EndpointDefinition, Status extends number> = T extends {
90
+ errorResponses: Record<number, z.ZodTypeAny>;
91
+ } ? Status extends keyof T['errorResponses'] ? T['errorResponses'][Status] extends z.ZodTypeAny ? InferZodType<T['errorResponses'][Status]> : never : never : never;
83
92
  export type ExtractChunk<T extends StreamingEndpointDefinition> = T['chunk'] extends z.ZodTypeAny ? InferZodType<T['chunk']> : never;
84
93
  export type ExtractFinalResponse<T extends StreamingEndpointDefinition> = T['finalResponse'] extends z.ZodTypeAny ? InferZodType<T['finalResponse']> : undefined;
85
94
  export type ExtractSSEEvents<T extends SSEEndpointDefinition> = {
@@ -101,25 +110,47 @@ export interface DownloadProgressEvent {
101
110
  total: number;
102
111
  progress: number;
103
112
  }
104
- export type ExtractPathParams<T extends string> = T extends `${infer _Start}:${infer Param}/${infer Rest}` ? Param | ExtractPathParams<`/${Rest}`> : T extends `${infer _Start}:${infer Param}` ? Param : never;
113
+ type ExtractRegularParams<T extends string> = T extends `${infer _Start}:${infer Param}/${infer Rest}` ? Param | ExtractRegularParams<`/${Rest}`> : T extends `${infer _Start}:${infer Param}` ? Param : never;
114
+ type ExtractWildcardParams<T extends string> = T extends `${infer _Start}*${infer Param}` ? Param : never;
115
+ export type ExtractPathParams<T extends string> = ExtractRegularParams<T> | ExtractWildcardParams<T>;
105
116
  export type PathParamsObject<T extends string> = {
106
117
  [K in ExtractPathParams<T>]: string;
107
118
  };
119
+ /**
120
+ * Detailed path parameter info distinguishing regular vs wildcard params
121
+ */
122
+ export interface ParsedPathParams {
123
+ regular: string[];
124
+ wildcard: string | null;
125
+ }
126
+ /**
127
+ * Parse path parameters with detailed type information
128
+ * e.g., "/users/:id/*path" => { regular: ["id"], wildcard: "path" }
129
+ */
130
+ export declare function parsePathParamsDetailed(path: string): ParsedPathParams;
108
131
  /**
109
132
  * Parse path parameters from a URL path pattern
110
133
  * e.g., "/users/:id/posts/:postId" => ["id", "postId"]
134
+ * e.g., "/files/*path" => ["path"]
111
135
  */
112
136
  export declare function parsePathParams(path: string): string[];
113
137
  /**
114
138
  * Match a URL path against a pattern and extract parameters
115
139
  * e.g., matchPath("/users/:id", "/users/123") => { id: "123" }
140
+ * e.g., matchPath("/files/*path", "/files/a/b/c.txt") => { path: "a/b/c.txt" }
116
141
  */
117
142
  export declare function matchPath(pattern: string, path: string): Record<string, string> | null;
118
143
  /**
119
144
  * Interpolate path parameters into a URL pattern
120
145
  * e.g., interpolatePath("/users/:id", { id: "123" }) => "/users/123"
146
+ * e.g., interpolatePath("/files/*path", { path: "a/b/c.txt" }) => "/files/a/b/c.txt"
121
147
  */
122
148
  export declare function interpolatePath(pattern: string, params: Record<string, string | number>): string;
149
+ /**
150
+ * Validate a path pattern for correct syntax
151
+ * @throws Error if pattern is invalid
152
+ */
153
+ export declare function validatePathPattern(pattern: string): void;
123
154
  /**
124
155
  * Build a complete URL with query parameters
125
156
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@richie-rpc/core",
3
- "version": "1.2.4",
3
+ "version": "2.0.0",
4
4
  "main": "./dist/cjs/index.cjs",
5
5
  "exports": {
6
6
  ".": {