@orpc/client 0.0.0-next.aa57fb6 → 0.0.0-next.ad0709a

Sign up to get free protection for your applications and to get access to all the features.
package/dist/fetch.js ADDED
@@ -0,0 +1,103 @@
1
+ // src/adapters/fetch/orpc-link.ts
2
+ import { ORPCError } from "@orpc/contract";
3
+ import { fetchReToStandardBody } from "@orpc/server/fetch";
4
+ import { RPCSerializer } from "@orpc/server/standard";
5
+ import { isPlainObject, trim } from "@orpc/shared";
6
+ import cd from "content-disposition";
7
+ var RPCLink = class {
8
+ fetch;
9
+ rpcSerializer;
10
+ maxURLLength;
11
+ fallbackMethod;
12
+ getMethod;
13
+ getHeaders;
14
+ url;
15
+ constructor(options) {
16
+ this.fetch = options.fetch ?? globalThis.fetch.bind(globalThis);
17
+ this.rpcSerializer = options.rpcSerializer ?? new RPCSerializer();
18
+ this.maxURLLength = options.maxURLLength ?? 2083;
19
+ this.fallbackMethod = options.fallbackMethod ?? "POST";
20
+ this.url = options.url;
21
+ this.getMethod = async (path, input, context) => {
22
+ return await options.method?.(path, input, context) ?? this.fallbackMethod;
23
+ };
24
+ this.getHeaders = async (path, input, context) => {
25
+ return new Headers(await options.headers?.(path, input, context));
26
+ };
27
+ }
28
+ async call(path, input, options) {
29
+ const clientContext = options.context;
30
+ const encoded = await this.encode(path, input, options);
31
+ if (encoded.body instanceof Blob && !encoded.headers.has("content-disposition")) {
32
+ encoded.headers.set("content-disposition", cd(encoded.body instanceof File ? encoded.body.name : "blob"));
33
+ }
34
+ const response = await this.fetch(encoded.url, {
35
+ method: encoded.method,
36
+ headers: encoded.headers,
37
+ body: encoded.body,
38
+ signal: options.signal
39
+ }, clientContext);
40
+ const body = await fetchReToStandardBody(response);
41
+ const deserialized = (() => {
42
+ try {
43
+ return this.rpcSerializer.deserialize(body);
44
+ } catch (error) {
45
+ if (response.ok) {
46
+ throw new ORPCError("INTERNAL_SERVER_ERROR", {
47
+ message: "Invalid RPC response",
48
+ cause: error
49
+ });
50
+ }
51
+ throw new ORPCError(response.status.toString(), {
52
+ message: response.statusText
53
+ });
54
+ }
55
+ })();
56
+ if (response.ok) {
57
+ return deserialized;
58
+ }
59
+ throw ORPCError.fromJSON(deserialized);
60
+ }
61
+ async encode(path, input, options) {
62
+ const clientContext = options.context;
63
+ const expectMethod = await this.getMethod(path, input, clientContext);
64
+ const headers = await this.getHeaders(path, input, clientContext);
65
+ const url = new URL(`${trim(this.url, "/")}/${path.map(encodeURIComponent).join("/")}`);
66
+ headers.append("x-orpc-handler", "rpc");
67
+ const serialized = this.rpcSerializer.serialize(input);
68
+ if (expectMethod === "GET" && isPlainObject(serialized)) {
69
+ const tryURL = new URL(url);
70
+ tryURL.searchParams.append("data", JSON.stringify(serialized));
71
+ if (tryURL.toString().length <= this.maxURLLength) {
72
+ return {
73
+ body: void 0,
74
+ method: expectMethod,
75
+ headers,
76
+ url: tryURL
77
+ };
78
+ }
79
+ }
80
+ const method = expectMethod === "GET" ? this.fallbackMethod : expectMethod;
81
+ if (isPlainObject(serialized)) {
82
+ if (!headers.has("content-type")) {
83
+ headers.set("content-type", "application/json");
84
+ }
85
+ return {
86
+ body: JSON.stringify(serialized),
87
+ method,
88
+ headers,
89
+ url
90
+ };
91
+ }
92
+ return {
93
+ body: serialized,
94
+ method,
95
+ headers,
96
+ url
97
+ };
98
+ }
99
+ };
100
+ export {
101
+ RPCLink
102
+ };
103
+ //# sourceMappingURL=fetch.js.map
package/dist/index.js CHANGED
@@ -1,83 +1,42 @@
1
- // src/procedure.ts
2
- import {
3
- ORPC_HEADER,
4
- ORPC_HEADER_VALUE
5
- } from "@orpc/contract";
6
- import { trim } from "@orpc/shared";
7
- import { ORPCError } from "@orpc/shared/error";
8
- import { ORPCDeserializer, ORPCSerializer } from "@orpc/transformer";
9
- function createProcedureClient(options) {
10
- const serializer = new ORPCSerializer();
11
- const deserializer = new ORPCDeserializer();
12
- const client = async (input) => {
13
- const fetch_ = options.fetch ?? fetch;
14
- const url = `${trim(options.baseURL, "/")}/${options.path.map(encodeURIComponent).join("/")}`;
15
- let headers = await options.headers?.(input);
16
- headers = headers instanceof Headers ? headers : new Headers(headers);
17
- const { body, headers: headers_ } = serializer.serialize(input);
18
- for (const [key, value] of headers_.entries()) {
19
- headers.set(key, value);
20
- }
21
- headers.set(ORPC_HEADER, ORPC_HEADER_VALUE);
22
- const response = await fetch_(url, {
23
- method: "POST",
24
- headers,
25
- body
26
- });
27
- const json = await (async () => {
28
- try {
29
- return await deserializer.deserialize(response);
30
- } catch (e) {
31
- throw new ORPCError({
32
- code: "INTERNAL_SERVER_ERROR",
33
- message: "Cannot parse response.",
34
- cause: e
35
- });
1
+ // src/client.ts
2
+ function createORPCClient(link, options) {
3
+ const path = options?.path ?? [];
4
+ const procedureClient = async (...[input, options2]) => {
5
+ return await link.call(path, input, options2 ?? {});
6
+ };
7
+ const recursive = new Proxy(procedureClient, {
8
+ get(target, key) {
9
+ if (typeof key !== "string") {
10
+ return Reflect.get(target, key);
36
11
  }
37
- })();
38
- if (!response.ok) {
39
- throw ORPCError.fromJSON(json) ?? new ORPCError({
40
- status: response.status,
41
- code: "INTERNAL_SERVER_ERROR",
42
- message: "Internal server error"
12
+ return createORPCClient(link, {
13
+ ...options,
14
+ path: [...path, key]
43
15
  });
44
16
  }
45
- return json;
46
- };
47
- return client;
17
+ });
18
+ return recursive;
48
19
  }
49
20
 
50
- // src/router.ts
51
- function createRouterClient(options) {
52
- const path = options?.path ?? [];
53
- const client = new Proxy(
54
- createProcedureClient({
55
- baseURL: options.baseURL,
56
- fetch: options.fetch,
57
- headers: options.headers,
58
- path
59
- }),
60
- {
61
- get(target, key) {
62
- if (typeof key !== "string") {
63
- return Reflect.get(target, key);
64
- }
65
- return createRouterClient({
66
- ...options,
67
- path: [...path, key]
68
- });
69
- }
70
- }
71
- );
72
- return client;
73
- }
21
+ // src/dynamic-link.ts
22
+ var DynamicLink = class {
23
+ constructor(linkResolver) {
24
+ this.linkResolver = linkResolver;
25
+ }
26
+ async call(path, input, options) {
27
+ const resolvedLink = await this.linkResolver(path, input, options.context);
28
+ const output = await resolvedLink.call(path, input, options);
29
+ return output;
30
+ }
31
+ };
74
32
 
75
33
  // src/index.ts
76
- export * from "@orpc/shared/error";
77
- var createORPCClient = createRouterClient;
34
+ import { isDefinedError, ORPCError, safe } from "@orpc/contract";
78
35
  export {
36
+ DynamicLink,
37
+ ORPCError,
79
38
  createORPCClient,
80
- createProcedureClient,
81
- createRouterClient
39
+ isDefinedError,
40
+ safe
82
41
  };
83
42
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,3 @@
1
+ export * from './orpc-link';
2
+ export * from './types';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,46 @@
1
+ import type { ClientOptions, HTTPMethod } from '@orpc/contract';
2
+ import type { Promisable } from '@orpc/shared';
3
+ import type { ClientLink } from '../../types';
4
+ import type { FetchWithContext } from './types';
5
+ import { RPCSerializer } from '@orpc/server/standard';
6
+ export interface RPCLinkOptions<TClientContext> {
7
+ /**
8
+ * Base url for all requests.
9
+ */
10
+ url: string;
11
+ /**
12
+ * The maximum length of the URL.
13
+ *
14
+ * @default 2083
15
+ */
16
+ maxURLLength?: number;
17
+ /**
18
+ * The method used to make the request.
19
+ *
20
+ * @default 'POST'
21
+ */
22
+ method?(path: readonly string[], input: unknown, context: TClientContext): Promisable<HTTPMethod | undefined>;
23
+ /**
24
+ * The method to use when the payload cannot safely pass to the server with method return from method function.
25
+ * GET is not allowed, it's very dangerous.
26
+ *
27
+ * @default 'POST'
28
+ */
29
+ fallbackMethod?: Exclude<HTTPMethod, 'GET'>;
30
+ headers?(path: readonly string[], input: unknown, context: TClientContext): Promisable<Headers | Record<string, string>>;
31
+ fetch?: FetchWithContext<TClientContext>;
32
+ rpcSerializer?: RPCSerializer;
33
+ }
34
+ export declare class RPCLink<TClientContext> implements ClientLink<TClientContext> {
35
+ private readonly fetch;
36
+ private readonly rpcSerializer;
37
+ private readonly maxURLLength;
38
+ private readonly fallbackMethod;
39
+ private readonly getMethod;
40
+ private readonly getHeaders;
41
+ private readonly url;
42
+ constructor(options: RPCLinkOptions<TClientContext>);
43
+ call(path: readonly string[], input: unknown, options: ClientOptions<TClientContext>): Promise<unknown>;
44
+ private encode;
45
+ }
46
+ //# sourceMappingURL=orpc-link.d.ts.map
@@ -0,0 +1,4 @@
1
+ export interface FetchWithContext<TClientContext> {
2
+ (input: RequestInfo | URL, init: RequestInit | undefined, context: TClientContext): Promise<Response>;
3
+ }
4
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,11 @@
1
+ import type { AnyContractRouter, ContractRouterClient } from '@orpc/contract';
2
+ import type { AnyRouter, RouterClient } from '@orpc/server';
3
+ import type { ClientLink } from './types';
4
+ export interface createORPCClientOptions {
5
+ /**
6
+ * Use as base path for all procedure, useful when you only want to call a subset of the procedure.
7
+ */
8
+ path?: string[];
9
+ }
10
+ export declare function createORPCClient<TRouter extends AnyRouter | AnyContractRouter, TClientContext = unknown>(link: ClientLink<TClientContext>, options?: createORPCClientOptions): TRouter extends AnyRouter ? RouterClient<TRouter, TClientContext> : TRouter extends AnyContractRouter ? ContractRouterClient<TRouter, TClientContext> : never;
11
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1,13 @@
1
+ import type { ClientOptions } from '@orpc/contract';
2
+ import type { Promisable } from '@orpc/shared';
3
+ import type { ClientLink } from './types';
4
+ /**
5
+ * DynamicLink provides a way to dynamically resolve and delegate calls to other ClientLinks
6
+ * based on the request path, input, and context.
7
+ */
8
+ export declare class DynamicLink<TClientContext> implements ClientLink<TClientContext> {
9
+ private readonly linkResolver;
10
+ constructor(linkResolver: (path: readonly string[], input: unknown, context: TClientContext) => Promisable<ClientLink<TClientContext>>);
11
+ call(path: readonly string[], input: unknown, options: ClientOptions<TClientContext>): Promise<unknown>;
12
+ }
13
+ //# sourceMappingURL=dynamic-link.d.ts.map
@@ -1,7 +1,6 @@
1
1
  /** unnoq */
2
- import { createRouterClient } from './router';
3
- export * from './procedure';
4
- export * from './router';
5
- export * from '@orpc/shared/error';
6
- export declare const createORPCClient: typeof createRouterClient;
2
+ export * from './client';
3
+ export * from './dynamic-link';
4
+ export * from './types';
5
+ export { isDefinedError, ORPCError, safe } from '@orpc/contract';
7
6
  //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,5 @@
1
+ import type { ClientOptions } from '@orpc/contract';
2
+ export interface ClientLink<TClientContext> {
3
+ call(path: readonly string[], input: unknown, options: ClientOptions<TClientContext>): Promise<unknown>;
4
+ }
5
+ //# sourceMappingURL=types.d.ts.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@orpc/client",
3
3
  "type": "module",
4
- "version": "0.0.0-next.aa57fb6",
4
+ "version": "0.0.0-next.ad0709a",
5
5
  "license": "MIT",
6
6
  "homepage": "https://orpc.unnoq.com",
7
7
  "repository": {
@@ -19,6 +19,11 @@
19
19
  "import": "./dist/index.js",
20
20
  "default": "./dist/index.js"
21
21
  },
22
+ "./fetch": {
23
+ "types": "./dist/src/adapters/fetch/index.d.ts",
24
+ "import": "./dist/fetch.js",
25
+ "default": "./dist/fetch.js"
26
+ },
22
27
  "./🔒/*": {
23
28
  "types": "./dist/src/*.d.ts"
24
29
  }
@@ -28,20 +33,18 @@
28
33
  "!**/*.tsbuildinfo",
29
34
  "dist"
30
35
  ],
31
- "peerDependencies": {
32
- "@orpc/contract": "0.0.0-next.aa57fb6",
33
- "@orpc/server": "0.0.0-next.aa57fb6"
34
- },
35
36
  "dependencies": {
36
- "@orpc/shared": "0.0.0-next.aa57fb6",
37
- "@orpc/transformer": "0.0.0-next.aa57fb6"
37
+ "content-disposition": "^0.5.4",
38
+ "@orpc/server": "0.0.0-next.ad0709a",
39
+ "@orpc/contract": "0.0.0-next.ad0709a",
40
+ "@orpc/shared": "0.0.0-next.ad0709a"
38
41
  },
39
42
  "devDependencies": {
40
- "zod": "^3.23.8",
41
- "@orpc/openapi": "0.0.0-next.aa57fb6"
43
+ "zod": "^3.24.1",
44
+ "@orpc/openapi": "0.0.0-next.ad0709a"
42
45
  },
43
46
  "scripts": {
44
- "build": "tsup --clean --sourcemap --entry.index=src/index.ts --format=esm --onSuccess='tsc -b --noCheck'",
47
+ "build": "tsup --clean --sourcemap --entry.index=src/index.ts --entry.fetch=src/adapters/fetch/index.ts --format=esm --onSuccess='tsc -b --noCheck'",
45
48
  "build:watch": "pnpm run build --watch",
46
49
  "type:check": "tsc -b"
47
50
  }
@@ -1,27 +0,0 @@
1
- import type { Promisable } from '@orpc/shared';
2
- import { type Schema, type SchemaInput, type SchemaOutput } from '@orpc/contract';
3
- export interface ProcedureClient<TInputSchema extends Schema, TOutputSchema extends Schema, TFuncOutput extends SchemaOutput<TOutputSchema>> {
4
- (input: SchemaInput<TInputSchema>): Promise<SchemaOutput<TOutputSchema, TFuncOutput>>;
5
- }
6
- export interface CreateProcedureClientOptions {
7
- /**
8
- * The base url of the server.
9
- */
10
- baseURL: string;
11
- /**
12
- * The fetch function used to make the request.
13
- * @default global fetch
14
- */
15
- fetch?: typeof fetch;
16
- /**
17
- * The headers used to make the request.
18
- * Invoked before the request is made.
19
- */
20
- headers?: (input: unknown) => Promisable<Headers | Record<string, string>>;
21
- /**
22
- * The path of the procedure on server.
23
- */
24
- path: string[];
25
- }
26
- export declare function createProcedureClient<TInputSchema extends Schema, TOutputSchema extends Schema, TFuncOutput extends SchemaOutput<TOutputSchema>>(options: CreateProcedureClientOptions): ProcedureClient<TInputSchema, TOutputSchema, TFuncOutput>;
27
- //# sourceMappingURL=procedure.d.ts.map
@@ -1,34 +0,0 @@
1
- import type { ContractProcedure, ContractRouter, SchemaOutput } from '@orpc/contract';
2
- import type { Lazy, Procedure, Router } from '@orpc/server';
3
- import type { Promisable } from '@orpc/shared';
4
- import { type ProcedureClient } from './procedure';
5
- export type RouterClientWithContractRouter<TRouter extends ContractRouter> = {
6
- [K in keyof TRouter]: TRouter[K] extends ContractProcedure<infer UInputSchema, infer UOutputSchema> ? ProcedureClient<UInputSchema, UOutputSchema, SchemaOutput<UOutputSchema>> : TRouter[K] extends ContractRouter ? RouterClientWithContractRouter<TRouter[K]> : never;
7
- };
8
- export type RouterClientWithRouter<TRouter extends Router<any>> = {
9
- [K in keyof TRouter]: TRouter[K] extends Procedure<any, any, infer UInputSchema, infer UOutputSchema, infer UFuncOutput> | Lazy<Procedure<any, any, infer UInputSchema, infer UOutputSchema, infer UFuncOutput>> ? ProcedureClient<UInputSchema, UOutputSchema, UFuncOutput> : TRouter[K] extends Router<any> ? RouterClientWithRouter<TRouter[K]> : never;
10
- };
11
- export interface CreateRouterClientOptions {
12
- /**
13
- * The base url of the server.
14
- */
15
- baseURL: string;
16
- /**
17
- * The fetch function used to make the request.
18
- * @default global fetch
19
- */
20
- fetch?: typeof fetch;
21
- /**
22
- * The headers used to make the request.
23
- * Invoked before the request is made.
24
- */
25
- headers?: (input: unknown) => Promisable<Headers | Record<string, string>>;
26
- /**
27
- * This used for internal purpose only.
28
- *
29
- * @internal
30
- */
31
- path?: string[];
32
- }
33
- export declare function createRouterClient<TRouter extends Router<any> | ContractRouter>(options: CreateRouterClientOptions): TRouter extends Router<any> ? RouterClientWithRouter<TRouter> : TRouter extends ContractRouter ? RouterClientWithContractRouter<TRouter> : never;
34
- //# sourceMappingURL=router.d.ts.map