@fleekhq/browser-rpc
Advanced tools
Comparing version 1.0.0 to 2.0.0
@@ -0,4 +1,7 @@ | ||
export { default as BackgroundController } from './BackgroundController'; | ||
export { default as BrowserRPC } from './BrowserRPC'; | ||
export { default as ProxyRPC } from './ProxyRPC'; | ||
export { default as PortRPC } from './PortRPC'; | ||
export { default as RPC } from './RPC'; | ||
export { default } from './BrowserRPC'; | ||
export * from './types'; |
@@ -43,3 +43,3 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
import { v4 as uuid } from 'uuid'; | ||
import validateMessageObject from './schema'; | ||
import { validateMessageSchema } from './helpers'; | ||
import { JSON_RPC_VERSION, JSON_RPC_ERROR_CODES } from './constants'; | ||
@@ -124,25 +124,8 @@ var RPC = (function () { | ||
} | ||
return [2, this._call(handler, (config === null || config === void 0 ? void 0 : config.target) || this.target, timeout, args)]; | ||
return [2, this._call(handler, (config === null || config === void 0 ? void 0 : config.target) || this.target, timeout, args || [])]; | ||
}); | ||
}); | ||
}; | ||
RPC.prototype.validateMessage = function (message) { | ||
var result = validateMessageObject(message); | ||
if (Array.isArray(result)) | ||
return { isValid: false, type: null }; | ||
var type = null; | ||
if (message.data.data.method && message.data.data.params) { | ||
type = 'req'; | ||
} | ||
else if (message.data.data.hasOwnProperty('result') | ||
|| typeof message.data.data.hasOwnProperty('error')) { | ||
type = 'res'; | ||
} | ||
return { | ||
type: type, | ||
isValid: (type === 'req' || type === 'res'), | ||
}; | ||
}; | ||
RPC.prototype.onMessage = function (eventMessage) { | ||
var _a = this.validateMessage(eventMessage), type = _a.type, isValid = _a.isValid; | ||
var _a = validateMessageSchema(eventMessage), type = _a.type, isValid = _a.isValid; | ||
if (!isValid) | ||
@@ -193,3 +176,6 @@ return; | ||
}; | ||
handler.apply(void 0, __spreadArray([callback], message.data.data.params)); | ||
handler.apply(void 0, __spreadArray([{ | ||
callback: callback, | ||
message: message, | ||
}], message.data.data.params)); | ||
} | ||
@@ -196,0 +182,0 @@ catch (error) { |
@@ -1,24 +0,20 @@ | ||
import Validator from 'fastest-validator'; | ||
var validator = new Validator(); | ||
var messageSchema = { | ||
target: 'string', | ||
data: { | ||
type: 'object', | ||
props: { | ||
name: 'string', | ||
data: { | ||
type: 'object', | ||
props: { | ||
id: ['string', 'number'], | ||
jsonrpc: { type: 'equal', value: '2.0' }, | ||
result: { type: 'any', optional: true }, | ||
error: { type: 'object', optional: true }, | ||
method: { type: 'string', optional: true }, | ||
params: { type: 'array', optional: true, items: 'any' } | ||
}, | ||
}, | ||
}, | ||
}, | ||
}; | ||
var validateMessageObject = validator.compile(messageSchema); | ||
export default validateMessageObject; | ||
import Joi from 'joi'; | ||
var schema = Joi.object({ | ||
target: Joi.string().required(), | ||
data: Joi.object({ | ||
name: Joi.string().required(), | ||
data: Joi.object({ | ||
id: Joi.alternatives().try(Joi.number(), Joi.string()).required(), | ||
jsonrpc: Joi.string().valid('2.0').required(), | ||
result: Joi.any(), | ||
method: Joi.string(), | ||
params: Joi.array().items(Joi.any()), | ||
error: Joi.object({ | ||
code: Joi.number().required(), | ||
message: Joi.string().required(), | ||
data: Joi.any(), | ||
}), | ||
}).required(), | ||
}).required(), | ||
}); | ||
export default schema; |
@@ -0,4 +1,7 @@ | ||
export { default as BackgroundController } from './BackgroundController'; | ||
export { default as BrowserRPC } from './BrowserRPC'; | ||
export { default as ProxyRPC } from './ProxyRPC'; | ||
export { default as PortRPC } from './PortRPC'; | ||
export { default as RPC } from './RPC'; | ||
export { default } from './BrowserRPC'; | ||
export * from './types'; |
@@ -16,5 +16,11 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.default = exports.RPC = exports.BrowserRPC = void 0; | ||
exports.default = exports.RPC = exports.PortRPC = exports.ProxyRPC = exports.BrowserRPC = exports.BackgroundController = void 0; | ||
var BackgroundController_1 = require("./BackgroundController"); | ||
Object.defineProperty(exports, "BackgroundController", { enumerable: true, get: function () { return __importDefault(BackgroundController_1).default; } }); | ||
var BrowserRPC_1 = require("./BrowserRPC"); | ||
Object.defineProperty(exports, "BrowserRPC", { enumerable: true, get: function () { return __importDefault(BrowserRPC_1).default; } }); | ||
var ProxyRPC_1 = require("./ProxyRPC"); | ||
Object.defineProperty(exports, "ProxyRPC", { enumerable: true, get: function () { return __importDefault(ProxyRPC_1).default; } }); | ||
var PortRPC_1 = require("./PortRPC"); | ||
Object.defineProperty(exports, "PortRPC", { enumerable: true, get: function () { return __importDefault(PortRPC_1).default; } }); | ||
var RPC_1 = require("./RPC"); | ||
@@ -21,0 +27,0 @@ Object.defineProperty(exports, "RPC", { enumerable: true, get: function () { return __importDefault(RPC_1).default; } }); |
@@ -1,7 +0,7 @@ | ||
import { Handler, Message, RpcConfig, CallConfigObject } from './types'; | ||
import { Handler, Message, RpcConfig, ReqMessage, CallConfigObject } from './types'; | ||
export default abstract class RPC { | ||
private name; | ||
private target; | ||
protected name: string; | ||
protected target: string; | ||
private timeout; | ||
private handlers; | ||
protected handlers: Map<string, Handler>; | ||
private calls; | ||
@@ -12,6 +12,5 @@ constructor(config: RpcConfig); | ||
private _call; | ||
call(handler: string, args: any[], config?: CallConfigObject): Promise<any>; | ||
private validateMessage; | ||
call(handler: string, args?: any[] | null, config?: CallConfigObject): Promise<any>; | ||
protected onMessage(eventMessage: any): void; | ||
private onRequestMessage; | ||
protected onRequestMessage(message: ReqMessage): void; | ||
private onResponseMessage; | ||
@@ -18,0 +17,0 @@ stop(): void; |
@@ -11,8 +11,5 @@ "use strict"; | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const uuid_1 = require("uuid"); | ||
const schema_1 = __importDefault(require("./schema")); | ||
const helpers_1 = require("./helpers"); | ||
const constants_1 = require("./constants"); | ||
@@ -90,24 +87,7 @@ class RPC { | ||
} | ||
return this._call(handler, (config === null || config === void 0 ? void 0 : config.target) || this.target, timeout, args); | ||
return this._call(handler, (config === null || config === void 0 ? void 0 : config.target) || this.target, timeout, args || []); | ||
}); | ||
} | ||
validateMessage(message) { | ||
const result = schema_1.default(message); | ||
if (Array.isArray(result)) | ||
return { isValid: false, type: null }; | ||
let type = null; | ||
if (message.data.data.method && message.data.data.params) { | ||
type = 'req'; | ||
} | ||
else if (message.data.data.hasOwnProperty('result') | ||
|| typeof message.data.data.hasOwnProperty('error')) { | ||
type = 'res'; | ||
} | ||
return { | ||
type, | ||
isValid: (type === 'req' || type === 'res'), | ||
}; | ||
} | ||
onMessage(eventMessage) { | ||
const { type, isValid } = this.validateMessage(eventMessage); | ||
const { type, isValid } = helpers_1.validateMessageSchema(eventMessage); | ||
if (!isValid) | ||
@@ -157,3 +137,6 @@ return; | ||
}; | ||
handler(callback, ...message.data.data.params); | ||
handler({ | ||
callback, | ||
message, | ||
}, ...message.data.data.params); | ||
} | ||
@@ -160,0 +143,0 @@ catch (error) { |
@@ -1,3 +0,3 @@ | ||
/// <reference types="fastest-validator" /> | ||
declare const validateMessageObject: (value: any) => true | import("fastest-validator").ValidationError[]; | ||
export default validateMessageObject; | ||
import Joi from 'joi'; | ||
declare const schema: Joi.ObjectSchema<any>; | ||
export default schema; |
@@ -6,25 +6,21 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
const fastest_validator_1 = __importDefault(require("fastest-validator")); | ||
const validator = new fastest_validator_1.default(); | ||
const messageSchema = { | ||
target: 'string', | ||
data: { | ||
type: 'object', | ||
props: { | ||
name: 'string', | ||
data: { | ||
type: 'object', | ||
props: { | ||
id: ['string', 'number'], | ||
jsonrpc: { type: 'equal', value: '2.0' }, | ||
result: { type: 'any', optional: true }, | ||
error: { type: 'object', optional: true }, | ||
method: { type: 'string', optional: true }, | ||
params: { type: 'array', optional: true, items: 'any' } | ||
}, | ||
}, | ||
}, | ||
}, | ||
}; | ||
const validateMessageObject = validator.compile(messageSchema); | ||
exports.default = validateMessageObject; | ||
const joi_1 = __importDefault(require("joi")); | ||
const schema = joi_1.default.object({ | ||
target: joi_1.default.string().required(), | ||
data: joi_1.default.object({ | ||
name: joi_1.default.string().required(), | ||
data: joi_1.default.object({ | ||
id: joi_1.default.alternatives().try(joi_1.default.number(), joi_1.default.string()).required(), | ||
jsonrpc: joi_1.default.string().valid('2.0').required(), | ||
result: joi_1.default.any(), | ||
method: joi_1.default.string(), | ||
params: joi_1.default.array().items(joi_1.default.any()), | ||
error: joi_1.default.object({ | ||
code: joi_1.default.number().required(), | ||
message: joi_1.default.string().required(), | ||
data: joi_1.default.any(), | ||
}), | ||
}).required(), | ||
}).required(), | ||
}); | ||
exports.default = schema; |
@@ -41,3 +41,7 @@ /// <reference types="node" /> | ||
export declare type CallBackHandler = (err: null | ErrorRes, res?: null | any) => void; | ||
export declare type Handler = (cb: CallBackHandler, ...args: any[]) => any; | ||
export declare type HandlerProps = { | ||
callback: CallBackHandler; | ||
message: ReqMessage; | ||
}; | ||
export declare type Handler = (props: HandlerProps, ...args: any[]) => any; | ||
export declare type CallConfigObject = { | ||
@@ -44,0 +48,0 @@ timeout: number; |
{ | ||
"name": "@fleekhq/browser-rpc", | ||
"version": "1.0.0", | ||
"version": "2.0.0", | ||
"main": "dist/index.js", | ||
@@ -35,2 +35,3 @@ "module": "dist/esm/index.js", | ||
"@babel/core": "^7.13.16", | ||
"@types/chrome": "^0.0.135", | ||
"@types/jest": "^26.0.22", | ||
@@ -56,5 +57,6 @@ "@types/react-test-renderer": "^17.0.1", | ||
"dependencies": { | ||
"fastest-validator": "^1.10.1", | ||
"extensionizer": "^1.0.1", | ||
"joi": "^17.4.0", | ||
"uuid": "^8.3.2" | ||
} | ||
} |
229
README.md
@@ -73,6 +73,7 @@ # BrowserRPC | ||
*/ | ||
server.exposeHandler('sum', (cb, val1, val2) => { | ||
server.exposeHandler('sum', ({ callback, message }, val1, val2) => { | ||
console.log(message); // Full message | ||
const result = val1 + val2; | ||
cb(null, result); | ||
callback(null, result); | ||
}); | ||
@@ -137,1 +138,225 @@ | ||
https://www.jsonrpc.org/specification#error_object | ||
# ProxyRPC | ||
This class is meant to be used on browser extensions content script. It allow proxy all the calls that arn't handled by the instance to the background script controller (see BackgroundController class). Same way, all the responses received from the BackgroundScript controller are redirected to the requester. (This class implements window.postMessage for the communication between webpages and content script, and browser.runtime.port for the cummunication between the content script and the background script). | ||
```js | ||
import { ProxyRPC } from '@fleekhq/browser-rpc'; | ||
// Create a new ProxyRPC instance | ||
// name property is injected as part of the responses messages to identify who processed the response | ||
// name property is also usid as the PORT name for the communication with the background script | ||
// target property specifies who can call methods on this new instance and receive the responses. If a different target try to call methods on this instance, the calls are ignored. | ||
const server = new ProxyRPC(window, { | ||
name: 'rpc-server', | ||
target: 'browser-client', | ||
}); | ||
server.exposeHandler('sum', ({ callback, message }, val1, val2) => { | ||
console.log(message); // Full message | ||
const result = val1 + val2; | ||
callback(null, result); | ||
}); | ||
server.start(); | ||
``` | ||
now if you try to call the method `sum` from your client, you'll receive the response from the ProxyRPC instance, but if you try to call a method not defined into the Proxy, the call is going to be redirected to the background script using `Port` communication in order to be processed. If the method also doesn't exists in the background script controller, the call is rejected with an error. | ||
# BackgroundController | ||
This class implements Port communication between the content script and other resources with access to the Port API. Similar to the BorwserRPC and ProxyRPC, you can expose handlers to process the requests. | ||
```js | ||
import { BackgroundController } from '@fleekhq/browser-rpc'; | ||
const backgroundController = new BackgroundController({ | ||
name: 'bg-script', | ||
trustedSources: [ | ||
'rpc-server', | ||
'another-port-source', | ||
], | ||
}); | ||
backgroundController.exposeController('hello', (opts, name) => { | ||
const { message, sender, callback } = opts; | ||
console.log(message); // Full message | ||
console.log(sender); // Sender information: port id, port name, port | ||
callback(null, `hello ${name}!!!`); | ||
}); | ||
backgroundController.exposeController('remoteCall', (opts, myNumber, callId, portId) => { | ||
const { callback } = opts; | ||
callback(null, myNumber); | ||
callback(null, myNumber, [{ portId, callId }]); | ||
}); | ||
backgroundController.start(); | ||
``` | ||
### BackgroundController.exposeHandler(name: string, handler: ControllerHandler): void | ||
Add a new handler to the instance | ||
#### handler: (opts, ...args) => void | ||
- opts (object): | ||
- sender: Sender information object | ||
- id: port id assigned by the instance | ||
- name: port name | ||
- port: port | ||
- ports: Map with all the ports handled by the instance: Map<id: number, port>; | ||
- message: The full message | ||
- callback: callback used to send a response back to the sender port or to another port | ||
- err: ErrorRes type if you want to send an error or null if is not required | ||
- res: any, the respose that you want to send back to the sender | ||
- targetPorts: optional, this is an array of objects with a portId and a callId (message call id). If you want to send a response to a different port, you have to pass this information, otherwise the callback is going to respond directly to the sender | ||
- portId: number, the target port id | ||
- callId: number | string, the call id | ||
```typescript | ||
type HandlerProps = { | ||
sender: { | ||
id: number, | ||
name: string, | ||
port: chrome.runtime.Port, | ||
}, | ||
ports: Map<number, chrome.runtime.Port>, | ||
message: Message, | ||
callback: ( | ||
err: ErrorRes | null, | ||
res: any, | ||
targetPorts?: { | ||
portId: number, | ||
callId: CallID, | ||
}[], | ||
) => void, | ||
}; | ||
type ControllerHandler = ( | ||
props: HandlerProps, | ||
...args: any[] | ||
) => void; | ||
``` | ||
# PortRPC | ||
Similar to BrowserRPC, this one is used to send requests to the BackgroundController using Port communication | ||
```js | ||
import { PortRPC } from '@fleekhq/browser-rpc'; | ||
const client = new PortRPC({ | ||
name: 'browser-port-rpc', | ||
target: 'bg-script', | ||
timeout: 10000, | ||
}); | ||
client.start(); | ||
client | ||
.call('sum', [1, 2]) | ||
.then((result) => { | ||
console.log(result); | ||
}) | ||
.catch((error) => { | ||
console.error(error); | ||
}); | ||
``` | ||
# Full Example | ||
Here is a full implementation of BrowserRPC, ProxyRPC, PortRPC and BackgroundController | ||
webpage | ||
```js | ||
import { BrowserRPC } from '@fleekhq/browser-rpc'; | ||
const client = new BrowserRPC(window, { | ||
name: 'browser-client', | ||
target: 'rpc-server', | ||
timeout: 10000, | ||
}); | ||
client.start(); | ||
// This call is handled directly by ProxyRPC since the handler is definded | ||
client | ||
.call('sum', [1, 2]) | ||
.then((result) => { | ||
console.log(result); // result: 3. response processed by ProxyRPC handler | ||
}); | ||
// In this case the method isn't defined on ProxyRPC so the call is redirected to the Background controller | ||
client | ||
.call('hello', ['Jhon']) | ||
.then((result) => { | ||
console.log(result); // result: "hello John!!!". response processed by BackgroundController handler | ||
}); | ||
``` | ||
content script | ||
```js | ||
import { ProxyRPC } from '@fleekhq/browser-rpc'; | ||
const server = new ProxyRPC(window, { | ||
name: 'rpc-server', | ||
target: 'browser-client', | ||
}); | ||
server.exposeHandler('sum', ({ callback, message }, val1, val2) => { | ||
console.log(message); // Full message | ||
const result = val1 + val2; | ||
callback(null, result); | ||
}); | ||
server.start(); | ||
``` | ||
background script | ||
```js | ||
import { BackgroundController } from '@fleekhq/browser-rpc'; | ||
const backgroundController = new BackgroundController({ | ||
name: 'bg-script', | ||
trustedSources: [ | ||
'rpc-server', | ||
'another-port-source', | ||
], | ||
}); | ||
backgroundController.exposeController('hello', (opts, name) => { | ||
const { message, sender, callback } = opts; | ||
console.log(message); // Full message | ||
console.log(sender); // Sender information: port id, port name, port | ||
callback(null, `hello ${name}!!!`); | ||
}); | ||
backgroundController.exposeController('remoteCall', (opts, myNumber, callId, portId) => { | ||
const { callback } = opts; | ||
callback(null, myNumber); | ||
// here you can use another callID and portID to send a response to a different port if you want | ||
callback(null, myNumber, [{ portId, callId }]); | ||
}); | ||
backgroundController.start(); | ||
``` |
@@ -0,4 +1,7 @@ | ||
export { default as BackgroundController } from './BackgroundController'; | ||
export { default as BrowserRPC } from './BrowserRPC'; | ||
export { default as ProxyRPC } from './ProxyRPC'; | ||
export { default as PortRPC } from './PortRPC'; | ||
export { default as RPC } from './RPC'; | ||
export { default } from './BrowserRPC'; | ||
export * from './types'; |
import { v4 as uuid } from 'uuid'; | ||
import validateMessageObject from './schema'; | ||
import { validateMessageSchema } from './helpers'; | ||
import { JSON_RPC_VERSION, JSON_RPC_ERROR_CODES } from './constants'; | ||
@@ -16,10 +16,9 @@ import { | ||
CallConfigObject, | ||
ValidationMessageObject, | ||
} from './types'; | ||
export default abstract class RPC { | ||
private name: string; | ||
private target: string; | ||
protected name: string; | ||
protected target: string; | ||
private timeout: number = 5000; | ||
private handlers = new Map<string, Handler>(); | ||
protected handlers = new Map<string, Handler>(); | ||
private calls = new Map<CallID, ResolverObject>(); | ||
@@ -98,3 +97,3 @@ | ||
async call(handler: string, args: any[], config?: CallConfigObject): Promise<any> { | ||
async call(handler: string, args?: any[] | null, config?: CallConfigObject): Promise<any> { | ||
let timeout = this.timeout; | ||
@@ -110,30 +109,8 @@ | ||
timeout, | ||
args, | ||
args || [], | ||
); | ||
} | ||
private validateMessage(message: any): ValidationMessageObject { | ||
const result = validateMessageObject(message); | ||
if (Array.isArray(result)) return { isValid: false, type: null }; | ||
let type: ValidationMessageObject['type'] = null; | ||
if (message.data.data.method && message.data.data.params) { | ||
type = 'req'; | ||
} else if ( | ||
message.data.data.hasOwnProperty('result') | ||
|| typeof message.data.data.hasOwnProperty('error') | ||
) { | ||
type = 'res'; | ||
} | ||
return { | ||
type, | ||
isValid: (type === 'req' || type === 'res'), | ||
}; | ||
} | ||
protected onMessage(eventMessage: any): void { | ||
const { type, isValid } = this.validateMessage(eventMessage); | ||
const { type, isValid } = validateMessageSchema(eventMessage); | ||
if (!isValid) return; | ||
@@ -156,3 +133,3 @@ | ||
private onRequestMessage(message: ReqMessage): void { | ||
protected onRequestMessage(message: ReqMessage): void { | ||
const resMessage: ResMessage = { | ||
@@ -193,3 +170,6 @@ target: this.target, | ||
handler(callback, ...message.data.data.params); | ||
handler({ | ||
callback, | ||
message, | ||
}, ...message.data.data.params); | ||
} catch (error) { | ||
@@ -196,0 +176,0 @@ if (resMessage.data.data.hasOwnProperty('error')) return; |
@@ -1,27 +0,22 @@ | ||
import Validator, { ValidationSchema } from 'fastest-validator'; | ||
import Joi from 'joi'; | ||
const validator = new Validator(); | ||
const schema = Joi.object({ | ||
target: Joi.string().required(), | ||
data: Joi.object({ | ||
name: Joi.string().required(), | ||
data: Joi.object({ | ||
id: Joi.alternatives().try(Joi.number(), Joi.string()).required(), | ||
jsonrpc: Joi.string().valid('2.0').required(), | ||
result: Joi.any(), | ||
method: Joi.string(), | ||
params: Joi.array().items(Joi.any()), | ||
error: Joi.object({ | ||
code: Joi.number().required(), | ||
message: Joi.string().required(), | ||
data: Joi.any(), | ||
}), | ||
}).required(), | ||
}).required(), | ||
}); | ||
const messageSchema: ValidationSchema = { | ||
target: 'string', | ||
data: { | ||
type: 'object', | ||
props: { | ||
name: 'string', | ||
data: { | ||
type: 'object', | ||
props: { | ||
id: ['string', 'number'], | ||
jsonrpc: { type: 'equal', value: '2.0' }, | ||
result: { type: 'any', optional: true }, | ||
error: { type: 'object', optional: true }, | ||
method: { type: 'string', optional: true }, | ||
params: { type: 'array', optional: true, items: 'any' } | ||
}, | ||
}, | ||
}, | ||
}, | ||
}; | ||
const validateMessageObject = validator.compile(messageSchema); | ||
export default validateMessageObject; | ||
export default schema; |
@@ -50,4 +50,10 @@ export type CallID = string | number; | ||
export type CallBackHandler = (err: null | ErrorRes, res?: null | any) => void; | ||
export type Handler = (cb: CallBackHandler, ...args: any[]) => any; | ||
export type HandlerProps = { | ||
callback: CallBackHandler, | ||
message: ReqMessage, | ||
}; | ||
export type Handler = (props: HandlerProps, ...args: any[]) => any; | ||
export type CallConfigObject = { | ||
@@ -54,0 +60,0 @@ timeout: number; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
87769
43
2162
361
3
19
+ Addedextensionizer@^1.0.1
+ Addedjoi@^17.4.0
+ Added@hapi/hoek@9.3.0(transitive)
+ Added@hapi/topo@5.1.0(transitive)
+ Added@sideway/address@4.1.5(transitive)
+ Added@sideway/formula@3.0.1(transitive)
+ Added@sideway/pinpoint@2.0.0(transitive)
+ Addedextensionizer@1.0.1(transitive)
+ Addedjoi@17.13.3(transitive)
- Removedfastest-validator@^1.10.1
- Removedfastest-validator@1.19.0(transitive)