@richie-rpc/core 1.2.3 → 1.2.5

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.
@@ -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}\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\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": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqeA;AA5dO,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;AAoMO,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": "CA0ED6C82DBBBD5564756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@richie-rpc/core",
3
- "version": "1.2.3",
3
+ "version": "1.2.5",
4
4
  "type": "commonjs"
5
5
  }
@@ -0,0 +1,41 @@
1
+ // @bun @bun-cjs
2
+ (function(exports, require, module, __filename, __dirname) {var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __moduleCache = /* @__PURE__ */ new WeakMap;
7
+ var __toCommonJS = (from) => {
8
+ var entry = __moduleCache.get(from), desc;
9
+ if (entry)
10
+ return entry;
11
+ entry = __defProp({}, "__esModule", { value: true });
12
+ if (from && typeof from === "object" || typeof from === "function")
13
+ __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
14
+ get: () => from[key],
15
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
16
+ }));
17
+ __moduleCache.set(from, entry);
18
+ return entry;
19
+ };
20
+ var __export = (target, all) => {
21
+ for (var name in all)
22
+ __defProp(target, name, {
23
+ get: all[name],
24
+ enumerable: true,
25
+ configurable: true,
26
+ set: (newValue) => all[name] = () => newValue
27
+ });
28
+ };
29
+
30
+ // packages/core/websocket.ts
31
+ var exports_websocket = {};
32
+ __export(exports_websocket, {
33
+ defineWebSocketContract: () => defineWebSocketContract
34
+ });
35
+ module.exports = __toCommonJS(exports_websocket);
36
+ function defineWebSocketContract(contract) {
37
+ return contract;
38
+ }
39
+ })
40
+
41
+ //# debugId=CF5F38370DDE9B5964756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../websocket.ts"],
4
+ "sourcesContent": [
5
+ "import type { z } from 'zod';\n\n/**\n * Definition for a single message type in WebSocket communication\n */\nexport interface WebSocketMessageDefinition {\n payload: z.ZodTypeAny;\n}\n\n/**\n * Definition for a WebSocket endpoint contract\n */\nexport interface WebSocketContractDefinition {\n path: string;\n params?: z.ZodTypeAny;\n query?: z.ZodTypeAny;\n headers?: z.ZodTypeAny;\n\n /** Messages client sends to server (validated with Zod on server) */\n clientMessages: Record<string, WebSocketMessageDefinition>;\n\n /** Messages server sends to client (type inference only, NOT validated) */\n serverMessages: Record<string, WebSocketMessageDefinition>;\n}\n\n/**\n * A collection of named WebSocket endpoints\n */\nexport type WebSocketContract = Record<string, WebSocketContractDefinition>;\n\n/**\n * Extract the union type of all client messages for a WebSocket endpoint\n */\nexport type ExtractClientMessage<T extends WebSocketContractDefinition> = {\n [K in keyof T['clientMessages']]: {\n type: K;\n payload: z.infer<T['clientMessages'][K]['payload']>;\n };\n}[keyof T['clientMessages']];\n\n/**\n * Extract the union type of all server messages for a WebSocket endpoint\n */\nexport type ExtractServerMessage<T extends WebSocketContractDefinition> = {\n [K in keyof T['serverMessages']]: {\n type: K;\n payload: z.infer<T['serverMessages'][K]['payload']>;\n };\n}[keyof T['serverMessages']];\n\n/**\n * Extract the payload type for a specific client message\n */\nexport type ExtractClientMessagePayload<\n T extends WebSocketContractDefinition,\n K extends keyof T['clientMessages'],\n> = z.infer<T['clientMessages'][K]['payload']>;\n\n/**\n * Extract the payload type for a specific server message\n */\nexport type ExtractServerMessagePayload<\n T extends WebSocketContractDefinition,\n K extends keyof T['serverMessages'],\n> = z.infer<T['serverMessages'][K]['payload']>;\n\n/**\n * Extract params type from WebSocket endpoint\n */\nexport type ExtractWSParams<T extends WebSocketContractDefinition> =\n T['params'] extends z.ZodTypeAny ? z.infer<T['params']> : never;\n\n/**\n * Extract query type from WebSocket endpoint\n */\nexport type ExtractWSQuery<T extends WebSocketContractDefinition> = T['query'] extends z.ZodTypeAny\n ? z.infer<T['query']>\n : never;\n\n/**\n * Extract headers type from WebSocket endpoint\n */\nexport type ExtractWSHeaders<T extends WebSocketContractDefinition> =\n T['headers'] extends z.ZodTypeAny ? z.infer<T['headers']> : never;\n\n/**\n * Type helper to ensure a value is a valid WebSocket contract\n */\nexport function defineWebSocketContract<T extends WebSocketContract>(contract: T): T {\n return contract;\n}\n"
6
+ ],
7
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwFO,SAAS,uBAAoD,CAAC,UAAgB;AAAA,EACnF,OAAO;AAAA;",
8
+ "debugId": "CF5F38370DDE9B5964756E2164756E21",
9
+ "names": []
10
+ }
@@ -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}\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\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": "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",
8
+ "debugId": "BA32EBFEE62F023664756E2164756E21",
9
9
  "names": []
