@kibinrpc/client 0.0.2 → 0.0.4

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.
package/README.md ADDED
@@ -0,0 +1,171 @@
1
+ # @kibinrpc/client
2
+
3
+ Type-safe fetch client for [kibinrpc](../../README.md) — fully inferred from your server router, with automatic batching, retry, and interceptors.
4
+
5
+ ## Installation
6
+
7
+ ```sh
8
+ npm install @kibinrpc/client
9
+ ```
10
+
11
+ ## Quick start
12
+
13
+ ```ts
14
+ import { createKibinClient } from '@kibinrpc/client'
15
+ import type { AppRouter } from './server/router'
16
+
17
+ const client = createKibinClient<AppRouter>({
18
+ baseUrl: '/api/rpc',
19
+ })
20
+
21
+ // Return types are inferred from the server — no manual annotations
22
+ const user = await client.user.getUser('1')
23
+ const posts = await client.post.listPosts()
24
+ ```
25
+
26
+ ## Automatic batching
27
+
28
+ Concurrent calls are automatically coalesced into a single HTTP request:
29
+
30
+ ```ts
31
+ // Both calls happen in the same tick → sent as one batched request
32
+ const [users, posts] = await Promise.all([
33
+ client.user.listUsers(),
34
+ client.post.listPosts(),
35
+ ])
36
+
37
+ // Sequential calls → two separate requests
38
+ const user = await client.user.getUser('1')
39
+ const posts = await client.post.listPosts()
40
+ ```
41
+
42
+ No configuration required. The server receives either a single object or an array — it handles both automatically.
43
+
44
+ ## Configuration
45
+
46
+ ```ts
47
+ const client = createKibinClient<AppRouter>({
48
+ baseUrl: '/api/rpc',
49
+
50
+ // Static headers sent with every request
51
+ headers: {
52
+ 'X-App-Version': '1.0.0',
53
+ },
54
+
55
+ // Retry on network errors and 5xx responses
56
+ retry: {
57
+ attempts: 3, // total attempts (default: 3)
58
+ delay: 300, // base delay in ms, doubles each retry (default: 300)
59
+ },
60
+
61
+ interceptors: {
62
+ request: (ctx) => ctx,
63
+ response: (ctx) => ctx.data,
64
+ error: (ctx) => { throw ctx.error },
65
+ },
66
+ })
67
+ ```
68
+
69
+ ## Interceptors
70
+
71
+ ### `request`
72
+
73
+ Runs before every call. Use it to attach auth tokens or modify arguments:
74
+
75
+ ```ts
76
+ interceptors: {
77
+ request(ctx) {
78
+ return { ...ctx, args: [{ ...ctx.args[0], token: getToken() }] }
79
+ },
80
+ }
81
+ ```
82
+
83
+ ### `response`
84
+
85
+ Runs after every successful call. Use it to transform or log responses:
86
+
87
+ ```ts
88
+ interceptors: {
89
+ response({ namespace, method, data }) {
90
+ console.log(`← ${namespace}.${method}`, data)
91
+ return data
92
+ },
93
+ }
94
+ ```
95
+
96
+ ### `error`
97
+
98
+ Runs on every failed call (after retries are exhausted). Return a fallback value or rethrow:
99
+
100
+ ```ts
101
+ interceptors: {
102
+ error({ error }) {
103
+ if (error.code === 'UNAUTHORIZED') {
104
+ window.location.href = '/login'
105
+ }
106
+ throw error
107
+ },
108
+ }
109
+ ```
110
+
111
+ ## Error handling
112
+
113
+ ```ts
114
+ import { isKibinError } from '@kibinrpc/client'
115
+
116
+ try {
117
+ await client.user.getUser('999')
118
+ } catch (err) {
119
+ if (isKibinError(err)) {
120
+ console.log(err.code) // e.g. 'NOT_FOUND'
121
+ console.log(err.message) // e.g. 'User not found'
122
+ }
123
+ }
124
+ ```
125
+
126
+ ## Retry behaviour
127
+
128
+ | Scenario | Single call | Batched call |
129
+ |---|---|---|
130
+ | Network error | retry (all attempts) | retry whole batch |
131
+ | HTTP 5xx | retry (all attempts) | retry only failed items |
132
+ | HTTP 4xx | no retry, throw immediately | no retry, reject that item |
133
+
134
+ ## API
135
+
136
+ ### `createKibinClient<Router>(config)`
137
+
138
+ Returns a typed proxy. Every namespace from your router becomes a property, every registered action becomes an async method.
139
+
140
+ ```ts
141
+ const client = createKibinClient<AppRouter>({ baseUrl: '/api/rpc' })
142
+
143
+ client.user.getUser('1') // Promise<User>
144
+ client.post.listPosts() // Promise<Post[]>
145
+ ```
146
+
147
+ ### `isKibinError(err)`
148
+
149
+ Type guard for `KibinError`:
150
+
151
+ ```ts
152
+ import { isKibinError, KibinError } from '@kibinrpc/client'
153
+
154
+ isKibinError(err) // err is KibinError
155
+ err.code // string
156
+ err.message // string
157
+ ```
158
+
159
+ ### Exported types
160
+
161
+ ```ts
162
+ import type {
163
+ KibinClient,
164
+ KibinClientConfig,
165
+ ClientInterceptors,
166
+ RequestCtx,
167
+ ResponseCtx,
168
+ ErrorCtx,
169
+ RetryConfig,
170
+ } from '@kibinrpc/client'
171
+ ```
package/dist/index.d.ts CHANGED
@@ -1,3 +1,10 @@
1
+ //#region src/errors.d.ts
2
+ declare class KibinError extends Error {
3
+ readonly code: string;
4
+ constructor(code: string, message: string);
5
+ }
6
+ declare function isKibinError(error: unknown): error is KibinError;
7
+ //#endregion
1
8
  //#region src/types.d.ts
