@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.
- package/dist/cjs/index.cjs +47 -9
- package/dist/cjs/index.cjs.map +3 -3
- package/dist/cjs/package.json +1 -1
- package/dist/cjs/websocket.cjs +41 -0
- package/dist/cjs/websocket.cjs.map +10 -0
- package/dist/mjs/index.mjs +47 -9
- package/dist/mjs/index.mjs.map +3 -3
- package/dist/mjs/package.json +1 -1
- package/dist/mjs/websocket.mjs +10 -0
- package/dist/mjs/websocket.mjs.map +10 -0
- package/dist/types/index.d.ts +23 -1
- package/package.json +1 -1
package/dist/cjs/index.cjs
CHANGED
|
@@ -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
|
|
86
|
-
|
|
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
|
|
92
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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=
|
|
241
|
+
//# debugId=CA0ED6C82DBBBD5564756E2164756E21
|
package/dist/cjs/index.cjs.map
CHANGED
|
@@ -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": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "CA0ED6C82DBBBD5564756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/cjs/package.json
CHANGED
|
@@ -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
|
+
}
|
package/dist/mjs/index.mjs
CHANGED
|
@@ -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
|
|
28
|
-
|
|
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
|
|
34
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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=
|
|
193
|
+
//# debugId=BA32EBFEE62F023664756E2164756E21
|
package/dist/mjs/index.mjs.map
CHANGED
|
@@ -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": ";;
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "BA32EBFEE62F023664756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/mjs/package.json
CHANGED
|
@@ -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
|
+
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -101,25 +101,47 @@ export interface DownloadProgressEvent {
|
|
|
101
101
|
total: number;
|
|
102
102
|
progress: number;
|
|
103
103
|
}
|
|
104
|
-
|
|
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
|
*/
|