@arrirpc/server 0.72.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/README.md +1 -66
- package/dist/index.cjs +56 -173
- package/dist/index.d.cts +26 -80
- package/dist/index.d.mts +26 -80
- package/dist/index.d.ts +26 -80
- package/dist/index.mjs +58 -172
- package/package.json +7 -7
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,
|
|
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,
|
|
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
|
|
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
|
|
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
|
-
|
|
354
|
-
|
|
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,
|
|
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) =>
|
|
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.
|
|
414
|
-
const errPath = err.instancePath
|
|
415
|
-
errPath
|
|
416
|
-
const propName = errPath
|
|
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.
|
|
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.
|
|
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:
|
|
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
|
-
|
|
465
|
-
|
|
466
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
540
|
+
data: this.validator.serialize(data).value
|
|
546
541
|
});
|
|
547
542
|
return { success: true };
|
|
548
543
|
}
|
|
549
|
-
const errors = this.
|
|
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
|
|
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.
|
|
659
|
+
const parsedQuery = a.coerce(route.query, query);
|
|
677
660
|
if (!parsedQuery.success) {
|
|
678
661
|
const errParts = [];
|
|
679
|
-
for (const err of parsedQuery.
|
|
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.
|
|
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:
|
|
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,
|
|
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.
|
|
3
|
+
"version": "0.74.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -23,16 +23,16 @@
|
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@types/source-map-support": "^0.5.10",
|
|
26
|
-
"crossws": "^0.3.
|
|
27
|
-
"h3": "^1.
|
|
26
|
+
"crossws": "^0.3.3",
|
|
27
|
+
"h3": "^1.14.0",
|
|
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.
|
|
32
|
-
"
|
|
33
|
-
"
|
|
31
|
+
"@arrirpc/codegen-utils": "0.74.0",
|
|
32
|
+
"@arrirpc/schema": "0.74.0",
|
|
33
|
+
"arri": "0.74.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
|
-
"bun-types": "^1.
|
|
36
|
+
"bun-types": "^1.2.0"
|
|
37
37
|
}
|
|
38
38
|
}
|