2
9
  type AsyncifyMethod<T> = T extends ((...args: infer Args) => infer Return) ? (...args: Args) => Promise<Awaited<Return>> : never;
3
10
  type ServiceClient<T> = { [K in keyof T as T[K] extends ((...args: never[]) => unknown) ? K : never]: AsyncifyMethod<T[K]> };
@@ -5,20 +12,37 @@ type ExtractServices<T> = T extends {
5
12
  services: infer S;
6
13
  } ? S : never;
7
14
  type KibinClient<Router> = { [K in keyof ExtractServices<Router>]: ServiceClient<ExtractServices<Router>[K]> };
15
+ interface RequestCtx {
16
+ namespace: string;
17
+ method: string;
18
+ args: unknown[];
19
+ }
20
+ interface ResponseCtx extends RequestCtx {
21
+ data: unknown;
22
+ }
23
+ interface ErrorCtx extends RequestCtx {
24
+ error: KibinError;
25
+ }
26
+ interface ClientInterceptors {
27
+ request?: (ctx: RequestCtx) => RequestCtx | Promise<RequestCtx>;
28
+ response?: (ctx: ResponseCtx) => unknown | Promise<unknown>;
29
+ error?: (ctx: ErrorCtx) => unknown | Promise<unknown>;
30
+ }
31
+ interface RetryConfig {
32
+ /** Total number of attempts. Default: 3 */
33
+ attempts?: number;
34
+ /** Base delay in ms — doubles each retry (exponential backoff). Default: 300 */
35
+ delay?: number;
36
+ }
8
37
  interface KibinClientConfig {
9
38
  baseUrl: string;
10
39
  headers?: Record<string, string>;
40
+ retry?: RetryConfig;
41
+ interceptors?: ClientInterceptors;
11
42
  }
12
43
  //#endregion
13
44
  //#region src/client.d.ts
14
45
  declare function createKibinClient<Router>(config: KibinClientConfig): KibinClient<Router>;
15
46
  //#endregion
