@enkaku/server 0.12.2 → 0.13.0

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,5 +1,12 @@
1
1
  import type { SignedToken } from '@enkaku/token';
2
- export type ProcedureAccessRecord = Record<string, boolean | Array<string>>;
2
+ export type EncryptionPolicy = 'required' | 'optional' | 'none';
3
+ export type ProcedureAccessConfig = {
4
+ allow?: boolean | Array<string>;
5
+ encryption?: EncryptionPolicy;
6
+ };
7
+ export type ProcedureAccessValue = boolean | Array<string> | ProcedureAccessConfig;
8
+ export type ProcedureAccessRecord = Record<string, ProcedureAccessValue>;
9
+ export declare function resolveEncryptionPolicy(procedure: string, record: ProcedureAccessRecord | undefined, globalPolicy: EncryptionPolicy): EncryptionPolicy;
3
10
  export type ProcedureAccessPayload = {
4
11
  iss: string;
5
12
  sub?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"access-control.d.ts","sourceRoot":"","sources":["../src/access-control.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAEhD,MAAM,MAAM,qBAAqB,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAA;AAE3E,MAAM,MAAM,sBAAsB,GAAG;IACnC,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;CACb,CAAA;AAED,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,qBAAqB,EAC7B,KAAK,EAAE,WAAW,CAAC,sBAAsB,CAAC,EAC1C,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAiCf;AAED,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,qBAAqB,EAC7B,KAAK,EAAE,WAAW,EAClB,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CA4Bf"}
1
+ {"version":3,"file":"access-control.d.ts","sourceRoot":"","sources":["../src/access-control.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAEhD,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,UAAU,GAAG,MAAM,CAAA;AAE/D,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,CAAC,EAAE,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,CAAA;IAC/B,UAAU,CAAC,EAAE,gBAAgB,CAAA;CAC9B,CAAA;AAED,MAAM,MAAM,oBAAoB,GAAG,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC,GAAG,qBAAqB,CAAA;AAElF,MAAM,MAAM,qBAAqB,GAAG,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAA;AAgBxE,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,qBAAqB,GAAG,SAAS,EACzC,YAAY,EAAE,gBAAgB,GAC7B,gBAAgB,CAYlB;AAED,MAAM,MAAM,sBAAsB,GAAG;IACnC,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;CACb,CAAA;AAED,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,qBAAqB,EAC7B,KAAK,EAAE,WAAW,CAAC,sBAAsB,CAAC,EAC1C,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CAkCf;AAED,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,qBAAqB,EAC7B,KAAK,EAAE,WAAW,EAClB,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,IAAI,CAAC,CA4Bf"}
@@ -1,23 +1,49 @@
1
1
  import { assertNonExpired, checkCapability, hasPartsMatch } from '@enkaku/capability';
2
+ function getAllowValue(access) {
3
+ if (typeof access === 'boolean' || Array.isArray(access)) {
4
+ return access;
5
+ }
6
+ return access.allow ?? false;
7
+ }
8
+ function getEncryptionPolicy(access) {
9
+ if (typeof access === 'boolean' || Array.isArray(access)) {
10
+ return undefined;
11
+ }
12
+ return access.encryption;
13
+ }
14
+ export function resolveEncryptionPolicy(procedure, record, globalPolicy) {
15
+ if (record != null) {
16
+ for (const [pattern, accessValue] of Object.entries(record)){
17
+ if (hasPartsMatch(procedure, pattern)) {
18
+ const procedurePolicy = getEncryptionPolicy(accessValue);
19
+ if (procedurePolicy != null) {
20
+ return procedurePolicy;
21
+ }
22
+ }
23
+ }
24
+ }
25
+ return globalPolicy;
26
+ }
2
27
  export async function checkProcedureAccess(serverID, record, token, atTime) {
3
28
  const payload = token.payload;
4
29
  if (payload.prc == null) {
5
30
  throw new Error('No procedure to check');
6
31
  }
7
- for (const [procedure, access] of Object.entries(record)){
32
+ for (const [procedure, accessValue] of Object.entries(record)){
8
33
  if (hasPartsMatch(payload.prc, procedure)) {
9
- if (access === true) {
34
+ const allow = getAllowValue(accessValue);
35
+ if (allow === true) {
10
36
  // Procedure can be publicly accessed
11
37
  return;
12
38
  }
13
- if (access === false) {
39
+ if (allow === false) {
14
40
  continue;
15
41
  }
16
- if (access.includes(payload.iss)) {
42
+ if (allow.includes(payload.iss)) {
17
43
  // Issuer is allowed directly
18
44
  return;
19
45
  }
20
- if (payload.sub == null || !access.includes(payload.sub)) {
46
+ if (payload.sub == null || !allow.includes(payload.sub)) {
21
47
  continue;
22
48
  }
23
49
  try {
package/lib/error.d.ts CHANGED
@@ -1,4 +1,14 @@
1
1
  import type { ErrorReplyPayload } from '@enkaku/protocol';
2
+ /**
3
+ * Error codes:
4
+ * - EK01: Handler execution failed
5
+ * - EK02: Access denied (authorization failure)
6
+ * - EK03: Server controller limit reached
7
+ * - EK04: Server handler concurrency limit reached
8
+ * - EK05: Request timeout (controller expired)
9
+ * - EK06: Message exceeds maximum size
10
+ * - EK07: Encryption required but message is not encrypted
11
+ */
2
12
  export type HandlerErrorParams<Code extends string = string, Data extends Record<string, unknown> = Record<string, unknown>> = {
3
13
  cause?: unknown;
4
14
  code: Code;
@@ -1 +1 @@
1
- {"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../src/error.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAEzD,MAAM,MAAM,kBAAkB,CAC5B,IAAI,SAAS,MAAM,GAAG,MAAM,EAC5B,IAAI,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAC5D;IACF,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,IAAI,EAAE,IAAI,CAAA;IACV,IAAI,CAAC,EAAE,IAAI,CAAA;IACX,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,qBAAa,YAAY,CACvB,IAAI,SAAS,MAAM,EACnB,IAAI,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAC9D,SAAQ,KAAK;;IACb,MAAM,CAAC,IAAI,CAAC,IAAI,SAAS,MAAM,EAAE,IAAI,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7F,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,kBAAkB,CAAC,IAAI,EAAE,IAAI,CAAC,GACrC,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC;gBAWf,MAAM,EAAE,kBAAkB,CAAC,IAAI,EAAE,IAAI,CAAC;IAOlD,IAAI,IAAI,IAAI,IAAI,CAEf;IAED,IAAI,IAAI,IAAI,IAAI,CAEf;IAED,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,CAAC,IAAI,EAAE,IAAI,CAAC;CAStD"}
1
+ {"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../src/error.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAEzD;;;;;;;;;GASG;AAEH,MAAM,MAAM,kBAAkB,CAC5B,IAAI,SAAS,MAAM,GAAG,MAAM,EAC5B,IAAI,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IAC5D;IACF,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,IAAI,EAAE,IAAI,CAAA;IACV,IAAI,CAAC,EAAE,IAAI,CAAA;IACX,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,qBAAa,YAAY,CACvB,IAAI,SAAS,MAAM,EACnB,IAAI,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAC9D,SAAQ,KAAK;;IACb,MAAM,CAAC,IAAI,CAAC,IAAI,SAAS,MAAM,EAAE,IAAI,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7F,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,kBAAkB,CAAC,IAAI,EAAE,IAAI,CAAC,GACrC,YAAY,CAAC,IAAI,EAAE,IAAI,CAAC;gBAWf,MAAM,EAAE,kBAAkB,CAAC,IAAI,EAAE,IAAI,CAAC;IAOlD,IAAI,IAAI,IAAI,IAAI,CAEf;IAED,IAAI,IAAI,IAAI,IAAI,CAEf;IAED,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,iBAAiB,CAAC,IAAI,EAAE,IAAI,CAAC;CAStD"}
@@ -1 +1 @@
1
- {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../../src/handlers/channel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,gBAAgB,EAChB,aAAa,EACb,kBAAkB,EACnB,MAAM,kBAAkB,CAAA;AAGzB,OAAO,KAAK,EAGV,cAAc,EAGf,MAAM,aAAa,CAAA;AAGpB,MAAM,MAAM,gBAAgB,CAC1B,QAAQ,SAAS,kBAAkB,EACnC,SAAS,SAAS,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,QAAQ,GAAG,MAAM,IACjE,aAAa,CAAC,gBAAgB,CAAC,SAAS,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAA;AAEnE,wBAAgB,aAAa,CAC3B,QAAQ,SAAS,kBAAkB,EACnC,SAAS,SAAS,MAAM,QAAQ,GAAG,MAAM,EAEzC,GAAG,EAAE,cAAc,CAAC,QAAQ,CAAC,EAC7B,GAAG,EAAE,gBAAgB,CAAC,QAAQ,EAAE,SAAS,CAAC,GACzC,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CA4CvB"}
1
+ {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../../src/handlers/channel.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,gBAAgB,EAChB,aAAa,EACb,kBAAkB,EACnB,MAAM,kBAAkB,CAAA;AAGzB,OAAO,KAAK,EAGV,cAAc,EAGf,MAAM,aAAa,CAAA;AAGpB,MAAM,MAAM,gBAAgB,CAC1B,QAAQ,SAAS,kBAAkB,EACnC,SAAS,SAAS,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,QAAQ,GAAG,MAAM,IACjE,aAAa,CAAC,gBAAgB,CAAC,SAAS,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAA;AAEnE,wBAAgB,aAAa,CAC3B,QAAQ,SAAS,kBAAkB,EACnC,SAAS,SAAS,MAAM,QAAQ,GAAG,MAAM,EAEzC,GAAG,EAAE,cAAc,CAAC,QAAQ,CAAC,EAC7B,GAAG,EAAE,gBAAgB,CAAC,QAAQ,EAAE,SAAS,CAAC,GACzC,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAsEvB"}
@@ -1,4 +1,4 @@
1
- import { createPipe, writeTo } from '@enkaku/stream';
1
+ import { createPipe, tap, writeTo } from '@enkaku/stream';
2
2
  import { executeHandler } from '../utils.js';
3
3
  export function handleChannel(ctx, msg) {
4
4
  const handler = ctx.handlers[msg.payload.prc];
@@ -22,19 +22,41 @@ export function handleChannel(ctx, msg) {
22
22
  if (controller.signal.aborted) {
23
23
  return;
24
24
  }
25
+ ctx.logger.trace('send value to channel {procedure} with ID {rid}', {
26
+ procedure: msg.payload.prc,
27
+ rid: msg.payload.rid
28
+ });
25
29
  await ctx.send({
26
30
  typ: 'receive',
27
31
  rid: msg.payload.rid,
28
32
  val
29
33
  });
30
34
  }));
35
+ const readable = sendStream.readable.pipeThrough(tap((value)=>{
36
+ ctx.logger.trace('received value from channel {procedure} with ID {rid}', {
37
+ procedure: msg.payload.prc,
38
+ rid: msg.payload.rid,
39
+ value
40
+ });
41
+ }));
31
42
  const handlerContext = {
32
43
  message: msg,
33
44
  param: msg.payload.prm,
34
- readable: sendStream.readable,
45
+ readable,
35
46
  signal: controller.signal,
36
47
  writable: receiveStream.writable
37
48
  };
38
- // @ts-expect-error context and handler types
39
- return executeHandler(ctx, msg.payload, ()=>handler(handlerContext));
49
+ // Wrap execution to ensure stream cleanup on handler crash
50
+ return (async ()=>{
51
+ try {
52
+ // @ts-expect-error context and handler types
53
+ await executeHandler(ctx, msg.payload, ()=>handler(handlerContext));
54
+ } finally{
55
+ try {
56
+ await receiveStream.writable.close();
57
+ } catch {
58
+ // Stream may already be closed
59
+ }
60
+ }
61
+ })();
40
62
  }
@@ -1 +1 @@
1
- {"version":3,"file":"event.d.ts","sourceRoot":"","sources":["../../src/handlers/event.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAEzF,OAAO,KAAK,EAAqC,cAAc,EAAE,MAAM,aAAa,CAAA;AAEpF,MAAM,MAAM,cAAc,CACxB,QAAQ,SAAS,kBAAkB,EACnC,SAAS,SAAS,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,QAAQ,GAAG,MAAM,IACjE,aAAa,CAAC,cAAc,CAAC,SAAS,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAA;AAEjE,wBAAgB,WAAW,CACzB,QAAQ,SAAS,kBAAkB,EACnC,SAAS,SAAS,MAAM,QAAQ,GAAG,MAAM,EACzC,GAAG,EAAE,cAAc,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,cAAc,CAAC,QAAQ,EAAE,SAAS,CAAC,GAAG,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAkBhG"}
1
+ {"version":3,"file":"event.d.ts","sourceRoot":"","sources":["../../src/handlers/event.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAGzF,OAAO,KAAK,EAAqC,cAAc,EAAE,MAAM,aAAa,CAAA;AAEpF,MAAM,MAAM,cAAc,CACxB,QAAQ,SAAS,kBAAkB,EACnC,SAAS,SAAS,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,QAAQ,GAAG,MAAM,IACjE,aAAa,CAAC,cAAc,CAAC,SAAS,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAA;AAEjE,wBAAgB,WAAW,CACzB,QAAQ,SAAS,kBAAkB,EACnC,SAAS,SAAS,MAAM,QAAQ,GAAG,MAAM,EACzC,GAAG,EAAE,cAAc,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,cAAc,CAAC,QAAQ,EAAE,SAAS,CAAC,GAAG,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CA2BhG"}
@@ -1,21 +1,30 @@
1
1
  import { toPromise } from '@enkaku/async';
2
+ import { HandlerError } from '../error.js';
2
3
  export function handleEvent(ctx, msg) {
3
4
  const handler = ctx.handlers[msg.payload.prc];
4
5
  if (handler == null) {
5
6
  return new Error(`No handler for procedure: ${msg.payload.prc}`);
6
7
  }
7
8
  ctx.logger.trace('handle event {procedure}', {
8
- procedure: msg.payload.prc
9
+ procedure: msg.payload.prc,
10
+ data: msg.payload.data
9
11
  });
10
12
  const handlerContext = {
11
13
  message: msg,
12
14
  data: msg.payload.data
13
15
  };
14
16
  return toPromise(()=>handler(handlerContext)).catch((cause)=>{
17
+ const error = HandlerError.from(cause, {
18
+ code: 'EK01',
19
+ message: cause.message ?? 'Handler execution failed'
20
+ });
21
+ ctx.logger.debug('handler error for event {procedure}', {
22
+ procedure: msg.payload.prc,
23
+ data: msg.payload.data,
24
+ error
25
+ });
15
26
  ctx.events.emit('handlerError', {
16
- error: new Error(`Error handling procedure: ${msg.payload.prc}`, {
17
- cause
18
- }),
27
+ error,
19
28
  payload: msg.payload
20
29
  });
21
30
  });
@@ -1 +1 @@
1
- {"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../src/handlers/request.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAE3F,OAAO,KAAK,EAAE,cAAc,EAAkB,MAAM,aAAa,CAAA;AAGjE,MAAM,MAAM,gBAAgB,CAC1B,QAAQ,SAAS,kBAAkB,EACnC,SAAS,SAAS,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,QAAQ,GAAG,MAAM,IACjE,aAAa,CAAC,gBAAgB,CAAC,SAAS,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAA;AAEnE,wBAAgB,aAAa,CAC3B,QAAQ,SAAS,kBAAkB,EACnC,SAAS,SAAS,MAAM,QAAQ,GAAG,MAAM,EAEzC,GAAG,EAAE,cAAc,CAAC,QAAQ,CAAC,EAC7B,GAAG,EAAE,gBAAgB,CAAC,QAAQ,EAAE,SAAS,CAAC,GACzC,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBvB"}
1
+ {"version":3,"file":"request.d.ts","sourceRoot":"","sources":["../../src/handlers/request.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAE3F,OAAO,KAAK,EAAE,cAAc,EAAkB,MAAM,aAAa,CAAA;AAGjE,MAAM,MAAM,gBAAgB,CAC1B,QAAQ,SAAS,kBAAkB,EACnC,SAAS,SAAS,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,QAAQ,GAAG,MAAM,IACjE,aAAa,CAAC,gBAAgB,CAAC,SAAS,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAA;AAEnE,wBAAgB,aAAa,CAC3B,QAAQ,SAAS,kBAAkB,EACnC,SAAS,SAAS,MAAM,QAAQ,GAAG,MAAM,EAEzC,GAAG,EAAE,cAAc,CAAC,QAAQ,CAAC,EAC7B,GAAG,EAAE,gBAAgB,CAAC,QAAQ,EAAE,SAAS,CAAC,GACzC,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBvB"}
@@ -6,7 +6,8 @@ export function handleRequest(ctx, msg) {
6
6
  }
7
7
  ctx.logger.trace('handle request {procedure} with ID {rid}', {
8
8
  procedure: msg.payload.prc,
9
- rid: msg.payload.rid
9
+ rid: msg.payload.rid,
10
+ param: msg.payload.prm
10
11
  });
11
12
  const controller = new AbortController();
12
13
  ctx.controllers[msg.payload.rid] = controller;
@@ -1 +1 @@
1
- {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../../src/handlers/stream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,aAAa,EACb,kBAAkB,EAClB,eAAe,EAChB,MAAM,kBAAkB,CAAA;AAGzB,OAAO,KAAK,EAAE,cAAc,EAA8B,MAAM,aAAa,CAAA;AAG7E,MAAM,MAAM,eAAe,CACzB,QAAQ,SAAS,kBAAkB,EACnC,SAAS,SAAS,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,QAAQ,GAAG,MAAM,IACjE,aAAa,CAAC,eAAe,CAAC,SAAS,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAA;AAElE,wBAAgB,YAAY,CAC1B,QAAQ,SAAS,kBAAkB,EACnC,SAAS,SAAS,MAAM,QAAQ,GAAG,MAAM,EACzC,GAAG,EAAE,cAAc,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,eAAe,CAAC,QAAQ,EAAE,SAAS,CAAC,GAAG,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAoCjG"}
1
+ {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../../src/handlers/stream.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAEV,aAAa,EACb,kBAAkB,EAClB,eAAe,EAChB,MAAM,kBAAkB,CAAA;AAGzB,OAAO,KAAK,EAAE,cAAc,EAA8B,MAAM,aAAa,CAAA;AAG7E,MAAM,MAAM,eAAe,CACzB,QAAQ,SAAS,kBAAkB,EACnC,SAAS,SAAS,MAAM,QAAQ,GAAG,MAAM,GAAG,MAAM,QAAQ,GAAG,MAAM,IACjE,aAAa,CAAC,eAAe,CAAC,SAAS,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAA;AAElE,wBAAgB,YAAY,CAC1B,QAAQ,SAAS,kBAAkB,EACnC,SAAS,SAAS,MAAM,QAAQ,GAAG,MAAM,EACzC,GAAG,EAAE,cAAc,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,eAAe,CAAC,QAAQ,EAAE,SAAS,CAAC,GAAG,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAoDjG"}
@@ -16,6 +16,10 @@ export function handleStream(ctx, msg) {
16
16
  if (controller.signal.aborted) {
17
17
  return;
18
18
  }
19
+ ctx.logger.trace('send value to stream {procedure} with ID {rid}', {
20
+ procedure: msg.payload.prc,
21
+ rid: msg.payload.rid
22
+ });
19
23
  await ctx.send({
20
24
  typ: 'receive',
21
25
  rid: msg.payload.rid,
@@ -28,6 +32,17 @@ export function handleStream(ctx, msg) {
28
32
  signal: controller.signal,
29
33
  writable: receiveStream.writable
30
34
  };
31
- // @ts-expect-error context and handler types
32
- return executeHandler(ctx, msg.payload, ()=>handler(handlerContext));
35
+ // Wrap execution to ensure stream cleanup on handler crash
36
+ return (async ()=>{
37
+ try {
38
+ // @ts-expect-error context and handler types
39
+ await executeHandler(ctx, msg.payload, ()=>handler(handlerContext));
40
+ } finally{
41
+ try {
42
+ await receiveStream.writable.close();
43
+ } catch {
44
+ // Stream may already be closed
45
+ }
46
+ }
47
+ })();
33
48
  }
package/lib/index.d.ts CHANGED
@@ -9,7 +9,9 @@
9
9
  *
10
10
  * @module server
11
11
  */
12
- export type { ProcedureAccessRecord } from './access-control.js';
12
+ export type { EncryptionPolicy, ProcedureAccessConfig, ProcedureAccessRecord, ProcedureAccessValue, } from './access-control.js';
13
+ export { resolveEncryptionPolicy } from './access-control.js';
14
+ export { createResourceLimiter, DEFAULT_RESOURCE_LIMITS, type ResourceLimiter, type ResourceLimits, } from './limits.js';
13
15
  export { type ServeParams, Server, type ServerParams, serve } from './server.js';
14
16
  export type { ChannelHandler, ChannelHandlerContext, EventHandler, EventHandlerContext, HandlerReturn, ProcedureHandlers, RequestHandler, RequestHandlerContext, ServerEmitter, ServerEvents, StreamHandler, StreamHandlerContext, } from './types.js';
15
17
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,YAAY,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAChE,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,EAAE,KAAK,YAAY,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAChF,YAAY,EACV,cAAc,EACd,qBAAqB,EACrB,YAAY,EACZ,mBAAmB,EACnB,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,qBAAqB,EACrB,aAAa,EACb,YAAY,EACZ,aAAa,EACb,oBAAoB,GACrB,MAAM,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,YAAY,EACV,gBAAgB,EAChB,qBAAqB,EACrB,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAA;AAC7D,OAAO,EACL,qBAAqB,EACrB,uBAAuB,EACvB,KAAK,eAAe,EACpB,KAAK,cAAc,GACpB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,EAAE,KAAK,YAAY,EAAE,KAAK,EAAE,MAAM,aAAa,CAAA;AAChF,YAAY,EACV,cAAc,EACd,qBAAqB,EACrB,YAAY,EACZ,mBAAmB,EACnB,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,qBAAqB,EACrB,aAAa,EACb,YAAY,EACZ,aAAa,EACb,oBAAoB,GACrB,MAAM,YAAY,CAAA"}
package/lib/index.js CHANGED
@@ -8,4 +8,6 @@
8
8
  * ```
9
9
  *
10
10
  * @module server
11
- */ export { Server, serve } from './server.js';
11
+ */ export { resolveEncryptionPolicy } from './access-control.js';
12
+ export { createResourceLimiter, DEFAULT_RESOURCE_LIMITS } from './limits.js';
13
+ export { Server, serve } from './server.js';
@@ -0,0 +1,26 @@
1
+ export type ResourceLimits = {
2
+ /** Maximum number of concurrent controllers (in-flight requests). Default: 10000 */
3
+ maxControllers: number;
4
+ /** Maximum number of concurrent handler executions. Default: 100 */
5
+ maxConcurrentHandlers: number;
6
+ /** Controller timeout in milliseconds. Default: 300000 (5 min) */
7
+ controllerTimeoutMs: number;
8
+ /** Cleanup timeout in milliseconds when disposing. Default: 30000 (30 sec) */
9
+ cleanupTimeoutMs: number;
10
+ /** Maximum size in bytes for any individual message payload. Default: 10485760 (10 MB) */
11
+ maxMessageSize: number;
12
+ };
13
+ export declare const DEFAULT_RESOURCE_LIMITS: ResourceLimits;
14
+ export type ResourceLimiter = {
15
+ limits: ResourceLimits;
16
+ controllerCount: number;
17
+ activeHandlers: number;
18
+ canAddController: () => boolean;
19
+ addController: (rid: string) => void;
20
+ removeController: (rid: string) => void;
21
+ getExpiredControllers: () => Array<string>;
22
+ acquireHandler: () => boolean;
23
+ releaseHandler: () => void;
24
+ };
25
+ export declare function createResourceLimiter(options?: Partial<ResourceLimits>): ResourceLimiter;
26
+ //# sourceMappingURL=limits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../src/limits.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GAAG;IAC3B,oFAAoF;IACpF,cAAc,EAAE,MAAM,CAAA;IACtB,oEAAoE;IACpE,qBAAqB,EAAE,MAAM,CAAA;IAC7B,kEAAkE;IAClE,mBAAmB,EAAE,MAAM,CAAA;IAC3B,8EAA8E;IAC9E,gBAAgB,EAAE,MAAM,CAAA;IACxB,0FAA0F;IAC1F,cAAc,EAAE,MAAM,CAAA;CACvB,CAAA;AAED,eAAO,MAAM,uBAAuB,EAAE,cAMrC,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,cAAc,CAAA;IACtB,eAAe,EAAE,MAAM,CAAA;IACvB,cAAc,EAAE,MAAM,CAAA;IACtB,gBAAgB,EAAE,MAAM,OAAO,CAAA;IAC/B,aAAa,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IACpC,gBAAgB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IACvC,qBAAqB,EAAE,MAAM,KAAK,CAAC,MAAM,CAAC,CAAA;IAC1C,cAAc,EAAE,MAAM,OAAO,CAAA;IAC7B,cAAc,EAAE,MAAM,IAAI,CAAA;CAC3B,CAAA;AAED,wBAAgB,qBAAqB,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,eAAe,CAiDxF"}
package/lib/limits.js ADDED
@@ -0,0 +1,56 @@
1
+ export const DEFAULT_RESOURCE_LIMITS = {
2
+ maxControllers: 10000,
3
+ maxConcurrentHandlers: 100,
4
+ controllerTimeoutMs: 300000,
5
+ cleanupTimeoutMs: 30000,
6
+ maxMessageSize: 10485760
7
+ };
8
+ export function createResourceLimiter(options) {
9
+ const limits = {
10
+ ...DEFAULT_RESOURCE_LIMITS,
11
+ ...options
12
+ };
13
+ const controllers = new Map() // rid -> timestamp
14
+ ;
15
+ let handlerCount = 0;
16
+ return {
17
+ limits,
18
+ get controllerCount () {
19
+ return controllers.size;
20
+ },
21
+ get activeHandlers () {
22
+ return handlerCount;
23
+ },
24
+ canAddController () {
25
+ return controllers.size < limits.maxControllers;
26
+ },
27
+ addController (rid) {
28
+ controllers.set(rid, Date.now());
29
+ },
30
+ removeController (rid) {
31
+ controllers.delete(rid);
32
+ },
33
+ getExpiredControllers () {
34
+ const now = Date.now();
35
+ const expired = [];
36
+ for (const [rid, timestamp] of controllers){
37
+ if (now - timestamp > limits.controllerTimeoutMs) {
38
+ expired.push(rid);
39
+ }
40
+ }
41
+ return expired;
42
+ },
43
+ acquireHandler () {
44
+ if (handlerCount >= limits.maxConcurrentHandlers) {
45
+ return false;
46
+ }
47
+ handlerCount++;
48
+ return true;
49
+ },
50
+ releaseHandler () {
51
+ if (handlerCount > 0) {
52
+ handlerCount--;
53
+ }
54
+ }
55
+ };
56
+ }
package/lib/server.d.ts CHANGED
@@ -2,9 +2,11 @@ import { Disposer } from '@enkaku/async';
2
2
  import { type Logger } from '@enkaku/log';
3
3
  import { type AnyClientMessageOf, type ProtocolDefinition, type ServerTransportOf } from '@enkaku/protocol';
4
4
  import { type Validator } from '@enkaku/schema';
5
- import { type ProcedureAccessRecord } from './access-control.js';
5
+ import { type Identity } from '@enkaku/token';
6
+ import { type EncryptionPolicy, type ProcedureAccessRecord } from './access-control.js';
7
+ import { type ResourceLimiter, type ResourceLimits } from './limits.js';
6
8
  import type { ProcedureHandlers, ServerEmitter } from './types.js';
7
- export type AccessControlParams = {
9
+ export type AccessControlParams = ({
8
10
  public: true;
9
11
  serverID?: string;
10
12
  access?: ProcedureAccessRecord;
@@ -12,10 +14,13 @@ export type AccessControlParams = {
12
14
  public: false;
13
15
  serverID: string;
14
16
  access: ProcedureAccessRecord;
17
+ }) & {
18
+ encryptionPolicy?: EncryptionPolicy;
15
19
  };
16
20
  export type HandleMessagesParams<Protocol extends ProtocolDefinition> = AccessControlParams & {
17
21
  events: ServerEmitter;
18
22
  handlers: ProcedureHandlers<Protocol>;
23
+ limiter: ResourceLimiter;
19
24
  logger: Logger;
20
25
  signal: AbortSignal;
21
26
  transport: ServerTransportOf<Protocol>;
@@ -23,8 +28,10 @@ export type HandleMessagesParams<Protocol extends ProtocolDefinition> = AccessCo
23
28
  };
24
29
  export type ServerParams<Protocol extends ProtocolDefinition> = {
25
30
  access?: ProcedureAccessRecord;
31
+ encryptionPolicy?: EncryptionPolicy;
26
32
  handlers: ProcedureHandlers<Protocol>;
27
- id?: string;
33
+ identity?: Identity;
34
+ limits?: Partial<ResourceLimits>;
28
35
  logger?: Logger;
29
36
  protocol?: Protocol;
30
37
  public?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,QAAQ,EAAE,MAAM,eAAe,CAAA;AAE7D,OAAO,EAAmB,KAAK,MAAM,EAAE,MAAM,aAAa,CAAA;AAC1D,OAAO,EACL,KAAK,kBAAkB,EAGvB,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACvB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,gBAAgB,CAAA;AAGvB,OAAO,EAAoB,KAAK,qBAAqB,EAAE,MAAM,qBAAqB,CAAA;AAKlF,OAAO,KAAK,EAIV,iBAAiB,EACjB,aAAa,EAEd,MAAM,YAAY,CAAA;AAQnB,MAAM,MAAM,mBAAmB,GAC3B;IAAE,MAAM,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,qBAAqB,CAAA;CAAE,GACnE;IAAE,MAAM,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,qBAAqB,CAAA;CAAE,CAAA;AAEtE,MAAM,MAAM,oBAAoB,CAAC,QAAQ,SAAS,kBAAkB,IAAI,mBAAmB,GAAG;IAC5F,MAAM,EAAE,aAAa,CAAA;IACrB,QAAQ,EAAE,iBAAiB,CAAC,QAAQ,CAAC,CAAA;IACrC,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,WAAW,CAAA;IACnB,SAAS,EAAE,iBAAiB,CAAC,QAAQ,CAAC,CAAA;IACtC,SAAS,CAAC,EAAE,SAAS,CAAC,kBAAkB,CAAC,QAAQ,CAAC,CAAC,CAAA;CACpD,CAAA;AA2JD,MAAM,MAAM,YAAY,CAAC,QAAQ,SAAS,kBAAkB,IAAI;IAC9D,MAAM,CAAC,EAAE,qBAAqB,CAAA;IAC9B,QAAQ,EAAE,iBAAiB,CAAC,QAAQ,CAAC,CAAA;IACrC,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,UAAU,CAAC,EAAE,KAAK,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC,CAAA;CAChD,CAAA;AAED,MAAM,MAAM,aAAa,GAAG;IAAE,MAAM,CAAC,EAAE,qBAAqB,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,CAAA;AAEjG,qBAAa,MAAM,CAAC,QAAQ,SAAS,kBAAkB,CAAE,SAAQ,QAAQ;;gBAS3D,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC;IAgD1C,IAAI,MAAM,IAAI,aAAa,CAE1B;IAED,MAAM,CAAC,SAAS,EAAE,iBAAiB,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;CAiC3F;AAED,MAAM,MAAM,WAAW,CAAC,QAAQ,SAAS,kBAAkB,IAAI,IAAI,CACjE,YAAY,CAAC,QAAQ,CAAC,EACtB,YAAY,CACb,GAAG;IACF,SAAS,EAAE,iBAAiB,CAAC,QAAQ,CAAC,CAAA;CACvC,CAAA;AAED,wBAAgB,KAAK,CAAC,QAAQ,SAAS,kBAAkB,EACvD,MAAM,EAAE,WAAW,CAAC,QAAQ,CAAC,GAC5B,MAAM,CAAC,QAAQ,CAAC,CAGlB"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
package/lib/server.js CHANGED
@@ -4,13 +4,15 @@ import { getEnkakuLogger } from '@enkaku/log';
4
4
  import { createClientMessageSchema } from '@enkaku/protocol';
5
5
  import { createValidator, ValidationError } from '@enkaku/schema';
6
6
  import { createUnsignedToken, isSignedToken } from '@enkaku/token';
7
- import { checkClientToken } from './access-control.js';
7
+ import { checkClientToken, resolveEncryptionPolicy } from './access-control.js';
8
+ import { HandlerError } from './error.js';
8
9
  import { handleChannel } from './handlers/channel.js';
9
10
  import { handleEvent } from './handlers/event.js';
10
11
  import { handleRequest } from './handlers/request.js';
11
12
  import { handleStream } from './handlers/stream.js';
13
+ import { createResourceLimiter } from './limits.js';
12
14
  async function handleMessages(params) {
13
- const { events, handlers, logger, signal, transport, validator } = params;
15
+ const { events, handlers, limiter, logger, signal, transport, validator } = params;
14
16
  const controllers = Object.create(null);
15
17
  const context = {
16
18
  controllers,
@@ -20,8 +22,34 @@ async function handleMessages(params) {
20
22
  send: (payload)=>transport.write(createUnsignedToken(payload))
21
23
  };
22
24
  const running = Object.create(null);
25
+ const encoder = new TextEncoder();
26
+ // Periodic cleanup of expired controllers
27
+ const cleanupInterval = setInterval(()=>{
28
+ const expired = limiter.getExpiredControllers();
29
+ for (const rid of expired){
30
+ const controller = controllers[rid];
31
+ if (controller != null) {
32
+ controller.abort('Timeout');
33
+ const error = new HandlerError({
34
+ code: 'EK05',
35
+ message: 'Request timeout'
36
+ });
37
+ context.send(error.toPayload(rid));
38
+ events.emit('handlerTimeout', {
39
+ rid
40
+ });
41
+ limiter.removeController(rid);
42
+ limiter.releaseHandler();
43
+ delete controllers[rid];
44
+ delete running[rid];
45
+ } else {
46
+ limiter.removeController(rid);
47
+ }
48
+ }
49
+ }, Math.min(limiter.limits.controllerTimeoutMs, 10000));
23
50
  const disposer = new Disposer({
24
51
  dispose: async ()=>{
52
+ clearInterval(cleanupInterval);
25
53
  const interruption = new DisposeInterruption();
26
54
  // Abort all currently running handlers
27
55
  for (const controller of Object.values(controllers)){
@@ -53,23 +81,96 @@ async function handleMessages(params) {
53
81
  return result.value;
54
82
  } : (message)=>message;
55
83
  function processHandler(message, handle) {
84
+ const rid = message.payload.typ === 'event' ? Math.random().toString(36).slice(2) : message.payload.rid;
85
+ // Check controller limit
86
+ if (!limiter.canAddController()) {
87
+ const error = new HandlerError({
88
+ code: 'EK03',
89
+ message: 'Server controller limit reached'
90
+ });
91
+ if (message.payload.typ !== 'event') {
92
+ context.send(error.toPayload(rid));
93
+ }
94
+ events.emit('handlerError', {
95
+ error,
96
+ payload: message.payload
97
+ });
98
+ return;
99
+ }
100
+ // Check handler concurrency (synchronous fast path)
101
+ if (limiter.activeHandlers >= limiter.limits.maxConcurrentHandlers) {
102
+ const error = new HandlerError({
103
+ code: 'EK04',
104
+ message: 'Server handler limit reached'
105
+ });
106
+ if (message.payload.typ !== 'event') {
107
+ context.send(error.toPayload(rid));
108
+ }
109
+ events.emit('handlerError', {
110
+ error,
111
+ payload: message.payload
112
+ });
113
+ return;
114
+ }
115
+ limiter.addController(rid);
116
+ limiter.acquireHandler();
56
117
  const returned = handle();
57
118
  if (returned instanceof Error) {
58
- const rid = message.payload.typ === 'event' ? undefined : message.payload.rid;
119
+ limiter.removeController(rid);
120
+ limiter.releaseHandler();
59
121
  events.emit('handlerError', {
60
- error: returned,
61
- payload: message.payload,
62
- rid
122
+ error: HandlerError.from(returned, {
123
+ code: 'EK01'
124
+ }),
125
+ payload: message.payload
63
126
  });
64
127
  } else {
65
- const id = message.payload.typ === 'event' ? Math.random().toString(36).slice(2) : message.payload.rid;
66
- running[id] = returned;
128
+ running[rid] = returned;
67
129
  returned.then(()=>{
68
- delete running[id];
130
+ // Guard against double-release if timeout cleanup already handled this rid
131
+ if (running[rid] === returned) {
132
+ limiter.removeController(rid);
133
+ limiter.releaseHandler();
134
+ delete running[rid];
135
+ }
69
136
  });
70
137
  }
71
138
  }
72
- const process = params.public ? processHandler : async (message, handle)=>{
139
+ function checkMessageEncryption(message) {
140
+ const globalPolicy = params.encryptionPolicy ?? 'none';
141
+ if (globalPolicy === 'none') {
142
+ return true;
143
+ }
144
+ const procedure = message.payload.prc;
145
+ const effectivePolicy = procedure != null ? resolveEncryptionPolicy(procedure, params.access, globalPolicy) : globalPolicy;
146
+ if (effectivePolicy !== 'required') {
147
+ return true;
148
+ }
149
+ // Detect if message was encrypted: jwe-in-jws mode has a 'jwe' field in the payload
150
+ const payload = message.payload;
151
+ return 'jwe' in payload && typeof payload.jwe === 'string';
152
+ }
153
+ function handleEncryptionViolation(message) {
154
+ const error = new HandlerError({
155
+ code: 'EK07',
156
+ message: 'Encryption required but message is not encrypted'
157
+ });
158
+ if (message.payload.typ === 'event') {
159
+ events.emit('handlerError', {
160
+ error,
161
+ payload: message.payload
162
+ });
163
+ } else {
164
+ context.send(error.toPayload(message.payload.rid));
165
+ }
166
+ }
167
+ const process = params.public ? (message, handle)=>{
168
+ if (!checkMessageEncryption(message)) {
169
+ handleEncryptionViolation(message);
170
+ return;
171
+ }
172
+ processHandler(message, handle);
173
+ } : async (message, handle)=>{
73
174
  try {
74
175
  if (!params.public) {
75
176
  if (!isSignedToken(message)) {
@@ -77,25 +178,30 @@ async function handleMessages(params) {
77
178
  }
78
179
  await checkClientToken(params.serverID, params.access, message);
79
180
  }
80
- } catch (err) {
81
- const errorMessage = err.message ?? 'Access denied';
181
+ } catch (cause) {
182
+ const error = new HandlerError({
183
+ cause,
184
+ code: 'EK02',
185
+ message: cause.message ?? 'Access denied'
186
+ });
82
187
  if (message.payload.typ === 'event') {
188
+ events.emit('eventAuthError', {
189
+ error,
190
+ payload: message.payload
191
+ });
83
192
  events.emit('handlerError', {
84
- error: new Error(errorMessage, {
85
- cause: err
86
- }),
193
+ error,
87
194
  payload: message.payload
88
195
  });
89
196
  } else {
90
- context.send({
91
- typ: 'error',
92
- rid: message.payload.rid,
93
- code: 'EK02',
94
- msg: errorMessage
95
- });
197
+ context.send(error.toPayload(message.payload.rid));
96
198
  }
97
199
  return;
98
200
  }
201
+ if (!checkMessageEncryption(message)) {
202
+ handleEncryptionViolation(message);
203
+ return;
204
+ }
99
205
  processHandler(message, handle);
100
206
  };
101
207
  async function handleNext() {
@@ -106,6 +212,22 @@ async function handleMessages(params) {
106
212
  }
107
213
  const msg = processMessage(next.value);
108
214
  if (msg != null) {
215
+ const msgSize = encoder.encode(JSON.stringify(msg.payload)).byteLength;
216
+ if (msgSize > limiter.limits.maxMessageSize) {
217
+ const error = new HandlerError({
218
+ code: 'EK06',
219
+ message: 'Message exceeds maximum size'
220
+ });
221
+ if ('rid' in msg.payload && msg.payload.rid != null) {
222
+ context.send(error.toPayload(msg.payload.rid));
223
+ }
224
+ events.emit('handlerError', {
225
+ error,
226
+ payload: msg.payload
227
+ });
228
+ handleNext();
229
+ return;
230
+ }
109
231
  switch(msg.payload.typ){
110
232
  case 'abort':
111
233
  controllers[msg.payload.rid]?.abort(msg.payload.rsn);
@@ -131,7 +253,35 @@ async function handleMessages(params) {
131
253
  case 'send':
132
254
  {
133
255
  const controller = controllers[msg.payload.rid];
134
- controller?.writer.write(msg.payload.val);
256
+ if (controller == null) {
257
+ logger.debug('received send for unknown channel {rid}', {
258
+ rid: msg.payload.rid
259
+ });
260
+ break;
261
+ }
262
+ // In non-public mode, validate send messages
263
+ if (!params.public) {
264
+ if (!isSignedToken(msg)) {
265
+ const error = new HandlerError({
266
+ code: 'EK02',
267
+ message: 'Channel send message must be signed'
268
+ });
269
+ context.send(error.toPayload(msg.payload.rid));
270
+ break;
271
+ }
272
+ try {
273
+ await checkClientToken(params.serverID, params.access, msg);
274
+ } catch (cause) {
275
+ const error = new HandlerError({
276
+ cause,
277
+ code: 'EK02',
278
+ message: cause.message ?? 'Send authorization denied'
279
+ });
280
+ context.send(error.toPayload(msg.payload.rid));
281
+ break;
282
+ }
283
+ }
284
+ controller.writer.write(msg.payload.val);
135
285
  break;
136
286
  }
137
287
  case 'stream':
@@ -153,6 +303,7 @@ export class Server extends Disposer {
153
303
  #events;
154
304
  #handlers;
155
305
  #handling = [];
306
+ #limiter;
156
307
  #logger;
157
308
  #validator;
158
309
  constructor(params){
@@ -160,40 +311,63 @@ export class Server extends Disposer {
160
311
  dispose: async ()=>{
161
312
  // Signal messages handler to stop execution and run cleanup logic
162
313
  this.#abortController.abort();
163
- // Dispose of all handling transports
164
- await Promise.all(this.#handling.map(async (handling)=>{
165
- // Wait until all handlers are done - they might still need to flush messages to the transport
166
- await handling.done;
167
- // Dispose transport
168
- await handling.transport.dispose();
169
- }));
314
+ const cleanupTimeout = this.#limiter.limits.cleanupTimeoutMs;
315
+ const timeoutPromise = new Promise((resolve)=>{
316
+ setTimeout(resolve, cleanupTimeout);
317
+ });
318
+ // Race between graceful cleanup and timeout
319
+ const gracefulDone = await Promise.race([
320
+ Promise.all(this.#handling.map(async (handling)=>{
321
+ // Wait until all handlers are done - they might still need to flush messages to the transport
322
+ await handling.done;
323
+ // Dispose transport
324
+ await handling.transport.dispose();
325
+ })).then(()=>true),
326
+ timeoutPromise.then(()=>false)
327
+ ]);
328
+ // Force dispose any remaining transports only if timed out
329
+ if (!gracefulDone) {
330
+ for (const handling of this.#handling){
331
+ try {
332
+ await handling.transport.dispose();
333
+ } catch {
334
+ // Ignore errors during forced cleanup
335
+ }
336
+ }
337
+ }
170
338
  },
171
339
  signal: params.signal
172
340
  });
173
341
  this.#abortController = new AbortController();
174
342
  this.#events = new EventEmitter();
175
343
  this.#handlers = params.handlers;
344
+ const serverID = params.identity?.id;
176
345
  this.#logger = params.logger ?? getEnkakuLogger('server', {
177
- serverID: params.id ?? crypto.randomUUID()
346
+ serverID: serverID ?? crypto.randomUUID()
178
347
  });
179
- if (params.id == null) {
348
+ if (serverID == null) {
180
349
  if (params.public) {
181
350
  this.#accessControl = {
182
351
  public: true,
183
- access: params.access
352
+ access: params.access,
353
+ encryptionPolicy: params.encryptionPolicy
184
354
  };
185
355
  } else {
186
- throw new Error('Invalid server parameters: either the server "id" must be provided or the "public" parameter must be set to true');
356
+ throw new Error('Invalid server parameters: either the server "identity" must be provided or the "public" parameter must be set to true');
187
357
  }
188
358
  } else {
189
359
  this.#accessControl = {
190
360
  public: !!params.public,
191
- serverID: params.id,
192
- access: params.access ?? {}
361
+ serverID,
362
+ access: params.access ?? {},
363
+ encryptionPolicy: params.encryptionPolicy
193
364
  };
194
365
  }
366
+ this.#limiter = createResourceLimiter(params.limits);
195
367
  if (params.protocol != null) {
196
368
  this.#validator = createValidator(createClientMessageSchema(params.protocol));
369
+ } else {
370
+ this.#logger.warn('No protocol provided: message validation is disabled. Pass a protocol definition to enable runtime type checking.');
197
371
  }
198
372
  for (const transport of params.transports ?? []){
199
373
  this.handle(transport);
@@ -208,11 +382,13 @@ export class Server extends Disposer {
208
382
  const logger = options.logger ?? this.#logger.getChild('handler').with({
209
383
  transportID: crypto.randomUUID()
210
384
  });
385
+ const encryptionPolicy = this.#accessControl.encryptionPolicy;
211
386
  let accessControl;
212
387
  if (publicAccess) {
213
388
  accessControl = {
214
389
  public: true,
215
- access
390
+ access,
391
+ encryptionPolicy
216
392
  };
217
393
  } else {
218
394
  const serverID = this.#accessControl.serverID;
@@ -222,12 +398,14 @@ export class Server extends Disposer {
222
398
  accessControl = {
223
399
  public: false,
224
400
  serverID,
225
- access
401
+ access,
402
+ encryptionPolicy
226
403
  };
227
404
  }
228
405
  const done = handleMessages({
229
406
  events: this.#events,
230
407
  handlers: this.#handlers,
408
+ limiter: this.#limiter,
231
409
  logger,
232
410
  signal: this.#abortController.signal,
233
411
  transport,
package/lib/types.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { EventEmitter } from '@enkaku/event';
2
2
  import type { Logger } from '@enkaku/log';
3
3
  import type { AnyRequestProcedureDefinition, AnyServerPayloadOf, ChannelProcedureDefinition, DataOf, EventCallPayload, EventProcedureDefinition, Message, ProtocolDefinition, RequestCallPayload, RequestProcedureDefinition, ReturnOf, StreamProcedureDefinition } from '@enkaku/protocol';
4
+ import type { HandlerError } from './error.js';
4
5
  export type RequestController = AbortController;
5
6
  export type ChannelController<Send = unknown> = AbortController & {
6
7
  writer: WritableStreamDefaultWriter<Send>;
@@ -35,13 +36,16 @@ export type ReceiveType<Protocol extends ProtocolDefinition, Procedure extends k
35
36
  export type ResultType<Protocol extends ProtocolDefinition, Procedure extends keyof Protocol & string> = Protocol[Procedure] extends AnyRequestProcedureDefinition ? DataOf<Protocol[Procedure]['result']> : never;
36
37
  export type SendType<Protocol extends ProtocolDefinition, Procedure extends keyof Protocol & string> = Protocol[Procedure] extends ChannelProcedureDefinition ? DataOf<Protocol[Procedure]['send']> : never;
37
38
  export type ServerEvents = {
39
+ eventAuthError: {
40
+ error: HandlerError<string>;
41
+ payload: Record<string, unknown>;
42
+ };
38
43
  handlerAbort: {
39
44
  rid: string;
40
45
  };
41
46
  handlerError: {
42
- error: Error;
47
+ error: HandlerError<string>;
43
48
  payload: Record<string, unknown>;
44
- rid?: string;
45
49
  };
46
50
  handlerTimeout: {
47
51
  rid: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,KAAK,EACV,6BAA6B,EAC7B,kBAAkB,EAClB,0BAA0B,EAC1B,MAAM,EACN,gBAAgB,EAChB,wBAAwB,EACxB,OAAO,EACP,kBAAkB,EAClB,kBAAkB,EAClB,0BAA0B,EAC1B,QAAQ,EACR,yBAAyB,EAC1B,MAAM,kBAAkB,CAAA;AAEzB,MAAM,MAAM,iBAAiB,GAAG,eAAe,CAAA;AAE/C,MAAM,MAAM,iBAAiB,CAAC,IAAI,GAAG,OAAO,IAAI,eAAe,GAAG;IAChE,MAAM,EAAE,2BAA2B,CAAC,IAAI,CAAC,CAAA;CAC1C,CAAA;AAED,MAAM,MAAM,iBAAiB,CAAC,IAAI,GAAG,OAAO,IAAI,iBAAiB,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAA;AAE3F,MAAM,MAAM,mBAAmB,CAC7B,QAAQ,SAAS,kBAAkB,EACnC,SAAS,SAAS,MAAM,QAAQ,GAAG,MAAM,IACvC,QAAQ,CAAC,SAAS,CAAC,SAAS,wBAAwB,GACpD;IACE,OAAO,EAAE,OAAO,CAAC,gBAAgB,CAAC,SAAS,EAAE,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAA;IAClF,IAAI,EAAE,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,CAAA;CAC1C,GACD,KAAK,CAAA;AAET,MAAM,MAAM,YAAY,CACtB,QAAQ,SAAS,kBAAkB,EACnC,SAAS,SAAS,MAAM,QAAQ,GAAG,MAAM,IACvC,CAAC,OAAO,EAAE,mBAAmB,CAAC,QAAQ,EAAE,SAAS,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;AAE/E,MAAM,MAAM,qBAAqB,CAC/B,QAAQ,SAAS,kBAAkB,EACnC,SAAS,SAAS,MAAM,QAAQ,GAAG,MAAM,IACvC,QAAQ,CAAC,SAAS,CAAC,SAAS,6BAA6B,GACzD;IACE,OAAO,EAAE,OAAO,CACd,kBAAkB,CAChB,QAAQ,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,EAC3B,SAAS,EACT,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,CACrC,CACF,CAAA;IACD,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,CAAA;IAC3C,MAAM,EAAE,WAAW,CAAA;CACpB,GACD,KAAK,CAAA;AAET,MAAM,MAAM,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,QAAQ,CAAC,YAAY,CAAC,IAAI,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;AAEnG,MAAM,MAAM,cAAc,CACxB,QAAQ,SAAS,kBAAkB,EACnC,SAAS,SAAS,MAAM,QAAQ,GAAG,MAAM,IACvC,QAAQ,CAAC,SAAS,CAAC,SAAS,6BAA6B,GACzD,CACE,OAAO,EAAE,qBAAqB,CAAC,QAAQ,EAAE,SAAS,CAAC,KAChD,aAAa,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,CAAC,GACjD,KAAK,CAAA;AAET,MAAM,MAAM,oBAAoB,CAC9B,QAAQ,SAAS,kBAAkB,EACnC,SAAS,SAAS,MAAM,QAAQ,GAAG,MAAM,IACvC,QAAQ,CAAC,SAAS,CAAC,SAAS,yBAAyB,GAAG,0BAA0B,GAClF,qBAAqB,CAAC,QAAQ,EAAE,SAAS,CAAC,GAAG;IAC3C,QAAQ,EAAE,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAA;CACjE,GACD,KAAK,CAAA;AAET,MAAM,MAAM,aAAa,CACvB,QAAQ,SAAS,kBAAkB,EACnC,SAAS,SAAS,MAAM,QAAQ,GAAG,MAAM,IACvC,QAAQ,CAAC,SAAS,CAAC,SAAS,yBAAyB,GAAG,0BAA0B,GAClF,CACE,OAAO,EAAE,oBAAoB,CAAC,QAAQ,EAAE,SAAS,CAAC,KAC/C,aAAa,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,CAAC,GACjD,KAAK,CAAA;AAET,MAAM,MAAM,qBAAqB,CAC/B,QAAQ,SAAS,kBAAkB,EACnC,SAAS,SAAS,MAAM,QAAQ,GAAG,MAAM,IACvC,QAAQ,CAAC,SAAS,CAAC,SAAS,0BAA0B,GACtD,oBAAoB,CAAC,QAAQ,EAAE,SAAS,CAAC,GAAG;IAC1C,QAAQ,EAAE,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAA;CAC9D,GACD,KAAK,CAAA;AAET,MAAM,MAAM,cAAc,CACxB,QAAQ,SAAS,kBAAkB,EACnC,SAAS,SAAS,MAAM,QAAQ,GAAG,MAAM,IACvC,QAAQ,CAAC,SAAS,CAAC,SAAS,0BAA0B,GACtD,CACE,OAAO,EAAE,qBAAqB,CAAC,QAAQ,EAAE,SAAS,CAAC,KAChD,aAAa,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,CAAC,GACjD,KAAK,CAAA;AAET,MAAM,MAAM,iBAAiB,CAAC,QAAQ,SAAS,kBAAkB,IAAI;KAClE,SAAS,IAAI,MAAM,QAAQ,GAAG,MAAM,GAAG,QAAQ,CAAC,SAAS,CAAC,SAAS,wBAAwB,GACxF,CAAC,OAAO,EAAE,mBAAmB,CAAC,QAAQ,EAAE,SAAS,CAAC,KAAK,IAAI,GAC3D,QAAQ,CAAC,SAAS,CAAC,SAAS,0BAA0B,GACpD,CACE,OAAO,EAAE,qBAAqB,CAAC,QAAQ,EAAE,SAAS,CAAC,KAChD,aAAa,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,CAAC,GACjD,QAAQ,CAAC,SAAS,CAAC,SAAS,yBAAyB,GACnD,CACE,OAAO,EAAE,oBAAoB,CAAC,QAAQ,EAAE,SAAS,CAAC,KAC/C,aAAa,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,CAAC,GACjD,QAAQ,CAAC,SAAS,CAAC,SAAS,0BAA0B,GACpD,CACE,OAAO,EAAE,qBAAqB,CAAC,QAAQ,EAAE,SAAS,CAAC,KAChD,aAAa,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,CAAC,GACjD,KAAK;CAChB,CAAA;AAED,MAAM,MAAM,aAAa,CACvB,QAAQ,SAAS,kBAAkB,EACnC,SAAS,SAAS,MAAM,QAAQ,GAAG,MAAM,IACvC,QAAQ,CAAC,SAAS,CAAC,SAAS,wBAAwB,GACpD,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,GACnC,KAAK,CAAA;AAET,MAAM,MAAM,SAAS,CACnB,QAAQ,SAAS,kBAAkB,EACnC,SAAS,SAAS,MAAM,QAAQ,GAAG,MAAM,IACvC,QAAQ,CAAC,SAAS,CAAC,SAAS,6BAA6B,GACzD,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,CAAC,GACpC,KAAK,CAAA;AAET,MAAM,MAAM,WAAW,CACrB,QAAQ,SAAS,kBAAkB,EACnC,SAAS,SAAS,MAAM,QAAQ,GAAG,MAAM,IACvC,QAAQ,CAAC,SAAS,CAAC,SAAS,yBAAyB,GACrD,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,CAAC,GACtC,QAAQ,CAAC,SAAS,CAAC,SAAS,0BAA0B,GACpD,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,SAAS,CAAC,CAAC,GACtC,KAAK,CAAA;AAEX,MAAM,MAAM,UAAU,CACpB,QAAQ,SAAS,kBAAkB,EACnC,SAAS,SAAS,MAAM,QAAQ,GAAG,MAAM,IACvC,QAAQ,CAAC,SAAS,CAAC,SAAS,6BAA6B,GACzD,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,CAAC,GACrC,KAAK,CAAA;AAET,MAAM,MAAM,QAAQ,CAClB,QAAQ,SAAS,kBAAkB,EACnC,SAAS,SAAS,MAAM,QAAQ,GAAG,MAAM,IACvC,QAAQ,CAAC,SAAS,CAAC,SAAS,0BAA0B,GACtD,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,GACnC,KAAK,CAAA;AAET,MAAM,MAAM,YAAY,GAAG;IACzB,YAAY,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;IAC7B,YAAY,EAAE;QAAE,KAAK,EAAE,KAAK,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC9E,cAAc,EAAE;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;IAC/B,cAAc,EAAE;QAAE,KAAK,EAAE,KAAK,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAA;CACnD,CAAA;AAED,MAAM,MAAM,aAAa,GAAG,YAAY,CAAC,YAAY,CAAC,CAAA;AAEtD,MAAM,MAAM,cAAc,CAAC,QAAQ,SAAS,kBAAkB,IAAI;IAChE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAA;IAC9C,MAAM,EAAE,aAAa,CAAA;IACrB,QAAQ,EAAE,iBAAiB,CAAC,QAAQ,CAAC,CAAA;IACrC,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,EAAE,CAAC,OAAO,EAAE,kBAAkB,CAAC,QAAQ,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC/D,CAAA"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAsB,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAGhG,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAO5D,wBAAsB,cAAc,CAClC,QAAQ,SAAS,kBAAkB,EACnC,SAAS,SAAS,MAAM,QAAQ,GAAG,MAAM,EACzC,MAAM,SAAS,UAAU,CAAC,QAAQ,EAAE,SAAS,CAAC,GAAG,UAAU,CAAC,QAAQ,EAAE,SAAS,CAAC,EAEhF,OAAO,EAAE,cAAc,CAAC,QAAQ,CAAC,EACjC,OAAO,EAAE,gBAAgB,CAAC,SAAS,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,EACzD,OAAO,EAAE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,GACtC,OAAO,CAAC,IAAI,CAAC,CA4Bf"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAsB,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAA;AAGhG,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAO5D,wBAAsB,cAAc,CAClC,QAAQ,SAAS,kBAAkB,EACnC,SAAS,SAAS,MAAM,QAAQ,GAAG,MAAM,EACzC,MAAM,SAAS,UAAU,CAAC,QAAQ,EAAE,SAAS,CAAC,GAAG,UAAU,CAAC,QAAQ,EAAE,SAAS,CAAC,EAEhF,OAAO,EAAE,cAAc,CAAC,QAAQ,CAAC,EACjC,OAAO,EAAE,gBAAgB,CAAC,SAAS,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,EACzD,OAAO,EAAE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,GACtC,OAAO,CAAC,IAAI,CAAC,CA6Cf"}
package/lib/utils.js CHANGED
@@ -9,6 +9,12 @@ export async function executeHandler(context, payload, execute) {
9
9
  try {
10
10
  const val = await toPromise(execute);
11
11
  if (canSend(controller.signal)) {
12
+ context.logger.trace('send result to {type} {procedure} with ID {rid}', {
13
+ type: payload.typ,
14
+ procedure: payload.prc,
15
+ rid: payload.rid,
16
+ result: val
17
+ });
12
18
  await context.send({
13
19
  typ: 'result',
14
20
  rid: payload.rid,
@@ -16,17 +22,28 @@ export async function executeHandler(context, payload, execute) {
16
22
  });
17
23
  }
18
24
  } catch (cause) {
25
+ const error = HandlerError.from(cause, {
26
+ code: 'EK01',
27
+ message: 'Handler execution failed'
28
+ });
19
29
  if (canSend(controller.signal)) {
20
- context.send(HandlerError.from(cause, {
21
- code: 'EK01',
22
- message: cause.message ?? 'Handler execution failed'
23
- }).toPayload(payload.rid));
30
+ context.logger.trace('send error to {type} {procedure} with ID {rid}', {
31
+ type: payload.typ,
32
+ procedure: payload.prc,
33
+ rid: payload.rid,
34
+ error
35
+ });
36
+ context.send(error.toPayload(payload.rid));
37
+ } else {
38
+ context.logger.debug('handler error for {type} {procedure} with ID {rid} cannot be sent to client', {
39
+ type: payload.typ,
40
+ procedure: payload.prc,
41
+ rid: payload.rid,
42
+ error
43
+ });
24
44
  }
25
45
  context.events.emit('handlerError', {
26
- error: new Error(`Error handling procedure: ${payload.prc}`, {
27
- cause
28
- }),
29
- rid: payload.rid,
46
+ error,
30
47
  payload
31
48
  });
32
49
  } finally{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enkaku/server",
3
- "version": "0.12.2",
3
+ "version": "0.13.0",
4
4
  "license": "MIT",
5
5
  "homepage": "https://enkaku.dev",
6
6
  "description": "Server logic for Enkaku RPC",
@@ -25,17 +25,17 @@
25
25
  ],
26
26
  "sideEffects": false,
27
27
  "dependencies": {
28
- "@enkaku/async": "^0.12.2",
29
- "@enkaku/capability": "^0.12.1",
30
- "@enkaku/event": "^0.12.1",
31
- "@enkaku/protocol": "^0.12.1",
32
- "@enkaku/log": "^0.12.0",
33
- "@enkaku/schema": "^0.12.1",
34
- "@enkaku/token": "^0.12.3",
35
- "@enkaku/stream": "^0.12.4"
28
+ "@enkaku/capability": "^0.13.0",
29
+ "@enkaku/async": "^0.13.0",
30
+ "@enkaku/protocol": "^0.13.0",
31
+ "@enkaku/log": "^0.13.0",
32
+ "@enkaku/event": "^0.13.0",
33
+ "@enkaku/schema": "^0.13.0",
34
+ "@enkaku/token": "^0.13.0",
35
+ "@enkaku/stream": "^0.13.0"
36
36
  },
37
37
  "devDependencies": {
38
- "@enkaku/transport": "^0.12.0"
38
+ "@enkaku/transport": "^0.13.0"
39
39
  },
40
40
  "scripts": {
41
41
  "build:clean": "del lib",