@kibinrpc/client 0.0.4 → 0.1.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.
package/README.md CHANGED
@@ -11,15 +11,15 @@ npm install @kibinrpc/client
11
11
  ## Quick start
12
12
 
13
13
  ```ts
14
- import { createKibinClient } from '@kibinrpc/client'
15
- import type { AppRouter } from './server/router'
14
+ import { createKibinClient } from "@kibinrpc/client"
15
+ import type { AppRouter } from "./server/router"
16
16
 
17
17
  const client = createKibinClient<AppRouter>({
18
- baseUrl: '/api/rpc',
18
+ baseUrl: "/api/rpc",
19
19
  })
20
20
 
21
21
  // Return types are inferred from the server — no manual annotations
22
- const user = await client.user.getUser('1')
22
+ const user = await client.user.getUser("1")
23
23
  const posts = await client.post.listPosts()
24
24
  ```
25
25
 
@@ -35,7 +35,7 @@ const [users, posts] = await Promise.all([
35
35
  ])
36
36
 
37
37
  // Sequential calls → two separate requests
38
- const user = await client.user.getUser('1')
38
+ const user = await client.user.getUser("1")
39
39
  const posts = await client.post.listPosts()
40
40
  ```
41
41
 
@@ -45,11 +45,11 @@ No configuration required. The server receives either a single object or an arra
45
45
 
46
46
  ```ts
