@richie-rpc/core 0.1.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 +109 -0
- package/dist/cjs/index.cjs +105 -0
- package/dist/cjs/index.cjs.map +10 -0
- package/dist/cjs/package.json +5 -0
- package/dist/mjs/index.mjs +74 -0
- package/dist/mjs/index.mjs.map +10 -0
- package/dist/mjs/package.json +5 -0
- package/dist/types/index.d.ts +49 -0
- package/package.json +49 -0
package/README.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# @richie-rpc/core
|
|
2
|
+
|
|
3
|
+
Core package for defining type-safe API contracts with Zod schemas.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun add @richie-rpc/core zod
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### Defining a Contract
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import { defineContract } from '@richie-rpc/core';
|
|
17
|
+
import { z } from 'zod';
|
|
18
|
+
|
|
19
|
+
const contract = defineContract({
|
|
20
|
+
getUser: {
|
|
21
|
+
method: 'GET',
|
|
22
|
+
path: '/users/:id',
|
|
23
|
+
params: z.object({ id: z.string() }),
|
|
24
|
+
responses: {
|
|
25
|
+
200: z.object({ id: z.string(), name: z.string() }),
|
|
26
|
+
404: z.object({ error: z.string() })
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
createUser: {
|
|
30
|
+
method: 'POST',
|
|
31
|
+
path: '/users',
|
|
32
|
+
body: z.object({ name: z.string(), email: z.string().email() }),
|
|
33
|
+
responses: {
|
|
34
|
+
201: z.object({ id: z.string(), name: z.string(), email: z.string() })
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Endpoint Definition Structure
|
|
41
|
+
|
|
42
|
+
Each endpoint can have:
|
|
43
|
+
|
|
44
|
+
- `method` (required): HTTP method (`GET`, `POST`, `PUT`, `PATCH`, `DELETE`, etc.)
|
|
45
|
+
- `path` (required): URL path with optional parameters (`:id` syntax)
|
|
46
|
+
- `params` (optional): Zod schema for path parameters
|
|
47
|
+
- `query` (optional): Zod schema for query parameters
|
|
48
|
+
- `headers` (optional): Zod schema for request headers
|
|
49
|
+
- `body` (optional): Zod schema for request body
|
|
50
|
+
- `responses` (required): Object mapping status codes to Zod schemas
|
|
51
|
+
|
|
52
|
+
## Features
|
|
53
|
+
|
|
54
|
+
- ✅ Type-safe contract definitions
|
|
55
|
+
- ✅ Zod v3+ schema validation
|
|
56
|
+
- ✅ Path parameter parsing and interpolation
|
|
57
|
+
- ✅ Query parameter handling
|
|
58
|
+
- ✅ Multiple response types per endpoint
|
|
59
|
+
- ✅ Full TypeScript inference
|
|
60
|
+
|
|
61
|
+
## Utilities
|
|
62
|
+
|
|
63
|
+
### Path Parameter Utilities
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { parsePathParams, matchPath, interpolatePath } from '@richie-rpc/core';
|
|
67
|
+
|
|
68
|
+
// Parse parameter names from path
|
|
69
|
+
parsePathParams('/users/:id/posts/:postId');
|
|
70
|
+
// => ['id', 'postId']
|
|
71
|
+
|
|
72
|
+
// Match a path and extract parameters
|
|
73
|
+
matchPath('/users/:id', '/users/123');
|
|
74
|
+
// => { id: '123' }
|
|
75
|
+
|
|
76
|
+
// Interpolate parameters into path
|
|
77
|
+
interpolatePath('/users/:id', { id: '123' });
|
|
78
|
+
// => '/users/123'
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### URL Building
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { buildUrl } from '@richie-rpc/core';
|
|
85
|
+
|
|
86
|
+
buildUrl('http://api.example.com', '/users', { limit: '10', offset: '0' });
|
|
87
|
+
// => 'http://api.example.com/users?limit=10&offset=0'
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Type Inference
|
|
91
|
+
|
|
92
|
+
The package exports several utility types for extracting types from endpoint definitions:
|
|
93
|
+
|
|
94
|
+
- `ExtractParams<T>`: Extract path parameters type
|
|
95
|
+
- `ExtractQuery<T>`: Extract query parameters type
|
|
96
|
+
- `ExtractHeaders<T>`: Extract headers type
|
|
97
|
+
- `ExtractBody<T>`: Extract request body type
|
|
98
|
+
- `ExtractResponses<T>`: Extract all response types
|
|
99
|
+
- `ExtractResponse<T, Status>`: Extract specific response type by status code
|
|
100
|
+
|
|
101
|
+
## Links
|
|
102
|
+
|
|
103
|
+
- **npm:** https://www.npmjs.com/package/@richie-rpc/core
|
|
104
|
+
- **Repository:** https://github.com/ricsam/richie-rpc
|
|
105
|
+
|
|
106
|
+
## License
|
|
107
|
+
|
|
108
|
+
MIT
|
|
109
|
+
|
|
@@ -0,0 +1,105 @@
|
|
|
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/index.ts
|
|
31
|
+
var exports_core = {};
|
|
32
|
+
__export(exports_core, {
|
|
33
|
+
parseQuery: () => parseQuery,
|
|
34
|
+
parsePathParams: () => parsePathParams,
|
|
35
|
+
matchPath: () => matchPath,
|
|
36
|
+
interpolatePath: () => interpolatePath,
|
|
37
|
+
defineContract: () => defineContract,
|
|
38
|
+
buildUrl: () => buildUrl
|
|
39
|
+
});
|
|
40
|
+
module.exports = __toCommonJS(exports_core);
|
|
41
|
+
function parsePathParams(path) {
|
|
42
|
+
const matches = path.match(/:([^/]+)/g);
|
|
43
|
+
if (!matches)
|
|
44
|
+
return [];
|
|
45
|
+
return matches.map((match) => match.slice(1));
|
|
46
|
+
}
|
|
47
|
+
function matchPath(pattern, path) {
|
|
48
|
+
const paramNames = parsePathParams(pattern);
|
|
49
|
+
const regexPattern = pattern.replace(/:[^/]+/g, "([^/]+)").replace(/\//g, "\\/");
|
|
50
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
51
|
+
const match = path.match(regex);
|
|
52
|
+
if (!match)
|
|
53
|
+
return null;
|
|
54
|
+
const params = {};
|
|
55
|
+
paramNames.forEach((name, index) => {
|
|
56
|
+
params[name] = match[index + 1] ?? "";
|
|
57
|
+
});
|
|
58
|
+
return params;
|
|
59
|
+
}
|
|
60
|
+
function interpolatePath(pattern, params) {
|
|
61
|
+
let result = pattern;
|
|
62
|
+
for (const [key, value] of Object.entries(params)) {
|
|
63
|
+
result = result.replace(`:${key}`, String(value));
|
|
64
|
+
}
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
function buildUrl(baseUrl, path, query) {
|
|
68
|
+
const url = new URL(path, baseUrl);
|
|
69
|
+
if (query) {
|
|
70
|
+
for (const [key, value] of Object.entries(query)) {
|
|
71
|
+
if (value !== undefined && value !== null) {
|
|
72
|
+
if (Array.isArray(value)) {
|
|
73
|
+
for (const v of value) {
|
|
74
|
+
url.searchParams.append(key, String(v));
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
url.searchParams.append(key, String(value));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return url.toString();
|
|
83
|
+
}
|
|
84
|
+
function parseQuery(searchParams) {
|
|
85
|
+
const result = {};
|
|
86
|
+
for (const [key, value] of searchParams.entries()) {
|
|
87
|
+
const existing = result[key];
|
|
88
|
+
if (existing) {
|
|
89
|
+
if (Array.isArray(existing)) {
|
|
90
|
+
existing.push(value);
|
|
91
|
+
} else {
|
|
92
|
+
result[key] = [existing, value];
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
result[key] = value;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
function defineContract(contract) {
|
|
101
|
+
return contract;
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
//# debugId=E90B1C296ECE391D64756E2164756E21
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../index.ts"],
|
|
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 url = new URL(path, baseUrl);\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
|
+
],
|
|
7
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4EO,SAAS,eAAe,CAAC,MAAwB;AAAA,EACtD,MAAM,UAAU,KAAK,MAAM,WAAW;AAAA,EACtC,IAAI,CAAC;AAAA,IAAS,OAAO,CAAC;AAAA,EACtB,OAAO,QAAQ,IAAI,CAAC,UAAU,MAAM,MAAM,CAAC,CAAC;AAAA;AAOvC,SAAS,SAAS,CAAC,SAAiB,MAA6C;AAAA,EACtF,MAAM,aAAa,gBAAgB,OAAO;AAAA,EAG1C,MAAM,eAAe,QAAQ,QAAQ,WAAW,SAAS,EAAE,QAAQ,OAAO,KAAK;AAAA,EAE/E,MAAM,QAAQ,IAAI,OAAO,IAAI,eAAe;AAAA,EAC5C,MAAM,QAAQ,KAAK,MAAM,KAAK;AAAA,EAE9B,IAAI,CAAC;AAAA,IAAO,OAAO;AAAA,EAEnB,MAAM,SAAiC,CAAC;AAAA,EACxC,WAAW,QAAQ,CAAC,MAAM,UAAU;AAAA,IAClC,OAAO,QAAQ,MAAM,QAAQ,MAAM;AAAA,GACpC;AAAA,EAED,OAAO;AAAA;AAOF,SAAS,eAAe,CAAC,SAAiB,QAAiD;AAAA,EAChG,IAAI,SAAS;AAAA,EACb,YAAY,KAAK,UAAU,OAAO,QAAQ,MAAM,GAAG;AAAA,IACjD,SAAS,OAAO,QAAQ,IAAI,OAAO,OAAO,KAAK,CAAC;AAAA,EAClD;AAAA,EACA,OAAO;AAAA;AAMF,SAAS,QAAQ,CACtB,SACA,MACA,OACQ;AAAA,EACR,MAAM,MAAM,IAAI,IAAI,MAAM,OAAO;AAAA,EAEjC,IAAI,OAAO;AAAA,IACT,YAAY,KAAK,UAAU,OAAO,QAAQ,KAAK,GAAG;AAAA,MAChD,IAAI,UAAU,aAAa,UAAU,MAAM;AAAA,QACzC,IAAI,MAAM,QAAQ,KAAK,GAAG;AAAA,UACxB,WAAW,KAAK,OAAO;AAAA,YACrB,IAAI,aAAa,OAAO,KAAK,OAAO,CAAC,CAAC;AAAA,UACxC;AAAA,QACF,EAAO;AAAA,UACL,IAAI,aAAa,OAAO,KAAK,OAAO,KAAK,CAAC;AAAA;AAAA,MAE9C;AAAA,IACF;AAAA,EACF;AAAA,EAEA,OAAO,IAAI,SAAS;AAAA;AAMf,SAAS,UAAU,CAAC,cAAkE;AAAA,EAC3F,MAAM,SAA4C,CAAC;AAAA,EAEnD,YAAY,KAAK,UAAU,aAAa,QAAQ,GAAG;AAAA,IACjD,MAAM,WAAW,OAAO;AAAA,IACxB,IAAI,UAAU;AAAA,MACZ,IAAI,MAAM,QAAQ,QAAQ,GAAG;AAAA,QAC3B,SAAS,KAAK,KAAK;AAAA,MACrB,EAAO;AAAA,QACL,OAAO,OAAO,CAAC,UAAU,KAAK;AAAA;AAAA,IAElC,EAAO;AAAA,MACL,OAAO,OAAO;AAAA;AAAA,EAElB;AAAA,EAEA,OAAO;AAAA;AAIF,SAAS,cAAkC,CAAC,UAAgB;AAAA,EACjE,OAAO;AAAA;",
|
|
8
|
+
"debugId": "E90B1C296ECE391D64756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/core/index.ts
|
|
3
|
+
function parsePathParams(path) {
|
|
4
|
+
const matches = path.match(/:([^/]+)/g);
|
|
5
|
+
if (!matches)
|
|
6
|
+
return [];
|
|
7
|
+
return matches.map((match) => match.slice(1));
|
|
8
|
+
}
|
|
9
|
+
function matchPath(pattern, path) {
|
|
10
|
+
const paramNames = parsePathParams(pattern);
|
|
11
|
+
const regexPattern = pattern.replace(/:[^/]+/g, "([^/]+)").replace(/\//g, "\\/");
|
|
12
|
+
const regex = new RegExp(`^${regexPattern}$`);
|
|
13
|
+
const match = path.match(regex);
|
|
14
|
+
if (!match)
|
|
15
|
+
return null;
|
|
16
|
+
const params = {};
|
|
17
|
+
paramNames.forEach((name, index) => {
|
|
18
|
+
params[name] = match[index + 1] ?? "";
|
|
19
|
+
});
|
|
20
|
+
return params;
|
|
21
|
+
}
|
|
22
|
+
function interpolatePath(pattern, params) {
|
|
23
|
+
let result = pattern;
|
|
24
|
+
for (const [key, value] of Object.entries(params)) {
|
|
25
|
+
result = result.replace(`:${key}`, String(value));
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
function buildUrl(baseUrl, path, query) {
|
|
30
|
+
const url = new URL(path, baseUrl);
|
|
31
|
+
if (query) {
|
|
32
|
+
for (const [key, value] of Object.entries(query)) {
|
|
33
|
+
if (value !== undefined && value !== null) {
|
|
34
|
+
if (Array.isArray(value)) {
|
|
35
|
+
for (const v of value) {
|
|
36
|
+
url.searchParams.append(key, String(v));
|
|
37
|
+
}
|
|
38
|
+
} else {
|
|
39
|
+
url.searchParams.append(key, String(value));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return url.toString();
|
|
45
|
+
}
|
|
46
|
+
function parseQuery(searchParams) {
|
|
47
|
+
const result = {};
|
|
48
|
+
for (const [key, value] of searchParams.entries()) {
|
|
49
|
+
const existing = result[key];
|
|
50
|
+
if (existing) {
|
|
51
|
+
if (Array.isArray(existing)) {
|
|
52
|
+
existing.push(value);
|
|
53
|
+
} else {
|
|
54
|
+
result[key] = [existing, value];
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
result[key] = value;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
function defineContract(contract) {
|
|
63
|
+
return contract;
|
|
64
|
+
}
|
|
65
|
+
export {
|
|
66
|
+
parseQuery,
|
|
67
|
+
parsePathParams,
|
|
68
|
+
matchPath,
|
|
69
|
+
interpolatePath,
|
|
70
|
+
defineContract,
|
|
71
|
+
buildUrl
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
//# debugId=AAE15A31FDF5132964756E2164756E21
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../index.ts"],
|
|
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 url = new URL(path, baseUrl);\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
|
+
],
|
|
7
|
+
"mappings": ";;AA4EO,SAAS,eAAe,CAAC,MAAwB;AAAA,EACtD,MAAM,UAAU,KAAK,MAAM,WAAW;AAAA,EACtC,IAAI,CAAC;AAAA,IAAS,OAAO,CAAC;AAAA,EACtB,OAAO,QAAQ,IAAI,CAAC,UAAU,MAAM,MAAM,CAAC,CAAC;AAAA;AAOvC,SAAS,SAAS,CAAC,SAAiB,MAA6C;AAAA,EACtF,MAAM,aAAa,gBAAgB,OAAO;AAAA,EAG1C,MAAM,eAAe,QAAQ,QAAQ,WAAW,SAAS,EAAE,QAAQ,OAAO,KAAK;AAAA,EAE/E,MAAM,QAAQ,IAAI,OAAO,IAAI,eAAe;AAAA,EAC5C,MAAM,QAAQ,KAAK,MAAM,KAAK;AAAA,EAE9B,IAAI,CAAC;AAAA,IAAO,OAAO;AAAA,EAEnB,MAAM,SAAiC,CAAC;AAAA,EACxC,WAAW,QAAQ,CAAC,MAAM,UAAU;AAAA,IAClC,OAAO,QAAQ,MAAM,QAAQ,MAAM;AAAA,GACpC;AAAA,EAED,OAAO;AAAA;AAOF,SAAS,eAAe,CAAC,SAAiB,QAAiD;AAAA,EAChG,IAAI,SAAS;AAAA,EACb,YAAY,KAAK,UAAU,OAAO,QAAQ,MAAM,GAAG;AAAA,IACjD,SAAS,OAAO,QAAQ,IAAI,OAAO,OAAO,KAAK,CAAC;AAAA,EAClD;AAAA,EACA,OAAO;AAAA;AAMF,SAAS,QAAQ,CACtB,SACA,MACA,OACQ;AAAA,EACR,MAAM,MAAM,IAAI,IAAI,MAAM,OAAO;AAAA,EAEjC,IAAI,OAAO;AAAA,IACT,YAAY,KAAK,UAAU,OAAO,QAAQ,KAAK,GAAG;AAAA,MAChD,IAAI,UAAU,aAAa,UAAU,MAAM;AAAA,QACzC,IAAI,MAAM,QAAQ,KAAK,GAAG;AAAA,UACxB,WAAW,KAAK,OAAO;AAAA,YACrB,IAAI,aAAa,OAAO,KAAK,OAAO,CAAC,CAAC;AAAA,UACxC;AAAA,QACF,EAAO;AAAA,UACL,IAAI,aAAa,OAAO,KAAK,OAAO,KAAK,CAAC;AAAA;AAAA,MAE9C;AAAA,IACF;AAAA,EACF;AAAA,EAEA,OAAO,IAAI,SAAS;AAAA;AAMf,SAAS,UAAU,CAAC,cAAkE;AAAA,EAC3F,MAAM,SAA4C,CAAC;AAAA,EAEnD,YAAY,KAAK,UAAU,aAAa,QAAQ,GAAG;AAAA,IACjD,MAAM,WAAW,OAAO;AAAA,IACxB,IAAI,UAAU;AAAA,MACZ,IAAI,MAAM,QAAQ,QAAQ,GAAG;AAAA,QAC3B,SAAS,KAAK,KAAK;AAAA,MACrB,EAAO;AAAA,QACL,OAAO,OAAO,CAAC,UAAU,KAAK;AAAA;AAAA,IAElC,EAAO;AAAA,MACL,OAAO,OAAO;AAAA;AAAA,EAElB;AAAA,EAEA,OAAO;AAAA;AAIF,SAAS,cAAkC,CAAC,UAAgB;AAAA,EACjE,OAAO;AAAA;",
|
|
8
|
+
"debugId": "AAE15A31FDF5132964756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { z } from 'zod';
|
|
2
|
+
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
|
|
3
|
+
export interface EndpointDefinition {
|
|
4
|
+
method: HttpMethod;
|
|
5
|
+
path: string;
|
|
6
|
+
params?: z.ZodTypeAny;
|
|
7
|
+
query?: z.ZodTypeAny;
|
|
8
|
+
headers?: z.ZodTypeAny;
|
|
9
|
+
body?: z.ZodTypeAny;
|
|
10
|
+
responses: Record<number, z.ZodTypeAny>;
|
|
11
|
+
}
|
|
12
|
+
export type Contract = Record<string, EndpointDefinition>;
|
|
13
|
+
export type InferZodType<T> = T extends z.ZodTypeAny ? z.infer<T> : never;
|
|
14
|
+
export type ExtractParams<T extends EndpointDefinition> = T['params'] extends z.ZodTypeAny ? InferZodType<T['params']> : never;
|
|
15
|
+
export type ExtractQuery<T extends EndpointDefinition> = T['query'] extends z.ZodTypeAny ? InferZodType<T['query']> : never;
|
|
16
|
+
export type ExtractHeaders<T extends EndpointDefinition> = T['headers'] extends z.ZodTypeAny ? InferZodType<T['headers']> : never;
|
|
17
|
+
export type ExtractBody<T extends EndpointDefinition> = T['body'] extends z.ZodTypeAny ? InferZodType<T['body']> : never;
|
|
18
|
+
export type ExtractResponses<T extends EndpointDefinition> = {
|
|
19
|
+
[K in keyof T['responses']]: T['responses'][K] extends z.ZodTypeAny ? InferZodType<T['responses'][K]> : never;
|
|
20
|
+
};
|
|
21
|
+
export type ExtractResponse<T extends EndpointDefinition, Status extends number> = Status extends keyof T['responses'] ? T['responses'][Status] extends z.ZodTypeAny ? InferZodType<T['responses'][Status]> : never : never;
|
|
22
|
+
export type ExtractPathParams<T extends string> = T extends `${infer _Start}:${infer Param}/${infer Rest}` ? Param | ExtractPathParams<`/${Rest}`> : T extends `${infer _Start}:${infer Param}` ? Param : never;
|
|
23
|
+
export type PathParamsObject<T extends string> = {
|
|
24
|
+
[K in ExtractPathParams<T>]: string;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Parse path parameters from a URL path pattern
|
|
28
|
+
* e.g., "/users/:id/posts/:postId" => ["id", "postId"]
|
|
29
|
+
*/
|
|
30
|
+
export declare function parsePathParams(path: string): string[];
|
|
31
|
+
/**
|
|
32
|
+
* Match a URL path against a pattern and extract parameters
|
|
33
|
+
* e.g., matchPath("/users/:id", "/users/123") => { id: "123" }
|
|
34
|
+
*/
|
|
35
|
+
export declare function matchPath(pattern: string, path: string): Record<string, string> | null;
|
|
36
|
+
/**
|
|
37
|
+
* Interpolate path parameters into a URL pattern
|
|
38
|
+
* e.g., interpolatePath("/users/:id", { id: "123" }) => "/users/123"
|
|
39
|
+
*/
|
|
40
|
+
export declare function interpolatePath(pattern: string, params: Record<string, string | number>): string;
|
|
41
|
+
/**
|
|
42
|
+
* Build a complete URL with query parameters
|
|
43
|
+
*/
|
|
44
|
+
export declare function buildUrl(baseUrl: string, path: string, query?: Record<string, string | number | boolean | string[]>): string;
|
|
45
|
+
/**
|
|
46
|
+
* Parse query parameters from URLSearchParams
|
|
47
|
+
*/
|
|
48
|
+
export declare function parseQuery(searchParams: URLSearchParams): Record<string, string | string[]>;
|
|
49
|
+
export declare function defineContract<T extends Contract>(contract: T): T;
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@richie-rpc/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"main": "./dist/cjs/index.cjs",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": {
|
|
7
|
+
"types": "./dist/types/index.d.ts",
|
|
8
|
+
"require": "./dist/cjs/index.cjs",
|
|
9
|
+
"import": "./dist/mjs/index.mjs"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"zod": "^3.23.8"
|
|
14
|
+
},
|
|
15
|
+
"peerDependencies": {
|
|
16
|
+
"typescript": "^5"
|
|
17
|
+
},
|
|
18
|
+
"module": "./dist/mjs/index.mjs",
|
|
19
|
+
"types": "./dist/types/index.d.ts",
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"README.md"
|
|
26
|
+
],
|
|
27
|
+
"author": "Richie <oss@ricsam.dev>",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/ricsam/richie-rpc.git"
|
|
32
|
+
},
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/ricsam/richie-rpc/issues"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/ricsam/richie-rpc#readme",
|
|
37
|
+
"keywords": [
|
|
38
|
+
"typescript",
|
|
39
|
+
"bun",
|
|
40
|
+
"zod",
|
|
41
|
+
"api",
|
|
42
|
+
"contract",
|
|
43
|
+
"rpc",
|
|
44
|
+
"rest",
|
|
45
|
+
"openapi",
|
|
46
|
+
"type-safe"
|
|
47
|
+
],
|
|
48
|
+
"description": "Core contract definitions and type utilities for Richie RPC"
|
|
49
|
+
}
|