10
10
  }
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@richie-rpc/core",
3
- "version": "1.2.3",
3
+ "version": "1.2.5",
4
4
  "type": "module"
5
5
  }
@@ -0,0 +1,10 @@
1
+ // @bun
2
+ // packages/core/websocket.ts
3
+ function defineWebSocketContract(contract) {
4
+ return contract;
5
+ }
6
+ export {
7
+ defineWebSocketContract
8
+ };
9
+
10
+ //# debugId=74D9972511B45B2164756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../websocket.ts"],
4
+ "sourcesContent": [
5
+ "import type { z } from 'zod';\n\n/**\n * Definition for a single message type in WebSocket communication\n */\nexport interface WebSocketMessageDefinition {\n payload: z.ZodTypeAny;\n}\n\n/**\n * Definition for a WebSocket endpoint contract\n */\nexport interface WebSocketContractDefinition {\n path: string;\n params?: z.ZodTypeAny;\n query?: z.ZodTypeAny;\n headers?: z.ZodTypeAny;\n\n /** Messages client sends to server (validated with Zod on server) */\n clientMessages: Record<string, WebSocketMessageDefinition>;\n\n /** Messages server sends to client (type inference only, NOT validated) */\n serverMessages: Record<string, WebSocketMessageDefinition>;\n}\n\n/**\n * A collection of named WebSocket endpoints\n */\nexport type WebSocketContract = Record<string, WebSocketContractDefinition>;\n\n/**\n * Extract the union type of all client messages for a WebSocket endpoint\n */\nexport type ExtractClientMessage<T extends WebSocketContractDefinition> = {\n [K in keyof T['clientMessages']]: {\n type: K;\n payload: z.infer<T['clientMessages'][K]['payload']>;\n };\n}[keyof T['clientMessages']];\n\n/**\n * Extract the union type of all server messages for a WebSocket endpoint\n */\nexport type ExtractServerMessage<T extends WebSocketContractDefinition> = {\n [K in keyof T['serverMessages']]: {\n type: K;\n payload: z.infer<T['serverMessages'][K]['payload']>;\n };\n}[keyof T['serverMessages']];\n\n/**\n * Extract the payload type for a specific client message\n */\nexport type ExtractClientMessagePayload<\n T extends WebSocketContractDefinition,\n K extends keyof T['clientMessages'],\n> = z.infer<T['clientMessages'][K]['payload']>;\n\n/**\n * Extract the payload type for a specific server message\n */\nexport type ExtractServerMessagePayload<\n T extends WebSocketContractDefinition,\n K extends keyof T['serverMessages'],\n> = z.infer<T['serverMessages'][K]['payload']>;\n\n/**\n * Extract params type from WebSocket endpoint\n */\nexport type ExtractWSParams<T extends WebSocketContractDefinition> =\n T['params'] extends z.ZodTypeAny ? z.infer<T['params']> : never;\n\n/**\n * Extract query type from WebSocket endpoint\n */\nexport type ExtractWSQuery<T extends WebSocketContractDefinition> = T['query'] extends z.ZodTypeAny\n ? z.infer<T['query']>\n : never;\n\n/**\n * Extract headers type from WebSocket endpoint\n */\nexport type ExtractWSHeaders<T extends WebSocketContractDefinition> =\n T['headers'] extends z.ZodTypeAny ? z.infer<T['headers']> : never;\n\n/**\n * Type helper to ensure a value is a valid WebSocket contract\n */\nexport function defineWebSocketContract<T extends WebSocketContract>(contract: T): T {\n return contract;\n}\n"
6
+ ],
7
+ "mappings": ";;AAwFO,SAAS,uBAAoD,CAAC,UAAgB;AAAA,EACnF,OAAO;AAAA;",
8
+ "debugId": "74D9972511B45B2164756E2164756E21",
9
+ "names": []
10
+ }
@@ -101,25 +101,47 @@ export interface DownloadProgressEvent {
101
101
  total: number;
102
102
  progress: number;
103
103
  }
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;
104
+ type ExtractRegularParams<T extends string> = T extends `${infer _Start}:${infer Param}/${infer Rest}` ? Param | ExtractRegularParams<`/${Rest}`> : T extends `${infer _Start}:${infer Param}` ? Param : never;
105
+ type ExtractWildcardParams<T extends string> = T extends `${infer _Start}*${infer Param}` ? Param : never;
106
+ export type ExtractPathParams<T extends string> = ExtractRegularParams<T> | ExtractWildcardParams<T>;
105
107
  export type PathParamsObject<T extends string> = {
106
108
  [K in ExtractPathParams<T>]: string;
107
109
  };
