@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/dist/shared.d.ts
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
import { SRPC as SRPC$1, AnySRPC } from './server.js';
|
|
2
|
+
|
|
3
|
+
type ProcedureType<TContext> = (
|
|
4
|
+
ctx: TContext,
|
|
5
|
+
...args: any[]
|
|
6
|
+
) => Promise<any> | any;
|
|
7
|
+
|
|
8
|
+
type AnyProcedure = ProcedureType<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
|
+
* Used from trpc.io
|
|
23
|
+
*/
|
|
24
|
+
interface ProxyCallbackOptions {
|
|
25
|
+
path: readonly string[];
|
|
26
|
+
args: readonly unknown[];
|
|
27
|
+
}
|
|
28
|
+
type ProxyCallback = (opts: ProxyCallbackOptions) => unknown;
|
|
29
|
+
/**
|
|
30
|
+
* Creates a proxy that calls the callback with the path and arguments
|
|
31
|
+
*
|
|
32
|
+
* @internal
|
|
33
|
+
*/
|
|
34
|
+
declare const createRecursiveProxy: <TFaux = unknown>(callback: ProxyCallback) => TFaux;
|
|
35
|
+
/**
|
|
36
|
+
* Used in place of `new Proxy` where each handler will map 1 level deep to another value.
|
|
37
|
+
*
|
|
38
|
+
* @internal
|
|
39
|
+
*/
|
|
40
|
+
declare const createFlatProxy: <TFaux>(callback: (path: string & keyof TFaux) => any) => TFaux;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* @module
|
|
44
|
+
*
|
|
45
|
+
* Shared SRPC utilities, types, and error handling.
|
|
46
|
+
*
|
|
47
|
+
* This module contains types and utilities used by both client and server sides:
|
|
48
|
+
* - Error handling with `SRPCError`
|
|
49
|
+
* - Serialization interfaces
|
|
50
|
+
* - Type inference utilities
|
|
51
|
+
* - Proxy utilities for dynamic API creation
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```typescript
|
|
55
|
+
* import { SRPCError, type InferRouterInputs, type InferRouterOutputs } from "@srpc/core/shared";
|
|
56
|
+
*
|
|
57
|
+
* // Throw typed errors
|
|
58
|
+
* throw new SRPCError("Not found", "NOT_FOUND");
|
|
59
|
+
*
|
|
60
|
+
* // Extract types from router
|
|
61
|
+
* type Inputs = InferRouterInputs<AppRouter>;
|
|
62
|
+
* type Outputs = InferRouterOutputs<AppRouter>;
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Interface for custom serialization strategies.
|
|
68
|
+
*
|
|
69
|
+
* Implement this interface to support serialization formats beyond JSON,
|
|
70
|
+
* such as MessagePack, Protocol Buffers, or superjson for complex types.
|
|
71
|
+
*
|
|
72
|
+
* @example Using superjson for Date, Map, Set support
|
|
73
|
+
* ```typescript
|
|
74
|
+
* import superjson from "superjson";
|
|
75
|
+
* import type { Serializer } from "@srpc/core/shared";
|
|
76
|
+
*
|
|
77
|
+
* const customSerializer: Serializer = {
|
|
78
|
+
* serialize: (value) => superjson.stringify(value),
|
|
79
|
+
* deserialize: (value) => superjson.parse(value)
|
|
80
|
+
* };
|
|
81
|
+
*
|
|
82
|
+
* // Use with client
|
|
83
|
+
* const client = createSRPCClient<AppRouter>({
|
|
84
|
+
* endpoint: "/api",
|
|
85
|
+
* transformer: customSerializer
|
|
86
|
+
* });
|
|
87
|
+
*
|
|
88
|
+
* // Use with server
|
|
89
|
+
* const { fetch } = srpcFetchApi({
|
|
90
|
+
* router: appRouter,
|
|
91
|
+
* endpoint: "/api",
|
|
92
|
+
* transformer: customSerializer
|
|
93
|
+
* });
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
interface Serializer {
|
|
97
|
+
serialize: (value: any) => any;
|
|
98
|
+
deserialize: (value: any) => any;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Default JSON-based serializer used by SRPC.
|
|
102
|
+
*
|
|
103
|
+
* Uses `JSON.stringify` for serialization and `JSON.parse` for deserialization.
|
|
104
|
+
* Works with JSON-compatible types (strings, numbers, booleans, objects, arrays, null).
|
|
105
|
+
* Does not support: Date, Map, Set, undefined, functions, symbols.
|
|
106
|
+
*
|
|
107
|
+
* For complex types, provide a custom `Serializer` implementation.
|
|
108
|
+
*/
|
|
109
|
+
declare const defaultSerializer: Serializer;
|
|
110
|
+
/**
|
|
111
|
+
* Union type of all standard error codes in SRPC.
|
|
112
|
+
*
|
|
113
|
+
* Each error code maps to a specific HTTP status code:
|
|
114
|
+
* - `BAD_REQUEST` → 400
|
|
115
|
+
* - `UNAUTHORIZED` → 401
|
|
116
|
+
* - `FORBIDDEN` → 403
|
|
117
|
+
* - `NOT_FOUND` → 404
|
|
118
|
+
* - `UNSUPPORTED_MEDIA_TYPE` → 415
|
|
119
|
+
* - `INTERNAL_SERVER_ERROR` → 500
|
|
120
|
+
* - `NOT_IMPLEMENTED` → 501
|
|
121
|
+
* - `GENERIC_ERROR` → 500 (fallback for unexpected errors)
|
|
122
|
+
*/
|
|
123
|
+
type ErrorCodes = "NOT_FOUND" | "FORBIDDEN" | "UNAUTHORIZED" | "INTERNAL_SERVER_ERROR" | "BAD_REQUEST" | "NOT_IMPLEMENTED" | "UNSUPPORTED_MEDIA_TYPE" | "GENERIC_ERROR";
|
|
124
|
+
/**
|
|
125
|
+
* Mapping of error codes to HTTP status codes.
|
|
126
|
+
*
|
|
127
|
+
* Used internally by server adapters to convert `SRPCError` instances
|
|
128
|
+
* to appropriate HTTP responses.
|
|
129
|
+
*/
|
|
130
|
+
declare const StatusCodeMap: {
|
|
131
|
+
BAD_REQUEST: number;
|
|
132
|
+
NOT_FOUND: number;
|
|
133
|
+
FORBIDDEN: number;
|
|
134
|
+
UNAUTHORIZED: number;
|
|
135
|
+
INTERNAL_SERVER_ERROR: number;
|
|
136
|
+
NOT_IMPLEMENTED: number;
|
|
137
|
+
UNSUPPORTED_MEDIA_TYPE: number;
|
|
138
|
+
GENERIC_ERROR: number;
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* Standard error class for SRPC procedures.
|
|
142
|
+
*
|
|
143
|
+
* Thrown errors are automatically serialized and sent to the client with
|
|
144
|
+
* the appropriate HTTP status code. The client reconstructs the error
|
|
145
|
+
* with the same message and code.
|
|
146
|
+
*
|
|
147
|
+
* @example Throwing errors in procedures
|
|
148
|
+
* ```typescript
|
|
149
|
+
* import { SRPCError } from "@srpc.org/core";
|
|
150
|
+
*
|
|
151
|
+
* const appRouter = s.router({
|
|
152
|
+
* getUser: async (_, id: number) => {
|
|
153
|
+
* const user = await db.users.findById(id);
|
|
154
|
+
* if (!user) {
|
|
155
|
+
* throw new SRPCError("User not found", "NOT_FOUND");
|
|
156
|
+
* }
|
|
157
|
+
* return user;
|
|
158
|
+
* },
|
|
159
|
+
* deleteUser: async (ctx, id: number) => {
|
|
160
|
+
* if (!ctx.user?.isAdmin) {
|
|
161
|
+
* throw new SRPCError("Insufficient permissions", "FORBIDDEN");
|
|
162
|
+
* }
|
|
163
|
+
* await db.users.delete(id);
|
|
164
|
+
* return { success: true };
|
|
165
|
+
* }
|
|
166
|
+
* });
|
|
167
|
+
* ```
|
|
168
|
+
*
|
|
169
|
+
* @example Handling errors on the client
|
|
170
|
+
* ```typescript
|
|
171
|
+
* import { SRPCError } from "@srpc.org/core";
|
|
172
|
+
*
|
|
173
|
+
* try {
|
|
174
|
+
* const user = await client.getUser(999);
|
|
175
|
+
* } catch (error) {
|
|
176
|
+
* if (error instanceof SRPCError) {
|
|
177
|
+
* switch (error.code) {
|
|
178
|
+
* case "NOT_FOUND":
|
|
179
|
+
* console.log("User not found");
|
|
180
|
+
* break;
|
|
181
|
+
* case "FORBIDDEN":
|
|
182
|
+
* console.log("Access denied");
|
|
183
|
+
* break;
|
|
184
|
+
* case "UNAUTHORIZED":
|
|
185
|
+
* redirectToLogin();
|
|
186
|
+
* break;
|
|
187
|
+
* }
|
|
188
|
+
* }
|
|
189
|
+
* }
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
192
|
+
declare class SRPCError extends Error {
|
|
193
|
+
readonly code: ErrorCodes;
|
|
194
|
+
constructor(message: string, code: ErrorCodes);
|
|
195
|
+
__BRAND__: string;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Type utility to extract input parameter types from a single procedure.
|
|
199
|
+
*
|
|
200
|
+
* Returns a tuple of the procedure's parameters, excluding the context parameter.
|
|
201
|
+
*
|
|
202
|
+
* @template T - The procedure type to extract inputs from
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* ```typescript
|
|
206
|
+
* import type { InferProcedureInput } from "@srpc/core/shared";
|
|
207
|
+
*
|
|
208
|
+
* type GetUserProcedure = (ctx: Context, id: number) => Promise<User>;
|
|
209
|
+
* type GetUserInput = InferProcedureInput<GetUserProcedure>; // [id: number]
|
|
210
|
+
*
|
|
211
|
+
* type CreateUserProcedure = (ctx: Context, name: string, email: string) => Promise<User>;
|
|
212
|
+
* type CreateUserInput = InferProcedureInput<CreateUserProcedure>; // [name: string, email: string]
|
|
213
|
+
* ```
|
|
214
|
+
*/
|
|
215
|
+
type InferProcedureInput<T extends AnyProcedure> = T extends (_ctx: any, ...args: infer TArgs) => any ? TArgs : never;
|
|
216
|
+
/**
|
|
217
|
+
* Type utility to convert a server procedure to a client-side callable function.
|
|
218
|
+
*
|
|
219
|
+
* Removes the context parameter and preserves the return type.
|
|
220
|
+
*
|
|
221
|
+
* @template T - The procedure type to convert
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* ```typescript
|
|
225
|
+
* import type { ClientProcedure } from "@srpc/core/shared";
|
|
226
|
+
*
|
|
227
|
+
* // Server procedure
|
|
228
|
+
* type ServerProc = (ctx: Context, id: number) => Promise<User>;
|
|
229
|
+
*
|
|
230
|
+
* // Client procedure (context removed)
|
|
231
|
+
* type ClientProc = ClientProcedure<ServerProc>; // (id: number) => Promise<User>
|
|
232
|
+
* ```
|
|
233
|
+
*/
|
|
234
|
+
type ClientProcedure<T extends AnyProcedure> = (...args: InferProcedureInput<T>) => ReturnType<T>;
|
|
235
|
+
/**
|
|
236
|
+
* Type utility that transforms a router's procedures into client-callable functions.
|
|
237
|
+
*
|
|
238
|
+
* Recursively processes nested routers and converts each procedure to a client
|
|
239
|
+
* procedure (with context parameter removed).
|
|
240
|
+
*
|
|
241
|
+
* @template TRouter - The router routes type
|
|
242
|
+
*
|
|
243
|
+
* @example
|
|
244
|
+
* ```typescript
|
|
245
|
+
* import type { DecoratedProcedureRecord } from "@srpc/core/shared";
|
|
246
|
+
*
|
|
247
|
+
* // Server router
|
|
248
|
+
* const appRouter = s.router({
|
|
249
|
+
* getUser: async (ctx, id: number) => ({ id, name: "John" }),
|
|
250
|
+
* users: s.router({
|
|
251
|
+
* admin: s.router({
|
|
252
|
+
* createUser: async (ctx, data: UserData) => ({ id: 1, ...data })
|
|
253
|
+
* })
|
|
254
|
+
* })
|
|
255
|
+
* });
|
|
256
|
+
*
|
|
257
|
+
* type Routes = typeof appRouter["ipc"];
|
|
258
|
+
* type ClientAPI = DecoratedProcedureRecord<Routes>;
|
|
259
|
+
* // {
|
|
260
|
+
* // getUser: (id: number) => Promise<{ id: number, name: string }>,
|
|
261
|
+
* // users: {
|
|
262
|
+
* // admin: {
|
|
263
|
+
* // createUser: (data: UserData) => Promise<{ id: number } & UserData>
|
|
264
|
+
* // }
|
|
265
|
+
* // }
|
|
266
|
+
* // }
|
|
267
|
+
* ```
|
|
268
|
+
*/
|
|
269
|
+
type DecoratedProcedureRecord<TRouter extends Routes<any>> = {
|
|
270
|
+
[TKey in keyof TRouter]: TRouter[TKey] extends AnyProcedure ? ClientProcedure<TRouter[TKey]> : TRouter[TKey] extends SRPC$1<any> ? DecoratedProcedureRecord<TRouter[TKey]["ipc"]> : never;
|
|
271
|
+
};
|
|
272
|
+
/**
|
|
273
|
+
* Type utility to extract the client API type from an SRPC router.
|
|
274
|
+
*
|
|
275
|
+
* Convenience type that wraps `DecoratedProcedureRecord`.
|
|
276
|
+
*
|
|
277
|
+
* @template TRouter - The SRPC router type
|
|
278
|
+
*/
|
|
279
|
+
type InferRPCFromRouter<TRouter extends AnySRPC> = DecoratedProcedureRecord<TRouter["ipc"]>;
|
|
280
|
+
/**
|
|
281
|
+
* Type utility that extracts return types from all procedures in a router.
|
|
282
|
+
*
|
|
283
|
+
* Recursively processes nested routers and extracts awaited return types
|
|
284
|
+
* from each procedure.
|
|
285
|
+
*
|
|
286
|
+
* @template TRouter - The router routes type
|
|
287
|
+
*
|
|
288
|
+
* @example
|
|
289
|
+
* ```typescript
|
|
290
|
+
* import type { DecoratedProcedureOutputs } from "@srpc/core/shared";
|
|
291
|
+
*
|
|
292
|
+
* const appRouter = s.router({
|
|
293
|
+
* getUser: async (ctx, id: number) => ({ id, name: "John" }),
|
|
294
|
+
* users: s.router({
|
|
295
|
+
* createUser: async (ctx, data: UserData) => ({ id: 1, ...data })
|
|
296
|
+
* })
|
|
297
|
+
* });
|
|
298
|
+
*
|
|
299
|
+
* type Routes = typeof appRouter["ipc"];
|
|
300
|
+
* type Outputs = DecoratedProcedureOutputs<Routes>;
|
|
301
|
+
* // {
|
|
302
|
+
* // getUser: { id: number, name: string },
|
|
303
|
+
* // users: {
|
|
304
|
+
* // createUser: { id: number } & UserData
|
|
305
|
+
* // }
|
|
306
|
+
* // }
|
|
307
|
+
* ```
|
|
308
|
+
*/
|
|
309
|
+
type DecoratedProcedureOutputs<TRouter extends Routes<any>> = {
|
|
310
|
+
[TKey in keyof TRouter]: TRouter[TKey] extends AnyProcedure ? Awaited<ReturnType<TRouter[TKey]>> : TRouter[TKey] extends SRPC$1<any> ? DecoratedProcedureOutputs<TRouter[TKey]["ipc"]> : never;
|
|
311
|
+
};
|
|
312
|
+
/**
|
|
313
|
+
* Type utility to extract output types from all procedures in an SRPC router.
|
|
314
|
+
*
|
|
315
|
+
* Returns a mapped type where each key is a procedure name and the value
|
|
316
|
+
* is the awaited return type of that procedure.
|
|
317
|
+
*
|
|
318
|
+
* @template TRouter - The SRPC router type
|
|
319
|
+
*
|
|
320
|
+
* @example
|
|
321
|
+
* ```typescript
|
|
322
|
+
* import type { InferRouterOutputs } from "@srpc.org/core";
|
|
323
|
+
* import type { AppRouter } from "./server";
|
|
324
|
+
*
|
|
325
|
+
* type RouterOutputs = InferRouterOutputs<AppRouter>;
|
|
326
|
+
*
|
|
327
|
+
* // Use output types in your code
|
|
328
|
+
* type User = RouterOutputs["getUser"];
|
|
329
|
+
* type Post = RouterOutputs["posts"]["getPost"];
|
|
330
|
+
* ```
|
|
331
|
+
*/
|
|
332
|
+
type InferRouterOutputs<TRouter extends AnySRPC> = DecoratedProcedureOutputs<TRouter["ipc"]>;
|
|
333
|
+
/**
|
|
334
|
+
* Type utility that extracts input parameter tuples from all procedures in a router.
|
|
335
|
+
*
|
|
336
|
+
* Recursively processes nested routers and extracts input parameters
|
|
337
|
+
* (excluding context) from each procedure.
|
|
338
|
+
*
|
|
339
|
+
* @template TRouter - The router routes type
|
|
340
|
+
*
|
|
341
|
+
* @example
|
|
342
|
+
* ```typescript
|
|
343
|
+
* import type { DecoratedProcedureInputs } from "@srpc/core/shared";
|
|
344
|
+
*
|
|
345
|
+
* const appRouter = s.router({
|
|
346
|
+
* getUser: async (ctx, id: number) => ({ id, name: "John" }),
|
|
347
|
+
* users: s.router({
|
|
348
|
+
* createUser: async (ctx, name: string, email: string) => ({ id: 1, name, email })
|
|
349
|
+
* })
|
|
350
|
+
* });
|
|
351
|
+
*
|
|
352
|
+
* type Routes = typeof appRouter["ipc"];
|
|
353
|
+
* type Inputs = DecoratedProcedureInputs<Routes>;
|
|
354
|
+
* // {
|
|
355
|
+
* // getUser: [id: number],
|
|
356
|
+
* // users: {
|
|
357
|
+
* // createUser: [name: string, email: string]
|
|
358
|
+
* // }
|
|
359
|
+
* // }
|
|
360
|
+
* ```
|
|
361
|
+
*/
|
|
362
|
+
type DecoratedProcedureInputs<TRouter extends Routes<any>> = {
|
|
363
|
+
[TKey in keyof TRouter]: TRouter[TKey] extends AnyProcedure ? InferProcedureInput<TRouter[TKey]> : TRouter[TKey] extends SRPC$1<any> ? DecoratedProcedureInputs<TRouter[TKey]["ipc"]> : never;
|
|
364
|
+
};
|
|
365
|
+
/**
|
|
366
|
+
* Type utility to extract input parameter types from all procedures in an SRPC router.
|
|
367
|
+
*
|
|
368
|
+
* Returns a mapped type where each key is a procedure name and the value
|
|
369
|
+
* is a tuple of that procedure's input parameters (excluding context).
|
|
370
|
+
*
|
|
371
|
+
* @template TRouter - The SRPC router type
|
|
372
|
+
*
|
|
373
|
+
* @example
|
|
374
|
+
* ```typescript
|
|
375
|
+
* import type { InferRouterInputs } from "@srpc.org/core";
|
|
376
|
+
* import type { AppRouter } from "./server";
|
|
377
|
+
*
|
|
378
|
+
* type RouterInputs = InferRouterInputs<AppRouter>;
|
|
379
|
+
*
|
|
380
|
+
* // Use input types in your code
|
|
381
|
+
* type GetUserArgs = RouterInputs["getUser"]; // [id: number]
|
|
382
|
+
* type CreatePostArgs = RouterInputs["posts"]["createPost"]; // [title: string, content: string]
|
|
383
|
+
*
|
|
384
|
+
* // Useful for creating type-safe helpers
|
|
385
|
+
* function callGetUser(...args: RouterInputs["getUser"]) {
|
|
386
|
+
* return client.getUser(...args);
|
|
387
|
+
* }
|
|
388
|
+
* ```
|
|
389
|
+
*/
|
|
390
|
+
type InferRouterInputs<TRouter extends AnySRPC> = DecoratedProcedureInputs<TRouter["ipc"]>;
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Type representing any routes object with any context.
|
|
394
|
+
*
|
|
395
|
+
* Used as a constraint for generic types that accept any routes.
|
|
396
|
+
*/
|
|
397
|
+
type AnyRoutes = Routes<any>;
|
|
398
|
+
|
|
399
|
+
export { SRPCError, StatusCodeMap, createFlatProxy, createRecursiveProxy, defaultSerializer };
|
|
400
|
+
export type { AnyProcedure, AnyRoutes, ClientProcedure, DecoratedProcedureInputs, DecoratedProcedureOutputs, DecoratedProcedureRecord, ErrorCodes, InferProcedureInput, InferRPCFromRouter, InferRouterInputs, InferRouterOutputs, Routes, Serializer };
|
package/dist/shared.js
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Used from trpc.io
|
|
3
|
+
*/ const noop = ()=>{
|
|
4
|
+
// noop
|
|
5
|
+
};
|
|
6
|
+
const freezeIfAvailable = (obj)=>{
|
|
7
|
+
if (Object.freeze) {
|
|
8
|
+
Object.freeze(obj);
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
function createInnerProxy(callback, path, memo) {
|
|
12
|
+
var _memo, _cacheKey;
|
|
13
|
+
const cacheKey = path.join(".");
|
|
14
|
+
(_memo = memo)[_cacheKey = cacheKey] ?? (_memo[_cacheKey] = new Proxy(noop, {
|
|
15
|
+
get (_obj, key) {
|
|
16
|
+
if (typeof key !== "string" || key === "then") {
|
|
17
|
+
// special case for if the proxy is accidentally treated
|
|
18
|
+
// like a PromiseLike (like in `Promise.resolve(proxy)`)
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
return createInnerProxy(callback, [
|
|
22
|
+
...path,
|
|
23
|
+
key
|
|
24
|
+
], memo);
|
|
25
|
+
},
|
|
26
|
+
apply (_1, _2, args) {
|
|
27
|
+
const lastOfPath = path[path.length - 1];
|
|
28
|
+
let opts = {
|
|
29
|
+
args,
|
|
30
|
+
path
|
|
31
|
+
};
|
|
32
|
+
// special handling for e.g. `trpc.hello.call(this, 'there')` and `trpc.hello.apply(this, ['there'])
|
|
33
|
+
if (lastOfPath === "call") {
|
|
34
|
+
opts = {
|
|
35
|
+
args: args.length >= 2 ? [
|
|
36
|
+
args[1]
|
|
37
|
+
] : [],
|
|
38
|
+
path: path.slice(0, -1)
|
|
39
|
+
};
|
|
40
|
+
} else if (lastOfPath === "apply") {
|
|
41
|
+
opts = {
|
|
42
|
+
args: args.length >= 2 ? args[1] : [],
|
|
43
|
+
path: path.slice(0, -1)
|
|
44
|
+
};
|
|
45
|
+
} else if (lastOfPath === "toString") {
|
|
46
|
+
return path.slice(0, -1).join(".");
|
|
47
|
+
} else if (lastOfPath === "toJSON") {
|
|
48
|
+
return {
|
|
49
|
+
path: path.slice(0, -1),
|
|
50
|
+
pathString: path.slice(0, -1).join("."),
|
|
51
|
+
__type: "SRPC"
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
freezeIfAvailable(opts.args);
|
|
55
|
+
freezeIfAvailable(opts.path);
|
|
56
|
+
return callback(opts);
|
|
57
|
+
}
|
|
58
|
+
}));
|
|
59
|
+
return memo[cacheKey];
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Creates a proxy that calls the callback with the path and arguments
|
|
63
|
+
*
|
|
64
|
+
* @internal
|
|
65
|
+
*/ const createRecursiveProxy = (callback)=>createInnerProxy(callback, [], Object.create(null));
|
|
66
|
+
/**
|
|
67
|
+
* Used in place of `new Proxy` where each handler will map 1 level deep to another value.
|
|
68
|
+
*
|
|
69
|
+
* @internal
|
|
70
|
+
*/ const createFlatProxy = (callback)=>{
|
|
71
|
+
return new Proxy(noop, {
|
|
72
|
+
get (_obj, name) {
|
|
73
|
+
if (typeof name !== "string" || name === "then") {
|
|
74
|
+
// special case for if the proxy is accidentally treated
|
|
75
|
+
// like a PromiseLike (like in `Promise.resolve(proxy)`)
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
return callback(name);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Default JSON-based serializer used by SRPC.
|
|
85
|
+
*
|
|
86
|
+
* Uses `JSON.stringify` for serialization and `JSON.parse` for deserialization.
|
|
87
|
+
* Works with JSON-compatible types (strings, numbers, booleans, objects, arrays, null).
|
|
88
|
+
* Does not support: Date, Map, Set, undefined, functions, symbols.
|
|
89
|
+
*
|
|
90
|
+
* For complex types, provide a custom `Serializer` implementation.
|
|
91
|
+
*/ const defaultSerializer = {
|
|
92
|
+
serialize: (value)=>JSON.stringify(value),
|
|
93
|
+
deserialize: (value)=>JSON.parse(value)
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Mapping of error codes to HTTP status codes.
|
|
97
|
+
*
|
|
98
|
+
* Used internally by server adapters to convert `SRPCError` instances
|
|
99
|
+
* to appropriate HTTP responses.
|
|
100
|
+
*/ const StatusCodeMap = {
|
|
101
|
+
BAD_REQUEST: 400,
|
|
102
|
+
NOT_FOUND: 404,
|
|
103
|
+
FORBIDDEN: 403,
|
|
104
|
+
UNAUTHORIZED: 401,
|
|
105
|
+
INTERNAL_SERVER_ERROR: 500,
|
|
106
|
+
NOT_IMPLEMENTED: 501,
|
|
107
|
+
UNSUPPORTED_MEDIA_TYPE: 415,
|
|
108
|
+
GENERIC_ERROR: 500
|
|
109
|
+
};
|
|
110
|
+
/**
|
|
111
|
+
* Standard error class for SRPC procedures.
|
|
112
|
+
*
|
|
113
|
+
* Thrown errors are automatically serialized and sent to the client with
|
|
114
|
+
* the appropriate HTTP status code. The client reconstructs the error
|
|
115
|
+
* with the same message and code.
|
|
116
|
+
*
|
|
117
|
+
* @example Throwing errors in procedures
|
|
118
|
+
* ```typescript
|
|
119
|
+
* import { SRPCError } from "@srpc.org/core";
|
|
120
|
+
*
|
|
121
|
+
* const appRouter = s.router({
|
|
122
|
+
* getUser: async (_, id: number) => {
|
|
123
|
+
* const user = await db.users.findById(id);
|
|
124
|
+
* if (!user) {
|
|
125
|
+
* throw new SRPCError("User not found", "NOT_FOUND");
|
|
126
|
+
* }
|
|
127
|
+
* return user;
|
|
128
|
+
* },
|
|
129
|
+
* deleteUser: async (ctx, id: number) => {
|
|
130
|
+
* if (!ctx.user?.isAdmin) {
|
|
131
|
+
* throw new SRPCError("Insufficient permissions", "FORBIDDEN");
|
|
132
|
+
* }
|
|
133
|
+
* await db.users.delete(id);
|
|
134
|
+
* return { success: true };
|
|
135
|
+
* }
|
|
136
|
+
* });
|
|
137
|
+
* ```
|
|
138
|
+
*
|
|
139
|
+
* @example Handling errors on the client
|
|
140
|
+
* ```typescript
|
|
141
|
+
* import { SRPCError } from "@srpc.org/core";
|
|
142
|
+
*
|
|
143
|
+
* try {
|
|
144
|
+
* const user = await client.getUser(999);
|
|
145
|
+
* } catch (error) {
|
|
146
|
+
* if (error instanceof SRPCError) {
|
|
147
|
+
* switch (error.code) {
|
|
148
|
+
* case "NOT_FOUND":
|
|
149
|
+
* console.log("User not found");
|
|
150
|
+
* break;
|
|
151
|
+
* case "FORBIDDEN":
|
|
152
|
+
* console.log("Access denied");
|
|
153
|
+
* break;
|
|
154
|
+
* case "UNAUTHORIZED":
|
|
155
|
+
* redirectToLogin();
|
|
156
|
+
* break;
|
|
157
|
+
* }
|
|
158
|
+
* }
|
|
159
|
+
* }
|
|
160
|
+
* ```
|
|
161
|
+
*/ class SRPCError extends Error {
|
|
162
|
+
constructor(message, code){
|
|
163
|
+
super(message), this.code = code, this.__BRAND__ = "SRPCError";
|
|
164
|
+
this.name = "SRPCError";
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export { SRPCError, StatusCodeMap, createFlatProxy, createRecursiveProxy, defaultSerializer };
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@srpc.org/core",
|
|
3
|
+
"version": "0.20.3",
|
|
4
|
+
"description": "A lightweight, type-safe RPC framework for TypeScript",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
},
|
|
16
|
+
"./server": {
|
|
17
|
+
"types": "./dist/server.d.ts",
|
|
18
|
+
"default": "./dist/server.js"
|
|
19
|
+
},
|
|
20
|
+
"./client": {
|
|
21
|
+
"types": "./dist/client.d.ts",
|
|
22
|
+
"default": "./dist/client.js"
|
|
23
|
+
},
|
|
24
|
+
"./shared": {
|
|
25
|
+
"types": "./dist/shared.d.ts",
|
|
26
|
+
"default": "./dist/shared.js"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"rpc",
|
|
31
|
+
"typescript",
|
|
32
|
+
"type-safe",
|
|
33
|
+
"api",
|
|
34
|
+
"client",
|
|
35
|
+
"server"
|
|
36
|
+
],
|
|
37
|
+
"author": "",
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"bunchee": "^6.6.2",
|
|
41
|
+
"get-port-please": "^3.1.2",
|
|
42
|
+
"vitest": "^4.0.10"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "bunchee",
|
|
46
|
+
"test": "vitest run",
|
|
47
|
+
"test:dev": "vitest",
|
|
48
|
+
"version": "node ../../version.mjs",
|
|
49
|
+
"release:npm": "pnpm publish --access=public",
|
|
50
|
+
"release:jsr": "pnpx jsr publish"
|
|
51
|
+
}
|
|
52
|
+
}
|