silgi 0.0.14 → 0.1.0-beta.1

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 (186) hide show
  1. package/README.md +102 -1
  2. package/dist/_virtual/_rolldown/runtime.mjs +5 -0
  3. package/dist/adapters/astro.d.mts +17 -0
  4. package/dist/adapters/astro.mjs +24 -0
  5. package/dist/adapters/aws-lambda.d.mts +31 -0
  6. package/dist/adapters/aws-lambda.mjs +85 -0
  7. package/dist/adapters/elysia.d.mts +17 -0
  8. package/dist/adapters/elysia.mjs +76 -0
  9. package/dist/adapters/express.d.mts +16 -0
  10. package/dist/adapters/express.mjs +78 -0
  11. package/dist/adapters/fastify.d.mts +15 -0
  12. package/dist/adapters/fastify.mjs +78 -0
  13. package/dist/adapters/message-port.d.mts +37 -0
  14. package/dist/adapters/message-port.mjs +129 -0
  15. package/dist/adapters/nestjs.d.mts +25 -0
  16. package/dist/adapters/nestjs.mjs +91 -0
  17. package/dist/adapters/nextjs.d.mts +21 -0
  18. package/dist/adapters/nextjs.mjs +30 -0
  19. package/dist/adapters/peer.d.mts +27 -0
  20. package/dist/adapters/peer.mjs +36 -0
  21. package/dist/adapters/remix.d.mts +17 -0
  22. package/dist/adapters/remix.mjs +24 -0
  23. package/dist/adapters/solidstart.d.mts +14 -0
  24. package/dist/adapters/solidstart.mjs +30 -0
  25. package/dist/adapters/sveltekit.d.mts +18 -0
  26. package/dist/adapters/sveltekit.mjs +33 -0
  27. package/dist/analyze.mjs +26 -0
  28. package/dist/broker/index.d.mts +62 -0
  29. package/dist/broker/index.mjs +153 -0
  30. package/dist/broker/nats.d.mts +33 -0
  31. package/dist/broker/nats.mjs +31 -0
  32. package/dist/broker/redis.d.mts +51 -0
  33. package/dist/broker/redis.mjs +92 -0
  34. package/dist/builder.d.mts +36 -0
  35. package/dist/builder.mjs +51 -0
  36. package/dist/callable.d.mts +17 -0
  37. package/dist/callable.mjs +42 -0
  38. package/dist/client/adapters/fetch/index.d.mts +17 -0
  39. package/dist/client/adapters/fetch/index.mjs +61 -0
  40. package/dist/client/adapters/ofetch/index.d.mts +41 -0
  41. package/dist/client/adapters/ofetch/index.mjs +92 -0
  42. package/dist/client/client.d.mts +29 -0
  43. package/dist/client/client.mjs +54 -0
  44. package/dist/client/dynamic-link.d.mts +15 -0
  45. package/dist/client/dynamic-link.mjs +16 -0
  46. package/dist/client/index.d.mts +7 -0
  47. package/dist/client/index.mjs +6 -0
  48. package/dist/client/interceptor.d.mts +31 -0
  49. package/dist/client/interceptor.mjs +34 -0
  50. package/dist/client/merge.d.mts +28 -0
  51. package/dist/client/merge.mjs +30 -0
  52. package/dist/client/openapi.d.mts +29 -0
  53. package/dist/client/openapi.mjs +89 -0
  54. package/dist/client/plugins/batch.d.mts +20 -0
  55. package/dist/client/plugins/batch.mjs +64 -0
  56. package/dist/client/plugins/csrf.d.mts +13 -0
  57. package/dist/client/plugins/csrf.mjs +20 -0
  58. package/dist/client/plugins/dedupe.d.mts +10 -0
  59. package/dist/client/plugins/dedupe.mjs +28 -0
  60. package/dist/client/plugins/index.d.mts +5 -0
  61. package/dist/client/plugins/index.mjs +5 -0
  62. package/dist/client/plugins/retry.d.mts +11 -0
  63. package/dist/client/plugins/retry.mjs +21 -0
  64. package/dist/client/server.d.mts +16 -0
  65. package/dist/client/server.mjs +60 -0
  66. package/dist/client/types.d.mts +29 -0
  67. package/dist/codec/devalue.d.mts +21 -0
  68. package/dist/codec/devalue.mjs +32 -0
  69. package/dist/codec/msgpack.d.mts +21 -0
  70. package/dist/codec/msgpack.mjs +59 -0
  71. package/dist/compile.d.mts +52 -0
  72. package/dist/compile.mjs +304 -0
  73. package/dist/contract.d.mts +36 -0
  74. package/dist/contract.mjs +40 -0
  75. package/dist/core/error.d.mts +104 -0
  76. package/dist/core/error.mjs +139 -0
  77. package/dist/core/handler.mjs +546 -0
  78. package/dist/core/iterator.d.mts +17 -0
  79. package/dist/core/iterator.mjs +79 -0
  80. package/dist/core/router-utils.mjs +16 -0
  81. package/dist/core/schema.d.mts +19 -0
  82. package/dist/core/schema.mjs +26 -0
  83. package/dist/core/serve.mjs +38 -0
  84. package/dist/core/sse.d.mts +16 -0
  85. package/dist/core/sse.mjs +95 -0
  86. package/dist/core/storage.d.mts +21 -0
  87. package/dist/core/storage.mjs +63 -0
  88. package/dist/core/utils.mjs +21 -0
  89. package/dist/fast-stringify.mjs +125 -0
  90. package/dist/index.d.mts +15 -37
  91. package/dist/index.mjs +13 -7
  92. package/dist/integrations/ai/index.d.mts +25 -0
  93. package/dist/integrations/ai/index.mjs +116 -0
  94. package/dist/integrations/react/index.d.mts +83 -0
  95. package/dist/integrations/react/index.mjs +197 -0
  96. package/dist/integrations/tanstack-query/index.d.mts +120 -0
  97. package/dist/integrations/tanstack-query/index.mjs +100 -0
  98. package/dist/integrations/tanstack-query/ssr.d.mts +51 -0
  99. package/dist/integrations/tanstack-query/ssr.mjs +89 -0
  100. package/dist/integrations/zod/converter.d.mts +75 -0
  101. package/dist/integrations/zod/converter.mjs +345 -0
  102. package/dist/integrations/zod/index.d.mts +2 -0
  103. package/dist/integrations/zod/index.mjs +2 -0
  104. package/dist/lazy.d.mts +24 -0
  105. package/dist/lazy.mjs +27 -0
  106. package/dist/lifecycle.d.mts +36 -0
  107. package/dist/lifecycle.mjs +46 -0
  108. package/dist/map-input.d.mts +17 -0
  109. package/dist/map-input.mjs +24 -0
  110. package/dist/plugins/analytics.d.mts +168 -0
  111. package/dist/plugins/analytics.mjs +459 -0
  112. package/dist/plugins/batch-server.d.mts +20 -0
  113. package/dist/plugins/batch-server.mjs +86 -0
  114. package/dist/plugins/body-limit.d.mts +16 -0
  115. package/dist/plugins/body-limit.mjs +44 -0
  116. package/dist/plugins/cache.d.mts +170 -0
  117. package/dist/plugins/cache.mjs +200 -0
  118. package/dist/plugins/coerce.d.mts +21 -0
  119. package/dist/plugins/coerce.mjs +46 -0
  120. package/dist/plugins/compression.d.mts +19 -0
  121. package/dist/plugins/compression.mjs +23 -0
  122. package/dist/plugins/cookies.d.mts +44 -0
  123. package/dist/plugins/cookies.mjs +67 -0
  124. package/dist/plugins/cors.d.mts +39 -0
  125. package/dist/plugins/cors.mjs +56 -0
  126. package/dist/plugins/custom-serializer.d.mts +57 -0
  127. package/dist/plugins/custom-serializer.mjs +40 -0
  128. package/dist/plugins/file-upload.d.mts +38 -0
  129. package/dist/plugins/file-upload.mjs +100 -0
  130. package/dist/plugins/index.d.mts +16 -0
  131. package/dist/plugins/index.mjs +16 -0
  132. package/dist/plugins/otel.d.mts +35 -0
  133. package/dist/plugins/otel.mjs +40 -0
  134. package/dist/plugins/pino.d.mts +60 -0
  135. package/dist/plugins/pino.mjs +42 -0
  136. package/dist/plugins/pubsub.d.mts +50 -0
  137. package/dist/plugins/pubsub.mjs +53 -0
  138. package/dist/plugins/ratelimit.d.mts +51 -0
  139. package/dist/plugins/ratelimit.mjs +81 -0
  140. package/dist/plugins/signing.d.mts +41 -0
  141. package/dist/plugins/signing.mjs +115 -0
  142. package/dist/plugins/strict-get.d.mts +10 -0
  143. package/dist/plugins/strict-get.mjs +33 -0
  144. package/dist/route/add.mjs +240 -0
  145. package/dist/route/compiler.mjs +373 -0
  146. package/dist/route/context.mjs +12 -0
  147. package/dist/route/types.d.mts +11 -0
  148. package/dist/route/utils.mjs +17 -0
  149. package/dist/scalar.d.mts +53 -0
  150. package/dist/scalar.mjs +315 -0
  151. package/dist/silgi.d.mts +139 -0
  152. package/dist/silgi.mjs +113 -0
  153. package/dist/trpc-interop.d.mts +22 -0
  154. package/dist/trpc-interop.mjs +68 -0
  155. package/dist/types.d.mts +82 -0
  156. package/dist/ws.d.mts +42 -0
  157. package/dist/ws.mjs +137 -0
  158. package/lib/dashboard/index.html +123 -0
  159. package/lib/ocache.d.mts +1 -0
  160. package/lib/ocache.mjs +1 -0
  161. package/lib/ofetch.d.mts +1 -0
  162. package/lib/ofetch.mjs +1 -0
  163. package/lib/srvx.d.mts +1 -0
  164. package/lib/srvx.mjs +1 -0
  165. package/lib/unstorage.d.mts +1 -0
  166. package/lib/unstorage.mjs +1 -0
  167. package/package.json +291 -65
  168. package/bin/silgi.mjs +0 -3
  169. package/dist/chunks/generate.mjs +0 -933
  170. package/dist/chunks/init.mjs +0 -21
  171. package/dist/cli/config.d.mts +0 -19
  172. package/dist/cli/config.d.ts +0 -19
  173. package/dist/cli/config.mjs +0 -5
  174. package/dist/cli/index.d.mts +0 -2
  175. package/dist/cli/index.d.ts +0 -2
  176. package/dist/cli/index.mjs +0 -119
  177. package/dist/index.d.ts +0 -37
  178. package/dist/plugins/openapi.d.mts +0 -138
  179. package/dist/plugins/openapi.d.ts +0 -138
  180. package/dist/plugins/openapi.mjs +0 -204
  181. package/dist/plugins/scalar.d.mts +0 -14
  182. package/dist/plugins/scalar.d.ts +0 -14
  183. package/dist/plugins/scalar.mjs +0 -66
  184. package/dist/shared/silgi.BMCYk2cR.mjs +0 -841
  185. package/dist/shared/silgi.D5qK9QOm.d.mts +0 -301
  186. package/dist/shared/silgi.D5qK9QOm.d.ts +0 -301
