silgi 0.53.3 → 0.53.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,59 @@
1
+ import { ClientContext, ClientLink, ClientOptions } from "../../types.mjs";
2
+ import { AfterResponseHook, BeforeErrorHook, BeforeRequestHook, MisinaHooks, RetryOptions } from "misina";
3
+
4
+ //#region src/client/adapters/misina/index.d.ts
5
+ type OnCompleteHook = Exclude<MisinaHooks['onComplete'], undefined> extends infer H ? (H extends readonly (infer U)[] ? U : H) : never;
6
+ interface LinkOptions<TClientContext extends ClientContext = ClientContext> {
7
+ /** Server base URL (e.g. "http://localhost:3000") */
8
+ url: string;
9
+ /** Static headers or dynamic header factory */
10
+ headers?: Record<string, string> | ((options: ClientOptions<TClientContext>) => Record<string, string>);
11
+ /**
12
+ * Retry policy. Number sets the limit; pass a full `RetryOptions` to
13
+ * tune backoff, status codes, jitter. `false` disables retries.
14
+ *
15
+ * @default 0
16
+ */
17
+ retry?: number | boolean | RetryOptions;
18
+ /** Per-attempt timeout in ms. `false` disables. (default: 30000) */
19
+ timeout?: number | false;
20
+ /** Wall-clock deadline across all attempts (ms). `false` disables. */
21
+ totalTimeout?: number | false;
22
+ /**
23
+ * Wire protocol for request/response encoding.
24
+ *
25
+ * - `'json'` — default, standard JSON
26
+ * - `'messagepack'` — 2-4x faster, ~50% smaller payloads
27
+ * - `'devalue'` — preserves Date, Map, Set, BigInt, circular refs
28
+ *
29
+ * @default 'json'
30
+ */
31
+ protocol?: 'json' | 'messagepack' | 'devalue';
32
+ /**
33
+ * Auto-generate `Idempotency-Key` for retried mutations. `'auto'` uses
34
+ * `crypto.randomUUID()` when retries are enabled. Pass a string to
35
+ * pin one, or a function for custom generation.
36
+ */
37
+ idempotencyKey?: false | 'auto' | string | ((request: Request) => string);
38
+ /** misina hooks — direct pass-through */
39
+ beforeRequest?: BeforeRequestHook;
40
+ afterResponse?: AfterResponseHook;
41
+ beforeError?: BeforeErrorHook;
42
+ onComplete?: OnCompleteHook;
43
+ }
44
+ /**
45
+ * Create a Silgi client link powered by misina.
46
+ *
47
+ * @example
48
+ * ```ts
49
+ * import { createClient } from "silgi/client"
50
+ * import { createLink } from "silgi/client/misina"
51
+ *
52
+ * const link = createLink({ url: "http://localhost:3000" })
53
+ * const client = createClient<AppRouter>(link)
54
+ * const users = await client.users.list({ limit: 10 })
55
+ * ```
56
+ */
57
+ declare function createLink<TClientContext extends ClientContext = ClientContext>(options: LinkOptions<TClientContext>): ClientLink<TClientContext>;
58
+ //#endregion
59
+ export { LinkOptions, createLink };
@@ -0,0 +1,107 @@
1
+ import { SilgiError, fromSilgiErrorJSON, isSilgiErrorJSON } from "../../../core/error.mjs";
2
+ import { eventStreamToIterator } from "../../../core/sse.mjs";
3
+ import { MSGPACK_CONTENT_TYPE, decode, encode } from "../../../codec/msgpack.mjs";
4
+ import { createMisina, isHTTPError, isNetworkError, isTimeoutError } from "misina";
5
+ //#region src/client/adapters/misina/index.ts
6
+ /**
7
+ * misina-based RPC transport — v2 client link.
8
+ *
9
+ * Uses misina for: retry (with Retry-After parsing), timeout, hook
10
+ * lifecycle, redirect security, NetworkError vs HTTPError taxonomy,
11
+ * idempotency keys for retried mutations.
12
+ *
13
+ * Side-by-side alternative to the ofetch link. Same Silgi semantics,
14
+ * different transport. Pick whichever fits your stack.
15
+ */
16
+ /**
17
+ * Create a Silgi client link powered by misina.
18
+ *
19
+ * @example
20
+ * ```ts
21
+ * import { createClient } from "silgi/client"
22
+ * import { createLink } from "silgi/client/misina"
23
+ *
24
+ * const link = createLink({ url: "http://localhost:3000" })
25
+ * const client = createClient<AppRouter>(link)
26
+ * const users = await client.users.list({ limit: 10 })
27
+ * ```
28
+ */
29
+ function createLink(options) {
30
+ const baseUrl = options.url.endsWith("/") ? options.url.slice(0, -1) : options.url;
31
+ const resolvedProtocol = options.protocol ?? "json";
32
+ const misina = createMisina({
33
+ timeout: options.timeout ?? 3e4,
34
+ totalTimeout: options.totalTimeout,
35
+ retry: options.retry ?? 0,
36
+ idempotencyKey: options.idempotencyKey,
37
+ throwHttpErrors: false,
38
+ hooks: {
39
+ beforeRequest: options.beforeRequest,
40
+ afterResponse: options.afterResponse,
41
+ beforeError: options.beforeError,
42
+ onComplete: options.onComplete
43
+ }
44
+ });
45
+ return { async call(path, input, callOptions) {
46
+ const url = `${baseUrl}/${path.map(encodeURIComponent).join("/")}`;
47
+ const headers = { ...typeof options.headers === "function" ? options.headers(callOptions) : options.headers };
48
+ let body;
49
+ if (resolvedProtocol === "messagepack") {
50
+ headers["content-type"] = MSGPACK_CONTENT_TYPE;
51
+ headers["accept"] = MSGPACK_CONTENT_TYPE;
52
+ body = input !== void 0 && input !== null ? encode(input) : void 0;
53
+ } else if (resolvedProtocol === "devalue") {
54
+ const { encode: devalueEncode, DEVALUE_CONTENT_TYPE } = await import("../../../codec/devalue.mjs");
55
+ headers["content-type"] = DEVALUE_CONTENT_TYPE;
56
+ headers["accept"] = DEVALUE_CONTENT_TYPE;
57
+ body = input !== void 0 && input !== null ? devalueEncode(input) : void 0;
58
+ } else body = input !== void 0 && input !== null ? input : void 0;
59
+ try {
60
+ const response = (await misina.post(url, body, {
61
+ headers,
62
+ signal: callOptions.signal,
63
+ responseType: "stream"
64
+ })).raw;
65
+ if ((response.headers.get("content-type") ?? "").includes("text/event-stream") && response.body) return eventStreamToIterator(response.body);
66
+ let decoded;
67
+ if (resolvedProtocol === "messagepack") {
68
+ const buf = new Uint8Array(await response.arrayBuffer());
69
+ decoded = buf.length > 0 ? decode(buf) : void 0;
70
+ } else if (resolvedProtocol === "devalue") {
71
+ const text = await response.text();
72
+ if (text) {
73
+ const { decode: devalueDecode } = await import("../../../codec/devalue.mjs");
74
+ decoded = devalueDecode(text);
75
+ } else decoded = void 0;
76
+ } else {
77
+ const text = await response.text();
78
+ if (!text) decoded = void 0;
79
+ else try {
80
+ decoded = JSON.parse(text);
81
+ } catch {
82
+ decoded = text;
83
+ }
84
+ }
85
+ if (isSilgiErrorJSON(decoded)) throw fromSilgiErrorJSON(decoded);
86
+ return decoded;
87
+ } catch (error) {
88
+ if (error instanceof SilgiError) throw error;
89
+ if (isHTTPError(error)) {
90
+ const data = error.data;
91
+ if (isSilgiErrorJSON(data)) throw fromSilgiErrorJSON(data);
92
+ throw new SilgiError("INTERNAL_SERVER_ERROR", {
93
+ status: error.status ?? 500,
94
+ message: error.message,
95
+ data
96
+ });
97
+ }
98
+ if (isNetworkError(error) || isTimeoutError(error)) throw new SilgiError("INTERNAL_SERVER_ERROR", {
99
+ status: 0,
100
+ message: error.message
101
+ });
102
+ throw error;
103
+ }
104
+ } };
105
+ }
106
+ //#endregion
107
+ export { createLink };
package/dist/types.d.mts CHANGED
@@ -98,8 +98,13 @@ interface ResolveContext<TCtx, TInput, TErrors extends ErrorDef> {
98
98
  type RouterDef = {
99
99
  [key: string]: ProcedureDef<any, any, any, any> | TaskDef<any, any> | RouterDef;
100
100
  };
101
- /** Return type wrapper — subscription yields, query/mutation returns Promise */
102
- type ProcedureResult<TType extends ProcedureType, TOutput> = TType extends 'subscription' ? AsyncIterableIterator<TOutput> : Promise<TOutput>;
101
+ /**
102
+ * Return type wrapper every procedure call resolves through the link's
103
+ * async `call`, so the client always returns a `Promise`. Subscriptions
104
+ * resolve to an `AsyncIterableIterator` (the open SSE/WS stream); queries
105
+ * and mutations resolve to the procedure's output value.
106
+ */
107
+ type ProcedureResult<TType extends ProcedureType, TOutput> = TType extends 'subscription' ? Promise<AsyncIterableIterator<TOutput>> : Promise<TOutput>;
103
108
  /** Infer client type from router */
104
109
  type InferClient<T> = T extends ProcedureDef<infer TType, infer TInput, infer TOutput> ? undefined extends TInput ? () => ProcedureResult<TType, TOutput> : (input: TInput) => ProcedureResult<TType, TOutput> : T extends Record<string, unknown> ? { [K in keyof T]: InferClient<T[K]> } : never;
105
110
  //#endregion
@@ -0,0 +1 @@
1
+ export * from 'misina'
package/lib/misina.mjs ADDED
@@ -0,0 +1 @@
1
+ export * from 'misina'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "silgi",
3
- "version": "0.53.3",
3
+ "version": "0.53.5",
4
4
  "private": false,
5
5
  "description": "The fastest end-to-end type-safe RPC framework for TypeScript — compiled pipelines, single package, every runtime",
6
6
  "keywords": [
@@ -59,6 +59,10 @@
59
59
  "import": "./dist/client/adapters/ofetch/index.mjs",
60
60
  "types": "./dist/client/adapters/ofetch/index.d.mts"
61
61
  },
62
+ "./client/misina": {
63
+ "import": "./dist/client/adapters/misina/index.mjs",
64
+ "types": "./dist/client/adapters/misina/index.d.mts"
65
+ },
62
66
  "./client/plugins": {
63
67
  "import": "./dist/client/plugins/index.mjs",
64
68
  "types": "./dist/client/plugins/index.d.mts"
@@ -231,6 +235,10 @@
231
235
  "import": "./lib/ofetch.mjs",
232
236
  "types": "./lib/ofetch.d.mts"
233
237
  },
238
+ "./misina": {
239
+ "import": "./lib/misina.mjs",
240
+ "types": "./lib/misina.d.mts"
241
+ },
234
242
  "./srvx": {
235
243
  "import": "./lib/srvx.mjs",
236
244
  "types": "./lib/srvx.d.mts"
@@ -245,6 +253,7 @@
245
253
  "devalue": "^5.6.4",
246
254
  "get-port-please": "^3.2.0",
247
255
  "hookable": "^6.1.0",
256
+ "misina": "^0.2.0",
248
257
  "msgpackr": "^1.11.9",
249
258
  "ocache": "^0.1.4",
250
259
  "ofetch": "2.0.0-alpha.3",