@arrirpc/server 0.73.0 → 0.74.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.
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { isRpcHttpMethod, removeDisallowedChars, SCHEMA_VERSION } from '@arrirpc/codegen-utils';
2
- import { isError, setResponseStatus, setResponseHeader, send, eventHandler, isPreflightRequest, readRawBody, getValidatedQuery, createEventStream, getHeader, defineEventHandler, getQuery, setHeader, defineWebSocketHandler, createRouter, createApp } from 'h3';
2
+ import { isError, setResponseStatus, setResponseHeader, send, eventHandler, isPreflightRequest, readRawBody, getValidatedQuery, createEventStream, getHeader, defineEventHandler, getQuery, setHeader, createRouter, createApp } from 'h3';
3
3
  export * from 'h3';
4
- import { a, isAObjectSchema, isADiscriminatorSchema, isAdaptedSchema, validatorFromAdaptedSchema } from '@arrirpc/schema';
4
+ import { a, isAObjectSchema, isADiscriminatorSchema, errorMessageFromErrors, isASchema } from '@arrirpc/schema';
5
5
  import { kebabCase, pascalCase } from 'scule';
6
6
 
7
7
  class arriError extends Error {
@@ -289,20 +289,14 @@ function getRpcParamName(rpcName, procedure) {
289
289
  if (!isRpcParamSchema(procedure.params)) {
290
290
  return undefined;
291
291
  }
292
- const nameParts = rpcName.split(".").map(
293
- (part) => removeDisallowedChars(part, `!@#$%^&*()+=[]{}|\\;:'"<>,./?`)
294
- );
295
- const paramName = procedure.params.metadata.id ?? pascalCase(`${nameParts.join(`_`)}_params`);
292
+ const paramName = getSchemaName(rpcName, "params", procedure.params);
296
293
  return paramName;
297
294
  }
298
295
  function getRpcResponseName(rpcName, procedure) {
299
296
  if (!isRpcParamSchema(procedure.response)) {
300
297
  return undefined;
301
298
  }
302
- const nameParts = rpcName.split(".").map(
303
- (part) => removeDisallowedChars(part, `!@#$%^&*()+=[]{}|\\;:'"<>,./?`)
304
- );
305
- const responseName = procedure.response.metadata.id ?? pascalCase(`${nameParts.join("_")}_response`);
299
+ const responseName = getSchemaName(rpcName, "response", procedure.response);
306
300
  return responseName;
307
301
  }
308
302
  function getRpcResponseDefinition(rpcName, procedure) {
@@ -337,7 +331,6 @@ function registerRpc(router, path, procedure, opts) {
337
331
  await validateRpcRequestInput(
338
332
  event,
339
333
  httpMethod,
340
- procedure.params,
341
334
  paramValidator
342
335
  );
343
336
  }
@@ -350,18 +343,15 @@ function registerRpc(router, path, procedure, opts) {
350
343
  await opts.onBeforeResponse(event);
351
344
  }
352
345
  if (typeof response === "object") {
353
- if (!responseValidator?.validate(response)) {
354
- const errors = a.errors(procedure.response, response);
346
+ const payload = responseValidator?.serialize(response);
347
+ if (payload && payload.success !== true) {
355
348
  throw defineError(500, {
356
349
  message: "Failed to serialize response. Response does not match specified schema",
357
- data: errors
350
+ data: payload?.errors
358
351
  });
359
352
  }
360
353
  setResponseHeader(event, "Content-Type", "application/json");
361
- await send(
362
- event,
363
- responseValidator?.serialize(response) ?? JSON.stringify(response)
364
- );
354
+ await send(event, payload?.value ?? JSON.stringify(response));
365
355
  } else {
366
356
  setResponseHeader(event, "Content-Type", "application/json");
367
357
  await send(event, `{}`);
@@ -399,22 +389,22 @@ function registerRpc(router, path, procedure, opts) {
399
389
  break;
400
390
  }
401
391
  }
402
- async function validateRpcRequestInput(event, httpMethod, schema, validator) {
392
+ async function validateRpcRequestInput(event, httpMethod, validator) {
403
393
  switch (httpMethod) {
404
394
  case "get": {
405
395
  const parsedParams = await getValidatedQuery(
406
396
  event,
407
- (input) => a.safeCoerce(schema, input)
397
+ (input) => validator.coerce(input)
408
398
  );
409
399
  if (parsedParams.success) {
410
400
  event.context.params = parsedParams.value;
411
401
  } else {
412
402
  const errParts = [];
413
- for (const err of parsedParams.error.errors) {
414
- const errPath = err.instancePath.split("/");
415
- errPath.shift();
416
- const propName = errPath.join(".");
417
- if (!errParts.includes(propName)) {
403
+ for (const err of parsedParams.errors) {
404
+ const errPath = err.instancePath?.split("/");
405
+ errPath?.shift();
406
+ const propName = errPath?.join(".");
407
+ if (propName && !errParts.includes(propName)) {
418
408
  errParts.push(propName);
419
409
  }
420
410
  }
@@ -423,7 +413,7 @@ async function validateRpcRequestInput(event, httpMethod, schema, validator) {
423
413
  )}]`;
424
414
  throw defineError(400, {
425
415
  message,
426
- data: parsedParams.error
416
+ data: parsedParams.errors
427
417
  });
428
418
  }
429
419
  break;
@@ -438,21 +428,11 @@ async function validateRpcRequestInput(event, httpMethod, schema, validator) {
438
428
  message: `Invalid request body. Expected object. Got undefined.`
439
429
  });
440
430
  }
441
- const parsedParams = validator.safeParse(body);
431
+ const parsedParams = validator.parse(body);
442
432
  if (!parsedParams?.success) {
443
- const errorParts = [];
444
- for (const err of parsedParams.error.errors) {
445
- const errPath = err.instancePath.split("/");
446
- errPath.shift();
447
- if (!errorParts.includes(errPath.join("."))) {
448
- errorParts.push(errPath.join("."));
449
- }
450
- }
451
433
  throw defineError(400, {
452
- message: `Invalid request body. Affected properties [${errorParts.join(
453
- ", "
454
- )}]`,
455
- data: parsedParams.error
434
+ message: errorMessageFromErrors(parsedParams.errors),
435
+ data: parsedParams.errors
456
436
  });
457
437
  }
458
438
  event.context.params = parsedParams.value;
@@ -461,15 +441,31 @@ async function validateRpcRequestInput(event, httpMethod, schema, validator) {
461
441
  }
462
442
  }
463
443
  function getSchemaValidator(rpcName, type, schema) {
464
- try {
465
- if (isAdaptedSchema(schema)) {
466
- return validatorFromAdaptedSchema(schema);
444
+ if (isASchema(schema)) {
445
+ try {
446
+ const validator = a.compile(schema);
447
+ return {
448
+ validate: validator.validate,
449
+ serialize: validator.serialize,
450
+ parse: validator.parse,
451
+ coerce: validator.coerce,
452
+ errors: (input) => a.errors(schema, input)
453
+ };
454
+ } catch (err) {
455
+ console.error(
456
+ `Error compiling ${type} validator for ${rpcName}. Error: ${err}`
457
+ );
467
458
  }
468
- return a.compile(schema);
469
- } catch (_) {
470
- console.error(`Error compiling ${type} validator for ${rpcName}`);
471
- return undefined;
472
459
  }
460
+ return undefined;
461
+ }
462
+ function getSchemaName(rpcName, type, schema) {
463
+ const cleanedName = rpcName.split(".").map(
464
+ (part) => removeDisallowedChars(part, `!@#$%^&*()+=[]{}|\\;:'"<>,./?`)
465
+ ).join("_");
466
+ return schema.metadata?.id ?? pascalCase(`${cleanedName}_${type}`, {
467
+ normalize: true
468
+ });
473
469
  }
474
470
 
475
471
  function defineEventStreamRpc(config) {
@@ -485,9 +481,7 @@ function isEventStreamRpc(input) {
485
481
  }
486
482
  class EventStreamConnection {
487
483
  lastEventId;
488
- validationErrors;
489
484
  validator;
490
- serializer;
491
485
  // for some reason Rollup cannot output DTS when this is set to NodeJS.Timeout
492
486
  pingInterval = undefined;
493
487
  pingIntervalMs;
@@ -496,9 +490,7 @@ class EventStreamConnection {
496
490
  this.eventStream = createEventStream(event);
497
491
  this.lastEventId = getHeader(event, "Last-Event-Id");
498
492
  this.pingIntervalMs = opts.pingInterval ?? 6e4;
499
- this.serializer = opts.serializer;
500
493
  this.validator = opts.validator;
501
- this.validationErrors = opts.validationErrors;
502
494
  this.eventStream.onClosed(() => {
503
495
  this.cleanup();
504
496
  });
@@ -516,20 +508,23 @@ class EventStreamConnection {
516
508
  }, this.pingIntervalMs);
517
509
  }
518
510
  async push(data, eventId) {
511
+ if (!this.validator) return;
519
512
  if (Array.isArray(data)) {
520
513
  const results = [];
521
514
  const events = [];
522
515
  for (const item of data) {
523
- if (this.validator(item)) {
516
+ if (this.validator.validate(item)) {
524
517
  events.push({
525
518
  id: eventId,
526
519
  event: "message",
527
- data: this.serializer(item)
520
+ data: this.validator.serialize(
521
+ item
522
+ ).value
528
523
  });
529
524
  results.push({ success: true });
530
525
  continue;
531
526
  }
532
- const errors2 = this.validationErrors(item);
527
+ const errors2 = this.validator.errors(item);
533
528
  results.push({
534
529
  success: false,
535
530
  errors: errors2
@@ -538,15 +533,15 @@ class EventStreamConnection {
538
533
  await this.eventStream.push(events);
539
534
  return results;
540
535
  }
541
- if (this.validator(data)) {
536
+ if (this.validator.validate(data)) {
542
537
  await this.eventStream.push({
543
538
  id: eventId,
544
539
  event: "message",
545
- data: this.serializer(data)
540
+ data: this.validator.serialize(data).value
546
541
  });
547
542
  return { success: true };
548
543
  }
549
- const errors = this.validationErrors(data);
544
+ const errors = this.validator.errors(data);
550
545
  return {
551
546
  success: false,
552
547
  errors
@@ -593,24 +588,12 @@ function registerEventStreamRpc(router, path, procedure, opts) {
593
588
  await validateRpcRequestInput(
594
589
  event,
595
590
  httpMethod,
596
- procedure.params,
597
591
  paramValidator
598
592
  );
599
593
  }
600
594
  const stream = new EventStreamConnection(event, {
601
595
  pingInterval: procedure.pingInterval,
602
- validator: responseValidator?.validate ?? function() {
603
- return true;
604
- },
605
- serializer: responseValidator?.serialize ?? function(_) {
606
- return "";
607
- },
608
- validationErrors(input) {
609
- if (procedure.response) {
610
- return a.errors(procedure.response, input);
611
- }
612
- return [];
613
- }
596
+ validator: responseValidator
614
597
  });
615
598
  event.context.stream = stream;
616
599
  await procedure.handler(
@@ -673,10 +656,10 @@ function handleRoute(router, method, route, opts) {
673
656
  }
674
657
  if (route.query) {
675
658
  const query = getQuery(event);
676
- const parsedQuery = a.safeCoerce(route.query, query);
659
+ const parsedQuery = a.coerce(route.query, query);
677
660
  if (!parsedQuery.success) {
678
661
  const errParts = [];
679
- for (const err of parsedQuery.error.errors) {
662
+ for (const err of parsedQuery.errors) {
680
663
  const errPath = err.instancePath.split("/");
681
664
  errPath.shift();
682
665
  const propName = errPath.join(".");
@@ -701,21 +684,11 @@ function handleRoute(router, method, route, opts) {
701
684
  ];
702
685
  if (route.body && !notAllowedBodyMethods.includes(event.method)) {
703
686
  const body = await readRawBody(event);
704
- const parsedBody = a.safeParse(route.body, body);
687
+ const parsedBody = a.parse(route.body, body);
705
688
  if (!parsedBody.success) {
706
- const errorParts = [];
707
- for (const err of parsedBody.error.errors) {
708
- const errPath = err.instancePath.split("/");
709
- errPath.shift();
710
- if (!errorParts.includes(errPath.join("."))) {
711
- errorParts.push(errPath.join("."));
712
- }
713
- }
714
689
  throw defineError(400, {
715
- message: `Invalid request body. Affected properties [${errorParts.join(
716
- ", "
717
- )}]`,
718
- data: parsedBody.error
690
+ message: errorMessageFromErrors(parsedBody.errors),
691
+ data: parsedBody.errors
719
692
  });
720
693
  }
721
694
  event.context.body = parsedBody.value;
@@ -821,88 +794,6 @@ function defineService(name, procedures) {
821
794
  return service;
822
795
  }
823
796
 
824
- class WsPeer {
825
- _peer;
826
- _validator;
827
- context;
828
- url;
829
- constructor(peer, opts) {
830
- this._peer = peer;
831
- this._validator = opts.validator;
832
- this.url = peer.websocket.url ?? "";
833
- this.context = opts.context;
834
- }
835
- send(data) {
836
- if (!this._validator) {
837
- return;
838
- }
839
- if (!this._validator.validate(data)) {
840
- return;
841
- }
842
- const payload = this._validator.serialize(data);
843
- return this._peer.send(`event: message
844
- data: ${payload}`);
845
- }
846
- sendError(err) {
847
- return this._peer.send(`event: error
848
- data: ${JSON.stringify(err)}`);
849
- }
850
- close() {
851
- throw new Error("close() is not yet implemented on WsPeer");
852
- }
853
- subscribe(channel) {
854
- this._peer.subscribe(channel);
855
- }
856
- unsubscribe(channel) {
857
- this._peer.unsubscribe(channel);
858
- }
859
- publish(channel, message) {
860
- if (!this._validator) {
861
- return;
862
- }
863
- if (!this._validator.validate(message)) {
864
- const err = {
865
- code: 500,
866
- message: `Error serializing message on server. The payload doesn't match the specified schema.`,
867
- data: {
868
- payload: message
869
- }
870
- };
871
- this.sendError(err);
872
- return;
873
- }
874
- const payload = this._validator.serialize(message);
875
- this._peer.publish(channel, payload);
876
- }
877
- }
878
- function defineWebsocketRpc(def) {
879
- def.transport = "ws";
880
- return def;
881
- }
882
- function registerWebsocketRpc(router, path, _rpc) {
883
- const handler = defineWebSocketHandler({
884
- upgrade(_req) {
885
- },
886
- open(_) {
887
- },
888
- message(_, __) {
889
- },
890
- close(_, __) {
891
- }
892
- });
893
- router.use(path, handler);
894
- }
895
- function createWsRpcDefinition(rpcName, path, rpc) {
896
- return {
897
- transport: "ws",
898
- path,
899
- params: getRpcParamName(rpcName, rpc),
900
- response: getRpcResponseName(rpcName, rpc),
901
- isDeprecated: rpc.isDeprecated,
902
- description: rpc.description
903
- };
904
- }
905
-
906
797
  const createAppDefinition = (def) => def;
907
798
  class ArriApp {
908
799
  __isArri__ = true;
@@ -1013,8 +904,6 @@ class ArriApp {
1013
904
  const path = p.path ?? getRpcPath(p.name, this._rpcRoutePrefix);
1014
905
  if (p.transport === "http") {
1015
906
  this._procedures[p.name] = createHttpRpcDefinition(p.name, path, p);
1016
- } else if (p.transport === "ws") {
1017
- this._procedures[p.name] = createWsRpcDefinition(p.name, path, p);
1018
907
  }
1019
908
  if (isRpcParamSchema(p.params)) {
1020
909
  const paramName = getRpcParamName(p.name, p);
@@ -1050,9 +939,6 @@ class ArriApp {
1050
939
  });
1051
940
  return;
1052
941
  }
1053
- if (p.transport === "ws") {
1054
- registerWebsocketRpc(this.h3Router, path);
1055
- }
1056
942
  }
1057
943
  route(route) {
1058
944
  registerRoute(this.h3Router, route, {
@@ -1086,4 +972,4 @@ class ArriApp {
1086
972
 
1087
973
  const defineMiddleware = (middleware) => middleware;
1088
974
 
1089
- export { ArriApp, ArriRouter, ArriService, EventStreamConnection, WsPeer, arriError, arriErrorResponse, createAppDefinition, createHttpRpcDefinition, createWsRpcDefinition, defineError, defineEventStreamRpc, defineMiddleware, defineRoute, defineRpc, defineService, defineWebsocketRpc, errorResponseFromValidationErrors, getRpcParamName, getRpcPath, getRpcResponseName, getSchemaValidator, handleH3Error, handleRoute, isEventStreamRpc, isRpc, isRpcParamSchema, registerEventStreamRpc, registerRoute, registerRpc, registerWebsocketRpc, validateRpcRequestInput };
975
+ export { ArriApp, ArriRouter, ArriService, EventStreamConnection, arriError, arriErrorResponse, createAppDefinition, createHttpRpcDefinition, defineError, defineEventStreamRpc, defineMiddleware, defineRoute, defineRpc, defineService, errorResponseFromValidationErrors, getRpcParamName, getRpcPath, getRpcResponseName, getSchemaName, getSchemaValidator, handleH3Error, handleRoute, isEventStreamRpc, isRpc, isRpcParamSchema, registerEventStreamRpc, registerRoute, registerRpc, validateRpcRequestInput };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arrirpc/server",
3
- "version": "0.73.0",
3
+ "version": "0.74.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -28,9 +28,9 @@
28
28
  "scule": "^1.3.0",
29
29
  "source-map-support": "^0.5.21",
30
30
  "uncrypto": "^0.1.3",
31
- "@arrirpc/codegen-utils": "0.73.0",
32
- "@arrirpc/schema": "0.73.0",
33
- "arri": "0.73.0"
31
+ "@arrirpc/codegen-utils": "0.74.0",
32
+ "@arrirpc/schema": "0.74.0",
33
+ "arri": "0.74.0"
34
34
  },
35
35
  "devDependencies": {
36
36
  "bun-types": "^1.2.0"