silgi 0.53.5 → 0.53.6

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.
@@ -1,59 +1,42 @@
1
1
  import { ClientContext, ClientLink, ClientOptions } from "../../types.mjs";
2
- import { AfterResponseHook, BeforeErrorHook, BeforeRequestHook, MisinaHooks, RetryOptions } from "misina";
2
+ import { Misina, Misina as Misina$1 } from "misina";
3
3
 
4
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
5
  interface LinkOptions<TClientContext extends ClientContext = ClientContext> {
7
- /** Server base URL (e.g. "http://localhost:3000") */
6
+ /** Server base URL (e.g. "http://localhost:3000"). */
8
7
  url: string;
9
- /** Static headers or dynamic header factory */
10
- headers?: Record<string, string> | ((options: ClientOptions<TClientContext>) => Record<string, string>);
11
8
  /**
12
- * Retry policy. Number sets the limit; pass a full `RetryOptions` to
13
- * tune backoff, status codes, jitter. `false` disables retries.
9
+ * Misina instance the adapter dispatches through. Configure retry,
10
+ * timeout, hooks, plugins (`use: [...]`), idempotency keys, redirect
11
+ * policy, custom drivers, etc. on this instance — the adapter does not
12
+ * accept those options directly.
14
13
  *
15
- * @default 0
14
+ * If omitted, the adapter calls `createMisina({ baseURL: url })`. Pass
15
+ * your own instance to opt into anything beyond default fetch behavior.
16
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;
17
+ misina?: Misina$1;
22
18
  /**
23
19
  * Wire protocol for request/response encoding.
24
20
  *
25
21
  * - `'json'` — default, standard JSON
26
- * - `'messagepack'` — 2-4x faster, ~50% smaller payloads
22
+ * - `'messagepack'` — 2–4× faster, ~50% smaller payloads
27
23
  * - `'devalue'` — preserves Date, Map, Set, BigInt, circular refs
28
24
  *
29
25
  * @default 'json'
30
26
  */
31
27
  protocol?: 'json' | 'messagepack' | 'devalue';
32
28
  /**
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.
29
+ * Static headers or a per-call factory. Headers configured on the
30
+ * misina instance still apply; these are merged on top per call.
36
31
  */
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;
32
+ headers?: HeadersInit | Record<string, string | undefined> | ((options: ClientOptions<TClientContext>) => HeadersInit | Record<string, string | undefined>);
43
33
  }
44
34
  /**
45
35
  * Create a Silgi client link powered by misina.
46
36
  *
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
- * ```
37
+ * @see {@link LinkOptions} for the full shape and the misina-instance
38
+ * pattern.
56
39
  */
57
40
  declare function createLink<TClientContext extends ClientContext = ClientContext>(options: LinkOptions<TClientContext>): ClientLink<TClientContext>;
58
41
  //#endregion
59
- export { LinkOptions, createLink };
42
+ export { LinkOptions, type Misina, createLink };
@@ -4,53 +4,76 @@ import { MSGPACK_CONTENT_TYPE, decode, encode } from "../../../codec/msgpack.mjs
4
4
  import { createMisina, isHTTPError, isNetworkError, isTimeoutError } from "misina";
5
5
  //#region src/client/adapters/misina/index.ts
6
6
  /**
7
- * misina-based RPC transport — v2 client link.
7
+ * misina-based RPC transport — silgi client link.
8
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.
9
+ * Thin shim over a misina instance. The adapter owns four things — and
10
+ * nothing else:
12
11
  *
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.
12
+ * 1. URL construction from the path tuple
13
+ * 2. Protocol negotiation (json / messagepack / devalue) — sets
14
+ * content-type/accept and encodes the body accordingly
15
+ * 3. Per-call `responseType: 'stream'` override so SSE subscription
16
+ * responses can be decoded lazily
17
+ * 4. SilgiError lifting from the response payload, plus mapping
18
+ * misina's HTTPError / NetworkError / TimeoutError onto SilgiError
19
+ *
20
+ * Everything else — retry, timeout, hooks, idempotency, redirect policy,
21
+ * plugins (cache, breaker, dedupe, cookies, auth, otel, …) — lives on the
22
+ * misina instance you pass in. Configure misina once with
23
+ * `createMisina({ baseURL, retry, use: [plugin(), …] })` and hand the
24
+ * result here.
18
25
  *
19
26
  * @example
20
27
  * ```ts
28
+ * import { createMisina } from "misina"
29
+ * import { cache } from "misina/cache"
30
+ * import { breaker } from "misina/breaker"
31
+ * import { bearer } from "misina/auth"
21
32
  * import { createClient } from "silgi/client"
22
33
  * import { createLink } from "silgi/client/misina"
23
34
  *
24
- * const link = createLink({ url: "http://localhost:3000" })
35
+ * const url = "http://localhost:3000"
36
+ *
37
+ * const link = createLink({
38
+ * url,
39
+ * misina: createMisina({
40
+ * baseURL: url,
41
+ * retry: 3,
42
+ * idempotencyKey: "auto",
43
+ * use: [
44
+ * bearer(() => store.token),
45
+ * cache({ ttl: 60_000 }),
46
+ * breaker({ failureThreshold: 5, windowMs: 30_000 }),
47
+ * ],
48
+ * }),
49
+ * })
50
+ *
25
51
  * const client = createClient<AppRouter>(link)
26
- * const users = await client.users.list({ limit: 10 })
27
52
  * ```
53
+ *
54
+ * If `misina` is omitted, the adapter constructs a minimal default
55
+ * instance (`createMisina({ baseURL })`). That's fine for plain RPC; opt
56
+ * into retries, plugins, hooks, etc. by passing your own instance.
57
+ */
58
+ /**
59
+ * Create a Silgi client link powered by misina.
60
+ *
61
+ * @see {@link LinkOptions} for the full shape and the misina-instance
62
+ * pattern.
28
63
  */
