@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,210 @@
1
+ import { Packr } from "msgpackr";
2
+ import { z } from "zod";
3
+ import { RPC_ERROR_KEY } from "../util/constants.js";
4
+ import { pipeline } from "../util/pipeline.js";
5
+ import { getMiddlewares } from "./middleware.js";
6
+ import { ServerContext } from "./server-context.js";
7
+ import { camelToKebabCase } from "../util/string.js";
8
+ const packr = new Packr({ structuredClone: true, useRecords: true });
9
+ const acceptedRequests = ["GET.query", "GET.get", "POST.command"];
10
+ const acceptedMethods = ["GET", "POST"];
11
+ export function flattenApiDefinition(apiDefinition) {
12
+ const map = new Map();
13
+ function traverse(obj, prefix, inheritedMiddlewares) {
14
+ const middlewares = [...inheritedMiddlewares, ...getMiddlewares(obj)];
15
+ for (const key of Object.keys(obj)) {
16
+ const value = obj[key];
17
+ if (!value || typeof value !== "object")
18
+ continue;
19
+ const fullKey = prefix ? `${prefix}.${camelToKebabCase(key)}` : camelToKebabCase(key);
20
+ if ("rpcType" in value) {
21
+ const { rpcType, implementation, zodSchema } = value;
22
+ const methodMiddlewares = [...middlewares, ...getMiddlewares(value)];
23
+ const handler = (ctx) => pipeline(ctx, ...methodMiddlewares, (ctx) => {
24
+ let args = ctx.getArgs();
25
+ if (zodSchema)
26
+ args = zodSchema.parse(args);
27
+ return implementation(args, ctx);
28
+ });
29
+ map.set(fullKey, { rpcType, handler });
30
+ }
31
+ else {
32
+ traverse(value, fullKey, middlewares);
33
+ }
34
+ }
35
+ }
36
+ traverse(apiDefinition, "", []);
37
+ return map;
38
+ }
39
+ /**
40
+ * Creates a framework-agnostic handler function for processing RPC requests.
41
+ *
42
+ * @param endpointMap - The flattened API endpoint map produced by `flattenApiDefinition`.
43
+ * @param options
44
+ * @return An async function that accepts a standard `Request`, route info, and an optional adapter context.
45
+ */
46
+ export function createCoreHandler(endpointMap, options) {
47
+ const createServerContext = options?.createServerContext ||
48
+ ((args, request, adapterContext) => new ServerContext(args, request, adapterContext));
49
+ return async function coreHandler(request, routeInfo, adapterContext) {
50
+ if (!acceptedMethods.includes(request.method))
51
+ return new Response("Method not allowed", { status: 405 });
52
+ if (!routeInfo.path)
53
+ return new Response("RPC method not found", { status: 404 });
54
+ const entry = endpointMap.get(routeInfo.path);
55
+ if (!entry)
56
+ return new Response("RPC method not found", { status: 404 });
57
+ const { rpcType, handler: rpcHandler } = entry;
58
+ if (!acceptedRequests.includes(request.method + "." + rpcType))
59
+ return new Response(`RPC type ${rpcType} not allowed for ${request.method} requests`, { status: 405 });
60
+ let args;
61
+ try {
62
+ switch (rpcType) {
63
+ case "get":
64
+ args = parseGet(new URL(request.url));
65
+ break;
66
+ case "query":
67
+ args = parseQuery(new URL(request.url));
68
+ break;
69
+ case "command":
70
+ const requestContentType = request.headers.get("Content-Type") || "";
71
+ if (requestContentType.includes("multipart/form-data")) {
72
+ args = await parseCommandMultipartFormData(request);
73
+ }
74
+ else if (requestContentType.includes("application/json")) {
75
+ args = await parseCommandJson(request);
76
+ }
77
+ else if (requestContentType.includes("application/msgpack") || requestContentType === "") {
78
+ args = await parseCommandMsgpackr(request);
79
+ }
80
+ else {
81
+ return new Response(`Unsupported Content-Type: ${requestContentType}`, { status: 415 });
82
+ }
83
+ break;
84
+ }
85
+ }
86
+ catch (e) {
87
+ if (e instanceof ParseError)
88
+ return new Response(e.message, { status: 400 });
89
+ console.error("[rpc] Unexpected error during request parsing:", e);
90
+ return new Response("Internal server error", { status: 500 });
91
+ }
92
+ const ctx = createServerContext(args, request, adapterContext);
93
+ try {
94
+ const result = await rpcHandler(ctx);
95
+ return makeResponse(result, ctx, request);
96
+ }
97
+ catch (e) {
98
+ if (e instanceof z.ZodError) {
99
+ ctx.cache.set(0);
100
+ return makeResponse({ [RPC_ERROR_KEY]: "INVALID_ARGUMENT", issues: e.issues }, ctx, request);
101
+ }
102
+ console.error("[rpc] Unhandled error in RPC handler:", e);
103
+ const correlationId = crypto.randomUUID();
104
+ return new Response(JSON.stringify({ [RPC_ERROR_KEY]: "INTERNAL_ERROR", correlationId }), { status: 500, headers: { "Content-Type": "application/json" } });
105
+ }
106
+ };
107
+ }
108
+ function makeResponse(result, ctx, request) {
109
+ const prefersJson = request.headers.get("Accept")?.includes("application/json");
110
+ ctx.headers.response.set("x-atom-forge-rpc-exec-time", `${ctx.elapsedTime}`);
111
+ ctx.headers.response.set("Content-Type", prefersJson ? "application/json" : "application/msgpack");
112
+ if (request.method === "GET" && ctx.cache.get()) {
113
+ ctx.headers.response.set("Cache-Control", `public, max-age=${ctx.cache.get()}`);
114
+ }
115
+ let body;
116
+ if (prefersJson) {
117
+ body = JSON.stringify(result);
118
+ }
119
+ else {
120
+ body = new Uint8Array(packr.pack(result));
121
+ }
122
+ return new Response(body, {
123
+ headers: ctx.headers.response,
124
+ status: ctx.status.get(),
125
+ });
126
+ }
127
+ function parseGet(url) {
128
+ let args = {};
129
+ url.searchParams.forEach((value, key) => (args[key] = value));
130
+ return args;
131
+ }
132
+ function parseQuery(url) {
133
+ let args = {};
134
+ try {
135
+ const argsParam = url.searchParams.get("args");
136
+ if (argsParam)
137
+ args = packr.unpack(Buffer.from(argsParam, "base64url"));
138
+ }
139
+ catch (e) {
140
+ throw new ParseError("Invalid msgpackr body");
141
+ }
142
+ return args;
143
+ }
144
+ async function parseCommandMsgpackr(request) {
145
+ let args = {};
146
+ try {
147
+ const buffer = new Uint8Array(await request.arrayBuffer());
148
+ if (buffer.length > 0)
149
+ args = packr.unpack(buffer) || {};
150
+ }
151
+ catch (e) {
152
+ throw new ParseError("Invalid msgpackr body");
153
+ }
154
+ return args;
155
+ }
156
+ async function parseCommandJson(request) {
157
+ let args = {};
158
+ try {
159
+ const text = await request.text();
160
+ if (text)
161
+ args = JSON.parse(text) || {};
162
+ }
163
+ catch (e) {
164
+ throw new ParseError("Invalid JSON body");
165
+ }
166
+ return args;
167
+ }
168
+ async function parseCommandMultipartFormData(request) {
169
+ let args = {};
170
+ const formData = await request.formData();
171
+ const argsBlob = formData.get("args");
172
+ if (argsBlob instanceof Blob) {
173
+ const buffer = new Uint8Array(await argsBlob.arrayBuffer());
174
+ switch (argsBlob.type) {
175
+ case "application/json":
176
+ try {
177
+ args = JSON.parse(new TextDecoder().decode(buffer)) || {};
178
+ }
179
+ catch (e) {
180
+ throw new ParseError("Invalid JSON in args blob");
181
+ }
182
+ break;
183
+ case "application/msgpack":
184
+ try {
185
+ args = packr.unpack(buffer) || {};
186
+ }
187
+ catch (e) {
188
+ throw new ParseError("Invalid msgpack in args blob");
189
+ }
190
+ break;
191
+ default:
192
+ throw new ParseError(`Unsupported args type: ${argsBlob.type}`);
193
+ }
194
+ }
195
+ const keys = new Set();
196
+ formData.forEach((_, key) => keys.add(key));
197
+ for (const key of keys) {
198
+ if (key === "args")
199
+ continue;
200
+ if (key.endsWith("[]")) {
201
+ args[key.substring(0, key.length - 2)] = formData.getAll(key);
202
+ }
203
+ else {
204
+ args[key] = formData.get(key);
205
+ }
206
+ }
207
+ return args;
208
+ }
209
+ class ParseError extends Error {
210
+ }
@@ -0,0 +1,10 @@
1
+ export declare function invalidArgument(details?: Record<string, any>): {
2
+ "atomforge.rpc.error": "INVALID_ARGUMENT";
3
+ };
4
+ export declare function permissionDenied(details?: Record<string, any>): {
5
+ "atomforge.rpc.error": "PERMISSION_DENIED";
6
+ };
7
+ export declare function internalError(details?: Record<string, any>): {
8
+ "atomforge.rpc.error": "INTERNAL_ERROR";
9
+ correlationId: `${string}-${string}-${string}-${string}-${string}`;
10
+ };
@@ -0,0 +1,14 @@
1
+ import { RPC_ERROR_KEY } from "../util/constants.js";
2
+ export function invalidArgument(details) {
3
+ return { [RPC_ERROR_KEY]: "INVALID_ARGUMENT", ...details };
4
+ }
5
+ export function permissionDenied(details) {
6
+ return { [RPC_ERROR_KEY]: "PERMISSION_DENIED", ...details };
7
+ }
8
+ export function internalError(details) {
9
+ return {
10
+ [RPC_ERROR_KEY]: "INTERNAL_ERROR",
11
+ correlationId: crypto.randomUUID(),
12
+ ...details,
13
+ };
14
+ }
@@ -0,0 +1,22 @@
1
+ import type { ServerContext } from "./server-context.js";
2
+ import type { ServerMiddleware } from "./types.js";
3
+ /**
4
+ * Retrieves the middleware functions associated with the given target.
5
+ * @internal
6
+ */
7
+ export declare function getMiddlewares<T = any>(target: any): T[];
8
+ /**
9
+ * Adds ServerMiddleware to the given target. When the target is an array, it adds the middlewares to all targets.
10
+ * @param target
11
+ * @param middleware
12
+ * @internal
13
+ */
14
+ export declare function addMiddleware<TARGET>(target: TARGET, middleware: ServerMiddleware | ServerMiddleware[]): TARGET;
15
+ /**
16
+ * Creates a ServerMiddleware function.
17
+ * @param middleware
18
+ * @param accessors
19
+ */
20
+ export declare function makeServerMiddleware<ACCESSORS extends {
21
+ [key: string]: (ctx: ServerContext) => any;
22
+ }>(middleware: ServerMiddleware, accessors?: ACCESSORS): ServerMiddleware & ACCESSORS;
@@ -0,0 +1,39 @@
1
+ const MIDDLEWARE = Symbol("MIDDLEWARE");
2
+ /**
3
+ * Retrieves the middleware functions associated with the given target.
4
+ * @internal
5
+ */
6
+ export function getMiddlewares(target) {
7
+ return target[MIDDLEWARE] || [];
8
+ }
9
+ /**
10
+ * Adds ServerMiddleware to the given target. When the target is an array, it adds the middlewares to all targets.
11
+ * @param target
12
+ * @param middleware
13
+ * @internal
14
+ */
15
+ export function addMiddleware(target, middleware) {
16
+ if (Array.isArray(target)) {
17
+ target.forEach((item) => addMiddleware(item, middleware));
18
+ }
19
+ else {
20
+ if (!Array.isArray(middleware))
21
+ middleware = [middleware];
22
+ const t = target;
23
+ if (!Array.isArray(t[MIDDLEWARE]))
24
+ t[MIDDLEWARE] = [];
25
+ t[MIDDLEWARE].push(...middleware);
26
+ }
27
+ return target;
28
+ }
29
+ /**
30
+ * Creates a ServerMiddleware function.
31
+ * @param middleware
32
+ * @param accessors
33
+ */
34
+ export function makeServerMiddleware(middleware, accessors) {
35
+ const handler = middleware;
36
+ if (accessors)
37
+ Object.assign(handler, accessors);
38
+ return handler;
39
+ }
@@ -0,0 +1,65 @@
1
+ import { z } from "zod";
2
+ import { invalidArgument, internalError, permissionDenied } from "./errors.js";
3
+ import { RPC_ERROR_KEY } from "../util/constants.js";
4
+ import type { ServerContext } from "./server-context.js";
5
+ import type { RpcMethodImplementationDescriptor, ServerMiddleware } from "./types.js";
6
+ export declare function rpcFactory<SERVER_CONTEXT extends ServerContext = ServerContext>(): {
7
+ query: <ARGS, RET>(impl: (args: ARGS, ctx: SERVER_CONTEXT) => RET | Promise<RET>) => RpcMethodImplementationDescriptor<ARGS, RET, "query">;
8
+ command: <ARGS, RET>(impl: (args: ARGS, ctx: SERVER_CONTEXT) => RET | Promise<RET>) => RpcMethodImplementationDescriptor<ARGS, RET, "command">;
9
+ get: <ARGS, RET>(impl: (args: ARGS, ctx: SERVER_CONTEXT) => RET | Promise<RET>) => RpcMethodImplementationDescriptor<ARGS, RET, "get">;
10
+ middleware(mw: ServerMiddleware | ServerMiddleware[]): {
11
+ on<TARGET>(target: TARGET): TARGET;
12
+ query: <ARGS, RET>(impl: (args: ARGS, ctx: SERVER_CONTEXT) => RET | Promise<RET>) => RpcMethodImplementationDescriptor<ARGS, RET, "query">;
13
+ command: <ARGS, RET>(impl: (args: ARGS, ctx: SERVER_CONTEXT) => RET | Promise<RET>) => RpcMethodImplementationDescriptor<ARGS, RET, "command">;
14
+ get: <ARGS, RET>(impl: (args: ARGS, ctx: SERVER_CONTEXT) => RET | Promise<RET>) => RpcMethodImplementationDescriptor<ARGS, RET, "get">;
15
+ zod: <TArgsSchema extends Record<string, z.ZodTypeAny>>(schemaDef: TArgsSchema) => {
16
+ query: <TRet>(impl: (args: z.core.$InferObjectOutput<{ -readonly [P in keyof TArgsSchema]: TArgsSchema[P]; }, {}>, ctx: SERVER_CONTEXT) => TRet | Promise<TRet>) => RpcMethodImplementationDescriptor<z.core.$InferObjectOutput<{ -readonly [P in keyof TArgsSchema]: TArgsSchema[P]; }, {}>, TRet, "query">;
17
+ command: <TRet>(impl: (args: z.core.$InferObjectOutput<{ -readonly [P in keyof TArgsSchema]: TArgsSchema[P]; }, {}>, ctx: SERVER_CONTEXT) => TRet | Promise<TRet>) => RpcMethodImplementationDescriptor<z.core.$InferObjectOutput<{ -readonly [P in keyof TArgsSchema]: TArgsSchema[P]; }, {}>, TRet, "command">;
18
+ get: <TRet>(impl: (args: z.core.$InferObjectOutput<{ -readonly [P in keyof TArgsSchema]: TArgsSchema[P]; }, {}>, ctx: SERVER_CONTEXT) => TRet | Promise<TRet>) => RpcMethodImplementationDescriptor<z.core.$InferObjectOutput<{ -readonly [P in keyof TArgsSchema]: TArgsSchema[P]; }, {}>, TRet, "get">;
19
+ };
20
+ };
21
+ zod: <TArgsSchema extends Record<string, z.ZodTypeAny>>(schemaDef: TArgsSchema) => {
22
+ query: <TRet>(impl: (args: z.core.$InferObjectOutput<{ -readonly [P in keyof TArgsSchema]: TArgsSchema[P]; }, {}>, ctx: SERVER_CONTEXT) => TRet | Promise<TRet>) => RpcMethodImplementationDescriptor<z.core.$InferObjectOutput<{ -readonly [P in keyof TArgsSchema]: TArgsSchema[P]; }, {}>, TRet, "query">;
23
+ command: <TRet>(impl: (args: z.core.$InferObjectOutput<{ -readonly [P in keyof TArgsSchema]: TArgsSchema[P]; }, {}>, ctx: SERVER_CONTEXT) => TRet | Promise<TRet>) => RpcMethodImplementationDescriptor<z.core.$InferObjectOutput<{ -readonly [P in keyof TArgsSchema]: TArgsSchema[P]; }, {}>, TRet, "command">;
24
+ get: <TRet>(impl: (args: z.core.$InferObjectOutput<{ -readonly [P in keyof TArgsSchema]: TArgsSchema[P]; }, {}>, ctx: SERVER_CONTEXT) => TRet | Promise<TRet>) => RpcMethodImplementationDescriptor<z.core.$InferObjectOutput<{ -readonly [P in keyof TArgsSchema]: TArgsSchema[P]; }, {}>, TRet, "get">;
25
+ };
26
+ error: {
27
+ make<TCode extends string, TResult extends Record<string, any> = Record<never, never>>(code: TCode, message?: string, result?: TResult): { [K in typeof RPC_ERROR_KEY]: TCode; } & {
28
+ message?: string;
29
+ } & TResult;
30
+ invalidArgument: typeof invalidArgument;
31
+ permissionDenied: typeof permissionDenied;
32
+ internalError: typeof internalError;
33
+ };
34
+ };
35
+ export declare const rpc: {
36
+ query: <ARGS, RET>(impl: (args: ARGS, ctx: ServerContext<unknown>) => RET | Promise<RET>) => RpcMethodImplementationDescriptor<ARGS, RET, "query">;
37
+ command: <ARGS, RET>(impl: (args: ARGS, ctx: ServerContext<unknown>) => RET | Promise<RET>) => RpcMethodImplementationDescriptor<ARGS, RET, "command">;
38
+ get: <ARGS, RET>(impl: (args: ARGS, ctx: ServerContext<unknown>) => RET | Promise<RET>) => RpcMethodImplementationDescriptor<ARGS, RET, "get">;
39
+ middleware(mw: ServerMiddleware | ServerMiddleware[]): {
40
+ on<TARGET>(target: TARGET): TARGET;
41
+ query: <ARGS, RET>(impl: (args: ARGS, ctx: ServerContext<unknown>) => RET | Promise<RET>) => RpcMethodImplementationDescriptor<ARGS, RET, "query">;
42
+ command: <ARGS, RET>(impl: (args: ARGS, ctx: ServerContext<unknown>) => RET | Promise<RET>) => RpcMethodImplementationDescriptor<ARGS, RET, "command">;
43
+ get: <ARGS, RET>(impl: (args: ARGS, ctx: ServerContext<unknown>) => RET | Promise<RET>) => RpcMethodImplementationDescriptor<ARGS, RET, "get">;
44
+ zod: <TArgsSchema extends Record<string, z.ZodTypeAny>>(schemaDef: TArgsSchema) => {
45
+ query: <TRet>(impl: (args: z.core.$InferObjectOutput<{ -readonly [P in keyof TArgsSchema]: TArgsSchema[P]; }, {}>, ctx: ServerContext<unknown>) => TRet | Promise<TRet>) => RpcMethodImplementationDescriptor<z.core.$InferObjectOutput<{ -readonly [P in keyof TArgsSchema]: TArgsSchema[P]; }, {}>, TRet, "query">;
46
+ command: <TRet>(impl: (args: z.core.$InferObjectOutput<{ -readonly [P in keyof TArgsSchema]: TArgsSchema[P]; }, {}>, ctx: ServerContext<unknown>) => TRet | Promise<TRet>) => RpcMethodImplementationDescriptor<z.core.$InferObjectOutput<{ -readonly [P in keyof TArgsSchema]: TArgsSchema[P]; }, {}>, TRet, "command">;
47
+ get: <TRet>(impl: (args: z.core.$InferObjectOutput<{ -readonly [P in keyof TArgsSchema]: TArgsSchema[P]; }, {}>, ctx: ServerContext<unknown>) => TRet | Promise<TRet>) => RpcMethodImplementationDescriptor<z.core.$InferObjectOutput<{ -readonly [P in keyof TArgsSchema]: TArgsSchema[P]; }, {}>, TRet, "get">;
48
+ };
49
+ };
50
+ zod: <TArgsSchema extends Record<string, z.ZodTypeAny>>(schemaDef: TArgsSchema) => {
51
+ query: <TRet>(impl: (args: z.core.$InferObjectOutput<{ -readonly [P in keyof TArgsSchema]: TArgsSchema[P]; }, {}>, ctx: ServerContext<unknown>) => TRet | Promise<TRet>) => RpcMethodImplementationDescriptor<z.core.$InferObjectOutput<{ -readonly [P in keyof TArgsSchema]: TArgsSchema[P]; }, {}>, TRet, "query">;
52
+ command: <TRet>(impl: (args: z.core.$InferObjectOutput<{ -readonly [P in keyof TArgsSchema]: TArgsSchema[P]; }, {}>, ctx: ServerContext<unknown>) => TRet | Promise<TRet>) => RpcMethodImplementationDescriptor<z.core.$InferObjectOutput<{ -readonly [P in keyof TArgsSchema]: TArgsSchema[P]; }, {}>, TRet, "command">;
53
+ get: <TRet>(impl: (args: z.core.$InferObjectOutput<{ -readonly [P in keyof TArgsSchema]: TArgsSchema[P]; }, {}>, ctx: ServerContext<unknown>) => TRet | Promise<TRet>) => RpcMethodImplementationDescriptor<z.core.$InferObjectOutput<{ -readonly [P in keyof TArgsSchema]: TArgsSchema[P]; }, {}>, TRet, "get">;
54
+ };
55
+ error: {
56
+ make<TCode extends string, TResult extends Record<string, any> = Record<never, never>>(code: TCode, message?: string, result?: TResult | undefined): {
57
+ "atomforge.rpc.error": TCode;
58
+ } & {
59
+ message?: string;
60
+ } & TResult;
61
+ invalidArgument: typeof invalidArgument;
62
+ permissionDenied: typeof permissionDenied;
63
+ internalError: typeof internalError;
64
+ };
65
+ };
@@ -0,0 +1,49 @@
1
+ import { z } from "zod";
2
+ import { invalidArgument, internalError, permissionDenied } from "./errors.js";
3
+ import { RPC_ERROR_KEY } from "../util/constants.js";
4
+ import { addMiddleware } from "./middleware.js";
5
+ export function rpcFactory() {
6
+ function makeDescriptor(rpcType, implementation, zodSchema, middleware) {
7
+ const descriptor = {
8
+ rpcType,
9
+ implementation: implementation,
10
+ ...(zodSchema && { zodSchema }),
11
+ };
12
+ return middleware ? addMiddleware(descriptor, middleware) : descriptor;
13
+ }
14
+ function makeZodMethodSet(schemaDef, middleware) {
15
+ const schema = z.object(schemaDef);
16
+ return {
17
+ query: (impl) => makeDescriptor("query", impl, schema, middleware),
18
+ command: (impl) => makeDescriptor("command", impl, schema, middleware),
19
+ get: (impl) => makeDescriptor("get", impl, schema, middleware),
20
+ };
21
+ }
22
+ return {
23
+ query: (impl) => makeDescriptor("query", impl),
24
+ command: (impl) => makeDescriptor("command", impl),
25
+ get: (impl) => makeDescriptor("get", impl),
26
+ middleware(mw) {
27
+ return {
28
+ on(target) {
29
+ addMiddleware(target, mw);
30
+ return target;
31
+ },
32
+ query: (impl) => makeDescriptor("query", impl, undefined, mw),
33
+ command: (impl) => makeDescriptor("command", impl, undefined, mw),
34
+ get: (impl) => makeDescriptor("get", impl, undefined, mw),
35
+ zod: (schemaDef) => makeZodMethodSet(schemaDef, mw),
36
+ };
37
+ },
38
+ zod: (schemaDef) => makeZodMethodSet(schemaDef),
39
+ error: {
40
+ make(code, message, result) {
41
+ return { [RPC_ERROR_KEY]: code, ...(message !== undefined && { message }), ...(result ?? {}) };
42
+ },
43
+ invalidArgument,
44
+ permissionDenied,
45
+ internalError,
46
+ },
47
+ };
48
+ }
49
+ export const rpc = rpcFactory();
@@ -0,0 +1,79 @@
1
+ import { CookieManager } from "../util/cookies.js";
2
+ /**
3
+ * The ServerContext class represents the context of a server request, holding
4
+ * details such as headers, arguments, environment variables, caching, and status management.
5
+ * It is designed to provide a streamlined interface for interacting with server-side request data.
6
+ * @internal
7
+ */
8
+ export declare class ServerContext<TAdapter = unknown> {
9
+ readonly request: Request;
10
+ readonly adapterContext: TAdapter;
11
+ private _cache;
12
+ private _status;
13
+ private readonly start;
14
+ /** A Map object representing arguments passed to the server */
15
+ readonly args: Map<string, any>;
16
+ /** An object containing request and response headers */
17
+ readonly headers: {
18
+ request: Headers;
19
+ response: Headers;
20
+ };
21
+ /** Framework-agnostic cookie manager */
22
+ readonly cookies: CookieManager;
23
+ /** Indicates the time elapsed since the creation of the ServerContext instance */
24
+ get elapsedTime(): number;
25
+ constructor(args: Record<string, any> | undefined, request: Request, adapterContext: TAdapter);
26
+ /** Retrieves all arguments as a plain JavaScript object */
27
+ getArgs(): Record<string, any>;
28
+ /** A Map of custom environment variables relevant to the server context. */
29
+ readonly env: Map<string | symbol, any>;
30
+ /** An object with methods to set and get cache duration. */
31
+ readonly cache: {
32
+ set: (seconds: number) => number;
33
+ get: () => number;
34
+ };
35
+ /** An object providing methods to set or retrieve response status codes, as well as predefined shortcuts for common HTTP status codes. */
36
+ readonly status: {
37
+ set: (status: number) => number;
38
+ get: () => number;
39
+ continue: () => number;
40
+ switchingProtocols: () => number;
41
+ processing: () => number;
42
+ ok: () => number;
43
+ created: () => number;
44
+ accepted: () => number;
45
+ noContent: () => number;
46
+ resetContent: () => number;
47
+ partialContent: () => number;
48
+ multipleChoices: () => number;
49
+ movedPermanently: () => number;
50
+ found: () => number;
51
+ seeOther: () => number;
52
+ notModified: () => number;
53
+ temporaryRedirect: () => number;
54
+ permanentRedirect: () => number;
55
+ badRequest: () => number;
56
+ unauthorized: () => number;
57
+ paymentRequired: () => number;
58
+ forbidden: () => number;
59
+ notFound: () => number;
60
+ methodNotAllowed: () => number;
61
+ notAcceptable: () => number;
62
+ conflict: () => number;
63
+ gone: () => number;
64
+ lengthRequired: () => number;
65
+ preconditionFailed: () => number;
66
+ payloadTooLarge: () => number;
67
+ uriTooLong: () => number;
68
+ badContent: () => number;
69
+ rangeNotSatisfiable: () => number;
70
+ expectationFailed: () => number;
71
+ tooManyRequests: () => number;
72
+ serverError: () => number;
73
+ notImplemented: () => number;
74
+ badGateway: () => number;
75
+ serviceUnavailable: () => number;
76
+ gatewayTimeout: () => number;
77
+ httpVersionNotSupported: () => number;
78
+ };
79
+ }
@@ -0,0 +1,86 @@
1
+ import { CookieManager } from "../util/cookies.js";
2
+ /**
3
+ * The ServerContext class represents the context of a server request, holding
4
+ * details such as headers, arguments, environment variables, caching, and status management.
5
+ * It is designed to provide a streamlined interface for interacting with server-side request data.
6
+ * @internal
7
+ */
8
+ export class ServerContext {
9
+ /** Indicates the time elapsed since the creation of the ServerContext instance */
10
+ get elapsedTime() {
11
+ return performance.now() - this.start;
12
+ }
13
+ constructor(args, request, adapterContext) {
14
+ this.request = request;
15
+ this.adapterContext = adapterContext;
16
+ this._cache = 0;
17
+ this._status = 200;
18
+ /** A Map of custom environment variables relevant to the server context. */
19
+ this.env = new Map();
20
+ /** An object with methods to set and get cache duration. */
21
+ this.cache = {
22
+ set: (seconds) => (this._cache = Math.max(0, Math.floor(seconds))),
23
+ get: () => this._cache,
24
+ };
25
+ /** An object providing methods to set or retrieve response status codes, as well as predefined shortcuts for common HTTP status codes. */
26
+ this.status = {
27
+ set: (status) => (this._status = status),
28
+ get: () => this._status,
29
+ // 1xx - Informational
30
+ continue: () => (this._status = 100),
31
+ switchingProtocols: () => (this._status = 101),
32
+ processing: () => (this._status = 102),
33
+ // 2xx - Success
34
+ ok: () => (this._status = 200),
35
+ created: () => (this._status = 201),
36
+ accepted: () => (this._status = 202),
37
+ noContent: () => (this._status = 204),
38
+ resetContent: () => (this._status = 205),
39
+ partialContent: () => (this._status = 206),
40
+ // 3xx - Redirection
41
+ multipleChoices: () => (this._status = 300),
42
+ movedPermanently: () => (this._status = 301),
43
+ found: () => (this._status = 302),
44
+ seeOther: () => (this._status = 303),
45
+ notModified: () => (this._status = 304),
46
+ temporaryRedirect: () => (this._status = 307),
47
+ permanentRedirect: () => (this._status = 308),
48
+ // 4xx - Client Error
49
+ badRequest: () => (this._status = 400),
50
+ unauthorized: () => (this._status = 401),
51
+ paymentRequired: () => (this._status = 402),
52
+ forbidden: () => (this._status = 403),
53
+ notFound: () => (this._status = 404),
54
+ methodNotAllowed: () => (this._status = 405),
55
+ notAcceptable: () => (this._status = 406),
56
+ conflict: () => (this._status = 409),
57
+ gone: () => (this._status = 410),
58
+ lengthRequired: () => (this._status = 411),
59
+ preconditionFailed: () => (this._status = 412),
60
+ payloadTooLarge: () => (this._status = 413),
61
+ uriTooLong: () => (this._status = 414),
62
+ badContent: () => (this._status = 415),
63
+ rangeNotSatisfiable: () => (this._status = 416),
64
+ expectationFailed: () => (this._status = 417),
65
+ tooManyRequests: () => (this._status = 429),
66
+ // 5xx - Server Error
67
+ serverError: () => (this._status = 500),
68
+ notImplemented: () => (this._status = 501),
69
+ badGateway: () => (this._status = 502),
70
+ serviceUnavailable: () => (this._status = 503),
71
+ gatewayTimeout: () => (this._status = 504),
72
+ httpVersionNotSupported: () => (this._status = 505),
73
+ };
74
+ this.start = performance.now();
75
+ this.args = new Map(Object.entries(args || {}));
76
+ this.headers = {
77
+ request: request.headers,
78
+ response: new Headers(),
79
+ };
80
+ this.cookies = new CookieManager(request.headers, this.headers.response);
81
+ }
82
+ /** Retrieves all arguments as a plain JavaScript object */
83
+ getArgs() {
84
+ return Object.fromEntries(this.args);
85
+ }
86
+ }
@@ -0,0 +1,30 @@
1
+ import { z } from "zod";
2
+ import type { RpcMethodDescriptor } from "../client/types.js";
3
+ import type { Middleware } from "../util/pipeline.js";
4
+ import type { ServerContext } from "./server-context.js";
5
+ /**
6
+ * Represents middleware for handling server-specific logic within the application lifecycle.
7
+ * @internal
8
+ */
9
+ export type ServerMiddleware<RET = any> = Middleware<ServerContext, RET>;
10
+ /**
11
+ * Server-side descriptor that contains the actual implementation.
12
+ * @internal
13
+ */
14
+ export interface RpcMethodImplementationDescriptor<ARGS, RET, Type extends "query" | "command" | "get"> extends RpcMethodDescriptor {
15
+ rpcType: Type;
16
+ zodSchema?: z.ZodSchema<ARGS>;
17
+ implementation: (args: ARGS) => RET | Promise<RET>;
18
+ }
19
+ /**
20
+ * Helper type for extracting the Zod schema
21
+ * @internal
22
+ */
23
+ export type GetZodSchema<T> = T extends RpcMethodImplementationDescriptor<any, any, any> ? T["zodSchema"] : undefined;
24
+ /**
25
+ * This generic type is for the API definition on the server side.
26
+ * @internal
27
+ */
28
+ export type ApiDefinition<T> = {
29
+ [K in keyof T]: T[K] extends RpcMethodImplementationDescriptor<any, any, any> ? T[K] : T[K] extends object ? ApiDefinition<T[K]> : T[K];
30
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export declare const RPC_ERROR_KEY: "atomforge.rpc.error";
@@ -0,0 +1 @@
1
+ export const RPC_ERROR_KEY = "atomforge.rpc.error";
@@ -0,0 +1,22 @@
1
+ export interface CookieOptions {
2
+ maxAge?: number;
3
+ expires?: Date;
4
+ path?: string;
5
+ domain?: string;
6
+ secure?: boolean;
7
+ httpOnly?: boolean;
8
+ sameSite?: "Strict" | "Lax" | "None";
9
+ }
10
+ export declare class CookieManager {
11
+ private readonly requestHeaders;
12
+ private readonly responseHeaders;
13
+ private readonly parsed;
14
+ constructor(requestHeaders: Headers, responseHeaders: Headers);
15
+ get(name: string): string | undefined;
16
+ getAll(): {
17
+ name: string;
18
+ value: string;
19
+ }[];
20
+ set(name: string, value: string, options?: CookieOptions): void;
21
+ delete(name: string, options?: Omit<CookieOptions, "maxAge">): void;
22
+ }