@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 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,5 @@
1
+ {
2
+ "name": "@richie-rpc/core",
3
+ "version": "0.1.0",
4
+ "type": "commonjs"
5
+ }
@@ -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,5 @@
1
+ {
2
+ "name": "@richie-rpc/core",
3
+ "version": "0.1.0",
4
+ "type": "module"
5
+ }
@@ -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
+ }