29
64
  function createLink(options) {
30
65
  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
- });
66
+ const protocol = options.protocol ?? "json";
67
+ const misina = options.misina ?? createMisina({ baseURL: baseUrl });
45
68
  return { async call(path, input, callOptions) {
46
69
  const url = `${baseUrl}/${path.map(encodeURIComponent).join("/")}`;
47
- const headers = { ...typeof options.headers === "function" ? options.headers(callOptions) : options.headers };
70
+ const headers = resolveHeaders(options.headers, callOptions);
48
71
  let body;
49
- if (resolvedProtocol === "messagepack") {
72
+ if (protocol === "messagepack") {
50
73
  headers["content-type"] = MSGPACK_CONTENT_TYPE;
51
74
  headers["accept"] = MSGPACK_CONTENT_TYPE;
52
75
  body = input !== void 0 && input !== null ? encode(input) : void 0;
53
- } else if (resolvedProtocol === "devalue") {
76
+ } else if (protocol === "devalue") {
54
77
  const { encode: devalueEncode, DEVALUE_CONTENT_TYPE } = await import("../../../codec/devalue.mjs");
55
78
  headers["content-type"] = DEVALUE_CONTENT_TYPE;
56
79
  headers["accept"] = DEVALUE_CONTENT_TYPE;
@@ -60,14 +83,15 @@ function createLink(options) {
60
83
  const response = (await misina.post(url, body, {
61
84
  headers,
62
85
  signal: callOptions.signal,
63
- responseType: "stream"
86
+ responseType: "stream",
87
+ throwHttpErrors: false
64
88
  })).raw;
65
89
  if ((response.headers.get("content-type") ?? "").includes("text/event-stream") && response.body) return eventStreamToIterator(response.body);
66
90
  let decoded;
67
- if (resolvedProtocol === "messagepack") {
91
+ if (protocol === "messagepack") {
68
92
  const buf = new Uint8Array(await response.arrayBuffer());
69
93
  decoded = buf.length > 0 ? decode(buf) : void 0;
70
- } else if (resolvedProtocol === "devalue") {
94
+ } else if (protocol === "devalue") {
71
95
  const text = await response.text();
72
96
  if (text) {
73
97
  const { decode: devalueDecode } = await import("../../../codec/devalue.mjs");
@@ -103,5 +127,28 @@ function createLink(options) {
103
127
  }
104
128
  } };
105
129
  }
130
+ function resolveHeaders(headers, callOptions) {
131
+ const raw = typeof headers === "function" ? headers(callOptions) : headers;
132
+ const out = {};
133
+ if (raw == null) return out;
134
+ if (raw instanceof Headers) {
135
+ raw.forEach((value, key) => {
136
+ out[key] = value;
137
+ });
138
+ return out;
139
+ }
140
+ if (Array.isArray(raw)) {
141
+ for (const [k, v] of raw) {
142
+ if (v == null) continue;
143
+ out[k] = String(v);
144
+ }
145
+ return out;
146
+ }
147
+ for (const [k, v] of Object.entries(raw)) {
148
+ if (v == null) continue;
149
+ out[k] = String(v);
150
+ }
151
+ return out;
152
+ }
106
153
  //#endregion
107
154
  export { createLink };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "silgi",
3
- "version": "0.53.5",
3
+ "version": "0.53.6",
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": [
@@ -253,7 +253,7 @@
253
253
  "devalue": "^5.6.4",
254
254
  "get-port-please": "^3.2.0",
255
255
  "hookable": "^6.1.0",
256
- "misina": "^0.2.0",
256
+ "misina": "^0.4.0",
257
257
  "msgpackr": "^1.11.9",
258
258
  "ocache": "^0.1.4",
259
259
  "ofetch": "2.0.0-alpha.3",