47
47
  const client = createKibinClient<AppRouter>({
48
- baseUrl: '/api/rpc',
48
+ baseUrl: "/api/rpc",
49
49
 
50
50
  // Static headers sent with every request
51
51
  headers: {
52
- 'X-App-Version': '1.0.0',
52
+ "X-App-Version": "1.0.0",
53
53
  },
54
54
 
55
55
  // Retry on network errors and 5xx responses
@@ -100,8 +100,8 @@ Runs on every failed call (after retries are exhausted). Return a fallback value
100
100
  ```ts
101
101
  interceptors: {
102
102
  error({ error }) {
103
- if (error.code === 'UNAUTHORIZED') {
104
- window.location.href = '/login'
103
+ if (error.code === "UNAUTHORIZED") {
104
+ window.location.href = "/login"
105
105
  }
106
106
  throw error
107
107
  },
@@ -111,14 +111,14 @@ interceptors: {
111
111
  ## Error handling
112
112
 
113
113
  ```ts
114
- import { isKibinError } from '@kibinrpc/client'
114
+ import { isKibinError } from "@kibinrpc/client"
115
115
 
116
116
  try {
117
- await client.user.getUser('999')
117
+ await client.user.getUser("999")
118
118
  } catch (err) {
119
119
  if (isKibinError(err)) {
120
- console.log(err.code) // e.g. 'NOT_FOUND'
121
- console.log(err.message) // e.g. 'User not found'
120
+ console.log(err.code) // e.g. "NOT_FOUND"
121
+ console.log(err.message) // e.g. "User not found"
122
122
  }
123
123
  }
124
124
  ```
@@ -138,9 +138,9 @@ try {
138
138
  Returns a typed proxy. Every namespace from your router becomes a property, every registered action becomes an async method.
139
139
 
140
140
  ```ts
141
- const client = createKibinClient<AppRouter>({ baseUrl: '/api/rpc' })
141
+ const client = createKibinClient<AppRouter>({ baseUrl: "/api/rpc" })
142
142
 
143
- client.user.getUser('1') // Promise<User>
143
+ client.user.getUser("1") // Promise<User>
144
144
  client.post.listPosts() // Promise<Post[]>
145
145
  ```
146
146
 
@@ -149,7 +149,7 @@ client.post.listPosts() // Promise<Post[]>
149
149
  Type guard for `KibinError`:
150
150
 
151
151
  ```ts
152
- import { isKibinError, KibinError } from '@kibinrpc/client'
152
+ import { isKibinError, KibinError } from "@kibinrpc/client"
153
153
 
154
154
  isKibinError(err) // err is KibinError
155
155
  err.code // string
@@ -167,5 +167,5 @@ import type {
167
167
  ResponseCtx,
168
168
  ErrorCtx,
169
169
  RetryConfig,
170
- } from '@kibinrpc/client'
170
+ } from "@kibinrpc/client"
171
171
  ```
package/dist/index.d.ts CHANGED
@@ -1,10 +1,5 @@
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
+ import { KibinError, isKibinError } from "@kibinrpc/shared";
2
+
8
3
  //#region src/types.d.ts
9
4
  type AsyncifyMethod<T> = T extends ((...args: infer Args) => infer Return) ? (...args: Args) => Promise<Awaited<Return>> : never;
10
5
  type ServiceClient<T> = { [K in keyof T as T[K] extends ((...args: never[]) => unknown) ? K : never]: AsyncifyMethod<T[K]> };
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/client.ts"],"mappings":";;;KAEK,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;EA7BJ;EA+BZ,QAAA;EA/BmB;EAiCnB,KAAK;AAAA;AAAA,UAGW,iBAAA;EAChB,OAAA;EACA,OAAA,GAAU,MAAA;EACV,KAAA,GAAQ,WAAA;EACR,YAAA,GAAe,kBAAA;AAAA;;;iBC9BA,iBAAA,QAAA,CAA0B,MAAA,EAAQ,iBAAA,GAAoB,WAAA,CAAY,MAAA"}
package/dist/index.js CHANGED
@@ -1,27 +1,43 @@
1
- //#region src/errors.ts
2
- var KibinError = class extends Error {
3
- code;
4
- constructor(code, message) {
5
- super(message);
6
- this.name = "KibinError";
7
- this.code = code;
8
- }
9
- };
10
- function isKibinError(error) {
11
- return error instanceof KibinError;
12
- }
13
- //#endregion
1
+ import { KibinError, isKibinError } from "@kibinrpc/shared";
14
2
  //#region src/client.ts
15
3
  const RETRY_DEFAULTS = {
16
4
  attempts: 3,
17
5
  delay: 300
18
6
  };
7
+ function rpcError(error) {
8
+ return new KibinError(error.code ?? "RPC_ERROR", error.message ?? "RPC Error");
9
+ }
10
+ function sleep(attempt, baseDelay) {
11
+ return new Promise((r) => setTimeout(r, baseDelay * 2 ** (attempt - 1)));
12
+ }
19
13
  function createKibinClient(config) {
20
14
  const maxAttempts = config.retry?.attempts ?? RETRY_DEFAULTS.attempts;
21
15
  const baseDelay = config.retry?.delay ?? RETRY_DEFAULTS.delay;
22
16
  const { interceptors } = config;
23
17
  let pendingBatch = [];
24
18
  let flushScheduled = false;
19
+ async function settleError(item, err) {
20
+ if (interceptors?.error) try {
21
+ item.resolve(await interceptors.error({
22
+ ...item.ctx,
23
+ error: err
24
+ }));
25
+ } catch (interceptorError) {
26
+ item.reject(interceptorError);
27
+ }
28
+ else item.reject(err);
29
+ }
30
+ async function settleSuccess(item, data) {
31
+ if (interceptors?.response) try {
32
+ item.resolve(await interceptors.response({
33
+ ...item.ctx,
34
+ data
35
+ }));
36
+ } catch (interceptorError) {
37
+ item.reject(interceptorError);
38
+ }
39
+ else item.resolve(data);
40
+ }
25
41
  async function rpcCall(namespace, method, args) {
26
42
  let ctx = {
27
43
  namespace,
@@ -50,10 +66,9 @@ function createKibinClient(config) {
50
66
  else flushBatch(batch);
51
67
  }
52
68
  async function flushSingle(item) {
53
- const { ctx } = item;
54
69
  let lastError;
55
70
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
56
- if (attempt > 0) await new Promise((resolve) => setTimeout(resolve, baseDelay * 2 ** (attempt - 1)));
71
+ if (attempt > 0) await sleep(attempt, baseDelay);
57
72
  try {
58
73
  const response = await fetch(config.baseUrl, {
59
74
  method: "POST",
@@ -61,47 +76,32 @@ function createKibinClient(config) {
61
76
  "Content-Type": "application/json",
62
77
  ...config.headers
63
78
  },
64
- body: JSON.stringify(ctx)
79
+ body: JSON.stringify(item.ctx)
65
80
  });
66
81
  const result = await response.json();
67
82
  if (result.error) {
68
- const err = new KibinError(result.error.code ?? "RPC_ERROR", result.error.message ?? "RPC Error");
83
+ const err = rpcError(result.error);
69
84
  if (response.status >= 500) {
70
85
  lastError = err;
71
86
  continue;
72
87
  }
73
- if (interceptors?.error) item.resolve(await interceptors.error({
74
- ...ctx,
75
- error: err
76
- }));
77
- else item.reject(err);
88
+ await settleError(item, err);
78
89
  return;
79
90
  }
80
- if (interceptors?.response) item.resolve(await interceptors.response({
81
- ...ctx,
82
- data: result.data
83
- }));
84
- else item.resolve(result.data);
91
+ await settleSuccess(item, result.data);
85
92
  return;
86
93
  } catch (err) {
87
- if (err instanceof KibinError) {
88
- item.reject(err);
89
- return;
90
- }
91
94
  lastError = err;
92
95
  }
93
96
  }
94
- if (lastError instanceof KibinError && interceptors?.error) item.resolve(await interceptors.error({
95
- ...ctx,
96
- error: lastError
97
- }));
97
+ if (lastError instanceof KibinError) await settleError(item, lastError);
98
98
  else item.reject(lastError);
99
99
  }
100
100
  async function flushBatch(batch) {
101
101
  let pending = [...batch];
102
102
  const lastErrors = /* @__PURE__ */ new Map();
103
103
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
104
- if (attempt > 0) await new Promise((r) => setTimeout(r, baseDelay * 2 ** (attempt - 1)));
104
+ if (attempt > 0) await sleep(attempt, baseDelay);
105
105
  let results;
106
106
  try {
107
107
  results = await (await fetch(config.baseUrl, {
@@ -117,39 +117,37 @@ function createKibinClient(config) {
117
117
  continue;
118
118
  }
119
119
  const retryItems = [];
120
+ const settlements = [];
120
121
  for (let i = 0; i < pending.length; i++) {
121
122
  const result = results[i];
122
123
  const item = pending[i];
123
- if (result?.error) {
124
- const err = new KibinError(result.error.code ?? "RPC_ERROR", result.error.message ?? "RPC Error");
124
+ if (!result) {
125
+ item.reject(new KibinError("BATCH_MISMATCH", "Server returned fewer results than expected"));
126
+ continue;
127
+ }
128
+ if (result.error) {
129
+ const err = rpcError(result.error);
125
130
  if (result.status >= 500) {
126
131
  retryItems.push(item);
127
132
  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);
133
+ } else settlements.push(settleError(item, err));
134
+ } else settlements.push(settleSuccess(item, result.data));
138
135
  }
136
+ await Promise.all(settlements);
139
137
  pending = retryItems;
140
138
  if (pending.length === 0) break;
141
139
  }
142
- for (const item of pending) {
140
+ await Promise.all(pending.map((item) => {
143
141
  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
- }
142
+ if (err instanceof KibinError) return settleError(item, err);
143
+ item.reject(err);
144
+ return Promise.resolve();
145
+ }));
150
146
  }
151
147
  return new Proxy({}, { get(_, key) {
148
+ if (typeof key !== "string") return void 0;
152
149
  return new Proxy({}, { get(_, method) {
150
+ if (typeof method !== "string") return void 0;
153
151
  return (...args) => rpcCall(key, method, args);
154
152
  } });
155
153
  } });
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, 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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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/client.ts"],"sourcesContent":["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\nfunction rpcError(error: { code?: string; message?: string }): KibinError {\n\treturn new KibinError(error.code ?? 'RPC_ERROR', error.message ?? 'RPC Error');\n}\n\nfunction sleep(attempt: number, baseDelay: number): Promise<void> {\n\treturn new Promise<void>((r) => setTimeout(r, baseDelay * 2 ** (attempt - 1)));\n}\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 settleError(item: QueueItem, err: KibinError): Promise<void> {\n\t\tif (interceptors?.error) {\n\t\t\ttry {\n\t\t\t\titem.resolve(await interceptors.error({ ...item.ctx, error: err }));\n\t\t\t} catch (interceptorError) {\n\t\t\t\titem.reject(interceptorError);\n\t\t\t}\n\t\t} else {\n\t\t\titem.reject(err);\n\t\t}\n\t}\n\n\tasync function settleSuccess(item: QueueItem, data: unknown): Promise<void> {\n\t\tif (interceptors?.response) {\n\t\t\ttry {\n\t\t\t\titem.resolve(await interceptors.response({ ...item.ctx, data }));\n\t\t\t} catch (interceptorError) {\n\t\t\t\titem.reject(interceptorError);\n\t\t\t}\n\t\t} else {\n\t\t\titem.resolve(data);\n\t\t}\n\t}\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\tlet lastError: unknown;\n\n\t\tfor (let attempt = 0; attempt < maxAttempts; attempt++) {\n\t\t\tif (attempt > 0) await sleep(attempt, baseDelay);\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(item.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 = rpcError(result.error);\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\tawait settleError(item, err);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tawait settleSuccess(item, result.data);\n\t\t\t\treturn;\n\t\t\t} catch (err) {\n\t\t\t\tlastError = err;\n\t\t\t}\n\t\t}\n\n\t\tif (lastError instanceof KibinError) {\n\t\t\tawait settleError(item, 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) await sleep(attempt, baseDelay);\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\t\t\tconst settlements: Promise<void>[] = [];\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) {\n\t\t\t\t\titem.reject(\n\t\t\t\t\t\tnew KibinError('BATCH_MISMATCH', 'Server returned fewer results than expected'),\n\t\t\t\t\t);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tif (result.error) {\n\t\t\t\t\tconst err = rpcError(result.error);\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\tsettlements.push(settleError(item, err));\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tsettlements.push(settleSuccess(item, result.data));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tawait Promise.all(settlements);\n\t\t\tpending = retryItems;\n\t\t\tif (pending.length === 0) break;\n\t\t}\n\n\t\tawait Promise.all(\n\t\t\tpending.map((item) => {\n\t\t\t\tconst err = lastErrors.get(item);\n\t\t\t\tif (err instanceof KibinError) return settleError(item, err);\n\t\t\t\titem.reject(err);\n\t\t\t\treturn Promise.resolve();\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) {\n\t\t\t\tif (typeof key !== 'string') return undefined;\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) {\n\t\t\t\t\t\t\tif (typeof method !== 'string') return undefined;\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.4",
3
+ "version": "0.1.1",
4
4
  "description": "Type-safe and developer-friendly RPC client with end-to-end type inference",
5
5
  "license": "MIT",
6
6
  "author": "ixexel661",
@@ -28,11 +28,15 @@
28
28
  "files": [
29
29
  "dist"
30
30
  ],
31
+ "dependencies": {
32
+ "@kibinrpc/shared": "0.1.1"
33
+ },
31
34
  "publishConfig": {
32
35
  "access": "public"
33
36
  },
34
37
  "scripts": {
35
38
  "build": "tsdown",
36
- "dev": "tsdown --watch"
39
+ "dev": "tsdown --watch",
40
+ "test": "vitest run"
37
41
  }
38
42
  }