@richie-rpc/core 1.0.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +62 -5
- package/dist/cjs/index.cjs +29 -3
- package/dist/cjs/index.cjs.map +3 -3
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/index.mjs +29 -3
- package/dist/mjs/index.mjs.map +3 -3
- package/dist/mjs/package.json +1 -1
- package/dist/types/index.d.ts +22 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@ bun add @richie-rpc/core zod@^4
|
|
|
13
13
|
### Defining a Contract
|
|
14
14
|
|
|
15
15
|
```typescript
|
|
16
|
-
import { defineContract } from '@richie-rpc/core';
|
|
16
|
+
import { defineContract, Status } from '@richie-rpc/core';
|
|
17
17
|
import { z } from 'zod';
|
|
18
18
|
|
|
19
19
|
const contract = defineContract({
|
|
@@ -22,8 +22,8 @@ const contract = defineContract({
|
|
|
22
22
|
path: '/users/:id',
|
|
23
23
|
params: z.object({ id: z.string() }),
|
|
24
24
|
responses: {
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
[Status.OK]: z.object({ id: z.string(), name: z.string() }),
|
|
26
|
+
[Status.NotFound]: z.object({ error: z.string() })
|
|
27
27
|
}
|
|
28
28
|
},
|
|
29
29
|
createUser: {
|
|
@@ -31,7 +31,7 @@ const contract = defineContract({
|
|
|
31
31
|
path: '/users',
|
|
32
32
|
body: z.object({ name: z.string(), email: z.string().email() }),
|
|
33
33
|
responses: {
|
|
34
|
-
|
|
34
|
+
[Status.Created]: z.object({ id: z.string(), name: z.string(), email: z.string() })
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
});
|
|
@@ -52,11 +52,12 @@ Each endpoint can have:
|
|
|
52
52
|
## Features
|
|
53
53
|
|
|
54
54
|
- ✅ Type-safe contract definitions
|
|
55
|
-
- ✅ Zod
|
|
55
|
+
- ✅ Zod v4+ schema validation
|
|
56
56
|
- ✅ Path parameter parsing and interpolation
|
|
57
57
|
- ✅ Query parameter handling
|
|
58
58
|
- ✅ Multiple response types per endpoint
|
|
59
59
|
- ✅ Full TypeScript inference
|
|
60
|
+
- ✅ Status code constants for cleaner code
|
|
60
61
|
|
|
61
62
|
## Utilities
|
|
62
63
|
|
|
@@ -87,6 +88,62 @@ buildUrl('http://api.example.com', '/users', { limit: '10', offset: '0' });
|
|
|
87
88
|
// => 'http://api.example.com/users?limit=10&offset=0'
|
|
88
89
|
```
|
|
89
90
|
|
|
91
|
+
## Status Codes
|
|
92
|
+
|
|
93
|
+
Use the `Status` const object for type-safe status codes:
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
import { Status } from '@richie-rpc/core';
|
|
97
|
+
|
|
98
|
+
const contract = defineContract({
|
|
99
|
+
getUser: {
|
|
100
|
+
method: 'GET',
|
|
101
|
+
path: '/users/:id',
|
|
102
|
+
params: z.object({ id: z.string() }),
|
|
103
|
+
responses: {
|
|
104
|
+
[Status.OK]: UserSchema,
|
|
105
|
+
[Status.NotFound]: ErrorSchema
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Or in handlers (when imported from `@richie-rpc/server`):
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
return { status: Status.OK, body: user };
|
|
115
|
+
return { status: Status.NotFound, body: { error: 'Not found' } };
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Available constants:**
|
|
119
|
+
- Success: `OK` (200), `Created` (201), `Accepted` (202), `NoContent` (204)
|
|
120
|
+
- Client Errors: `BadRequest` (400), `Unauthorized` (401), `Forbidden` (403), `NotFound` (404), `Conflict` (409), `UnprocessableEntity` (422), `TooManyRequests` (429)
|
|
121
|
+
- Server Errors: `InternalServerError` (500), `BadGateway` (502), `ServiceUnavailable` (503)
|
|
122
|
+
|
|
123
|
+
**Using custom status codes:**
|
|
124
|
+
|
|
125
|
+
For status codes not in the `Status` object, use numeric literals in the contract:
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
const contract = defineContract({
|
|
129
|
+
customEndpoint: {
|
|
130
|
+
method: 'GET',
|
|
131
|
+
path: '/teapot',
|
|
132
|
+
responses: {
|
|
133
|
+
[Status.OK]: z.object({ message: z.string() }),
|
|
134
|
+
418: z.object({ message: z.string() }), // I'm a teapot
|
|
135
|
+
451: z.object({ reason: z.string() }), // Unavailable for legal reasons
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Then use `as const` in the handler response:
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
return { status: 418 as const, body: { message: "I'm a teapot" } };
|
|
145
|
+
```
|
|
146
|
+
|
|
90
147
|
## Type Inference
|
|
91
148
|
|
|
92
149
|
The package exports several utility types for extracting types from endpoint definitions:
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -35,9 +35,32 @@ __export(exports_core, {
|
|
|
35
35
|
matchPath: () => matchPath,
|
|
36
36
|
interpolatePath: () => interpolatePath,
|
|
37
37
|
defineContract: () => defineContract,
|
|
38
|
-
buildUrl: () => buildUrl
|
|
38
|
+
buildUrl: () => buildUrl,
|
|
39
|
+
Status: () => Status
|
|
39
40
|
});
|
|
40
41
|
module.exports = __toCommonJS(exports_core);
|
|
42
|
+
var Status = {
|
|
43
|
+
OK: 200,
|
|
44
|
+
Created: 201,
|
|
45
|
+
Accepted: 202,
|
|
46
|
+
NoContent: 204,
|
|
47
|
+
MovedPermanently: 301,
|
|
48
|
+
Found: 302,
|
|
49
|
+
NotModified: 304,
|
|
50
|
+
BadRequest: 400,
|
|
51
|
+
Unauthorized: 401,
|
|
52
|
+
Forbidden: 403,
|
|
53
|
+
NotFound: 404,
|
|
54
|
+
MethodNotAllowed: 405,
|
|
55
|
+
Conflict: 409,
|
|
56
|
+
UnprocessableEntity: 422,
|
|
57
|
+
TooManyRequests: 429,
|
|
58
|
+
InternalServerError: 500,
|
|
59
|
+
NotImplemented: 501,
|
|
60
|
+
BadGateway: 502,
|
|
61
|
+
ServiceUnavailable: 503,
|
|
62
|
+
GatewayTimeout: 504
|
|
63
|
+
};
|
|
41
64
|
function parsePathParams(path) {
|
|
42
65
|
const matches = path.match(/:([^/]+)/g);
|
|
43
66
|
if (!matches)
|
|
@@ -65,7 +88,10 @@ function interpolatePath(pattern, params) {
|
|
|
65
88
|
return result;
|
|
66
89
|
}
|
|
67
90
|
function buildUrl(baseUrl, path, query) {
|
|
68
|
-
const
|
|
91
|
+
const normalizedBase = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
|
|
92
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
93
|
+
const fullPath = normalizedBase + normalizedPath;
|
|
94
|
+
const url = new URL(fullPath);
|
|
69
95
|
if (query) {
|
|
70
96
|
for (const [key, value] of Object.entries(query)) {
|
|
71
97
|
if (value !== undefined && value !== null) {
|
|
@@ -102,4 +128,4 @@ function defineContract(contract) {
|
|
|
102
128
|
}
|
|
103
129
|
})
|
|
104
130
|
|
|
105
|
-
//# debugId=
|
|
131
|
+
//# debugId=ACC9A49236395C2D64756E2164756E21
|
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// Endpoint definition structure\nexport interface EndpointDefinition {\n method: HttpMethod;\n path: string;\n params?: z.ZodTypeAny;\n query?: z.ZodTypeAny;\n headers?: z.ZodTypeAny;\n body?: z.ZodTypeAny;\n responses: Record<number, z.ZodTypeAny>;\n}\n\n// Contract is a collection of named endpoints\nexport type Contract = Record<string, EndpointDefinition>;\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\nexport type ExtractBody<T extends EndpointDefinition> = T['body'] extends z.ZodTypeAny\n ? InferZodType<T['body']>\n : never;\n\n// Extract response types for all status codes\nexport type ExtractResponses<T extends EndpointDefinition> = {\n [K in keyof T['responses']]: T['responses'][K] extends z.ZodTypeAny\n ? InferZodType<T['responses'][K]>\n : never;\n};\n\n// Extract a specific response type by status code\nexport type ExtractResponse<\n T extends EndpointDefinition,\n Status extends number,\n> = Status extends keyof T['responses']\n ? T['responses'][Status] extends z.ZodTypeAny\n ? InferZodType<T['responses'][Status]>\n : never\n : never;\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 const
|
|
5
|
+
"import type { z } from 'zod';\n\n// HTTP methods supported\nexport type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';\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// Endpoint definition structure\nexport interface EndpointDefinition {\n method: HttpMethod;\n path: string;\n params?: z.ZodTypeAny;\n query?: z.ZodTypeAny;\n headers?: z.ZodTypeAny;\n body?: z.ZodTypeAny;\n responses: Record<number, z.ZodTypeAny>;\n}\n\n// Contract is a collection of named endpoints\nexport type Contract = Record<string, EndpointDefinition>;\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\nexport type ExtractBody<T extends EndpointDefinition> = T['body'] extends z.ZodTypeAny\n ? InferZodType<T['body']>\n : never;\n\n// Extract response types for all status codes\nexport type ExtractResponses<T extends EndpointDefinition> = {\n [K in keyof T['responses']]: T['responses'][K] extends z.ZodTypeAny\n ? InferZodType<T['responses'][K]>\n : never;\n};\n\n// Extract a specific response type by status code\nexport type ExtractResponse<\n T extends EndpointDefinition,\n Status extends number,\n> = Status extends keyof T['responses']\n ? T['responses'][Status] extends z.ZodTypeAny\n ? InferZodType<T['responses'][Status]>\n : never\n : never;\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"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "ACC9A49236395C2D64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/cjs/package.json
CHANGED
package/dist/mjs/index.mjs
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// packages/core/index.ts
|
|
3
|
+
var Status = {
|
|
4
|
+
OK: 200,
|
|
5
|
+
Created: 201,
|
|
6
|
+
Accepted: 202,
|
|
7
|
+
NoContent: 204,
|
|
8
|
+
MovedPermanently: 301,
|
|
9
|
+
Found: 302,
|
|
10
|
+
NotModified: 304,
|
|
11
|
+
BadRequest: 400,
|
|
12
|
+
Unauthorized: 401,
|
|
13
|
+
Forbidden: 403,
|
|
14
|
+
NotFound: 404,
|
|
15
|
+
MethodNotAllowed: 405,
|
|
16
|
+
Conflict: 409,
|
|
17
|
+
UnprocessableEntity: 422,
|
|
18
|
+
TooManyRequests: 429,
|
|
19
|
+
InternalServerError: 500,
|
|
20
|
+
NotImplemented: 501,
|
|
21
|
+
BadGateway: 502,
|
|
22
|
+
ServiceUnavailable: 503,
|
|
23
|
+
GatewayTimeout: 504
|
|
24
|
+
};
|
|
3
25
|
function parsePathParams(path) {
|
|
4
26
|
const matches = path.match(/:([^/]+)/g);
|
|
5
27
|
if (!matches)
|
|
@@ -27,7 +49,10 @@ function interpolatePath(pattern, params) {
|
|
|
27
49
|
return result;
|
|
28
50
|
}
|
|
29
51
|
function buildUrl(baseUrl, path, query) {
|
|
30
|
-
const
|
|
52
|
+
const normalizedBase = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
|
|
53
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
54
|
+
const fullPath = normalizedBase + normalizedPath;
|
|
55
|
+
const url = new URL(fullPath);
|
|
31
56
|
if (query) {
|
|
32
57
|
for (const [key, value] of Object.entries(query)) {
|
|
33
58
|
if (value !== undefined && value !== null) {
|
|
@@ -68,7 +93,8 @@ export {
|
|
|
68
93
|
matchPath,
|
|
69
94
|
interpolatePath,
|
|
70
95
|
defineContract,
|
|
71
|
-
buildUrl
|
|
96
|
+
buildUrl,
|
|
97
|
+
Status
|
|
72
98
|
};
|
|
73
99
|
|
|
74
|
-
//# debugId=
|
|
100
|
+
//# debugId=6A3EE2DEC11404CC64756E2164756E21
|
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// Endpoint definition structure\nexport interface EndpointDefinition {\n method: HttpMethod;\n path: string;\n params?: z.ZodTypeAny;\n query?: z.ZodTypeAny;\n headers?: z.ZodTypeAny;\n body?: z.ZodTypeAny;\n responses: Record<number, z.ZodTypeAny>;\n}\n\n// Contract is a collection of named endpoints\nexport type Contract = Record<string, EndpointDefinition>;\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\nexport type ExtractBody<T extends EndpointDefinition> = T['body'] extends z.ZodTypeAny\n ? InferZodType<T['body']>\n : never;\n\n// Extract response types for all status codes\nexport type ExtractResponses<T extends EndpointDefinition> = {\n [K in keyof T['responses']]: T['responses'][K] extends z.ZodTypeAny\n ? InferZodType<T['responses'][K]>\n : never;\n};\n\n// Extract a specific response type by status code\nexport type ExtractResponse<\n T extends EndpointDefinition,\n Status extends number,\n> = Status extends keyof T['responses']\n ? T['responses'][Status] extends z.ZodTypeAny\n ? InferZodType<T['responses'][Status]>\n : never\n : never;\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 const
|
|
5
|
+
"import type { z } from 'zod';\n\n// HTTP methods supported\nexport type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';\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// Endpoint definition structure\nexport interface EndpointDefinition {\n method: HttpMethod;\n path: string;\n params?: z.ZodTypeAny;\n query?: z.ZodTypeAny;\n headers?: z.ZodTypeAny;\n body?: z.ZodTypeAny;\n responses: Record<number, z.ZodTypeAny>;\n}\n\n// Contract is a collection of named endpoints\nexport type Contract = Record<string, EndpointDefinition>;\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\nexport type ExtractBody<T extends EndpointDefinition> = T['body'] extends z.ZodTypeAny\n ? InferZodType<T['body']>\n : never;\n\n// Extract response types for all status codes\nexport type ExtractResponses<T extends EndpointDefinition> = {\n [K in keyof T['responses']]: T['responses'][K] extends z.ZodTypeAny\n ? InferZodType<T['responses'][K]>\n : never;\n};\n\n// Extract a specific response type by status code\nexport type ExtractResponse<\n T extends EndpointDefinition,\n Status extends number,\n> = Status extends keyof T['responses']\n ? T['responses'][Status] extends z.ZodTypeAny\n ? InferZodType<T['responses'][Status]>\n : never\n : never;\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"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "6A3EE2DEC11404CC64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/mjs/package.json
CHANGED
package/dist/types/index.d.ts
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
import type { z } from 'zod';
|
|
2
2
|
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
|
|
3
|
+
export declare const Status: {
|
|
4
|
+
readonly OK: 200;
|
|
5
|
+
readonly Created: 201;
|
|
6
|
+
readonly Accepted: 202;
|
|
7
|
+
readonly NoContent: 204;
|
|
8
|
+
readonly MovedPermanently: 301;
|
|
9
|
+
readonly Found: 302;
|
|
10
|
+
readonly NotModified: 304;
|
|
11
|
+
readonly BadRequest: 400;
|
|
12
|
+
readonly Unauthorized: 401;
|
|
13
|
+
readonly Forbidden: 403;
|
|
14
|
+
readonly NotFound: 404;
|
|
15
|
+
readonly MethodNotAllowed: 405;
|
|
16
|
+
readonly Conflict: 409;
|
|
17
|
+
readonly UnprocessableEntity: 422;
|
|
18
|
+
readonly TooManyRequests: 429;
|
|
19
|
+
readonly InternalServerError: 500;
|
|
20
|
+
readonly NotImplemented: 501;
|
|
21
|
+
readonly BadGateway: 502;
|
|
22
|
+
readonly ServiceUnavailable: 503;
|
|
23
|
+
readonly GatewayTimeout: 504;
|
|
24
|
+
};
|
|
3
25
|
export interface EndpointDefinition {
|
|
4
26
|
method: HttpMethod;
|
|
5
27
|
path: string;
|