@atom-forge/rpc 0.3.2

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.
Files changed (42) hide show
  1. package/LICENSE +28 -0
  2. package/README.en.md +612 -0
  3. package/README.hu.md +613 -0
  4. package/README.llm.md +343 -0
  5. package/README.md +25 -0
  6. package/dist/client/client-context.d.ts +45 -0
  7. package/dist/client/client-context.js +48 -0
  8. package/dist/client/create-client.d.ts +9 -0
  9. package/dist/client/create-client.js +277 -0
  10. package/dist/client/logger.d.ts +6 -0
  11. package/dist/client/logger.js +41 -0
  12. package/dist/client/middleware.d.ts +6 -0
  13. package/dist/client/middleware.js +7 -0
  14. package/dist/client/rpc-response.d.ts +27 -0
  15. package/dist/client/rpc-response.js +46 -0
  16. package/dist/client/types.d.ts +151 -0
  17. package/dist/client/types.js +1 -0
  18. package/dist/index.d.ts +7 -0
  19. package/dist/index.js +7 -0
  20. package/dist/server/create-handler.d.ts +18 -0
  21. package/dist/server/create-handler.js +210 -0
  22. package/dist/server/errors.d.ts +10 -0
  23. package/dist/server/errors.js +14 -0
  24. package/dist/server/middleware.d.ts +22 -0
  25. package/dist/server/middleware.js +39 -0
  26. package/dist/server/rpc.d.ts +65 -0
  27. package/dist/server/rpc.js +49 -0
  28. package/dist/server/server-context.d.ts +79 -0
  29. package/dist/server/server-context.js +86 -0
  30. package/dist/server/types.d.ts +30 -0
  31. package/dist/server/types.js +1 -0
  32. package/dist/util/constants.d.ts +1 -0
  33. package/dist/util/constants.js +1 -0
  34. package/dist/util/cookies.d.ts +22 -0
  35. package/dist/util/cookies.js +54 -0
  36. package/dist/util/pipeline.d.ts +23 -0
  37. package/dist/util/pipeline.js +22 -0
  38. package/dist/util/string.d.ts +6 -0
  39. package/dist/util/string.js +11 -0
  40. package/dist/util/types.d.ts +5 -0
  41. package/dist/util/types.js +1 -0
  42. package/package.json +38 -0
