@kibinrpc/server 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,13 +11,13 @@ npm install @kibinrpc/server
11
11
  ## Quick start
12
12
 
13
13
  ```ts
14
- import { ServerAction, defineActions, createRouter, KibinError } from '@kibinrpc/server'
14
+ import { ServerAction, defineActions, createRouter, KibinError } from "@kibinrpc/server"
15
15
 
16
16
  class UserActions {
17
17
  @ServerAction()
18
18
  async getUser(id: string) {
19
19
  const user = await db.users.find(id)
20
- if (!user) throw new KibinError('NOT_FOUND', 'User not found')
20
+ if (!user) throw new KibinError("NOT_FOUND", "User not found")
21
21
  return user
22
22
  }
23
23
  }
@@ -40,7 +40,7 @@ Mount on any framework that supports the Web `Request`/`Response` API:
40
40
 
41
41
  ```ts
42
42
  // Hono
43
- app.post('/api/rpc', (c) => router.handler(c.req.raw))
43
+ app.post("/api/rpc", (c) => router.handler(c.req.raw))
44
44
 
45
45
  // Next.js App Router
46
46
  export const POST = router.handler
@@ -55,7 +55,7 @@ Only explicitly registered functions are callable. Everything else is rejected w
55
55
  ### Class decorator
56
56
 
57
57
  ```ts
58
- import { ServerAction } from '@kibinrpc/server'
58
+ import { ServerAction } from "@kibinrpc/server"
59
59
 
60
60
  class UserActions {
61
61
  @ServerAction()
@@ -69,7 +69,7 @@ class UserActions {
69
69
  ### Functional
70
70
 
71
71
  ```ts
72
- import { defineActions, serverAction } from '@kibinrpc/server'
72
+ import { defineActions, serverAction } from "@kibinrpc/server"
73
73
 
74
74
  // Register a whole namespace at once