16
- //#region src/errors.d.ts
17
- declare class KibinError extends Error {
18
- readonly code: string;
19
- constructor(code: string, message: string);
20
- }
21
- declare function isKibinError(error: unknown): error is KibinError;
22
- //#endregion
23
- export { type KibinClient, type KibinClientConfig, KibinError, createKibinClient, isKibinError };
47
+ export { type ClientInterceptors, type ErrorCtx, type KibinClient, type KibinClientConfig, KibinError, type RequestCtx, type ResponseCtx, type RetryConfig, createKibinClient, isKibinError };
24
48
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/client.ts","../src/errors.ts"],"mappings":";KAAK,cAAA,MAAoB,CAAA,cAAc,IAAA,qCAChC,IAAA,EAAM,IAAA,KAAS,OAAA,CAAQ,OAAA,CAAQ,MAAA;AAAA,KAGjC,aAAA,oBACQ,CAAA,IAAK,CAAA,CAAE,CAAA,eAAe,IAAA,yBAA4B,CAAA,WAAY,cAAA,CAAe,CAAA,CAAE,CAAA;AAAA,KAGvF,eAAA,MAAqB,CAAC;EAAW,QAAA;AAAA,IAAsB,CAAA;AAAA,KAEhD,WAAA,yBACC,eAAA,CAAgB,MAAA,IAAU,aAAA,CAAc,eAAA,CAAgB,MAAA,EAAQ,CAAA;AAAA,UAG5D,iBAAA;EAChB,OAAA;EACA,OAAA,GAAU,MAAM;AAAA;;;iBCbD,iBAAA,QAAA,CAA0B,MAAA,EAAQ,iBAAA,GAAoB,WAAA,CAAY,MAAA;;;cCHrE,UAAA,SAAmB,KAAK;EAAA,SAC3B,IAAA;cAEG,IAAA,UAAc,OAAA;AAAA;AAAA,iBAOX,YAAA,CAAa,KAAA,YAAiB,KAAA,IAAS,UAAU"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/errors.ts","../src/types.ts","../src/client.ts"],"mappings":";cAAa,UAAA,SAAmB,KAAK;EAAA,SAC3B,IAAA;cAEG,IAAA,UAAc,OAAA;AAAA;AAAA,iBAOX,YAAA,CAAa,KAAA,YAAiB,KAAA,IAAS,UAAU;;;KCR5D,cAAA,MAAoB,CAAA,cAAc,IAAA,qCAChC,IAAA,EAAM,IAAA,KAAS,OAAA,CAAQ,OAAA,CAAQ,MAAA;AAAA,KAGjC,aAAA,oBACQ,CAAA,IAAK,CAAA,CAAE,CAAA,eAAe,IAAA,yBAA4B,CAAA,WAAY,cAAA,CAAe,CAAA,CAAE,CAAA;AAAA,KAGvF,eAAA,MAAqB,CAAC;EAAW,QAAA;AAAA,IAAsB,CAAA;AAAA,KAEhD,WAAA,yBACC,eAAA,CAAgB,MAAA,IAAU,aAAA,CAAc,eAAA,CAAgB,MAAA,EAAQ,CAAA;AAAA,UAG5D,UAAA;EAChB,SAAA;EACA,MAAA;EACA,IAAA;AAAA;AAAA,UAGgB,WAAA,SAAoB,UAAU;EAC9C,IAAI;AAAA;AAAA,UAGY,QAAA,SAAiB,UAAU;EAC3C,KAAA,EAAO,UAAA;AAAA;AAAA,UAGS,kBAAA;EAChB,OAAA,IAAW,GAAA,EAAK,UAAA,KAAe,UAAA,GAAa,OAAA,CAAQ,UAAA;EACpD,QAAA,IAAY,GAAA,EAAK,WAAA,eAA0B,OAAA;EAC3C,KAAA,IAAS,GAAA,EAAK,QAAA,eAAuB,OAAA;AAAA;AAAA,UAGrB,WAAA;EAjCJ;EAmCZ,QAAA;EAnC6B;EAqC7B,KAAK;AAAA;AAAA,UAGW,iBAAA;EAChB,OAAA;EACA,OAAA,GAAU,MAAA;EACV,KAAA,GAAQ,WAAA;EACR,YAAA,GAAe,kBAAA;AAAA;;;iBCtCA,iBAAA,QAAA,CAA0B,MAAA,EAAQ,iBAAA,GAAoB,WAAA,CAAY,MAAA"}
package/dist/index.js CHANGED
@@ -12,25 +12,145 @@ function isKibinError(error) {
12
12
  }
