@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.
@@ -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 };