@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/server.d.ts
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { Serializer, DecoratedProcedureRecord } from './shared.js';
|
|
2
|
+
|
|
3
|
+
type ProcedureType<TContext> = (
|
|
4
|
+
ctx: TContext,
|
|
5
|
+
...args: any[]
|
|
6
|
+
) => Promise<any> | any;
|
|
7
|
+
|
|
8
|
+
type Routes<TContext> = {
|
|
9
|
+
[key: string]: ProcedureType<TContext> | SRPC<TContext>;
|
|
10
|
+
};
|
|
11
|
+
type AnySRPC = SRPC<any>;
|
|
12
|
+
type SrpcBaseOptions<TRouter extends AnySRPC> = {
|
|
13
|
+
router: TRouter;
|
|
14
|
+
};
|
|
15
|
+
declare class SRPC<TContext, TRoutes extends Routes<TContext> = {}> {
|
|
16
|
+
__context: TContext;
|
|
17
|
+
ipc: TRoutes;
|
|
18
|
+
constructor(routes?: TRoutes);
|
|
19
|
+
context<T>(): SRPC<T>;
|
|
20
|
+
router<T extends Routes<TContext>>(routes: T): SRPC<TContext, T>;
|
|
21
|
+
}
|
|
22
|
+
declare const initSRPC: () => SRPC<unknown>;
|
|
23
|
+
|
|
24
|
+
declare const srpcFetchApi: <TRouter extends AnySRPC>({ router, endpoint, createContext, transformer: serializer, }: SrpcBaseOptions<TRouter> & {
|
|
25
|
+
createContext?: (req: Request) => Promise<TRouter["__context"]>;
|
|
26
|
+
transformer?: Serializer;
|
|
27
|
+
endpoint: string;
|
|
28
|
+
}) => {
|
|
29
|
+
fetch: (req: Request) => Promise<Response>;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* @module
|
|
34
|
+
*
|
|
35
|
+
* Server-side SRPC module for creating routers and handling RPC requests.
|
|
36
|
+
*
|
|
37
|
+
* This module provides the core server functionality including:
|
|
38
|
+
* - Router initialization and composition
|
|
39
|
+
* - Fetch API adapter for edge runtimes
|
|
40
|
+
* - Server-side procedure calling without HTTP
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```typescript
|
|
44
|
+
* import { initSRPC, srpcFetchApi } from "@srpc/core/server";
|
|
45
|
+
*
|
|
46
|
+
* // Initialize and create router
|
|
47
|
+
* const s = initSRPC();
|
|
48
|
+
* const appRouter = s.router({
|
|
49
|
+
* sayHello: async (_, name: string) => `Hello ${name}!`
|
|
50
|
+
* });
|
|
51
|
+
*
|
|
52
|
+
* // Create Fetch API handler
|
|
53
|
+
* const { fetch: handleRequest } = srpcFetchApi({
|
|
54
|
+
* router: appRouter,
|
|
55
|
+
* endpoint: "/api"
|
|
56
|
+
* });
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Creates a server-side caller for direct procedure invocation without HTTP.
|
|
62
|
+
*
|
|
63
|
+
* Useful for:
|
|
64
|
+
* - Calling procedures from server components
|
|
65
|
+
* - Server-side rendering (SSR)
|
|
66
|
+
* - Internal server-to-server communication
|
|
67
|
+
* - Testing procedures without HTTP overhead
|
|
68
|
+
*
|
|
69
|
+
* @template TRouter - The SRPC router type
|
|
70
|
+
*
|
|
71
|
+
* @param options - Configuration object
|
|
72
|
+
* @param options.router - The SRPC router to create a caller for
|
|
73
|
+
* @param options.createContext - Optional function to create context for calls
|
|
74
|
+
*
|
|
75
|
+
* @returns A type-safe caller proxy matching your router structure
|
|
76
|
+
*
|
|
77
|
+
* @example Basic usage
|
|
78
|
+
* ```typescript
|
|
79
|
+
* import { createSRPCCaller, initSRPC } from "@srpc/core/server";
|
|
80
|
+
*
|
|
81
|
+
* const s = initSRPC();
|
|
82
|
+
* const appRouter = s.router({
|
|
83
|
+
* sayHello: async (_, name: string) => `Hello ${name}!`,
|
|
84
|
+
* getUser: async (_, id: number) => ({ id, name: "John" })
|
|
85
|
+
* });
|
|
86
|
+
*
|
|
87
|
+
* const caller = createSRPCCaller({ router: appRouter });
|
|
88
|
+
*
|
|
89
|
+
* // Call procedures directly
|
|
90
|
+
* const greeting = await caller.sayHello("World"); // "Hello World!"
|
|
91
|
+
* const user = await caller.getUser(1); // { id: 1, name: "John" }
|
|
92
|
+
* ```
|
|
93
|
+
*
|
|
94
|
+
* @example With context
|
|
95
|
+
* ```typescript
|
|
96
|
+
* type Context = { db: Database; user?: User };
|
|
97
|
+
*
|
|
98
|
+
* const s = initSRPC().context<Context>();
|
|
99
|
+
* const appRouter = s.router({
|
|
100
|
+
* getCurrentUser: async (ctx) => {
|
|
101
|
+
* if (!ctx.user) throw new Error("Not authenticated");
|
|
102
|
+
* return ctx.user;
|
|
103
|
+
* },
|
|
104
|
+
* getPosts: async (ctx) => {
|
|
105
|
+
* return ctx.db.posts.findAll();
|
|
106
|
+
* }
|
|
107
|
+
* });
|
|
108
|
+
*
|
|
109
|
+
* const caller = createSRPCCaller({
|
|
110
|
+
* router: appRouter,
|
|
111
|
+
* createContext: async () => ({
|
|
112
|
+
* db: getDatabase(),
|
|
113
|
+
* user: await getCurrentUser()
|
|
114
|
+
* })
|
|
115
|
+
* });
|
|
116
|
+
*
|
|
117
|
+
* const posts = await caller.getPosts();
|
|
118
|
+
* ```
|
|
119
|
+
*
|
|
120
|
+
* @example In Next.js Server Components
|
|
121
|
+
* ```typescript
|
|
122
|
+
* // server/rpc.ts
|
|
123
|
+
* export const rpcCaller = createSRPCCaller({
|
|
124
|
+
* router: appRouter,
|
|
125
|
+
* createContext: async () => ({
|
|
126
|
+
* db: getDatabase(),
|
|
127
|
+
* user: await getServerSession()
|
|
128
|
+
* })
|
|
129
|
+
* });
|
|
130
|
+
*
|
|
131
|
+
* // app/page.tsx
|
|
132
|
+
* import { rpcCaller } from "@/server/rpc";
|
|
133
|
+
*
|
|
134
|
+
* export default async function Page() {
|
|
135
|
+
* const user = await rpcCaller.getCurrentUser();
|
|
136
|
+
* return <div>Hello {user.name}</div>;
|
|
137
|
+
* }
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
declare const createSRPCCaller: <TRouter extends AnySRPC>({ createContext, router, }: {
|
|
141
|
+
createContext?: () => Promise<TRouter["__context"]>;
|
|
142
|
+
router: TRouter;
|
|
143
|
+
}) => DecoratedProcedureRecord<TRouter["ipc"]>;
|
|
144
|
+
|
|
145
|
+
export { SRPC, createSRPCCaller, initSRPC, srpcFetchApi };
|
|
146
|
+
export type { AnySRPC };
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { _ } from '@swc/helpers/_/_class_private_field_loose_base';
|
|
2
|
+
import { _ as _$1 } from '@swc/helpers/_/_class_private_field_loose_key';
|
|
3
|
+
import { SRPCError, defaultSerializer, StatusCodeMap } from './shared.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Used from trpc.io
|
|
7
|
+
*/ const noop = ()=>{
|
|
8
|
+
// noop
|
|
9
|
+
};
|
|
10
|
+
const freezeIfAvailable = (obj)=>{
|
|
11
|
+
if (Object.freeze) {
|
|
12
|
+
Object.freeze(obj);
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
function createInnerProxy(callback, path, memo) {
|
|
16
|
+
var _memo, _cacheKey;
|
|
17
|
+
const cacheKey = path.join(".");
|
|
18
|
+
(_memo = memo)[_cacheKey = cacheKey] ?? (_memo[_cacheKey] = new Proxy(noop, {
|
|
19
|
+
get (_obj, key) {
|
|
20
|
+
if (typeof key !== "string" || key === "then") {
|
|
21
|
+
// special case for if the proxy is accidentally treated
|
|
22
|
+
// like a PromiseLike (like in `Promise.resolve(proxy)`)
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
return createInnerProxy(callback, [
|
|
26
|
+
...path,
|
|
27
|
+
key
|
|
28
|
+
], memo);
|
|
29
|
+
},
|
|
30
|
+
apply (_1, _2, args) {
|
|
31
|
+
const lastOfPath = path[path.length - 1];
|
|
32
|
+
let opts = {
|
|
33
|
+
args,
|
|
34
|
+
path
|
|
35
|
+
};
|
|
36
|
+
// special handling for e.g. `trpc.hello.call(this, 'there')` and `trpc.hello.apply(this, ['there'])
|
|
37
|
+
if (lastOfPath === "call") {
|
|
38
|
+
opts = {
|
|
39
|
+
args: args.length >= 2 ? [
|
|
40
|
+
args[1]
|
|
41
|
+
] : [],
|
|
42
|
+
path: path.slice(0, -1)
|
|
43
|
+
};
|
|
44
|
+
} else if (lastOfPath === "apply") {
|
|
45
|
+
opts = {
|
|
46
|
+
args: args.length >= 2 ? args[1] : [],
|
|
47
|
+
path: path.slice(0, -1)
|
|
48
|
+
};
|
|
49
|
+
} else if (lastOfPath === "toString") {
|
|
50
|
+
return path.slice(0, -1).join(".");
|
|
51
|
+
} else if (lastOfPath === "toJSON") {
|
|
52
|
+
return {
|
|
53
|
+
path: path.slice(0, -1),
|
|
54
|
+
pathString: path.slice(0, -1).join("."),
|
|
55
|
+
__type: "SRPC"
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
freezeIfAvailable(opts.args);
|
|
59
|
+
freezeIfAvailable(opts.path);
|
|
60
|
+
return callback(opts);
|
|
61
|
+
}
|
|
62
|
+
}));
|
|
63
|
+
return memo[cacheKey];
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Creates a proxy that calls the callback with the path and arguments
|
|
67
|
+
*
|
|
68
|
+
* @internal
|
|
69
|
+
*/ const createRecursiveProxy = (callback)=>createInnerProxy(callback, [], Object.create(null));
|
|
70
|
+
|
|
71
|
+
class SRPC {
|
|
72
|
+
context() {
|
|
73
|
+
return new SRPC();
|
|
74
|
+
}
|
|
75
|
+
router(routes) {
|
|
76
|
+
return new SRPC(routes);
|
|
77
|
+
}
|
|
78
|
+
constructor(routes){
|
|
79
|
+
if (routes) {
|
|
80
|
+
this.ipc = routes;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const initSRPC = ()=>{
|
|
85
|
+
return new SRPC();
|
|
86
|
+
};
|
|
87
|
+
var _router = /*#__PURE__*/ _$1("_router"), _options = /*#__PURE__*/ _$1("_options");
|
|
88
|
+
class sRPC_API {
|
|
89
|
+
getRoute(path) {
|
|
90
|
+
const pathString = path.toString();
|
|
91
|
+
const value = _(this, _router)[_router].ipc[path];
|
|
92
|
+
if (value) {
|
|
93
|
+
return value;
|
|
94
|
+
}
|
|
95
|
+
if (pathString.includes(".")) {
|
|
96
|
+
let current = _(this, _router)[_router].ipc;
|
|
97
|
+
const pathParts = pathString.split(".");
|
|
98
|
+
for (const key of pathParts){
|
|
99
|
+
if (current && current instanceof SRPC) {
|
|
100
|
+
current = current.ipc[key];
|
|
101
|
+
continue;
|
|
102
|
+
} else if (current && typeof current === "object" && key in current) {
|
|
103
|
+
current = current[key];
|
|
104
|
+
} else {
|
|
105
|
+
current = null;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return current;
|
|
109
|
+
}
|
|
110
|
+
return _(this, _router)[_router].ipc[path];
|
|
111
|
+
}
|
|
112
|
+
async call(path, context, deserializedArgs) {
|
|
113
|
+
const route = this.getRoute(path);
|
|
114
|
+
if (typeof route !== "function") {
|
|
115
|
+
throw new SRPCError(`Route ${String(path)} not found`, "NOT_FOUND");
|
|
116
|
+
}
|
|
117
|
+
return route(context, ...deserializedArgs);
|
|
118
|
+
}
|
|
119
|
+
constructor(options){
|
|
120
|
+
Object.defineProperty(this, _router, {
|
|
121
|
+
writable: true,
|
|
122
|
+
value: void 0
|
|
123
|
+
});
|
|
124
|
+
Object.defineProperty(this, _options, {
|
|
125
|
+
writable: true,
|
|
126
|
+
value: void 0
|
|
127
|
+
});
|
|
128
|
+
_(this, _router)[_router] = options.router;
|
|
129
|
+
_(this, _options)[_options] = options;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const srpcFetchApi = ({ router, endpoint, createContext, transformer: serializer = defaultSerializer })=>{
|
|
134
|
+
const api = new sRPC_API({
|
|
135
|
+
router
|
|
136
|
+
});
|
|
137
|
+
return {
|
|
138
|
+
fetch: async (req)=>{
|
|
139
|
+
const url = new URL(req.url);
|
|
140
|
+
const context = await createContext?.(req);
|
|
141
|
+
const path = url.pathname.replace(`${endpoint}/`, "");
|
|
142
|
+
const body = await req.text();
|
|
143
|
+
const deserializedArgs = serializer.deserialize(body);
|
|
144
|
+
try {
|
|
145
|
+
const data = await api.call(path, context, deserializedArgs);
|
|
146
|
+
return new Response(serializer.serialize(data), {
|
|
147
|
+
headers: {
|
|
148
|
+
"Content-Type": "application/json"
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
} catch (error) {
|
|
152
|
+
if (error instanceof SRPCError) {
|
|
153
|
+
return new Response(serializer.serialize(error), {
|
|
154
|
+
status: StatusCodeMap[error.code],
|
|
155
|
+
headers: {
|
|
156
|
+
"Content-Type": "application/json"
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
161
|
+
console.error(error);
|
|
162
|
+
return new Response(serializer.serialize(new SRPCError(message, "INTERNAL_SERVER_ERROR")), {
|
|
163
|
+
status: 500,
|
|
164
|
+
headers: {
|
|
165
|
+
"Content-Type": "application/json"
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Creates a server-side caller for direct procedure invocation without HTTP.
|
|
175
|
+
*
|
|
176
|
+
* Useful for:
|
|
177
|
+
* - Calling procedures from server components
|
|
178
|
+
* - Server-side rendering (SSR)
|
|
179
|
+
* - Internal server-to-server communication
|
|
180
|
+
* - Testing procedures without HTTP overhead
|
|
181
|
+
*
|
|
182
|
+
* @template TRouter - The SRPC router type
|
|
183
|
+
*
|
|
184
|
+
* @param options - Configuration object
|
|
185
|
+
* @param options.router - The SRPC router to create a caller for
|
|
186
|
+
* @param options.createContext - Optional function to create context for calls
|
|
187
|
+
*
|
|
188
|
+
* @returns A type-safe caller proxy matching your router structure
|
|
189
|
+
*
|
|
190
|
+
* @example Basic usage
|
|
191
|
+
* ```typescript
|
|
192
|
+
* import { createSRPCCaller, initSRPC } from "@srpc/core/server";
|
|
193
|
+
*
|
|
194
|
+
* const s = initSRPC();
|
|
195
|
+
* const appRouter = s.router({
|
|
196
|
+
* sayHello: async (_, name: string) => `Hello ${name}!`,
|
|
197
|
+
* getUser: async (_, id: number) => ({ id, name: "John" })
|
|
198
|
+
* });
|
|
199
|
+
*
|
|
200
|
+
* const caller = createSRPCCaller({ router: appRouter });
|
|
201
|
+
*
|
|
202
|
+
* // Call procedures directly
|
|
203
|
+
* const greeting = await caller.sayHello("World"); // "Hello World!"
|
|
204
|
+
* const user = await caller.getUser(1); // { id: 1, name: "John" }
|
|
205
|
+
* ```
|
|
206
|
+
*
|
|
207
|
+
* @example With context
|
|
208
|
+
* ```typescript
|
|
209
|
+
* type Context = { db: Database; user?: User };
|
|
210
|
+
*
|
|
211
|
+
* const s = initSRPC().context<Context>();
|
|
212
|
+
* const appRouter = s.router({
|
|
213
|
+
* getCurrentUser: async (ctx) => {
|
|
214
|
+
* if (!ctx.user) throw new Error("Not authenticated");
|
|
215
|
+
* return ctx.user;
|
|
216
|
+
* },
|
|
217
|
+
* getPosts: async (ctx) => {
|
|
218
|
+
* return ctx.db.posts.findAll();
|
|
219
|
+
* }
|
|
220
|
+
* });
|
|
221
|
+
*
|
|
222
|
+
* const caller = createSRPCCaller({
|
|
223
|
+
* router: appRouter,
|
|
224
|
+
* createContext: async () => ({
|
|
225
|
+
* db: getDatabase(),
|
|
226
|
+
* user: await getCurrentUser()
|
|
227
|
+
* })
|
|
228
|
+
* });
|
|
229
|
+
*
|
|
230
|
+
* const posts = await caller.getPosts();
|
|
231
|
+
* ```
|
|
232
|
+
*
|
|
233
|
+
* @example In Next.js Server Components
|
|
234
|
+
* ```typescript
|
|
235
|
+
* // server/rpc.ts
|
|
236
|
+
* export const rpcCaller = createSRPCCaller({
|
|
237
|
+
* router: appRouter,
|
|
238
|
+
* createContext: async () => ({
|
|
239
|
+
* db: getDatabase(),
|
|
240
|
+
* user: await getServerSession()
|
|
241
|
+
* })
|
|
242
|
+
* });
|
|
243
|
+
*
|
|
244
|
+
* // app/page.tsx
|
|
245
|
+
* import { rpcCaller } from "@/server/rpc";
|
|
246
|
+
*
|
|
247
|
+
* export default async function Page() {
|
|
248
|
+
* const user = await rpcCaller.getCurrentUser();
|
|
249
|
+
* return <div>Hello {user.name}</div>;
|
|
250
|
+
* }
|
|
251
|
+
* ```
|
|
252
|
+
*/ const createSRPCCaller = ({ createContext, router })=>{
|
|
253
|
+
return createRecursiveProxy(async ({ path, args })=>{
|
|
254
|
+
const api = new sRPC_API({
|
|
255
|
+
router
|
|
256
|
+
});
|
|
257
|
+
const context = await createContext?.();
|
|
258
|
+
return api.call(path.join("."), context, args);
|
|
259
|
+
});
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
export { SRPC, createSRPCCaller, initSRPC, srpcFetchApi };
|