@@ -0,0 +1,92 @@
1
+ import { SilgiError, fromSilgiErrorJSON, isSilgiErrorJSON } from "../../../core/error.mjs";
2
+ import { MSGPACK_CONTENT_TYPE, decode, encode } from "../../../codec/msgpack.mjs";
3
+ import { FetchError, ofetch } from "ofetch";
4
+ //#region src/client/adapters/ofetch/index.ts
5
+ /**
6
+ * ofetch-based RPC transport — v2 client link.
7
+ *
8
+ * Uses ofetch for: retry, timeout, interceptors, auto-JSON.
9
+ * Replaces manual fetch + retry/dedupe plugins with a single link.
10
+ */
11
+ /**
12
+ * Create a Silgi client link powered by ofetch.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * import { createClient } from "silgi/client"
17
+ * import { createLink } from "silgi/client/ofetch"
18
+ *
19
+ * const link = createLink({ url: "http://localhost:3000" })
20
+ * const client = createClient<AppRouter>(link)
21
+ * const users = await client.users.list({ limit: 10 })
22
+ * ```
23
+ */
24
+ function createLink(options) {
25
+ const baseUrl = options.url.endsWith("/") ? options.url.slice(0, -1) : options.url;
26
+ const defaultTimeout = options.timeout ?? 3e4;
27
+ const defaultRetry = options.retry;
28
+ const defaultRetryDelay = options.retryDelay ?? 0;
29
+ const binary = options.binary ?? false;
30
+ const useDevalue = options.devalue ?? false;
31
+ return { async call(path, input, callOptions) {
32
+ const url = `${baseUrl}/${path.map(encodeURIComponent).join("/")}`;
33
+ const headers = { ...typeof options.headers === "function" ? options.headers(callOptions) : options.headers };
34
+ let body;
35
+ if (binary) {
36
+ headers["content-type"] = MSGPACK_CONTENT_TYPE;
37
+ headers["accept"] = MSGPACK_CONTENT_TYPE;
38
+ body = input !== void 0 && input !== null ? encode(input) : void 0;
39
+ } else if (useDevalue) {
40
+ const { encode: devalueEncode, DEVALUE_CONTENT_TYPE } = await import("../../../codec/devalue.mjs");
41
+ headers["content-type"] = DEVALUE_CONTENT_TYPE;
42
+ headers["accept"] = DEVALUE_CONTENT_TYPE;
43
+ body = input !== void 0 && input !== null ? devalueEncode(input) : void 0;
44
+ } else body = input !== void 0 && input !== null ? input : void 0;
45
+ try {
46
+ const data = await ofetch(url, {
47
+ method: "POST",
48
+ headers,
49
+ body,
50
+ signal: callOptions.signal,
51
+ timeout: defaultTimeout,
52
+ retry: defaultRetry ?? 0,
53
+ retryDelay: defaultRetryDelay,
54
+ ignoreResponseError: true,
55
+ onRequest: options.onRequest,
56
+ onResponse: options.onResponse,
57
+ onRequestError: options.onRequestError,
58
+ onResponseError: options.onResponseError,
59
+ ...binary ? { responseType: "arrayBuffer" } : useDevalue ? { responseType: "text" } : { parseResponse(text) {
60
+ if (!text) return void 0;
61
+ try {
62
+ return JSON.parse(text);
63
+ } catch {
64
+ return text;
65
+ }
66
+ } }
67
+ });
68
+ let decoded;
69
+ if (binary) decoded = decode(new Uint8Array(data));
70
+ else if (useDevalue) {
71
+ const { decode: devalueDecode } = await import("../../../codec/devalue.mjs");
72
+ decoded = data ? devalueDecode(data) : void 0;
73
+ } else decoded = data;
74
+ if (isSilgiErrorJSON(decoded)) throw fromSilgiErrorJSON(decoded);
75
+ return decoded;
76
+ } catch (error) {
77
+ if (error instanceof SilgiError) throw error;
78
+ if (error instanceof FetchError) {
79
+ const responseData = error.data;
80
+ if (isSilgiErrorJSON(responseData)) throw fromSilgiErrorJSON(responseData);
81
+ throw new SilgiError("INTERNAL_SERVER_ERROR", {
82
+ status: error.status ?? 500,
83
+ message: error.message,
84
+ data: responseData
85
+ });
86
+ }
87
+ throw error;
88
+ }
89
+ } };
90
+ }
91
+ //#endregion
92
+ export { createLink };
@@ -0,0 +1,29 @@
1
+ import { InferClient } from "../types.mjs";
2
+ import { ClientContext, ClientLink } from "./types.mjs";
3
+
4
+ //#region src/client/client.d.ts
5
+ /**
6
+ * Create a type-safe client from a link.
7
+ *
8
+ * Accepts either a router type (auto-inferred) or a pre-inferred client type:
9
+ * ```ts
10
+ * // Recommended — pass AppRouter directly
11
+ * const client = createClient<AppRouter>(link)
12
+ *
13
+ * // Also works — explicit InferClient
14
+ * const client = createClient<InferClient<AppRouter>>(link)
15
+ * ```
16
+ */
17
+ declare function createClient<T, TClientContext extends ClientContext = Record<never, never>>(link: ClientLink<TClientContext>): InferClient<T> extends never ? T : InferClient<T>;
18
+ /**
19
+ * Safe client wrapper — returns [error, data] tuples instead of throwing.
20
+ */
21
+ interface SafeResult<TOutput, TError> {
22
+ error: TError | null;
23
+ data: TOutput | undefined;
24
+ isError: boolean;
25
+ isSuccess: boolean;
26
+ }
27
+ declare function safe<TOutput, TError = unknown>(promise: Promise<TOutput>): Promise<SafeResult<TOutput, TError>>;
28
+ //#endregion
29
+ export { SafeResult, createClient, safe };
@@ -0,0 +1,54 @@
1
+ //#region src/client/client.ts
2
+ /**
3
+ * Create a type-safe client from a link.
4
+ *
5
+ * Accepts either a router type (auto-inferred) or a pre-inferred client type:
6
+ * ```ts
7
+ * // Recommended — pass AppRouter directly
8
+ * const client = createClient<AppRouter>(link)
9
+ *
10
+ * // Also works — explicit InferClient
11
+ * const client = createClient<InferClient<AppRouter>>(link)
12
+ * ```
13
+ */
14
+ function createClient(link) {
15
+ return createClientProxy(link, []);
16
+ }
17
+ function createClientProxy(link, path) {
18
+ const cache = /* @__PURE__ */ new Map();
19
+ const procedureClient = (input, options) => link.call(path, input, options ?? {});
20
+ return new Proxy(procedureClient, {
21
+ get(_target, prop) {
22
+ if (prop === "then") return void 0;
23
+ if (typeof prop !== "string") return void 0;
24
+ let cached = cache.get(prop);
25
+ if (!cached) {
26
+ cached = createClientProxy(link, Object.freeze([...path, prop]));
27
+ cache.set(prop, cached);
28
+ }
29
+ return cached;
30
+ },
31
+ apply(_target, _thisArg, args) {
32
+ return procedureClient(args[0], args[1]);
33
+ }
34
+ });
35
+ }
36
+ async function safe(promise) {
37
+ try {
38
+ return {
39
+ error: null,
40
+ data: await promise,
41
+ isError: false,
42
+ isSuccess: true
43
+ };
44
+ } catch (error) {
45
+ return {
46
+ error,
47
+ data: void 0,
48
+ isError: true,
49
+ isSuccess: false
50
+ };
51
+ }
52
+ }
53
+ //#endregion
54
+ export { createClient, safe };
@@ -0,0 +1,15 @@
1
+ import { ClientContext, ClientLink, ClientOptions } from "./types.mjs";
2
+
3
+ //#region src/client/dynamic-link.d.ts
4
+ type LinkSelector<TClientContext extends ClientContext = ClientContext> = (path: readonly string[], input: unknown, options: ClientOptions<TClientContext>) => ClientLink<TClientContext>;
5
+ /**
6
+ * A link that delegates to other links based on a selector function.
7
+ * The selector runs on every call, so it can use dynamic state.
8
+ */
9
+ declare class DynamicLink<TClientContext extends ClientContext = ClientContext> implements ClientLink<TClientContext> {
10
+ #private;
11
+ constructor(selector: LinkSelector<TClientContext>);
12
+ call(path: readonly string[], input: unknown, options: ClientOptions<TClientContext>): Promise<unknown>;
13
+ }
14
+ //#endregion
15
+ export { DynamicLink, LinkSelector };
@@ -0,0 +1,16 @@
1
+ //#region src/client/dynamic-link.ts
2
+ /**
3
+ * A link that delegates to other links based on a selector function.
4
+ * The selector runs on every call, so it can use dynamic state.
5
+ */
6
+ var DynamicLink = class {
7
+ #selector;
8
+ constructor(selector) {
9
+ this.#selector = selector;
10
+ }
11
+ call(path, input, options) {
12
+ return this.#selector(path, input, options).call(path, input, options);
13
+ }
14
+ };
15
+ //#endregion
16
+ export { DynamicLink };
@@ -0,0 +1,7 @@
1
+ import { SilgiError, SilgiErrorCode, SilgiErrorJSON, isDefinedError } from "../core/error.mjs";
2
+ import { Client, ClientContext, ClientLink, ClientOptions, ClientRest, InferClientInputs, InferClientOutputs, NestedClient } from "./types.mjs";
3
+ import { SafeResult, createClient, safe } from "./client.mjs";
4
+ import { DynamicLink, LinkSelector } from "./dynamic-link.mjs";
5
+ import { mergeClients } from "./merge.mjs";
6
+ import { ClientInterceptors, withInterceptors } from "./interceptor.mjs";
7
+ export { type Client, type ClientContext, type ClientInterceptors, type ClientLink, type ClientOptions, type ClientRest, DynamicLink, type InferClientInputs, type InferClientOutputs, type LinkSelector, type NestedClient, type SafeResult, SilgiError, type SilgiErrorCode, type SilgiErrorJSON, createClient, isDefinedError, mergeClients, safe, withInterceptors };
@@ -0,0 +1,6 @@
1
+ import { SilgiError, isDefinedError } from "../core/error.mjs";
2
+ import { createClient, safe } from "./client.mjs";
3
+ import { DynamicLink } from "./dynamic-link.mjs";
4
+ import { mergeClients } from "./merge.mjs";
5
+ import { withInterceptors } from "./interceptor.mjs";
6
+ export { DynamicLink, SilgiError, createClient, isDefinedError, mergeClients, safe, withInterceptors };
@@ -0,0 +1,31 @@
1
+ import { ClientContext, ClientLink, ClientOptions } from "./types.mjs";
2
+
3
+ //#region src/client/interceptor.d.ts
4
+ interface ClientInterceptors<TClientContext extends ClientContext = ClientContext> {
5
+ /** Called before every request. Can modify input or options. */
6
+ onRequest?: (event: {
7
+ path: readonly string[];
8
+ input: unknown;
9
+ options: ClientOptions<TClientContext>;
10
+ }) => void | Promise<void>;
11
+ /** Called after a successful response. */
12
+ onResponse?: (event: {
13
+ path: readonly string[];
14
+ input: unknown;
15
+ output: unknown;
16
+ durationMs: number;
17
+ }) => void | Promise<void>;
18
+ /** Called when a request fails. */
19
+ onError?: (event: {
20
+ path: readonly string[];
21
+ input: unknown;
22
+ error: unknown;
23
+ }) => void | Promise<void>;
24
+ }
25
+ /**
26
+ * Wrap a ClientLink with interceptor hooks.
27
+ * Returns a new link that calls the interceptors around the original.
28
+ */
29
+ declare function withInterceptors<TClientContext extends ClientContext = ClientContext>(link: ClientLink<TClientContext>, interceptors: ClientInterceptors<TClientContext>): ClientLink<TClientContext>;
30
+ //#endregion
31
+ export { ClientInterceptors, withInterceptors };
@@ -0,0 +1,34 @@
1
+ //#region src/client/interceptor.ts
2
+ /**
3
+ * Wrap a ClientLink with interceptor hooks.
4
+ * Returns a new link that calls the interceptors around the original.
5
+ */
6
+ function withInterceptors(link, interceptors) {
7
+ return { async call(path, input, options) {
8
+ if (interceptors.onRequest) await interceptors.onRequest({
9
+ path,
10
+ input,
11
+ options
12
+ });
13
+ const t0 = performance.now();
14
+ try {
15
+ const output = await link.call(path, input, options);
16
+ if (interceptors.onResponse) await interceptors.onResponse({
17
+ path,
18
+ input,
19
+ output,
20
+ durationMs: performance.now() - t0
21
+ });
22
+ return output;
23
+ } catch (error) {
24
+ if (interceptors.onError) await interceptors.onError({
25
+ path,
26
+ input,
27
+ error
28
+ });
29
+ throw error;
30
+ }
31
+ } };
32
+ }
33
+ //#endregion
34
+ export { withInterceptors };
@@ -0,0 +1,28 @@
1
+ //#region src/client/merge.d.ts
2
+ /**
3
+ * Client merging — combine multiple typed clients into one.
4
+ *
5
+ * Useful when different parts of your API live on different servers
6
+ * or use different transports.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { mergeClients } from "silgi/client"
11
+ *
12
+ * const client = mergeClients({
13
+ * users: usersClient,
14
+ * billing: billingClient,
15
+ * analytics: analyticsClient,
16
+ * })
17
+ *
18
+ * await client.users.list({ limit: 10 })
19
+ * await client.billing.invoices()
20
+ * ```
21
+ */
22
+ /**
23
+ * Merge multiple clients into a single typed object.
24
+ * Each key maps to a separate client — they can use different links.
25
+ */
26
+ declare function mergeClients<T extends Record<string, unknown>>(clients: T): T;
27
+ //#endregion
28
+ export { mergeClients };
@@ -0,0 +1,30 @@
1
+ //#region src/client/merge.ts
2
+ /**
3
+ * Client merging — combine multiple typed clients into one.
4
+ *
5
+ * Useful when different parts of your API live on different servers
6
+ * or use different transports.
7
+ *
8
+ * @example
9
+ * ```ts
10
+ * import { mergeClients } from "silgi/client"
11
+ *
12
+ * const client = mergeClients({
13
+ * users: usersClient,
14
+ * billing: billingClient,
15
+ * analytics: analyticsClient,
16
+ * })
17
+ *
18
+ * await client.users.list({ limit: 10 })
19
+ * await client.billing.invoices()
20
+ * ```
21
+ */
22
+ /**
23
+ * Merge multiple clients into a single typed object.
24
+ * Each key maps to a separate client — they can use different links.
25
+ */
26
+ function mergeClients(clients) {
27
+ return clients;
28
+ }
29
+ //#endregion
30
+ export { mergeClients };
@@ -0,0 +1,29 @@
1
+ import { ClientContext, ClientLink, ClientOptions } from "./types.mjs";
2
+
3
+ //#region src/client/openapi.d.ts
4
+ interface OpenAPILinkOptions {
5
+ /** Base URL of the API */
6
+ url: string;
7
+ /** OpenAPI 3.x specification object (parsed JSON) */
8
+ spec?: Record<string, unknown>;
9
+ /** Default headers */
10
+ headers?: Record<string, string> | (() => Record<string, string>);
11
+ /** Custom fetch implementation */
12
+ fetch?: typeof globalThis.fetch;
13
+ }
14
+ /**
15
+ * A ClientLink that makes REST requests to an OpenAPI endpoint.
16
+ *
17
+ * Maps procedure paths to REST paths:
18
+ * - `client.users.list({ limit: 10 })` → `GET /users/list?limit=10` or `POST /users/list`
19
+ *
20
+ * Without a spec, it defaults to POST requests with JSON body.
21
+ * With a spec, it uses the correct HTTP method and parameter placement.
22
+ */
23
+ declare class OpenAPILink<TCtx extends ClientContext = ClientContext> implements ClientLink<TCtx> {
24
+ #private;
25
+ constructor(options: OpenAPILinkOptions);
26
+ call(path: readonly string[], input: unknown, options: ClientOptions<TCtx>): Promise<unknown>;
27
+ }
28
+ //#endregion
29
+ export { OpenAPILink, OpenAPILinkOptions };
@@ -0,0 +1,89 @@
1
+ import { SilgiError } from "../core/error.mjs";
2
+ //#region src/client/openapi.ts
3
+ /**
4
+ * OpenAPI Client Link — consume any OpenAPI endpoint as a Silgi client.
5
+ *
6
+ * Unlike RPCLink (which uses Silgi's wire protocol), OpenAPILink sends
7
+ * standard REST requests based on an OpenAPI spec.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * import { OpenAPILink } from "silgi/client/openapi"
12
+ * import { createClient } from "silgi/client"
13
+ *
14
+ * const link = new OpenAPILink({
15
+ * url: "https://api.example.com",
16
+ * spec: await fetch("/openapi.json").then(r => r.json()),
17
+ * })
18
+ *
19
+ * const client = createClient<ExternalAPI>(link)
20
+ * const users = await client.users.list({ limit: 10 })
21
+ * ```
22
+ */
23
+ /**
24
+ * A ClientLink that makes REST requests to an OpenAPI endpoint.
25
+ *
26
+ * Maps procedure paths to REST paths:
27
+ * - `client.users.list({ limit: 10 })` → `GET /users/list?limit=10` or `POST /users/list`
28
+ *
29
+ * Without a spec, it defaults to POST requests with JSON body.
30
+ * With a spec, it uses the correct HTTP method and parameter placement.
31
+ */
32
+ var OpenAPILink = class {
33
+ #url;
34
+ #spec;
35
+ #headers;
36
+ #fetch;
37
+ constructor(options) {
38
+ this.#url = options.url.replace(/\/$/, "");
39
+ this.#spec = options.spec;
40
+ this.#headers = options.headers ?? {};
41
+ this.#fetch = options.fetch ?? globalThis.fetch;
42
+ }
43
+ async call(path, input, options) {
44
+ const endpoint = "/" + path.join("/");
45
+ const url = this.#url + endpoint;
46
+ const headers = {
47
+ "content-type": "application/json",
48
+ ...typeof this.#headers === "function" ? this.#headers() : this.#headers
49
+ };
50
+ let method = "POST";
51
+ if (this.#spec) {
52
+ const paths = this.#spec.paths;
53
+ if (paths?.[endpoint]) {
54
+ if (paths[endpoint].get) method = "GET";
55
+ else if (paths[endpoint].post) method = "POST";
56
+ else if (paths[endpoint].put) method = "PUT";
57
+ else if (paths[endpoint].patch) method = "PATCH";
58
+ else if (paths[endpoint].delete) method = "DELETE";
59
+ }
60
+ }
61
+ let requestUrl = url;
62
+ let body;
63
+ if (method === "GET" || method === "HEAD") {
64
+ if (input && typeof input === "object") {
65
+ const params = new URLSearchParams();
66
+ for (const [k, v] of Object.entries(input)) if (v !== void 0) params.set(k, String(v));
67
+ const qs = params.toString();
68
+ if (qs) requestUrl = `${url}?${qs}`;
69
+ }
70
+ } else body = input !== void 0 ? JSON.stringify(input) : void 0;
71
+ const response = await this.#fetch(requestUrl, {
72
+ method,
73
+ headers,
74
+ body,
75
+ signal: options.signal
76
+ });
77
+ if (!response.ok) {
78
+ const errorBody = await response.json().catch(() => null);
79
+ throw new SilgiError(errorBody?.code ?? `HTTP_${response.status}`, {
80
+ status: response.status,
81
+ message: errorBody?.message ?? response.statusText,
82
+ data: errorBody
83
+ });
84
+ }
85
+ return response.json();
86
+ }
87
+ };
88
+ //#endregion
89
+ export { OpenAPILink };
@@ -0,0 +1,20 @@
1
+ import { ClientContext, ClientLink, ClientOptions } from "../types.mjs";
2
+
3
+ //#region src/client/plugins/batch.d.ts
4
+ interface BatchLinkOptions {
5
+ /** The underlying link to send the batch through */
6
+ link: ClientLink;
7
+ /** Batch endpoint path (default: /__batch__) */
8
+ path?: string;
9
+ /** Maximum batch size (default: 20) */
10
+ maxSize?: number;
11
+ /** URL for the batch endpoint */
12
+ url: string | URL;
13
+ }
14
+ declare class BatchLink<TClientContext extends ClientContext = ClientContext> implements ClientLink<TClientContext> {
15
+ #private;
16
+ constructor(options: BatchLinkOptions);
17
+ call(path: readonly string[], input: unknown, options: ClientOptions<TClientContext>): Promise<unknown>;
18
+ }
19
+ //#endregion
20
+ export { BatchLink, BatchLinkOptions };
@@ -0,0 +1,64 @@
1
+ //#region src/client/plugins/batch.ts
2
+ var BatchLink = class {
3
+ #link;
4
+ #batchPath;
5
+ #maxSize;
6
+ #pending = [];
7
+ #scheduled = false;
8
+ constructor(options) {
9
+ this.#link = options.link;
10
+ this.#batchPath = options.path ?? "/__batch__";
11
+ this.#maxSize = options.maxSize ?? 20;
12
+ }
13
+ call(path, input, options) {
14
+ return new Promise((resolve, reject) => {
15
+ this.#pending.push({
16
+ path,
17
+ input,
18
+ options,
19
+ resolve,
20
+ reject
21
+ });
22
+ if (!this.#scheduled) {
23
+ this.#scheduled = true;
24
+ queueMicrotask(() => this.#flush());
25
+ }
26
+ });
27
+ }
28
+ async #flush() {
29
+ this.#scheduled = false;
30
+ const batch = this.#pending.splice(0);
31
+ if (batch.length === 0) return;
32
+ const chunks = [];
33
+ for (let i = 0; i < batch.length; i += this.#maxSize) chunks.push(batch.slice(i, i + this.#maxSize));
34
+ for (const chunk of chunks) await this.#sendChunk(chunk);
35
+ }
36
+ async #sendChunk(chunk) {
37
+ const batchBody = chunk.map((call) => ({
38
+ path: "/" + call.path.join("/"),
39
+ method: "POST",
40
+ body: call.input
41
+ }));
42
+ try {
43
+ const responses = await this.#link.call([this.#batchPath.slice(1)], batchBody, { signal: chunk[0]?.options.signal });
44
+ if (!Array.isArray(responses)) {
45
+ for (const call of chunk) call.reject(/* @__PURE__ */ new Error("Invalid batch response"));
46
+ return;
47
+ }
48
+ for (const response of responses) {
49
+ const call = chunk[response.index];
50
+ if (!call) continue;
51
+ if (response.status >= 400) call.reject(response.body ?? /* @__PURE__ */ new Error(`HTTP ${response.status}`));
52
+ else call.resolve(response.body);
53
+ }
54
+ for (let i = 0; i < chunk.length; i++) {
55
+ const call = chunk[i];
56
+ if (!responses.some((r) => r.index === i)) call.reject(/* @__PURE__ */ new Error("No response in batch"));
57
+ }
58
+ } catch (error) {
59
+ for (const call of chunk) call.reject(error);
60
+ }
61
+ }
62
+ };
63
+ //#endregion
64
+ export { BatchLink };
@@ -0,0 +1,13 @@
1
+ import { ClientContext, ClientLink } from "../types.mjs";
2
+
3
+ //#region src/client/plugins/csrf.d.ts
4
+ interface CSRFLinkOptions {
5
+ headerName?: string;
6
+ headerValue?: string;
7
+ }
8
+ /**
9
+ * Wrap a link to automatically inject the CSRF header on every request.
10
+ */
11
+ declare function withCSRF<TClientContext extends ClientContext>(link: ClientLink<TClientContext>, options?: CSRFLinkOptions): ClientLink<TClientContext>;
12
+ //#endregion
13
+ export { CSRFLinkOptions, withCSRF };
@@ -0,0 +1,20 @@
1
+ //#region src/client/plugins/csrf.ts
2
+ /**
3
+ * Wrap a link to automatically inject the CSRF header on every request.
4
+ */
5
+ function withCSRF(link, options = {}) {
6
+ const headerName = options.headerName ?? "x-csrf-token";
7
+ const headerValue = options.headerValue ?? "silgi";
8
+ return { call(path, input, callOptions) {
9
+ const enhancedOptions = {
10
+ ...callOptions,
11
+ headers: {
12
+ ...callOptions.headers,
13
+ [headerName]: headerValue
14
+ }
15
+ };
16
+ return link.call(path, input, enhancedOptions);
17
+ } };
18
+ }
19
+ //#endregion
20
+ export { withCSRF };
@@ -0,0 +1,10 @@
1
+ import { ClientContext, ClientLink } from "../types.mjs";
2
+
3
+ //#region src/client/plugins/dedupe.d.ts
4
+ interface DedupeOptions {
5
+ /** Custom key function. Default: JSON.stringify(path + input) */
6
+ keyFn?: (path: readonly string[], input: unknown) => string;
7
+ }
8
+ declare function withDedupe<TClientContext extends ClientContext>(link: ClientLink<TClientContext>, options?: DedupeOptions): ClientLink<TClientContext>;
9
+ //#endregion
10
+ export { DedupeOptions, withDedupe };
@@ -0,0 +1,28 @@
1
+ import { stringifyJSON } from "../../core/utils.mjs";
2
+ //#region src/client/plugins/dedupe.ts
3
+ /**
4
+ * Dedupe Plugin — collapses identical concurrent requests into one.
5
+ *
6
+ * If two calls have the same path and serialized input within
7
+ * the same microtask, only one actual request is sent.
8
+ * All callers receive the same response.
9
+ */
10
+ function withDedupe(link, options = {}) {
11
+ const inflight = /* @__PURE__ */ new Map();
12
+ const keyFn = options.keyFn ?? ((path, input) => stringifyJSON({
13
+ path,
14
+ input
15
+ }));
16
+ return { call(path, input, callOptions) {
17
+ const key = keyFn(path, input);
18
+ const existing = inflight.get(key);
19
+ if (existing) return existing;
20
+ const promise = link.call(path, input, callOptions).finally(() => {
21
+ inflight.delete(key);
22
+ });
23
+ inflight.set(key, promise);
24
+ return promise;
25
+ } };
26
+ }
27
+ //#endregion
28
+ export { withDedupe };
@@ -0,0 +1,5 @@
1
+ import { RetryOptions, withRetry } from "./retry.mjs";
2
+ import { BatchLink, BatchLinkOptions } from "./batch.mjs";
3
+ import { DedupeOptions, withDedupe } from "./dedupe.mjs";
4
+ import { CSRFLinkOptions, withCSRF } from "./csrf.mjs";
5
+ export { BatchLink, type BatchLinkOptions, type CSRFLinkOptions, type DedupeOptions, type RetryOptions, withCSRF, withDedupe, withRetry };
@@ -0,0 +1,5 @@
1
+ import { withRetry } from "./retry.mjs";
2
+ import { BatchLink } from "./batch.mjs";
3
+ import { withDedupe } from "./dedupe.mjs";
4
+ import { withCSRF } from "./csrf.mjs";
5
+ export { BatchLink, withCSRF, withDedupe, withRetry };
@@ -0,0 +1,11 @@
1
+ import { ClientContext, ClientLink } from "../types.mjs";
2
+
3
+ //#region src/client/plugins/retry.d.ts
4
+ interface RetryOptions {
5
+ maxRetries?: number;
6
+ retryDelay?: number | ((attempt: number) => number);
7
+ shouldRetry?: (error: unknown) => boolean;
8
+ }
9
+ declare function withRetry<TClientContext extends ClientContext>(link: ClientLink<TClientContext>, options?: RetryOptions): ClientLink<TClientContext>;
10
+ //#endregion
11
+ export { RetryOptions, withRetry };