@@ -0,0 +1,277 @@
1
+ import { Packr } from "msgpackr";
2
+ import { RPC_ERROR_KEY } from "../util/constants.js";
3
+ import { pipeline } from "../util/pipeline.js";
4
+ import { ClientContext } from "./client-context.js";
5
+ import { RpcResponse } from "./rpc-response.js";
6
+ import { camelToKebabCase } from "../util/string.js";
7
+ const rpcMethods = ["$command", "$query", "$get"];
8
+ const rpcMethodTypeMap = {
9
+ $command: "command",
10
+ $query: "query",
11
+ $get: "get",
12
+ };
13
+ const packr = new Packr({ structuredClone: true, useRecords: true });
14
+ /**
15
+ * Creates an API client and a corresponding middleware configuration object.
16
+ *
17
+ * @template T
18
+ * @param {string} [baseUrl='/api'] - The base URL for the API client.
19
+ * @returns {[ApiClientDefinition<T>, MiddlewareConfig<T>]} A tuple containing the API client and the middleware config object.
20
+ */
21
+ export function createClient(baseUrl = "/api") {
22
+ const middlewareMap = new Map();
23
+ function createSetterProxy(path) {
24
+ const handler = {
25
+ get(_, prop) {
26
+ return createSetterProxy([...path, prop]);
27
+ },
28
+ set(_, prop, value) {
29
+ const keyPath = prop === "$" ? path : [...path, prop];
30
+ const key = keyPath.map(camelToKebabCase).join(".");
31
+ const existing = middlewareMap.get(key) || [];
32
+ const newMiddlewares = Array.isArray(value) ? value : [value];
33
+ middlewareMap.set(key, [...existing, ...newMiddlewares]);
34
+ return true;
35
+ },
36
+ };
37
+ return new Proxy({}, handler);
38
+ }
39
+ function createRecursiveProxy(pathSegments = []) {
40
+ return new Proxy({}, {
41
+ get(_target, prop) {
42
+ if (typeof prop !== "string")
43
+ return undefined;
44
+ const isResultRequested = rpcMethods.includes(prop);
45
+ if (isResultRequested) {
46
+ const rpcType = rpcMethodTypeMap[prop];
47
+ return async (args, options = {}) => {
48
+ const ctx = new ClientContext(pathSegments, args, rpcType, options);
49
+ const middlewares = [];
50
+ if (middlewareMap.has(""))
51
+ middlewares.push(...middlewareMap.get(""));
52
+ for (let i = 1; i <= ctx.path.length; i++) {
53
+ const key = ctx.path.slice(0, i).map(camelToKebabCase).join(".");
54
+ if (middlewareMap.has(key))
55
+ middlewares.push(...middlewareMap.get(key));
56
+ }
57
+ await pipeline(ctx, ...middlewares, async (ctx) => {
58
+ let result = await call(baseUrl, ctx);
59
+ ctx.result = result;
60
+ return result;
61
+ });
62
+ const rpcResponse = ctx.result;
63
+ rpcResponse._attachCtx(ctx);
64
+ return rpcResponse;
65
+ };
66
+ }
67
+ return createRecursiveProxy([...pathSegments, prop]);
68
+ },
69
+ });
70
+ }
71
+ const api = createRecursiveProxy();
72
+ const cfg = createSetterProxy([]);
73
+ return [api, cfg];
74
+ }
75
+ async function call(baseUrl, ctx) {
76
+ const args = ctx.args;
77
+ const uploads = new Map();
78
+ const isGet = ctx.rpcType === "get";
79
+ const isQuery = ctx.rpcType === "query";
80
+ const isCommand = ctx.rpcType === "command";
81
+ if (isCommand) {
82
+ args.forEach((value, key) => {
83
+ if (value instanceof File ||
84
+ (Array.isArray(value) && value.length > 0 && value.every((v) => v instanceof File))) {
85
+ uploads.set(key, value);
86
+ args.delete(key);
87
+ }
88
+ });
89
+ }
90
+ const hasUploads = !!uploads.size;
91
+ const pathString = ctx.path.map(camelToKebabCase).join(".");
92
+ const url = typeof window !== "undefined" && typeof window.document !== "undefined"
93
+ ? new URL(`${baseUrl}/${pathString}`, window ? window.location.origin : undefined)
94
+ : new URL(`${baseUrl}/${pathString}`);
95
+ const onProgress = ctx.onProgress;
96
+ const hasProgress = !!onProgress;
97
+ const signal = ctx.abortSignal;
98
+ const headers = ctx.request.headers;
99
+ const requestType = isGet
100
+ ? "GET"
101
+ : isQuery
102
+ ? "QUERY"
103
+ : hasUploads
104
+ ? "UPLOAD"
105
+ : "COMMAND";
106
+ const method = isCommand ? "POST" : "GET";
107
+ let body = null;
108
+ switch (requestType) {
109
+ case "GET":
110
+ args.forEach((value, key) => value !== undefined &&
111
+ value !== null &&
112
+ url.searchParams.set(key, String(value)));
113
+ break;
114
+ case "QUERY":
115
+ if (args.size > 0)
116
+ url.searchParams.set("args", encodeToBase64Url(new Uint8Array(packr.pack(Object.fromEntries(args)))));
117
+ break;
118
+ case "UPLOAD":
119
+ body = new FormData();
120
+ if (args.size > 0) {
121
+ const argsObj = Object.fromEntries(args);
122
+ body.append("args", new Blob([new Uint8Array(packr.pack(argsObj))], {
123
+ type: "application/msgpack",
124
+ }));
125
+ }
126
+ for (let key of uploads.keys()) {
127
+ const file = uploads.get(key);
128
+ if (Array.isArray(file)) {
129
+ for (const f of file) {
130
+ body.append(key.endsWith("[]") ? key : `${key}[]`, f, f.name);
131
+ }
132
+ }
133
+ else if (file) {
134
+ body.append(key, file, file.name);
135
+ }
136
+ }
137
+ break;
138
+ case "COMMAND":
139
+ body = new Uint8Array(packr.pack(Object.fromEntries(args)));
140
+ headers.set("Content-Type", "application/msgpack");
141
+ break;
142
+ }
143
+ let response;
144
+ try {
145
+ response = hasProgress
146
+ ? await fetchWithXhr(url, method, body, headers, onProgress, signal)
147
+ : await fetch(url, {
148
+ method,
149
+ headers,
150
+ body,
151
+ signal,
152
+ credentials: "include",
153
+ window: null,
154
+ });
155
+ }
156
+ catch (e) {
157
+ return RpcResponse.error("NETWORK_ERROR", { message: e.message });
158
+ }
159
+ ctx._response = response;
160
+ const buffer = await response.arrayBuffer();
161
+ if (buffer.byteLength === 0) {
162
+ if (response.ok)
163
+ return RpcResponse.ok(null);
164
+ return RpcResponse.error(`HTTP:${response.status}`, null);
165
+ }
166
+ let unpacked;
167
+ try {
168
+ unpacked = packr.unpack(new Uint8Array(buffer));
169
+ }
170
+ catch {
171
+ try {
172
+ unpacked = JSON.parse(new TextDecoder().decode(buffer));
173
+ }
174
+ catch {
175
+ unpacked = null;
176
+ }
177
+ }
178
+ const errorCode = unpacked?.[RPC_ERROR_KEY];
179
+ if (errorCode) {
180
+ const { [RPC_ERROR_KEY]: _, ...rest } = unpacked;
181
+ return RpcResponse.error(errorCode, rest);
182
+ }
183
+ if (!response.ok) {
184
+ return RpcResponse.error(`HTTP:${response.status}`, unpacked);
185
+ }
186
+ return RpcResponse.ok(unpacked);
187
+ }
188
+ /**
189
+ * Fetch with XMLHttpRequest for progress tracking and AbortSignal support
190
+ */
191
+ function fetchWithXhr(url, method, body, headers, onProgress, signal) {
192
+ return new Promise((resolve, reject) => {
193
+ const xhr = new XMLHttpRequest();
194
+ // 👇 AbortSignal kezelés - ha már megszakított, throw azonnal
195
+ if (signal?.aborted) {
196
+ reject(new DOMException("Request aborted", "AbortError"));
197
+ return;
198
+ }
199
+ // 👇 Signal listener - ha megszakítják, abort az XHR-t
200
+ const abortHandler = () => {
201
+ xhr.abort();
202
+ reject(new DOMException("Request aborted", "AbortError"));
203
+ };
204
+ signal?.addEventListener("abort", abortHandler);
205
+ if (onProgress) {
206
+ if (method === "POST" && body) {
207
+ xhr.upload.addEventListener("progress", (e) => {
208
+ if (e.lengthComputable)
209
+ onProgress({
210
+ loaded: e.loaded,
211
+ total: e.total,
212
+ percent: Math.round((e.loaded / e.total) * 100),
213
+ phase: "upload",
214
+ });
215
+ });
216
+ }
217
+ xhr.addEventListener("progress", (e) => {
218
+ if (e.lengthComputable)
219
+ onProgress({
220
+ loaded: e.loaded,
221
+ total: e.total,
222
+ percent: Math.round((e.loaded / e.total) * 100),
223
+ phase: "download",
224
+ });
225
+ });
226
+ }
227
+ xhr.addEventListener("load", () => {
228
+ signal?.removeEventListener("abort", abortHandler);
229
+ const headers = new Headers();
230
+ const headerMap = xhr.getAllResponseHeaders();
231
+ headerMap.trim().split(/[\r\n]+/).forEach(line => {
232
+ const separatorIndex = line.indexOf(':');
233
+ if (separatorIndex > 0) {
234
+ const key = line.substring(0, separatorIndex).trim();
235
+ const value = line.substring(separatorIndex + 1).trim();
236
+ headers.append(key, value);
237
+ }
238
+ });
239
+ resolve(new Response(xhr.response, {
240
+ status: xhr.status,
241
+ statusText: xhr.statusText,
242
+ headers,
243
+ }));
244
+ });
245
+ xhr.addEventListener("error", () => {
246
+ signal?.removeEventListener("abort", abortHandler);
247
+ reject(new Error("Network error"));
248
+ });
249
+ xhr.addEventListener("abort", () => {
250
+ signal?.removeEventListener("abort", abortHandler);
251
+ reject(new DOMException("Request aborted", "AbortError"));
252
+ });
253
+ xhr.open(method, url);
254
+ headers.forEach((value, key) => {
255
+ xhr.setRequestHeader(key, value);
256
+ });
257
+ xhr.responseType = "arraybuffer";
258
+ xhr.send(body);
259
+ });
260
+ }
261
+ /**
262
+ * Encodes the given binary data into a Base64 URL-safe string.
263
+ *
264
+ * This method converts the provided binary data into a standard Base64 string,
265
+ * then makes it URL-safe by replacing `+` with `-`, `/` with `_`, and removing
266
+ * any padding characters (`=`) from the end.
267
+ *
268
+ * @param {Uint8Array} data - The input binary data to be encoded as a Base64 URL-safe string.
269
+ * @return {string} The Base64 URL-safe encoded string representation of the input data.
270
+ */
271
+ function encodeToBase64Url(data) {
272
+ const binStr = Array.from(data)
273
+ .map((b) => String.fromCharCode(b))
274
+ .join("");
275
+ const base64 = btoa(binStr);
276
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
277
+ }
@@ -0,0 +1,6 @@
1
+ import type { ClientMiddleware } from "./types.js";
2
+ /**
3
+ * A client middleware that logs RPC call details to the browser console.
4
+ * Logs the request path, arguments, response, timing, and HTTP status.
5
+ */
6
+ export declare function clientLogger(baseUrl?: string): ClientMiddleware;
@@ -0,0 +1,41 @@
1
+ import { camelToKebabCase } from "../util/string.js";
2
+ /**
3
+ * A client middleware that logs RPC call details to the browser console.
4
+ * Logs the request path, arguments, response, timing, and HTTP status.
5
+ */
6
+ export function clientLogger(baseUrl = "/api") {
7
+ return async (ctx, next) => {
8
+ console.groupCollapsed(`🔆 %c${baseUrl}/%c${ctx.path.map(camelToKebabCase).join(".")}`, "font-weight:200; color:gray", "font-weight:800;");
9
+ console.log("ARG:", ctx.getArgs());
10
+ try {
11
+ await next();
12
+ }
13
+ catch (e) {
14
+ console.log("PIPELINE ERR:", e);
15
+ console.groupEnd();
16
+ throw e;
17
+ }
18
+ const duration = ctx.elapsedTime.toFixed(2);
19
+ console.log("RES:", ctx.result);
20
+ if (ctx.response) {
21
+ console.log(`%c${duration} %cms / %c${parseFloat(ctx.response.headers.get("x-atom-forge-rpc-exec-time") || "0").toFixed(2)} %cms`, "font-weight:800;", "font-weight:200;", "font-weight:800;", "font-weight:200;");
22
+ console.groupEnd();
23
+ let color;
24
+ if (ctx.response.status < 200)
25
+ color = "#3498db";
26
+ else if (ctx.response.status < 300)
27
+ color = "#2ecc71";
28
+ else if (ctx.response.status < 400)
29
+ color = "#f1c40f";
30
+ else if (ctx.response.status < 500)
31
+ color = "#e74c3c";
32
+ else
33
+ color = "#9b59b6";
34
+ console.log(`️ ↘ %c${ctx.response.status} %c${ctx.response.statusText}`, `font-weight:800; color: ${color}`, `font-weight:200; color: ${color}`);
35
+ }
36
+ else {
37
+ console.log(`%c${duration} %cms %c(no response object)`, "font-weight:800;", "font-weight:200; color:gray");
38
+ console.groupEnd();
39
+ }
40
+ };
41
+ }
@@ -0,0 +1,6 @@
1
+ import type { ClientMiddleware } from "./types.js";
2
+ /**
3
+ * Creates a ClientMiddleware function.
4
+ * @param middleware
5
+ */
6
+ export declare function makeClientMiddleware(middleware: ClientMiddleware): ClientMiddleware;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Creates a ClientMiddleware function.
3
+ * @param middleware
4
+ */
5
+ export function makeClientMiddleware(middleware) {
6
+ return middleware;
7
+ }
@@ -0,0 +1,27 @@
1
+ import { RPC_ERROR_KEY } from "../util/constants.js";
2
+ import type { ClientContext } from "./client-context.js";
3
+ export { RPC_ERROR_KEY };
4
+ type RpcErrorKey = typeof RPC_ERROR_KEY;
5
+ type RpcErrorObject = {
6
+ [K in RpcErrorKey]: string;
7
+ };
8
+ export declare class RpcResponse<TSuccess, TError extends RpcErrorObject = never> {
9
+ private readonly _status;
10
+ private readonly _result;
11
+ private _ctx?;
12
+ constructor(status: string, result: TSuccess | Omit<TError, RpcErrorKey>);
13
+ isOK(): this is RpcResponse<TSuccess, never>;
14
+ isError<TCode extends string>(code?: TCode): this is RpcResponse<never, Extract<TError, {
15
+ [K in RpcErrorKey]: TCode;
16
+ }>>;
17
+ getStatus(): string;
18
+ get status(): string;
19
+ getResult(): TSuccess | Omit<TError, RpcErrorKey>;
20
+ get result(): TSuccess | Omit<TError, RpcErrorKey>;
21
+ getCtx(): ClientContext;
22
+ get ctx(): ClientContext;
23
+ /** @internal */
24
+ _attachCtx(ctx: ClientContext): void;
25
+ static ok<T>(result: T): RpcResponse<T, never>;
26
+ static error(code: string, result: unknown): RpcResponse<any, any>;
27
+ }
@@ -0,0 +1,46 @@
1
+ import { RPC_ERROR_KEY } from "../util/constants.js";
2
+ export { RPC_ERROR_KEY };
3
+ export class RpcResponse {
4
+ constructor(status, result) {
5
+ this._status = status;
6
+ this._result = result;
7
+ }
8
+ isOK() {
9
+ return this._status === "OK";
10
+ }
11
+ isError(code) {
12
+ if (code !== undefined)
13
+ return this._status === code;
14
+ return this._status !== "OK";
15
+ }
16
+ getStatus() {
17
+ return this._status;
18
+ }
19
+ get status() {
20
+ return this._status;
21
+ }
22
+ getResult() {
23
+ return this._result;
24
+ }
25
+ get result() {
26
+ return this._result;
27
+ }
28
+ getCtx() {
29
+ if (!this._ctx)
30
+ throw new Error("ClientContext is not available on this RpcResponse");
31
+ return this._ctx;
32
+ }
33
+ get ctx() {
34
+ return this.getCtx();
35
+ }
36
+ /** @internal */
37
+ _attachCtx(ctx) {
38
+ this._ctx = ctx;
39
+ }
40
+ static ok(result) {
41
+ return new RpcResponse("OK", result);
42
+ }
43
+ static error(code, result) {
44
+ return new RpcResponse(code, result);
45
+ }
46
+ }
@@ -0,0 +1,151 @@
1
+ import { z } from "zod";
2
+ import type { Middleware } from "../util/pipeline.js";
3
+ import type { UnwrappedPromise } from "../util/types.js";
4
+ import type { ClientContext } from "./client-context.js";
5
+ import { RPC_ERROR_KEY } from "../util/constants.js";
6
+ import type { RpcResponse } from "./rpc-response.js";
7
+ export type { RpcResponse };
8
+ type RpcErrorKey = typeof RPC_ERROR_KEY;
9
+ type RpcErrorShape = {
10
+ [K in RpcErrorKey]: string;
11
+ };
12
+ type ExtractSuccess<R> = R extends RpcErrorShape ? never : R;
13
+ type ExtractErrors<R> = R extends RpcErrorShape ? R : never;
14
+ /**
15
+ * Represents a middleware function that operates on the client context during
16
+ * the execution cycle. This middleware type is used to manipulate or process
17
+ * the client context or the result during the middleware chain.
18
+ * @internal
19
+ */
20
+ export type ClientMiddleware<RET = any> = Middleware<ClientContext, RET>;
21
+ /**
22
+ * A callback type representing a handler for monitoring the progress of a data transfer operation.
23
+ *
24
+ * This type is used to track the progress of an upload or download process by providing
25
+ * periodic updates about the amount of data transferred, the total data size, the percentage
26
+ * completed, and the current phase of the operation (upload or download).
27
+ *
28
+ * @callback OnProgress
29
+ * @param {Object} progress - The progress information of the operation.
30
+ * @param {number} progress.loaded - The amount of data that has been transferred so far.
31
+ * @param {number} progress.total - The total size of the data to be transferred.
32
+ * @param {number} progress.percent - The percentage of the data transfer that has been completed.
33
+ * @param {'upload' | 'download'} progress.phase - The current phase of the data transfer operation, either "upload" or "download".
34
+ * @internal
35
+ */
36
+ export type OnProgress = (progress: {
37
+ loaded: number;
38
+ total: number;
39
+ percent: number;
40
+ phase: "upload" | "download";
41
+ }) => void;
42
+ /**
43
+ * Represents options that can be used to customize the behavior of a call or request.
44
+ * @internal
45
+ */
46
+ export type CallOptions = {
47
+ /**
48
+ * A callback function that gets invoked to report the progress of an ongoing operation.
49
+ *
50
+ * This optional property can be used to track and handle progress updates during a process.
51
+ * The function typically receives information related to the current state or percentage
52
+ * of completion of the operation.
53
+ */
54
+ onProgress?: OnProgress;
55
+ /**
56
+ * An optional AbortSignal object that allows you to communicate with, or to monitor, a cancellation or abort request.
57
+ * Can be used to terminate an ongoing operation if the associated signal is triggered.
58
+ */
59
+ abortSignal?: AbortSignal;
60
+ /**
61
+ * Optional `headers` parameter that represents the HTTP headers
62
+ * to be sent with the request. It can include custom headers
63
+ * and standard headers to define how the request should be processed.
64
+ */
65
+ headers?: Headers;
66
+ };
67
+ /** A type that can be either a single middleware or an array of them. */
68
+ type MiddlewareAssignable = ClientMiddleware | ClientMiddleware[];
69
+ /**
70
+ * This type recursively transforms the structure of ApiClientDefinition<T>
71
+ * into the structure needed for the middleware configuration.
72
+ * Each branch node (group) will have a '$' property for its own middlewares,
73
+ * and each leaf node (endpoint) will be directly assignable.
74
+ */
75
+ type MiddlewareConfigFromApi<T> = {
76
+ [K in keyof T]: T[K] extends {
77
+ $command: any;
78
+ } | {
79
+ $query: any;
80
+ } | {
81
+ $get: any;
82
+ } ? MiddlewareAssignable : T[K] extends object ? {
83
+ $?: MiddlewareAssignable;
84
+ } & MiddlewareConfigFromApi<T[K]> : never;
85
+ };
86
+ /**
87
+ * The main middleware configuration type. It combines the global '$' with this transformed structure.
88
+ * @internal
89
+ */
90
+ export type MiddlewareConfig<T> = {
91
+ $?: MiddlewareAssignable;
92
+ } & MiddlewareConfigFromApi<ApiClientDefinition<T>>;
93
+ /**
94
+ * Base interface for all RPC method descriptors.
95
+ * @internal
96
+ */
97
+ export interface RpcMethodDescriptor {
98
+ rpcType: "query" | "command" | "get";
99
+ zodSchema?: z.ZodSchema<any>;
100
+ }
101
+ /**
102
+ *The properties are transformed to the target type, or to `never` if they need to be filtered out.
103
+ */
104
+ type MappedApi<T> = {
105
+ [K in keyof T]: T[K] extends RpcMethodDescriptor ? CallableRpcMethod<GetArgs<T[K]>, GetSuccessRet<T[K]>, GetErrorRet<T[K]>, GetRpcType<T[K]>> : T[K] extends object ? T[K] extends Function | RpcMethodDescriptor ? never : keyof ApiClientDefinition<T[K]> extends never ? never : ApiClientDefinition<T[K]> : never;
106
+ };
107
+ /**
108
+ * We collect the keys whose values are not `never`.
109
+ */
110
+ type FilteredKeys<T> = {
111
+ [K in keyof T]: T[K] extends never ? never : K;
112
+ }[keyof T];
113
+ /**
114
+ * The final type only contains filtered and transformed properties.
115
+ * @internal
116
+ */
117
+ export type ApiClientDefinition<T> = Pick<MappedApi<T>, FilteredKeys<MappedApi<T>>>;
118
+ /**
119
+ *Extract the ARGS type from the zod schema or the implementation function.
120
+ */
121
+ type GetArgs<T> = T extends {
122
+ zodSchema: z.ZodSchema<infer ZodArgs>;
123
+ } ? ZodArgs : T extends {
124
+ implementation: (args: infer ARGS) => any;
125
+ } ? ARGS : never;
126
+ /**
127
+ * Extract the success return type (non-error returns).
128
+ */
129
+ type GetSuccessRet<T> = T extends {
130
+ implementation: (args: any) => infer R;
131
+ } ? ExtractSuccess<UnwrappedPromise<R>> : never;
132
+ /**
133
+ * Extract the error return type (rpc error objects).
134
+ */
135
+ type GetErrorRet<T> = T extends {
136
+ implementation: (args: any) => infer R;
137
+ } ? ExtractErrors<UnwrappedPromise<R>> : never;
138
+ /**
139
+ * Extract the RPC type ('query', 'command', 'get').
140
+ */
141
+ type GetRpcType<T> = T extends RpcMethodDescriptor ? T["rpcType"] : never;
142
+ /**
143
+ * Describes the callable method that the proxy returns.
144
+ */
145
+ type CallableRpcMethod<ARGS, TSuccess, TError extends RpcErrorShape, Type extends "query" | "command" | "get"> = Type extends "command" ? {
146
+ $command: (args: ARGS, options?: CallOptions) => Promise<RpcResponse<TSuccess, TError>>;
147
+ } : Type extends "query" ? {
148
+ $query: (args: ARGS, options?: CallOptions) => Promise<RpcResponse<TSuccess, TError>>;
149
+ } : Type extends "get" ? {
150
+ $get: (args: ARGS, options?: CallOptions) => Promise<RpcResponse<TSuccess, TError>>;
151
+ } : never;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ export { createClient } from "./client/create-client.js";
2
+ export { makeClientMiddleware } from "./client/middleware.js";
3
+ export { clientLogger } from "./client/logger.js";
4
+ export { RpcResponse } from "./client/rpc-response.js";
5
+ export { createCoreHandler, flattenApiDefinition } from "./server/create-handler.js";
6
+ export { makeServerMiddleware } from "./server/middleware.js";
7
+ export { rpcFactory, rpc } from "./server/rpc.js";
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ export { createClient } from "./client/create-client.js";
2
+ export { makeClientMiddleware } from "./client/middleware.js";
3
+ export { clientLogger } from "./client/logger.js";
4
+ export { RpcResponse } from "./client/rpc-response.js";
5
+ export { createCoreHandler, flattenApiDefinition } from "./server/create-handler.js";
6
+ export { makeServerMiddleware } from "./server/middleware.js";
7
+ export { rpcFactory, rpc } from "./server/rpc.js";
@@ -0,0 +1,18 @@
1
+ import { ServerContext } from "./server-context.js";
2
+ import type { ApiDefinition } from "./types.js";
3
+ export declare function flattenApiDefinition(apiDefinition: ApiDefinition<any>): Map<string, {
4
+ rpcType: string;
5
+ handler: (ctx: ServerContext<any>) => Promise<any>;
6
+ }>;
7
+ /**
8
+ * Creates a framework-agnostic handler function for processing RPC requests.
9
+ *
10
+ * @param endpointMap - The flattened API endpoint map produced by `flattenApiDefinition`.
11
+ * @param options
12
+ * @return An async function that accepts a standard `Request`, route info, and an optional adapter context.
13
+ */
14
+ export declare function createCoreHandler<TAdapter = unknown>(endpointMap: ReturnType<typeof flattenApiDefinition>, options?: {
15
+ createServerContext?: (args: any, request: Request, adapterContext: TAdapter) => ServerContext<TAdapter>;
16
+ }): (request: Request, routeInfo: {
17
+ path: string;
18
+ }, adapterContext?: TAdapter) => Promise<Response>;