13
13
  //#endregion
14
14
  //#region src/client.ts
15
+ const RETRY_DEFAULTS = {
16
+ attempts: 3,
17
+ delay: 300
18
+ };
15
19
  function createKibinClient(config) {
16
- return new Proxy({}, { get(_, namespace) {
17
- return new Proxy({}, { get(_, method) {
18
- return async (...args) => {
19
- const result = await (await fetch(config.baseUrl, {
20
+ const maxAttempts = config.retry?.attempts ?? RETRY_DEFAULTS.attempts;
21
+ const baseDelay = config.retry?.delay ?? RETRY_DEFAULTS.delay;
22
+ const { interceptors } = config;
23
+ let pendingBatch = [];
24
+ let flushScheduled = false;
25
+ async function rpcCall(namespace, method, args) {
26
+ let ctx = {
27
+ namespace,
28
+ method,
29
+ args
30
+ };
31
+ if (interceptors?.request) ctx = await interceptors.request(ctx);
32
+ return new Promise((resolve, reject) => {
33
+ pendingBatch.push({
34
+ ctx,
35
+ resolve,
36
+ reject
37
+ });
38
+ if (!flushScheduled) {
39
+ flushScheduled = true;
40
+ queueMicrotask(flush);
41
+ }
42
+ });
43
+ }
44
+ function flush() {
45
+ const batch = pendingBatch;
46
+ pendingBatch = [];
47
+ flushScheduled = false;
48
+ if (batch.length === 0) return;
49
+ if (batch.length === 1) flushSingle(batch[0]);
50
+ else flushBatch(batch);
51
+ }
52
+ async function flushSingle(item) {
53
+ const { ctx } = item;
54
+ let lastError;
55
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
56
+ if (attempt > 0) await new Promise((resolve) => setTimeout(resolve, baseDelay * 2 ** (attempt - 1)));
57
+ try {
58
+ const response = await fetch(config.baseUrl, {
59
+ method: "POST",
60
+ headers: {
61
+ "Content-Type": "application/json",
62
+ ...config.headers
63
+ },
64
+ body: JSON.stringify(ctx)
65
+ });
66
+ const result = await response.json();
67
+ if (result.error) {
68
+ const err = new KibinError(result.error.code ?? "RPC_ERROR", result.error.message ?? "RPC Error");
69
+ if (response.status >= 500) {
70
+ lastError = err;
71
+ continue;
72
+ }
73
+ if (interceptors?.error) item.resolve(await interceptors.error({
74
+ ...ctx,
75
+ error: err
76
+ }));
77
+ else item.reject(err);
78
+ return;
79
+ }
80
+ if (interceptors?.response) item.resolve(await interceptors.response({
81
+ ...ctx,
82
+ data: result.data
83
+ }));
84
+ else item.resolve(result.data);
85
+ return;
86
+ } catch (err) {
87
+ if (err instanceof KibinError) {
88
+ item.reject(err);
89
+ return;
90
+ }
91
+ lastError = err;
92
+ }
93
+ }
94
+ if (lastError instanceof KibinError && interceptors?.error) item.resolve(await interceptors.error({
95
+ ...ctx,
96
+ error: lastError
97
+ }));
98
+ else item.reject(lastError);
99
+ }
100
+ async function flushBatch(batch) {
101
+ let pending = [...batch];
102
+ const lastErrors = /* @__PURE__ */ new Map();
103
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
104
+ if (attempt > 0) await new Promise((r) => setTimeout(r, baseDelay * 2 ** (attempt - 1)));
105
+ let results;
106
+ try {
107
+ results = await (await fetch(config.baseUrl, {
20
108
  method: "POST",
21
109
  headers: {
22
110
  "Content-Type": "application/json",
23
111
  ...config.headers
24
112
  },
25
- body: JSON.stringify({
26
- namespace,
27
- method,
28
- args
29
- })
113
+ body: JSON.stringify(pending.map((item) => item.ctx))
30
114
  })).json();
31
- if (result.error) throw new KibinError(result.error.code ?? "RPC_ERROR", result.error.message ?? "RPC Error");
32
- return result.data;
33
- };
115
+ } catch (err) {
116
+ for (const item of pending) lastErrors.set(item, err);
117
+ continue;
118
+ }
119
+ const retryItems = [];
120
+ for (let i = 0; i < pending.length; i++) {
121
+ const result = results[i];
122
+ const item = pending[i];
123
+ if (result?.error) {
124
+ const err = new KibinError(result.error.code ?? "RPC_ERROR", result.error.message ?? "RPC Error");
125
+ if (result.status >= 500) {
126
+ retryItems.push(item);
127
+ lastErrors.set(item, err);
128
+ } else if (interceptors?.error) item.resolve(await interceptors.error({
129
+ ...item.ctx,
130
+ error: err
131
+ }));
132
+ else item.reject(err);
133
+ } else if (interceptors?.response) item.resolve(await interceptors.response({
134
+ ...item.ctx,
135
+ data: result?.data
136
+ }));
137
+ else item.resolve(result?.data);
138
+ }
139
+ pending = retryItems;
140
+ if (pending.length === 0) break;
141
+ }
142
+ for (const item of pending) {
143
+ const err = lastErrors.get(item);
144
+ if (err instanceof KibinError && interceptors?.error) item.resolve(await interceptors.error({
145
+ ...item.ctx,
146
+ error: err
147
+ }));
148
+ else item.reject(err);
149
+ }
150
+ }
151
+ return new Proxy({}, { get(_, key) {
152
+ return new Proxy({}, { get(_, method) {
153
+ return (...args) => rpcCall(key, method, args);
34
154
  } });
35
155
  } });
36
156
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/errors.ts","../src/client.ts"],"sourcesContent":["export class KibinError extends Error {\n\treadonly code: string;\n\n\tconstructor(code: string, message: string) {\n\t\tsuper(message);\n\t\tthis.name = 'KibinError';\n\t\tthis.code = code;\n\t}\n}\n\nexport function isKibinError(error: unknown): error is KibinError {\n\treturn error instanceof KibinError;\n}\n","import { KibinError } from './errors.js';\nimport type { KibinClient, KibinClientConfig } from './types.js';\n\nexport function createKibinClient<Router>(config: KibinClientConfig): KibinClient<Router> {\n\treturn new Proxy(\n\t\t{},\n\t\t{\n\t\t\tget(_, namespace: string) {\n\t\t\t\treturn new Proxy(\n\t\t\t\t\t{},\n\t\t\t\t\t{\n\t\t\t\t\t\tget(_, method: string) {\n\t\t\t\t\t\t\treturn async (...args: unknown[]) => {\n\t\t\t\t\t\t\t\tconst response = await fetch(config.baseUrl, {\n\t\t\t\t\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\t\t\t\t\theaders: {\n\t\t\t\t\t\t\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t\t\t\t\t\t\t...config.headers,\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tbody: JSON.stringify({ namespace, method, args }),\n\t\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t\tconst result = await response.json();\n\n\t\t\t\t\t\t\t\tif (result.error) {\n\t\t\t\t\t\t\t\t\tthrow new KibinError(\n\t\t\t\t\t\t\t\t\t\tresult.error.code ?? 'RPC_ERROR',\n\t\t\t\t\t\t\t\t\t\tresult.error.message ?? 'RPC Error',\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\treturn result.data;\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t},\n\t\t},\n\t) as unknown as KibinClient<Router>;\n}\n"],"mappings":";AAAA,IAAa,aAAb,cAAgC,MAAM;CACrC;CAEA,YAAY,MAAc,SAAiB;EAC1C,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,OAAO;CACb;AACD;AAEA,SAAgB,aAAa,OAAqC;CACjE,OAAO,iBAAiB;AACzB;;;ACTA,SAAgB,kBAA0B,QAAgD;CACzF,OAAO,IAAI,MACV,CAAC,GACD,EACC,IAAI,GAAG,WAAmB;EACzB,OAAO,IAAI,MACV,CAAC,GACD,EACC,IAAI,GAAG,QAAgB;GACtB,OAAO,OAAO,GAAG,SAAoB;IAUpC,MAAM,SAAS,OAAM,MATE,MAAM,OAAO,SAAS;KAC5C,QAAQ;KACR,SAAS;MACR,gBAAgB;MAChB,GAAG,OAAO;KACX;KACA,MAAM,KAAK,UAAU;MAAE;MAAW;MAAQ;KAAK,CAAC;IACjD,CAAC,GAE6B,KAAK;IAEnC,IAAI,OAAO,OACV,MAAM,IAAI,WACT,OAAO,MAAM,QAAQ,aACrB,OAAO,MAAM,WAAW,WACzB;IAGD,OAAO,OAAO;GACf;EACD,EACD,CACD;CACD,EACD,CACD;AACD"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/errors.ts","../src/client.ts"],"sourcesContent":["export class KibinError extends Error {\n\treadonly code: string;\n\n\tconstructor(code: string, message: string) {\n\t\tsuper(message);\n\t\tthis.name = 'KibinError';\n\t\tthis.code = code;\n\t}\n}\n\nexport function isKibinError(error: unknown): error is KibinError {\n\treturn error instanceof KibinError;\n}\n","import { KibinError } from './errors.js';\nimport type { KibinClient, KibinClientConfig, RequestCtx } from './types.js';\n\nconst RETRY_DEFAULTS = { attempts: 3, delay: 300 };\n\ntype RpcResult = { data?: unknown; error?: { code?: string; message?: string } };\ntype BatchRpcResult = RpcResult & { status: number };\ntype QueueItem = { ctx: RequestCtx; resolve: (v: unknown) => void; reject: (e: unknown) => void };\n\nexport function createKibinClient<Router>(config: KibinClientConfig): KibinClient<Router> {\n\tconst maxAttempts = config.retry?.attempts ?? RETRY_DEFAULTS.attempts;\n\tconst baseDelay = config.retry?.delay ?? RETRY_DEFAULTS.delay;\n\tconst { interceptors } = config;\n\n\tlet pendingBatch: QueueItem[] = [];\n\tlet flushScheduled = false;\n\n\tasync function rpcCall(namespace: string, method: string, args: unknown[]): Promise<unknown> {\n\t\tlet ctx: RequestCtx = { namespace, method, args };\n\n\t\tif (interceptors?.request) {\n\t\t\tctx = await interceptors.request(ctx);\n\t\t}\n\n\t\treturn new Promise<unknown>((resolve, reject) => {\n\t\t\tpendingBatch.push({ ctx, resolve, reject });\n\t\t\tif (!flushScheduled) {\n\t\t\t\tflushScheduled = true;\n\t\t\t\tqueueMicrotask(flush);\n\t\t\t}\n\t\t});\n\t}\n\n\tfunction flush() {\n\t\tconst batch = pendingBatch;\n\t\tpendingBatch = [];\n\t\tflushScheduled = false;\n\n\t\tif (batch.length === 0) return;\n\t\tif (batch.length === 1) {\n\t\t\tflushSingle(batch[0]);\n\t\t} else {\n\t\t\tflushBatch(batch);\n\t\t}\n\t}\n\n\tasync function flushSingle(item: QueueItem) {\n\t\tconst { ctx } = item;\n\t\tlet lastError: unknown;\n\n\t\tfor (let attempt = 0; attempt < maxAttempts; attempt++) {\n\t\t\tif (attempt > 0) {\n\t\t\t\tawait new Promise<void>((resolve) => setTimeout(resolve, baseDelay * 2 ** (attempt - 1)));\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(config.baseUrl, {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { 'Content-Type': 'application/json', ...config.headers },\n\t\t\t\t\tbody: JSON.stringify(ctx),\n\t\t\t\t});\n\n\t\t\t\tconst result = (await response.json()) as RpcResult;\n\n\t\t\t\tif (result.error) {\n\t\t\t\t\tconst err = new KibinError(\n\t\t\t\t\t\tresult.error.code ?? 'RPC_ERROR',\n\t\t\t\t\t\tresult.error.message ?? 'RPC Error',\n\t\t\t\t\t);\n\t\t\t\t\tif (response.status >= 500) {\n\t\t\t\t\t\tlastError = err;\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tif (interceptors?.error) {\n\t\t\t\t\t\titem.resolve(await interceptors.error({ ...ctx, error: err }));\n\t\t\t\t\t} else {\n\t\t\t\t\t\titem.reject(err);\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (interceptors?.response) {\n\t\t\t\t\titem.resolve(await interceptors.response({ ...ctx, data: result.data }));\n\t\t\t\t} else {\n\t\t\t\t\titem.resolve(result.data);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t} catch (err) {\n\t\t\t\tif (err instanceof KibinError) {\n\t\t\t\t\titem.reject(err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tlastError = err;\n\t\t\t}\n\t\t}\n\n\t\tif (lastError instanceof KibinError && interceptors?.error) {\n\t\t\titem.resolve(await interceptors.error({ ...ctx, error: lastError }));\n\t\t} else {\n\t\t\titem.reject(lastError);\n\t\t}\n\t}\n\n\tasync function flushBatch(batch: QueueItem[]) {\n\t\tlet pending = [...batch];\n\t\tconst lastErrors = new Map<QueueItem, unknown>();\n\n\t\tfor (let attempt = 0; attempt < maxAttempts; attempt++) {\n\t\t\tif (attempt > 0) {\n\t\t\t\tawait new Promise<void>((r) => setTimeout(r, baseDelay * 2 ** (attempt - 1)));\n\t\t\t}\n\n\t\t\tlet results: BatchRpcResult[];\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(config.baseUrl, {\n\t\t\t\t\tmethod: 'POST',\n\t\t\t\t\theaders: { 'Content-Type': 'application/json', ...config.headers },\n\t\t\t\t\tbody: JSON.stringify(pending.map((item) => item.ctx)),\n\t\t\t\t});\n\t\t\t\tresults = (await response.json()) as BatchRpcResult[];\n\t\t\t} catch (err) {\n\t\t\t\tfor (const item of pending) lastErrors.set(item, err);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tconst retryItems: QueueItem[] = [];\n\n\t\t\tfor (let i = 0; i < pending.length; i++) {\n\t\t\t\tconst result = results[i];\n\t\t\t\tconst item = pending[i];\n\n\t\t\t\tif (result?.error) {\n\t\t\t\t\tconst err = new KibinError(\n\t\t\t\t\t\tresult.error.code ?? 'RPC_ERROR',\n\t\t\t\t\t\tresult.error.message ?? 'RPC Error',\n\t\t\t\t\t);\n\t\t\t\t\tif (result.status >= 500) {\n\t\t\t\t\t\tretryItems.push(item);\n\t\t\t\t\t\tlastErrors.set(item, err);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif (interceptors?.error) {\n\t\t\t\t\t\t\titem.resolve(await interceptors.error({ ...item.ctx, error: err }));\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\titem.reject(err);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif (interceptors?.response) {\n\t\t\t\t\t\titem.resolve(await interceptors.response({ ...item.ctx, data: result?.data }));\n\t\t\t\t\t} else {\n\t\t\t\t\t\titem.resolve(result?.data);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tpending = retryItems;\n\t\t\tif (pending.length === 0) break;\n\t\t}\n\n\t\tfor (const item of pending) {\n\t\t\tconst err = lastErrors.get(item);\n\t\t\tif (err instanceof KibinError && interceptors?.error) {\n\t\t\t\titem.resolve(await interceptors.error({ ...item.ctx, error: err }));\n\t\t\t} else {\n\t\t\t\titem.reject(err);\n\t\t\t}\n\t\t}\n\t}\n\n\treturn new Proxy(\n\t\t{},\n\t\t{\n\t\t\tget(_, key: string) {\n\t\t\t\treturn new Proxy(\n\t\t\t\t\t{},\n\t\t\t\t\t{\n\t\t\t\t\t\tget(_, method: string) {\n\t\t\t\t\t\t\treturn (...args: unknown[]) => rpcCall(key, method, args);\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t},\n\t\t},\n\t) as unknown as KibinClient<Router>;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kibinrpc/client",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "Type-safe and developer-friendly RPC client with end-to-end type inference",
5
5
  "license": "MIT",
6
6
  "author": "ixexel661",