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 {
|
|
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
|
-
*
|
|
13
|
-
*
|
|
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
|
-
*
|
|
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
|
-
|
|
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
|
|
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
|
-
*
|
|
34
|
-
*
|
|
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
|
-
|
|
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
|
-
* @
|
|
48
|
-
*
|
|
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 —
|
|
7
|
+
* misina-based RPC transport — silgi client link.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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
|
-
*
|
|
14
|
-
*
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
*
|
|
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
|
|
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
|
|
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 =
|
|
70
|
+
const headers = resolveHeaders(options.headers, callOptions);
|
|
48
71
|
let body;
|
|
49
|
-
if (
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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.
|
|
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.
|
|
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",
|