75
75
  const postActions = defineActions({
@@ -88,8 +88,8 @@ Interceptors run for every call — including each item inside a batched request
88
88
  ```ts
89
89
  const router = createRouter({ user, post }, {
90
90
  beforeAction({ namespace, method, args, request }) {
91
- const token = request.headers.get('Authorization')
92
- if (!token) throw new KibinError('UNAUTHORIZED', 'Missing token')
91
+ const token = request.headers.get("Authorization")
92
+ if (!token) throw new KibinError("UNAUTHORIZED", "Missing token")
93
93
  },
94
94
 
95
95
  afterAction({ namespace, method, result }) {
@@ -107,11 +107,11 @@ const router = createRouter({ user, post }, {
107
107
  Throw `KibinError` to send a structured error to the client:
108
108
 
109
109
  ```ts
110
- import { KibinError } from '@kibinrpc/server'
110
+ import { KibinError } from "@kibinrpc/server"
111
111
 
112
- throw new KibinError('NOT_FOUND', 'User not found')
113
- throw new KibinError('UNAUTHORIZED', 'Invalid token')
114
- throw new KibinError('BAD_REQUEST', 'Invalid input')
112
+ throw new KibinError("NOT_FOUND", "User not found")
113
+ throw new KibinError("UNAUTHORIZED", "Invalid token")
114
+ throw new KibinError("BAD_REQUEST", "Invalid input")
115
115
  ```
116
116
 
117
117
  Any other thrown error becomes `{ code: 'INTERNAL_ERROR' }` — the original message is not leaked.
@@ -157,5 +157,5 @@ import type {
157
157
  RpcRequest,
158
158
  RpcResponse,
159
159
  RpcBatchItemResponse,
160
- } from '@kibinrpc/server'
160
+ } from "@kibinrpc/server"
161
161
  ```
package/dist/index.d.mts CHANGED
@@ -1,13 +1,8 @@
1
+ import { KibinError } from "@kibinrpc/shared";
2
+
1
3
  //#region src/decorator.d.ts
2
4
  declare function ServerAction(): <This, Args extends unknown[], Return>(target: (this: This, ...args: Args) => Return, context: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>) => (this: This, ...args: Args) => Return;
3
5
  //#endregion
4
- //#region src/errors.d.ts
5
- declare class KibinError extends Error {
6
- readonly code: string;
7
- readonly statusCode: number;
8
- constructor(code: string, message: string, statusCode?: number);
9
- }
10
- //#endregion
11
6
  //#region src/fn.d.ts
12
7
  declare function serverAction<T extends (...args: never[]) => unknown>(fn: T): T;
13
8
  declare function defineActions<T extends Record<string, (...args: never[]) => unknown>>(actions: T): T;
@@ -48,7 +43,11 @@ interface RouterInterceptors {
48
43
  //#endregion
49
44
  //#region src/router.d.ts
50
45
  type Services = Record<string, object>;
51
- declare function createRouter<T extends Services>(services: T, interceptors?: RouterInterceptors): {
46
+ declare function createRouter<T extends Services>(services: T, interceptors?: RouterInterceptors, {
47
+ maxBatchSize
48
+ }?: {
49
+ maxBatchSize?: number;
50
+ }): {
52
51
  services: T;
53
52
  handler: (request: Request) => Promise<Response>;
54
53
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/decorator.ts","../src/errors.ts","../src/fn.ts","../src/types.ts","../src/router.ts"],"mappings":";iBAEgB,YAAA,CAAA,0CAEd,MAAA,GAAS,IAAA,EAAM,IAAA,KAAS,IAAA,EAAM,IAAA,KAAS,MAAA,EACvC,OAAA,EAAS,2BAAA,CAA4B,IAAA,GAAO,IAAA,EAAM,IAAA,KAAS,IAAA,EAAM,IAAA,KAAS,MAAA,OAAO,IAAA,EADlE,IAAA,KAAI,IAAA,EAAW,IAAA,KAAS,MAAA;;;cCJ5B,UAAA,SAAmB,KAAK;EAAA,SAC3B,IAAA;EAAA,SACA,UAAA;cAEG,IAAA,UAAc,OAAA,UAAiB,UAAA;AAAA;;;iBCF5B,YAAA,eAA2B,IAAA,sBAAA,CAA2B,EAAA,EAAI,CAAA,GAAI,CAAC;AAAA,iBAK/D,aAAA,WAAwB,MAAA,aAAmB,IAAA,uBAAA,CAC1D,OAAA,EAAS,CAAA,GACP,CAAA;;;UCTc,UAAA;EAChB,SAAA;EACA,MAAA;EACA,IAAA;AAAA;AAAA,UAGgB,WAAA;EAChB,IAAA,GAAO,CAAC;EACR,KAAA;IAAU,IAAA;IAAc,OAAA;EAAA;AAAA;AAAA,UAGR,oBAAA,SAA6B,WAAW;EACxD,MAAM;AAAA;AAAA,UAGU,SAAA;EAChB,SAAA;EACA,MAAA;EACA,IAAA;EACA,OAAA,EAAS,OAAO;AAAA;AAAA,UAGA,cAAA,SAAuB,SAAS;EAChD,MAAM;AAAA;AAAA,UAGU,cAAA,SAAuB,SAAS;EAChD,KAAK;AAAA;AAAA,UAGW,kBAAA;EAChB,YAAA,IAAgB,GAAA,EAAK,SAAA,YAAqB,OAAA;EAC1C,WAAA,IAAe,GAAA,EAAK,cAAA,eAA6B,OAAA;EACjD,OAAA,IAAW,GAAA,EAAK,cAAA,YAA0B,OAAA;AAAA;;;KC7BtC,QAAA,GAAW,MAAM;AAAA,iBAwEN,YAAA,WAAuB,QAAA,CAAA,CAAU,QAAA,EAAU,CAAA,EAAG,YAAA,GAAe,kBAAA;;qBAC5C,OAAA,KAAU,OAAA,CAAQ,QAAA;AAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/decorator.ts","../src/fn.ts","../src/types.ts","../src/router.ts"],"mappings":";;;iBAEgB,YAAA,CAAA,0CAEd,MAAA,GAAS,IAAA,EAAM,IAAA,KAAS,IAAA,EAAM,IAAA,KAAS,MAAA,EACvC,OAAA,EAAS,2BAAA,CAA4B,IAAA,GAAO,IAAA,EAAM,IAAA,KAAS,IAAA,EAAM,IAAA,KAAS,MAAA,OAAO,IAAA,EADlE,IAAA,KAAI,IAAA,EAAW,IAAA,KAAS,MAAA;;;iBCFzB,YAAA,eAA2B,IAAA,sBAAA,CAA2B,EAAA,EAAI,CAAA,GAAI,CAAC;AAAA,iBAK/D,aAAA,WAAwB,MAAA,aAAmB,IAAA,uBAAA,CAC1D,OAAA,EAAS,CAAA,GACP,CAAA;;;UCTc,UAAA;EAChB,SAAA;EACA,MAAA;EACA,IAAA;AAAA;AAAA,UAGgB,WAAA;EAChB,IAAA,GAAO,CAAC;EACR,KAAA;IAAU,IAAA;IAAc,OAAA;EAAA;AAAA;AAAA,UAGR,oBAAA,SAA6B,WAAW;EACxD,MAAM;AAAA;AAAA,UAGU,SAAA;EAChB,SAAA;EACA,MAAA;EACA,IAAA;EACA,OAAA,EAAS,OAAO;AAAA;AAAA,UAGA,cAAA,SAAuB,SAAS;EAChD,MAAM;AAAA;AAAA,UAGU,cAAA,SAAuB,SAAS;EAChD,KAAK;AAAA;AAAA,UAGW,kBAAA;EAChB,YAAA,IAAgB,GAAA,EAAK,SAAA,YAAqB,OAAA;EAC1C,WAAA,IAAe,GAAA,EAAK,cAAA,eAA6B,OAAA;EACjD,OAAA,IAAW,GAAA,EAAK,cAAA,YAA0B,OAAA;AAAA;;;KC7BtC,QAAA,GAAW,MAAM;AAAA,iBA8EN,YAAA,WAAuB,QAAA,CAAA,CACtC,QAAA,EAAU,CAAA,EACV,YAAA,GAAe,kBAAA;EACb;AAAA;EAA2C,YAAA;AAAA;;qBAEb,OAAA,KAAU,OAAA,CAAQ,QAAA;AAAA"}
package/dist/index.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import { KibinError } from "@kibinrpc/shared";
1
2
  //#region src/registry.ts
2
3
  const SERVER_ACTIONS_KEY = Symbol("serverActions");
3
4
  const FUNCTION_ACTION_KEY = Symbol("functionAction");
@@ -12,25 +13,14 @@ function isBrandedAction(fn) {
12
13
  function ServerAction() {
13
14
  return (target, context) => {
14
15
  context.addInitializer(function() {
16
+ if (typeof context.name === "symbol") return;
15
17
  if (!Reflect.has(this, SERVER_ACTIONS_KEY)) Reflect.set(this, SERVER_ACTIONS_KEY, /* @__PURE__ */ new Set());
16
- Reflect.get(this, SERVER_ACTIONS_KEY).add(String(context.name));
18
+ Reflect.get(this, SERVER_ACTIONS_KEY).add(context.name);
17
19
  });
18
20
  return target;
19
21
  };
20
22
  }
21
23
  //#endregion
22
- //#region src/errors.ts
23
- var KibinError = class extends Error {
24
- code;
25
- statusCode;
26
- constructor(code, message, statusCode = 400) {
27
- super(message);
28
- this.name = "KibinError";
29
- this.code = code;
30
- this.statusCode = statusCode;
31
- }
32
- };
33
- //#endregion
34
24
  //#region src/fn.ts
35
25
  function serverAction(fn) {
36
26
  Reflect.set(fn, FUNCTION_ACTION_KEY, true);
@@ -62,14 +52,14 @@ async function executeRpcCall(body, request, services, interceptors) {
62
52
  message: `Namespace "${namespace}" not found`
63
53
  } };
64
54
  const fn = service[method];
65
- if (!(getRegisteredActions(service).has(method) || isBrandedAction(fn))) return { error: {
66
- code: "METHOD_NOT_FOUND",
67
- message: `"${method}" is not a registered server action`
68
- } };
69
55
  if (typeof fn !== "function") return { error: {
70
56
  code: "METHOD_NOT_FOUND",
71
57
  message: `Method "${method}" not found`
72
58
  } };
59
+ if (!(getRegisteredActions(service).has(method) || isBrandedAction(fn))) return { error: {
60
+ code: "METHOD_NOT_FOUND",
61
+ message: `"${method}" is not a registered server action`
62
+ } };
73
63
  const ctx = {
74
64
  namespace,
75
65
  method,
@@ -89,10 +79,12 @@ async function executeRpcCall(body, request, services, interceptors) {
89
79
  }
90
80
  return { data: finalResult };
91
81
  } catch (error) {
92
- if (interceptors?.onError) await interceptors.onError({
93
- ...ctx,
94
- error
95
- });
82
+ if (interceptors?.onError) try {
83
+ await interceptors.onError({
84
+ ...ctx,
85
+ error
86
+ });
87
+ } catch {}
96
88
  if (error instanceof KibinError) return { error: {
97
89
  code: error.code,
98
90
  message: error.message
@@ -103,7 +95,8 @@ async function executeRpcCall(body, request, services, interceptors) {
103
95
  } };
104
96
  }
105
97
  }
106
- function createRouter(services, interceptors) {
98
+ const DEFAULT_MAX_BATCH_SIZE = 50;
99
+ function createRouter(services, interceptors, { maxBatchSize = DEFAULT_MAX_BATCH_SIZE } = {}) {
107
100
  async function handler(request) {
108
101
  let body;
109
102
  try {
@@ -115,6 +108,10 @@ function createRouter(services, interceptors) {
115
108
  } }, 400);
116
109
  }
117
110
  if (Array.isArray(body)) {
111
+ if (body.length > maxBatchSize) return jsonResponse({ error: {
112
+ code: "BAD_REQUEST",
113
+ message: `Batch size exceeds limit of ${maxBatchSize}`
114
+ } }, 400);
118
115
  const responses = (await Promise.all(body.map((b) => executeRpcCall(b, request, services, interceptors)))).map((r) => ({
119
116
  ...r,
120
117
  status: statusFromResponse(r)
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/registry.ts","../src/decorator.ts","../src/errors.ts","../src/fn.ts","../src/router.ts"],"sourcesContent":["export const SERVER_ACTIONS_KEY = Symbol('serverActions');\nexport const FUNCTION_ACTION_KEY = Symbol('functionAction');\n\nexport function getRegisteredActions(instance: object): Set<string> {\n\treturn (Reflect.get(instance, SERVER_ACTIONS_KEY) as Set<string> | undefined) ?? new Set();\n}\n\nexport function isBrandedAction(fn: unknown): boolean {\n\treturn typeof fn === 'function' && Reflect.get(fn, FUNCTION_ACTION_KEY) === true;\n}\n","import { SERVER_ACTIONS_KEY } from './registry.js';\n\nexport function ServerAction() {\n\treturn <This, Args extends unknown[], Return>(\n\t\ttarget: (this: This, ...args: Args) => Return,\n\t\tcontext: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>,\n\t) => {\n\t\tcontext.addInitializer(function (this: This) {\n\t\t\tif (!Reflect.has(this as object, SERVER_ACTIONS_KEY)) {\n\t\t\t\tReflect.set(this as object, SERVER_ACTIONS_KEY, new Set<string>());\n\t\t\t}\n\t\t\t(Reflect.get(this as object, SERVER_ACTIONS_KEY) as Set<string>).add(String(context.name));\n\t\t});\n\t\treturn target;\n\t};\n}\n","export class KibinError extends Error {\n\treadonly code: string;\n\treadonly statusCode: number;\n\n\tconstructor(code: string, message: string, statusCode = 400) {\n\t\tsuper(message);\n\t\tthis.name = 'KibinError';\n\t\tthis.code = code;\n\t\tthis.statusCode = statusCode;\n\t}\n}\n","import { FUNCTION_ACTION_KEY } from './registry.js';\n\nexport function serverAction<T extends (...args: never[]) => unknown>(fn: T): T {\n\tReflect.set(fn, FUNCTION_ACTION_KEY, true);\n\treturn fn;\n}\n\nexport function defineActions<T extends Record<string, (...args: never[]) => unknown>>(\n\tactions: T,\n): T {\n\tfor (const key of Object.keys(actions)) {\n\t\tserverAction(actions[key]);\n\t}\n\treturn actions;\n}\n","import { KibinError } from './errors.js';\nimport { getRegisteredActions, isBrandedAction } from './registry.js';\nimport type { RouterInterceptors, RpcBatchItemResponse, RpcRequest, RpcResponse } from './types.js';\n\ntype Services = Record<string, object>;\n\nfunction jsonResponse(body: RpcResponse | RpcBatchItemResponse[], status: number): Response {\n\treturn new Response(JSON.stringify(body), {\n\t\tstatus,\n\t\theaders: { 'Content-Type': 'application/json' },\n\t});\n}\n\nfunction statusFromResponse(result: RpcResponse): number {\n\tif (!result.error) return 200;\n\tif (result.error.code === 'NOT_FOUND' || result.error.code === 'METHOD_NOT_FOUND') return 404;\n\tif (result.error.code === 'BAD_REQUEST') return 400;\n\treturn 500;\n}\n\nasync function executeRpcCall(\n\tbody: RpcRequest,\n\trequest: Request,\n\tservices: Services,\n\tinterceptors: RouterInterceptors | undefined,\n): Promise<RpcResponse> {\n\tconst { namespace, method, args } = body;\n\n\tconst service = services[namespace];\n\tif (!service) {\n\t\treturn { error: { code: 'NOT_FOUND', message: `Namespace \"${namespace}\" not found` } };\n\t}\n\n\tconst fn = (service as Record<string, unknown>)[method];\n\tconst isAllowed = getRegisteredActions(service).has(method) || isBrandedAction(fn);\n\tif (!isAllowed) {\n\t\treturn {\n\t\t\terror: {\n\t\t\t\tcode: 'METHOD_NOT_FOUND',\n\t\t\t\tmessage: `\"${method}\" is not a registered server action`,\n\t\t\t},\n\t\t};\n\t}\n\n\tif (typeof fn !== 'function') {\n\t\treturn { error: { code: 'METHOD_NOT_FOUND', message: `Method \"${method}\" not found` } };\n\t}\n\n\tconst ctx = { namespace, method, args, request };\n\n\ttry {\n\t\tif (interceptors?.beforeAction) {\n\t\t\tawait interceptors.beforeAction(ctx);\n\t\t}\n\n\t\tconst result = await fn.apply(service, args);\n\n\t\tlet finalResult = result;\n\t\tif (interceptors?.afterAction) {\n\t\t\tconst transformed = await interceptors.afterAction({ ...ctx, result });\n\t\t\tif (transformed !== undefined) finalResult = transformed;\n\t\t}\n\n\t\treturn { data: finalResult };\n\t} catch (error) {\n\t\tif (interceptors?.onError) {\n\t\t\tawait interceptors.onError({ ...ctx, error });\n\t\t}\n\n\t\tif (error instanceof KibinError) {\n\t\t\treturn { error: { code: error.code, message: error.message } };\n\t\t}\n\t\treturn { error: { code: 'INTERNAL_ERROR', message: 'Internal server error' } };\n\t}\n}\n\nexport function createRouter<T extends Services>(services: T, interceptors?: RouterInterceptors) {\n\tasync function handler(request: Request): Promise<Response> {\n\t\tlet body: RpcRequest | RpcRequest[];\n\t\ttry {\n\t\t\tbody = (await request.json()) as RpcRequest | RpcRequest[];\n\t\t} catch {\n\t\t\treturn jsonResponse({ error: { code: 'BAD_REQUEST', message: 'Invalid JSON body' } }, 400);\n\t\t}\n\n\t\tif (Array.isArray(body)) {\n\t\t\tconst results = await Promise.all(\n\t\t\t\tbody.map((b) => executeRpcCall(b, request, services, interceptors)),\n\t\t\t);\n\t\t\tconst responses: RpcBatchItemResponse[] = results.map((r) => ({\n\t\t\t\t...r,\n\t\t\t\tstatus: statusFromResponse(r),\n\t\t\t}));\n\t\t\tconst httpStatus = responses.every((r) => r.status === 200) ? 200 : 207;\n\t\t\treturn jsonResponse(responses, httpStatus);\n\t\t}\n\n\t\tconst result = await executeRpcCall(body, request, services, interceptors);\n\t\treturn jsonResponse(result, statusFromResponse(result));\n\t}\n\n\treturn { services, handler };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/registry.ts","../src/decorator.ts","../src/fn.ts","../src/router.ts"],"sourcesContent":["export const SERVER_ACTIONS_KEY = Symbol('serverActions');\nexport const FUNCTION_ACTION_KEY = Symbol('functionAction');\n\nexport function getRegisteredActions(instance: object): Set<string> {\n\treturn (Reflect.get(instance, SERVER_ACTIONS_KEY) as Set<string> | undefined) ?? new Set();\n}\n\nexport function isBrandedAction(fn: unknown): boolean {\n\treturn typeof fn === 'function' && Reflect.get(fn, FUNCTION_ACTION_KEY) === true;\n}\n","import { SERVER_ACTIONS_KEY } from './registry.js';\n\nexport function ServerAction() {\n\treturn <This, Args extends unknown[], Return>(\n\t\ttarget: (this: This, ...args: Args) => Return,\n\t\tcontext: ClassMethodDecoratorContext<This, (this: This, ...args: Args) => Return>,\n\t) => {\n\t\tcontext.addInitializer(function (this: This) {\n\t\t\tif (typeof context.name === 'symbol') return;\n\t\t\tif (!Reflect.has(this as object, SERVER_ACTIONS_KEY)) {\n\t\t\t\tReflect.set(this as object, SERVER_ACTIONS_KEY, new Set<string>());\n\t\t\t}\n\t\t\t(Reflect.get(this as object, SERVER_ACTIONS_KEY) as Set<string>).add(context.name);\n\t\t});\n\t\treturn target;\n\t};\n}\n","import { FUNCTION_ACTION_KEY } from './registry.js';\n\nexport function serverAction<T extends (...args: never[]) => unknown>(fn: T): T {\n\tReflect.set(fn, FUNCTION_ACTION_KEY, true);\n\treturn fn;\n}\n\nexport function defineActions<T extends Record<string, (...args: never[]) => unknown>>(\n\tactions: T,\n): T {\n\tfor (const key of Object.keys(actions)) {\n\t\tserverAction(actions[key]);\n\t}\n\treturn actions;\n}\n","import { KibinError } from './errors.js';\nimport { getRegisteredActions, isBrandedAction } from './registry.js';\nimport type { RouterInterceptors, RpcBatchItemResponse, RpcRequest, RpcResponse } from './types.js';\n\ntype Services = Record<string, object>;\n\nfunction jsonResponse(body: RpcResponse | RpcBatchItemResponse[], status: number): Response {\n\treturn new Response(JSON.stringify(body), {\n\t\tstatus,\n\t\theaders: { 'Content-Type': 'application/json' },\n\t});\n}\n\nfunction statusFromResponse(result: RpcResponse): number {\n\tif (!result.error) return 200;\n\tif (result.error.code === 'NOT_FOUND' || result.error.code === 'METHOD_NOT_FOUND') return 404;\n\tif (result.error.code === 'BAD_REQUEST') return 400;\n\treturn 500;\n}\n\nasync function executeRpcCall(\n\tbody: RpcRequest,\n\trequest: Request,\n\tservices: Services,\n\tinterceptors: RouterInterceptors | undefined,\n): Promise<RpcResponse> {\n\tconst { namespace, method, args } = body;\n\n\tconst service = services[namespace];\n\tif (!service) {\n\t\treturn { error: { code: 'NOT_FOUND', message: `Namespace \"${namespace}\" not found` } };\n\t}\n\n\tconst fn = (service as Record<string, unknown>)[method];\n\tif (typeof fn !== 'function') {\n\t\treturn { error: { code: 'METHOD_NOT_FOUND', message: `Method \"${method}\" not found` } };\n\t}\n\n\tconst isAllowed = getRegisteredActions(service).has(method) || isBrandedAction(fn);\n\tif (!isAllowed) {\n\t\treturn {\n\t\t\terror: {\n\t\t\t\tcode: 'METHOD_NOT_FOUND',\n\t\t\t\tmessage: `\"${method}\" is not a registered server action`,\n\t\t\t},\n\t\t};\n\t}\n\n\tconst ctx = { namespace, method, args, request };\n\n\ttry {\n\t\tif (interceptors?.beforeAction) {\n\t\t\tawait interceptors.beforeAction(ctx);\n\t\t}\n\n\t\tconst result = await fn.apply(service, args);\n\n\t\tlet finalResult = result;\n\t\tif (interceptors?.afterAction) {\n\t\t\tconst transformed = await interceptors.afterAction({ ...ctx, result });\n\t\t\tif (transformed !== undefined) finalResult = transformed;\n\t\t}\n\n\t\treturn { data: finalResult };\n\t} catch (error) {\n\t\tif (interceptors?.onError) {\n\t\t\ttry {\n\t\t\t\tawait interceptors.onError({ ...ctx, error });\n\t\t\t} catch {\n\t\t\t\t// onError hook errors are silently ignored to protect the response path\n\t\t\t}\n\t\t}\n\n\t\tif (error instanceof KibinError) {\n\t\t\treturn { error: { code: error.code, message: error.message } };\n\t\t}\n\t\treturn { error: { code: 'INTERNAL_ERROR', message: 'Internal server error' } };\n\t}\n}\n\nconst DEFAULT_MAX_BATCH_SIZE = 50;\n\nexport function createRouter<T extends Services>(\n\tservices: T,\n\tinterceptors?: RouterInterceptors,\n\t{ maxBatchSize = DEFAULT_MAX_BATCH_SIZE }: { maxBatchSize?: number } = {},\n) {\n\tasync function handler(request: Request): Promise<Response> {\n\t\tlet body: RpcRequest | RpcRequest[];\n\t\ttry {\n\t\t\tbody = (await request.json()) as RpcRequest | RpcRequest[];\n\t\t} catch {\n\t\t\treturn jsonResponse({ error: { code: 'BAD_REQUEST', message: 'Invalid JSON body' } }, 400);\n\t\t}\n\n\t\tif (Array.isArray(body)) {\n\t\t\tif (body.length > maxBatchSize) {\n\t\t\t\treturn jsonResponse(\n\t\t\t\t\t{\n\t\t\t\t\t\terror: { code: 'BAD_REQUEST', message: `Batch size exceeds limit of ${maxBatchSize}` },\n\t\t\t\t\t},\n\t\t\t\t\t400,\n\t\t\t\t);\n\t\t\t}\n\t\t\tconst results = await Promise.all(\n\t\t\t\tbody.map((b) => executeRpcCall(b, request, services, interceptors)),\n\t\t\t);\n\t\t\tconst responses: RpcBatchItemResponse[] = results.map((r) => ({\n\t\t\t\t...r,\n\t\t\t\tstatus: statusFromResponse(r),\n\t\t\t}));\n\t\t\tconst httpStatus = responses.every((r) => r.status === 200) ? 200 : 207;\n\t\t\treturn jsonResponse(responses, httpStatus);\n\t\t}\n\n\t\tconst result = await executeRpcCall(body, request, services, interceptors);\n\t\treturn jsonResponse(result, statusFromResponse(result));\n\t}\n\n\treturn { services, handler };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kibinrpc/server",
3
- "version": "0.0.4",
3
+ "version": "0.1.1",
4
4
  "description": "Devloper-friendly RPC server router with full type inference",
5
5
  "license": "MIT",
6
6
  "author": "ixexel661",
@@ -30,11 +30,15 @@
30
30
  "files": [
31
31
  "dist"
32
32
  ],
33
+ "dependencies": {
34
+ "@kibinrpc/shared": "0.1.1"
35
+ },
33
36
  "publishConfig": {
34
37
  "access": "public"
35
38
  },
36
39
  "scripts": {
37
40
  "build": "tsdown",
38
- "dev": "tsdown --watch"
41
+ "dev": "tsdown --watch",
42
+ "test": "vitest run"
39
43
  }
40
44
  }