@srpc.org/core 0.20.3
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 +322 -0
- package/dist/client.d.ts +149 -0
- package/dist/client.js +158 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/server.d.ts +146 -0
- package/dist/server.js +262 -0
- package/dist/shared.d.ts +400 -0
- package/dist/shared.js +168 -0
- package/package.json +52 -0
package/README.md
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
# @srpc/core
|
|
2
|
+
|
|
3
|
+
A lightweight, type-safe RPC framework for TypeScript with automatic type inference, multiple runtime support, and zero dependencies.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Using JSR (recommended)
|
|
9
|
+
deno add @srpc/core
|
|
10
|
+
npx jsr add @srpc/core
|
|
11
|
+
yarn dlx jsr add @srpc/core
|
|
12
|
+
pnpm dlx jsr add @srpc/core
|
|
13
|
+
bunx jsr add @srpc/core
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- **Type-safe**: Full TypeScript support with automatic type inference
|
|
19
|
+
- **Zero dependencies**: Lightweight core with no external dependencies
|
|
20
|
+
- **Runtime agnostic**: Works with Node.js, Deno, Bun, Cloudflare Workers, and more
|
|
21
|
+
- **Multiple adapters**: Support for both Node.js HTTP and standard Fetch API
|
|
22
|
+
- **Nested routers**: Organize procedures into nested namespaces
|
|
23
|
+
- **Custom serialization**: Pluggable serialization layer
|
|
24
|
+
- **Error handling**: Built-in error types with HTTP status code mapping
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
### Server Setup
|
|
29
|
+
|
|
30
|
+
#### Using Fetch API (Recommended for Edge Runtimes)
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { initSRPC, srpcFetchApi } from "@srpc/core/server";
|
|
34
|
+
|
|
35
|
+
// Initialize SRPC
|
|
36
|
+
const s = initSRPC();
|
|
37
|
+
|
|
38
|
+
// Define your router
|
|
39
|
+
const appRouter = s.router({
|
|
40
|
+
sayHello: async (_, name: string) => {
|
|
41
|
+
return `Hello ${name}!`;
|
|
42
|
+
},
|
|
43
|
+
getUser: async (_, id: number) => {
|
|
44
|
+
return { id, name: "John Doe", email: "john@example.com" };
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Export the router type for client usage
|
|
49
|
+
export type AppRouter = typeof appRouter;
|
|
50
|
+
|
|
51
|
+
// Create Fetch API handler
|
|
52
|
+
const { fetch: handleRequest } = srpcFetchApi({
|
|
53
|
+
router: appRouter,
|
|
54
|
+
endpoint: "/api",
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Use with any framework supporting Fetch API
|
|
58
|
+
export default {
|
|
59
|
+
fetch: handleRequest,
|
|
60
|
+
};
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
#### Using Node.js HTTP Server
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { initSRPC } from "@srpc/core/server";
|
|
67
|
+
import { createSrpcServer } from "@srpc/core/server";
|
|
68
|
+
|
|
69
|
+
const s = initSRPC();
|
|
70
|
+
|
|
71
|
+
const appRouter = s.router({
|
|
72
|
+
sayHello: async (_, name: string) => {
|
|
73
|
+
return `Hello ${name}!`;
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
export type AppRouter = typeof appRouter;
|
|
78
|
+
|
|
79
|
+
// Create Node.js server
|
|
80
|
+
const server = createSrpcServer({
|
|
81
|
+
router: appRouter,
|
|
82
|
+
endpoint: "/api",
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
server.listen(3000, () => {
|
|
86
|
+
console.log("SRPC server listening on port 3000");
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Client Setup
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
import { createSRPCClient } from "@srpc/core/client";
|
|
94
|
+
import type { AppRouter } from "./server";
|
|
95
|
+
|
|
96
|
+
// Create type-safe client
|
|
97
|
+
const client = createSRPCClient<AppRouter>({
|
|
98
|
+
endpoint: "http://localhost:3000/api",
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Call procedures with full type safety
|
|
102
|
+
const greeting = await client.sayHello("World"); // Type: string
|
|
103
|
+
const user = await client.getUser(1); // Type: { id: number, name: string, email: string }
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Advanced Usage
|
|
107
|
+
|
|
108
|
+
### Nested Routers
|
|
109
|
+
|
|
110
|
+
Organize your procedures into logical groups:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
import { initSRPC } from "@srpc/core/server";
|
|
114
|
+
|
|
115
|
+
const s = initSRPC();
|
|
116
|
+
|
|
117
|
+
// Admin procedures
|
|
118
|
+
const adminRouter = s.router({
|
|
119
|
+
createUser: async (_, data: { name: string; email: string }) => {
|
|
120
|
+
// Create user logic
|
|
121
|
+
return { id: 1, ...data };
|
|
122
|
+
},
|
|
123
|
+
deleteUser: async (_, id: number) => {
|
|
124
|
+
// Delete user logic
|
|
125
|
+
return { success: true };
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Public user procedures
|
|
130
|
+
const usersRouter = s.router({
|
|
131
|
+
getUser: async (_, id: number) => {
|
|
132
|
+
return { id, name: "John Doe" };
|
|
133
|
+
},
|
|
134
|
+
admin: adminRouter, // Nested router
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// Main app router
|
|
138
|
+
const appRouter = s.router({
|
|
139
|
+
users: usersRouter,
|
|
140
|
+
sayHello: async (_, name: string) => `Hello ${name}!`,
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Client usage:
|
|
144
|
+
// client.users.getUser(1)
|
|
145
|
+
// client.users.admin.createUser({ name: "Jane", email: "jane@example.com" })
|
|
146
|
+
// client.sayHello("World")
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Context
|
|
150
|
+
|
|
151
|
+
Add context (authentication, database connections, etc.) to your procedures:
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
import { initSRPC, srpcFetchApi } from "@srpc/core/server";
|
|
155
|
+
|
|
156
|
+
// Define your context type
|
|
157
|
+
type Context = {
|
|
158
|
+
user?: { id: number; name: string };
|
|
159
|
+
db: Database;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
// Initialize with context type
|
|
163
|
+
const s = initSRPC().context<Context>();
|
|
164
|
+
|
|
165
|
+
// Use context in procedures
|
|
166
|
+
const appRouter = s.router({
|
|
167
|
+
getCurrentUser: async (ctx) => {
|
|
168
|
+
if (!ctx.user) throw new Error("Not authenticated");
|
|
169
|
+
return ctx.user;
|
|
170
|
+
},
|
|
171
|
+
getPost: async (ctx, id: number) => {
|
|
172
|
+
return await ctx.db.posts.findById(id);
|
|
173
|
+
},
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Provide context creation function
|
|
177
|
+
const { fetch: handleRequest } = srpcFetchApi({
|
|
178
|
+
router: appRouter,
|
|
179
|
+
endpoint: "/api",
|
|
180
|
+
createContext: async (req) => {
|
|
181
|
+
const user = await authenticateRequest(req);
|
|
182
|
+
return { user, db: getDatabase() };
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Error Handling
|
|
188
|
+
|
|
189
|
+
Use `SRPCError` for proper HTTP status code mapping:
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
import { initSRPC } from "@srpc/core/server";
|
|
193
|
+
import { SRPCError } from "@srpc.org/core";
|
|
194
|
+
|
|
195
|
+
const s = initSRPC();
|
|
196
|
+
|
|
197
|
+
const appRouter = s.router({
|
|
198
|
+
getUser: async (_, id: number) => {
|
|
199
|
+
const user = await db.users.findById(id);
|
|
200
|
+
if (!user) {
|
|
201
|
+
throw new SRPCError("User not found", "NOT_FOUND"); // Returns HTTP 404
|
|
202
|
+
}
|
|
203
|
+
return user;
|
|
204
|
+
},
|
|
205
|
+
deleteUser: async (_, id: number) => {
|
|
206
|
+
if (!hasPermission()) {
|
|
207
|
+
throw new SRPCError("Unauthorized", "UNAUTHORIZED"); // Returns HTTP 401
|
|
208
|
+
}
|
|
209
|
+
await db.users.delete(id);
|
|
210
|
+
return { success: true };
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// Client-side error handling
|
|
215
|
+
try {
|
|
216
|
+
await client.getUser(999);
|
|
217
|
+
} catch (error) {
|
|
218
|
+
if (error instanceof SRPCError) {
|
|
219
|
+
console.log(error.code); // "NOT_FOUND"
|
|
220
|
+
console.log(error.message); // "User not found"
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Custom Serialization
|
|
226
|
+
|
|
227
|
+
Use custom serializers for complex data types:
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
import { createSRPCClient } from "@srpc/core/client";
|
|
231
|
+
import type { Serializer } from "@srpc/core/shared";
|
|
232
|
+
|
|
233
|
+
// Example: Using superjson for Date, Map, Set support
|
|
234
|
+
import superjson from "superjson";
|
|
235
|
+
|
|
236
|
+
const customSerializer: Serializer = {
|
|
237
|
+
serialize: (value) => superjson.stringify(value),
|
|
238
|
+
deserialize: (value) => superjson.parse(value),
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const client = createSRPCClient<AppRouter>({
|
|
242
|
+
endpoint: "http://localhost:3000/api",
|
|
243
|
+
transformer: customSerializer,
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// Server-side
|
|
247
|
+
const { fetch: handleRequest } = srpcFetchApi({
|
|
248
|
+
router: appRouter,
|
|
249
|
+
endpoint: "/api",
|
|
250
|
+
transformer: customSerializer,
|
|
251
|
+
});
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### Type Inference Utilities
|
|
255
|
+
|
|
256
|
+
Extract input and output types from your router:
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
import type { InferRouterInputs, InferRouterOutputs } from "@srpc.org/core";
|
|
260
|
+
import type { AppRouter } from "./server";
|
|
261
|
+
|
|
262
|
+
// Get all input types
|
|
263
|
+
type RouterInputs = InferRouterInputs<AppRouter>;
|
|
264
|
+
type GetUserInput = RouterInputs["getUser"]; // [id: number]
|
|
265
|
+
|
|
266
|
+
// Get all output types
|
|
267
|
+
type RouterOutputs = InferRouterOutputs<AppRouter>;
|
|
268
|
+
type GetUserOutput = RouterOutputs["getUser"]; // { id: number, name: string, email: string }
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Server-Side Calling
|
|
272
|
+
|
|
273
|
+
Call procedures directly on the server without HTTP:
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
import { createSRPCCaller } from "@srpc/core/server";
|
|
277
|
+
import { appRouter } from "./router";
|
|
278
|
+
|
|
279
|
+
const caller = createSRPCCaller({
|
|
280
|
+
router: appRouter,
|
|
281
|
+
createContext: async () => ({ user: null, db: getDatabase() }),
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// Call procedures directly
|
|
285
|
+
const result = await caller.sayHello("World");
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## API Reference
|
|
289
|
+
|
|
290
|
+
### Server
|
|
291
|
+
|
|
292
|
+
- `initSRPC()` - Initialize SRPC builder
|
|
293
|
+
- `srpcFetchApi(options)` - Create Fetch API handler
|
|
294
|
+
- `createSrpcServer(options)` - Create Node.js HTTP server
|
|
295
|
+
- `createSRPCCaller(options)` - Create server-side caller
|
|
296
|
+
|
|
297
|
+
### Client
|
|
298
|
+
|
|
299
|
+
- `createSRPCClient<TRouter>(options)` - Create type-safe client
|
|
300
|
+
|
|
301
|
+
### Shared
|
|
302
|
+
|
|
303
|
+
- `SRPCError` - Error class with HTTP status mapping
|
|
304
|
+
- `ErrorCodes` - Available error codes
|
|
305
|
+
- `Serializer` - Serialization interface
|
|
306
|
+
- `InferRouterInputs<TRouter>` - Extract input types
|
|
307
|
+
- `InferRouterOutputs<TRouter>` - Extract output types
|
|
308
|
+
|
|
309
|
+
## Error Codes
|
|
310
|
+
|
|
311
|
+
- `BAD_REQUEST` → HTTP 400
|
|
312
|
+
- `UNAUTHORIZED` → HTTP 401
|
|
313
|
+
- `FORBIDDEN` → HTTP 403
|
|
314
|
+
- `NOT_FOUND` → HTTP 404
|
|
315
|
+
- `UNSUPPORTED_MEDIA_TYPE` → HTTP 415
|
|
316
|
+
- `INTERNAL_SERVER_ERROR` → HTTP 500
|
|
317
|
+
- `NOT_IMPLEMENTED` → HTTP 501
|
|
318
|
+
- `GENERIC_ERROR` → HTTP 500
|
|
319
|
+
|
|
320
|
+
## License
|
|
321
|
+
|
|
322
|
+
MIT
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { AnySRPC } from './server.js';
|
|
2
|
+
import { Serializer, DecoratedProcedureRecord } from './shared.js';
|
|
3
|
+
export * from './shared.js';
|
|
4
|
+
|
|
5
|
+
type ProcedureType<TContext> = (
|
|
6
|
+
ctx: TContext,
|
|
7
|
+
...args: any[]
|
|
8
|
+
) => Promise<any> | any;
|
|
9
|
+
|
|
10
|
+
type Routes<TContext> = {
|
|
11
|
+
[key: string]: ProcedureType<TContext> | SRPC<TContext>;
|
|
12
|
+
};
|
|
13
|
+
declare class SRPC<TContext, TRoutes extends Routes<TContext> = {}> {
|
|
14
|
+
__context: TContext;
|
|
15
|
+
ipc: TRoutes;
|
|
16
|
+
constructor(routes?: TRoutes);
|
|
17
|
+
context<T>(): SRPC<T>;
|
|
18
|
+
router<T extends Routes<TContext>>(routes: T): SRPC<TContext, T>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @module
|
|
23
|
+
*
|
|
24
|
+
* Client-side SRPC module for creating type-safe RPC clients.
|
|
25
|
+
*
|
|
26
|
+
* This module provides the `createSRPCClient` function which creates a proxy-based
|
|
27
|
+
* client that converts method calls into HTTP requests to your SRPC server.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* import { createSRPCClient } from "@srpc/core/client";
|
|
32
|
+
* import type { AppRouter } from "./server";
|
|
33
|
+
*
|
|
34
|
+
* const client = createSRPCClient<AppRouter>({
|
|
35
|
+
* endpoint: "http://localhost:3000/api",
|
|
36
|
+
* headers: async () => ({
|
|
37
|
+
* "Authorization": `Bearer ${await getToken()}`
|
|
38
|
+
* })
|
|
39
|
+
* });
|
|
40
|
+
*
|
|
41
|
+
* // Type-safe procedure calls
|
|
42
|
+
* const user = await client.users.getUser(1);
|
|
43
|
+
* const result = await client.users.admin.createUser({ name: "Jane" });
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Configuration options for creating an SRPC client.
|
|
49
|
+
*
|
|
50
|
+
* @property endpoint - Base URL for the SRPC server (e.g., "http://localhost:3000/api")
|
|
51
|
+
* @property headers - Optional function to provide custom headers for each request
|
|
52
|
+
* @property transformer - Optional custom serializer for request/response data (default: JSON)
|
|
53
|
+
* @property fetch - Optional custom fetch implementation (default: globalThis.fetch)
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```typescript
|
|
57
|
+
* const options: SRPCClientOptions = {
|
|
58
|
+
* endpoint: "http://localhost:3000/api",
|
|
59
|
+
* headers: async ({ path }) => {
|
|
60
|
+
* const token = await getAuthToken();
|
|
61
|
+
* return {
|
|
62
|
+
* "Authorization": `Bearer ${token}`,
|
|
63
|
+
* "X-Request-Path": path.join(".")
|
|
64
|
+
* };
|
|
65
|
+
* },
|
|
66
|
+
* transformer: customSerializer,
|
|
67
|
+
* fetch: customFetch
|
|
68
|
+
* };
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
type SRPCClientOptions = {
|
|
72
|
+
endpoint: string;
|
|
73
|
+
headers?: (op: {
|
|
74
|
+
path: readonly string[];
|
|
75
|
+
}) => HeadersInit | Promise<HeadersInit>;
|
|
76
|
+
transformer?: Serializer;
|
|
77
|
+
fetch?: typeof fetch;
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* Creates a type-safe SRPC client that converts method calls into HTTP requests.
|
|
81
|
+
*
|
|
82
|
+
* The client uses JavaScript Proxies to intercept property access and method calls,
|
|
83
|
+
* automatically converting them into HTTP POST requests to the server. All procedure
|
|
84
|
+
* calls are fully type-safe based on the router type provided.
|
|
85
|
+
*
|
|
86
|
+
* @template TRouter - The SRPC router type from your server
|
|
87
|
+
* @template TRoutes - Internal routes type (inferred automatically)
|
|
88
|
+
*
|
|
89
|
+
* @param options - Client configuration options
|
|
90
|
+
* @returns A type-safe client proxy matching your server's router structure
|
|
91
|
+
*
|
|
92
|
+
* @example Basic usage
|
|
93
|
+
* ```typescript
|
|
94
|
+
* import { createSRPCClient } from "@srpc/core/client";
|
|
95
|
+
* import type { AppRouter } from "./server";
|
|
96
|
+
*
|
|
97
|
+
* const client = createSRPCClient<AppRouter>({
|
|
98
|
+
* endpoint: "http://localhost:3000/api"
|
|
99
|
+
* });
|
|
100
|
+
*
|
|
101
|
+
* // All calls are type-checked
|
|
102
|
+
* const greeting = await client.sayHello("World"); // string
|
|
103
|
+
* const user = await client.users.getUser(1); // User type
|
|
104
|
+
* ```
|
|
105
|
+
*
|
|
106
|
+
* @example With authentication headers
|
|
107
|
+
* ```typescript
|
|
108
|
+
* const client = createSRPCClient<AppRouter>({
|
|
109
|
+
* endpoint: "http://localhost:3000/api",
|
|
110
|
+
* headers: async () => ({
|
|
111
|
+
* "Authorization": `Bearer ${await getAuthToken()}`
|
|
112
|
+
* })
|
|
113
|
+
* });
|
|
114
|
+
* ```
|
|
115
|
+
*
|
|
116
|
+
* @example With custom serialization
|
|
117
|
+
* ```typescript
|
|
118
|
+
* import superjson from "superjson";
|
|
119
|
+
*
|
|
120
|
+
* const client = createSRPCClient<AppRouter>({
|
|
121
|
+
* endpoint: "http://localhost:3000/api",
|
|
122
|
+
* transformer: {
|
|
123
|
+
* serialize: (value) => superjson.stringify(value),
|
|
124
|
+
* deserialize: (value) => superjson.parse(value)
|
|
125
|
+
* }
|
|
126
|
+
* });
|
|
127
|
+
*
|
|
128
|
+
* // Now Date, Map, Set, etc. are properly serialized
|
|
129
|
+
* await client.createEvent({ date: new Date() });
|
|
130
|
+
* ```
|
|
131
|
+
*
|
|
132
|
+
* @example Error handling
|
|
133
|
+
* ```typescript
|
|
134
|
+
* import { SRPCError } from "@srpc.org/core";
|
|
135
|
+
*
|
|
136
|
+
* try {
|
|
137
|
+
* await client.users.getUser(999);
|
|
138
|
+
* } catch (error) {
|
|
139
|
+
* if (error instanceof SRPCError) {
|
|
140
|
+
* console.log(error.code); // "NOT_FOUND"
|
|
141
|
+
* console.log(error.message); // "User not found"
|
|
142
|
+
* }
|
|
143
|
+
* }
|
|
144
|
+
* ```
|
|
145
|
+
*/
|
|
146
|
+
declare const createSRPCClient: <TRouter extends AnySRPC, TRoutes extends Routes<any> = TRouter["ipc"]>({ endpoint, headers: getHeaders, transformer, fetch, }: SRPCClientOptions) => DecoratedProcedureRecord<TRoutes>;
|
|
147
|
+
|
|
148
|
+
export { createSRPCClient };
|
|
149
|
+
export type { SRPCClientOptions };
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { defaultSerializer, SRPCError } from './shared.js';
|
|
2
|
+
export * from './shared.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Used from trpc.io
|
|
6
|
+
*/ const noop = ()=>{
|
|
7
|
+
// noop
|
|
8
|
+
};
|
|
9
|
+
const freezeIfAvailable = (obj)=>{
|
|
10
|
+
if (Object.freeze) {
|
|
11
|
+
Object.freeze(obj);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
function createInnerProxy(callback, path, memo) {
|
|
15
|
+
var _memo, _cacheKey;
|
|
16
|
+
const cacheKey = path.join(".");
|
|
17
|
+
(_memo = memo)[_cacheKey = cacheKey] ?? (_memo[_cacheKey] = new Proxy(noop, {
|
|
18
|
+
get (_obj, key) {
|
|
19
|
+
if (typeof key !== "string" || key === "then") {
|
|
20
|
+
// special case for if the proxy is accidentally treated
|
|
21
|
+
// like a PromiseLike (like in `Promise.resolve(proxy)`)
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
return createInnerProxy(callback, [
|
|
25
|
+
...path,
|
|
26
|
+
key
|
|
27
|
+
], memo);
|
|
28
|
+
},
|
|
29
|
+
apply (_1, _2, args) {
|
|
30
|
+
const lastOfPath = path[path.length - 1];
|
|
31
|
+
let opts = {
|
|
32
|
+
args,
|
|
33
|
+
path
|
|
34
|
+
};
|
|
35
|
+
// special handling for e.g. `trpc.hello.call(this, 'there')` and `trpc.hello.apply(this, ['there'])
|
|
36
|
+
if (lastOfPath === "call") {
|
|
37
|
+
opts = {
|
|
38
|
+
args: args.length >= 2 ? [
|
|
39
|
+
args[1]
|
|
40
|
+
] : [],
|
|
41
|
+
path: path.slice(0, -1)
|
|
42
|
+
};
|
|
43
|
+
} else if (lastOfPath === "apply") {
|
|
44
|
+
opts = {
|
|
45
|
+
args: args.length >= 2 ? args[1] : [],
|
|
46
|
+
path: path.slice(0, -1)
|
|
47
|
+
};
|
|
48
|
+
} else if (lastOfPath === "toString") {
|
|
49
|
+
return path.slice(0, -1).join(".");
|
|
50
|
+
} else if (lastOfPath === "toJSON") {
|
|
51
|
+
return {
|
|
52
|
+
path: path.slice(0, -1),
|
|
53
|
+
pathString: path.slice(0, -1).join("."),
|
|
54
|
+
__type: "SRPC"
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
freezeIfAvailable(opts.args);
|
|
58
|
+
freezeIfAvailable(opts.path);
|
|
59
|
+
return callback(opts);
|
|
60
|
+
}
|
|
61
|
+
}));
|
|
62
|
+
return memo[cacheKey];
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Creates a proxy that calls the callback with the path and arguments
|
|
66
|
+
*
|
|
67
|
+
* @internal
|
|
68
|
+
*/ const createRecursiveProxy = (callback)=>createInnerProxy(callback, [], Object.create(null));
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Creates a type-safe SRPC client that converts method calls into HTTP requests.
|
|
72
|
+
*
|
|
73
|
+
* The client uses JavaScript Proxies to intercept property access and method calls,
|
|
74
|
+
* automatically converting them into HTTP POST requests to the server. All procedure
|
|
75
|
+
* calls are fully type-safe based on the router type provided.
|
|
76
|
+
*
|
|
77
|
+
* @template TRouter - The SRPC router type from your server
|
|
78
|
+
* @template TRoutes - Internal routes type (inferred automatically)
|
|
79
|
+
*
|
|
80
|
+
* @param options - Client configuration options
|
|
81
|
+
* @returns A type-safe client proxy matching your server's router structure
|
|
82
|
+
*
|
|
83
|
+
* @example Basic usage
|
|
84
|
+
* ```typescript
|
|
85
|
+
* import { createSRPCClient } from "@srpc/core/client";
|
|
86
|
+
* import type { AppRouter } from "./server";
|
|
87
|
+
*
|
|
88
|
+
* const client = createSRPCClient<AppRouter>({
|
|
89
|
+
* endpoint: "http://localhost:3000/api"
|
|
90
|
+
* });
|
|
91
|
+
*
|
|
92
|
+
* // All calls are type-checked
|
|
93
|
+
* const greeting = await client.sayHello("World"); // string
|
|
94
|
+
* const user = await client.users.getUser(1); // User type
|
|
95
|
+
* ```
|
|
96
|
+
*
|
|
97
|
+
* @example With authentication headers
|
|
98
|
+
* ```typescript
|
|
99
|
+
* const client = createSRPCClient<AppRouter>({
|
|
100
|
+
* endpoint: "http://localhost:3000/api",
|
|
101
|
+
* headers: async () => ({
|
|
102
|
+
* "Authorization": `Bearer ${await getAuthToken()}`
|
|
103
|
+
* })
|
|
104
|
+
* });
|
|
105
|
+
* ```
|
|
106
|
+
*
|
|
107
|
+
* @example With custom serialization
|
|
108
|
+
* ```typescript
|
|
109
|
+
* import superjson from "superjson";
|
|
110
|
+
*
|
|
111
|
+
* const client = createSRPCClient<AppRouter>({
|
|
112
|
+
* endpoint: "http://localhost:3000/api",
|
|
113
|
+
* transformer: {
|
|
114
|
+
* serialize: (value) => superjson.stringify(value),
|
|
115
|
+
* deserialize: (value) => superjson.parse(value)
|
|
116
|
+
* }
|
|
117
|
+
* });
|
|
118
|
+
*
|
|
119
|
+
* // Now Date, Map, Set, etc. are properly serialized
|
|
120
|
+
* await client.createEvent({ date: new Date() });
|
|
121
|
+
* ```
|
|
122
|
+
*
|
|
123
|
+
* @example Error handling
|
|
124
|
+
* ```typescript
|
|
125
|
+
* import { SRPCError } from "@srpc.org/core";
|
|
126
|
+
*
|
|
127
|
+
* try {
|
|
128
|
+
* await client.users.getUser(999);
|
|
129
|
+
* } catch (error) {
|
|
130
|
+
* if (error instanceof SRPCError) {
|
|
131
|
+
* console.log(error.code); // "NOT_FOUND"
|
|
132
|
+
* console.log(error.message); // "User not found"
|
|
133
|
+
* }
|
|
134
|
+
* }
|
|
135
|
+
* ```
|
|
136
|
+
*/ const createSRPCClient = ({ endpoint, headers: getHeaders, transformer = defaultSerializer, fetch = globalThis.fetch })=>{
|
|
137
|
+
return createRecursiveProxy(async ({ path, args })=>{
|
|
138
|
+
const headers = await getHeaders?.({
|
|
139
|
+
path: path
|
|
140
|
+
});
|
|
141
|
+
const response = await fetch(`${endpoint}/${path.join(".")}`, {
|
|
142
|
+
method: "POST",
|
|
143
|
+
body: transformer.serialize(args),
|
|
144
|
+
headers
|
|
145
|
+
});
|
|
146
|
+
const responseText = await response.text();
|
|
147
|
+
const data = transformer.deserialize(responseText);
|
|
148
|
+
if (!response.ok) {
|
|
149
|
+
if ("__BRAND__" in data && data.__BRAND__ === "SRPCError") {
|
|
150
|
+
throw new SRPCError(data.message, data.code);
|
|
151
|
+
}
|
|
152
|
+
throw new SRPCError(response.statusText || "Unknown error", "GENERIC_ERROR");
|
|
153
|
+
}
|
|
154
|
+
return data;
|
|
155
|
+
});
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
export { createSRPCClient };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { ErrorCodes, InferRouterInputs, InferRouterOutputs, SRPCError } from './shared.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { SRPCError } from './shared.js';
|