110
+ /**
111
+ * Detailed path parameter info distinguishing regular vs wildcard params
112
+ */
113
+ export interface ParsedPathParams {
114
+ regular: string[];
115
+ wildcard: string | null;
116
+ }
117
+ /**
118
+ * Parse path parameters with detailed type information
119
+ * e.g., "/users/:id/*path" => { regular: ["id"], wildcard: "path" }
120
+ */
121
+ export declare function parsePathParamsDetailed(path: string): ParsedPathParams;
108
122
  /**
109
123
  * Parse path parameters from a URL path pattern
110
124
  * e.g., "/users/:id/posts/:postId" => ["id", "postId"]
125
+ * e.g., "/files/*path" => ["path"]
111
126
  */
112
127
  export declare function parsePathParams(path: string): string[];
113
128
  /**
114
129
  * Match a URL path against a pattern and extract parameters
115
130
  * e.g., matchPath("/users/:id", "/users/123") => { id: "123" }
131
+ * e.g., matchPath("/files/*path", "/files/a/b/c.txt") => { path: "a/b/c.txt" }
116
132
  */
117
133
  export declare function matchPath(pattern: string, path: string): Record<string, string> | null;
118
134
  /**
119
135
  * Interpolate path parameters into a URL pattern
120
136
  * e.g., interpolatePath("/users/:id", { id: "123" }) => "/users/123"
137
+ * e.g., interpolatePath("/files/*path", { path: "a/b/c.txt" }) => "/files/a/b/c.txt"
121
138
  */
122
139
  export declare function interpolatePath(pattern: string, params: Record<string, string | number>): string;
140
+ /**
141
+ * Validate a path pattern for correct syntax
142
+ * @throws Error if pattern is invalid
143
+ */
144
+ export declare function validatePathPattern(pattern: string): void;
123
145
  /**
124
146
  * Build a complete URL with query parameters
125
147
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@richie-rpc/core",
3
- "version": "1.2.3",
3
+ "version": "1.2.5",
4
4
  "main": "./dist/cjs/index.cjs",
5
5
  "exports": {
6
6
  ".": {