Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
A dynamic API Gateway which updates REST endpoints, GraphQL schema, WebSocket handlers and access control policies by integrating metadata of discovered remote services.
moleculer-api는 MSA 환경에서 마이크로 서비스들의 API 스키마 조각을 수집하고, 무중단으로 통합 API를 업데이트하여 제공하는 웹 서버 컴포넌트입니다.
서비스 API 스키마는 분산 서비스 프로시저의 호출, 또는 중앙 메시징 서비스에 대한 이벤트 발행 및 구독을 응용 프로토콜(REST, GraphQL, WebSocket 등)에 맵핑합니다. 서비스 API 스키마는 JSON 포맷으로 구성되어있으며, 응용 프로토콜 별 API 맵핑과 그에 대한 접근 제어로 구성되어 있습니다.
{
branch: "master",
policy: {},
protocol: {
REST: {
basePath: "/storage",
routes: [
{
path: "/",
method: "GET",
call: {
action: "storage.get",
params: {
offset: "@query.offset:number",
limit: "@query.limit:number",
},
},
},
{
path: "/upload",
method: "POST",
call: {
action: "storage.create",
params: {
file: "@body.file",
meta: {
tags: {
identityId: "@context.auth.identity.sub",
},
allowedContentTypes: ["text/*", "image/*", "application/pdf"],
private: false,
},
},
},
},
{
path: "/upload-stream",
method: "POST",
description: "not a production purpose, need a wrapper action to make this safe",
call: {
action: "storage.createWithStream",
params: "@body.file",
implicitParams: false,
},
},
{
path: "/download/:id",
method: "GET",
call: {
action: "storage.getURL",
params: {
id: "@path.id",
expiryHours: "@query.expiryHours:number",
prompt: "@query.prompt:boolean",
promptAs: "@query.promptAs:string",
},
map: `({ response }) => ({
$status: response ? 303 : 404,
$headers: response ? { "Location": response } : undefined,
})`,
},
},
{
path: "/:id",
method: "GET",
call: {
action: "storage.find",
params: {
id: "@path.id",
},
map: `({ response }) => ({
$status: response ? 200 : 404,
$body: response,
})`,
},
},
{
path: "/:id",
method: "PUT",
call: {
action: "storage.update",
params: {
id: "@path.id",
// id, name, tags, private, contentType
},
},
},
{
path: "/:id",
method: "DELETE",
call: {
action: "storage.delete",
params: {
id: "@path.id",
},
},
},
],
},
},
}
{
branch: "master",
policy: {},
protocol: {
GraphQL: {
typeDefs: `
extend type Query {
storage: StorageQuery!
}
type StorageQuery {
files(offset: Int = 0, limit: Int = 10): FileList!
file(id: ID!): File
}
type File {
id: ID!
name: String!
contentType: String!
tags: JSON!
private: Boolean!
byteSize: Int!
size: String!
url(expiryHours: Int = 2, prompt: Boolean = false, promptAs: String): String!
updatedAt: DateTime!
createdAt: DateTime!
}
type FileList {
offset: Int!
limit: Int!
# it is not exact value
total: Int!
entries: [File!]!
hasNext: Boolean!
hasPrev: Boolean!
}
extend type Mutation {
storage: StorageMutation!
}
type StorageMutation {
upload(file: Upload!): File!
update(id: ID!, name: String, tags: JSON, private: Boolean): File!
delete(id: ID!): Boolean!
}
`,
resolvers: {
Query: {
storage: `() => ({})`,
},
Mutation: {
storage: `() => ({})`,
},
StorageMutation: {
upload: {
call: {
action: "storage.create",
params: {
file: "@args.file",
meta: {
allowedContentTypes: [],
private: false,
},
},
},
},
update: {
call: {
action: "storage.update",
params: {},
},
},
delete: {
call: {
action: "storage.delete",
params: {},
}
},
},
StorageQuery: {
file: {
call: {
action: "storage.find",
params: {
id: "@args.id[]",
},
},
},
files: {
call: {
action: "storage.get",
params: {
offset: "@args.offset",
limit: "@args.limit",
},
},
},
},
File: {
url: {
call: {
action: "storage.getURL",
params: {
id: "@source.id[]",
expiryHours: "@args.expiryHours",
prompt: "@args.prompt",
promptAs: "@args.promptAs",
},
},
},
size: (({ source }: any) => {
const { byteSize = 0 } = source;
const boundary = 1000;
if (byteSize < boundary) {
return `${byteSize} B`;
}
let div = boundary;
let exp = 0;
for (let n = byteSize / boundary; n >= boundary; n /= boundary) {
div *= boundary;
exp++;
}
const size = byteSize / div;
return `${isNaN(size) ? "-" : size.toLocaleString()} ${"KMGTPE"[exp]}B`;
}).toString(),
},
FileList: {
hasPrev: `({ source }) => source.offset > 0`,
}
},
},
},
}
{
branch: "master",
policy: {
call: [
{
actions: ["user.update"],
description: "A user can update the user profile which is belongs to own account.",
scope: ["user.write"],
filter: (({ context, params }) => {
console.log(context);
if (params.id) {
return context.auth.identity.sub === params.id;
}
return false;
}).toString(),
},
],
},
protocol: {
GraphQL: {
typeDefs: `
extend type Query {
viewer: User
user(id: Int, identityId: String, username: String, where: JSON): User
}
extend type Mutation {
createUser(input: UserInput!): User!
updateUser(input: UserInput!): User!
}
input SportsUserInput {
id: Int
username: String
birthdate: Date
gender: Gender
name: String
pictureFileId: String
}
type User {
id: Int!
username: String!
name: String!
birthdate: Date
gender: Gender
pictureFile: File
}
`,
resolvers: {
User: {
pictureFile: {
call: {
if: "({ source }) => !!source.pictureFileId",
action: "storage.find",
params: {
id: "@source.pictureFileId[]",
},
},
},
},
Mutation: {
createUser: {
call: {
action: "user.create",
params: "@args.input",
implicitParams: false,
},
},
updateUser: {
call: {
action: "user.update",
params: "@args.input",
implicitParams: false,
},
},
},
},
},
},
}
{
branch: "master",
policy: {},
protocol: {
REST: {
description: "update user's FCM registration token",
basePath: "/notification",
routes: [
{
method: "PUT",
path: "/update-token",
call: {
action: "notification.updateToken",
params: {
identityId: "@context.auth.identity.sub",
token: "@body.token",
},
},
},
],
},
},
}
{
branch: "master",
policy: {},
protocol: {
WebSocket: {
basePath: "/chat",
description: "...",
routes: [
/* bidirectional streaming chat */
{
path: "/message-stream/:roomId",
call: {
action: "chat.message.stream",
params: {
roomId: "@path.roomId",
},
},
},
/* pub/sub chat */
{
path: "/message-pubsub/:roomId",
subscribe: {
events: `({ path }) => ["chat.message." + path.roomId]`,
},
publish: {
event: `({ path }) => "chat.message." + path.roomId`,
params: "@message",
},
},
/* pub/sub video */
{
path: "/video-pubsub",
subscribe: {
events: ["chat.video"],
},
publish: {
event: "chat.video",
params: {
id: "@context.id",
username: "@query.username",
data: "@message",
},
filter: `({ params }) => params.id && params.username && params.data`,
},
},
/* streaming video */
{
path: "/video-stream/:type",
call: {
action: "chat.video.stream",
params: {
id: "@context.id",
type: "@path.type",
},
},
},
],
},
},
}
Gateway는 특정 서비스 API 스키마의 추가, 제거 및 업데이트시 기존 통합 API 스키마에 병합을 시도하고, 성공시 무중단으로 라우터를 업데이트하며 그 결과를 원격 서비스에 다시 보고합니다.
분산 서비스의 API 스키마를 수집하고 병합하여 API를 실시간으로 업데이트
Polyglot 하거나 서로 다른 서비스 브로커에 기반한 서비스들의 API 스키마를 수집하고 병합 할 수 있음
개발 편의 및 충돌 방지를 위한 API 브랜칭 및 버저닝 기능
상태 검사 및 API 문서 생성 (WIP)
미들웨어 방식의 요청 흐름 제어
Error
Logging
Body Parser
Helmet
CORS
Serve Static File
HTTP/HTTPS/HTTP2
(확장 가능)
미들웨어 방식의 컨텍스트 생성 제어
Authn/Authz
Locale
Correlation ID
IP Address
User-Agent
Request
(확장 가능)
응용 프로토콜 플러그인
REST
GraphQL
WebSocket
(확장 가능)
접근 제어 정책 플러그인
OAuth2 scope 기반 접근 제어
JavaScript FBAC; Function Based Access Control 기반 접근 제어
(확장 가능)
The project is available under the MIT license.
요청 상태를 기반으로 API 요청에 활용되는 커넥터입니다. 접근 제어 및 stateless 커넥터를 연계 할 수 있습니다.
커넥터
어댑터에 위임
연결 가능한 커넥터
개요
call
O
params
, map
분산 서비스 액션을 호출합니다.
publish
O
params
중앙 메시징 서비스에 이벤트를 발행합니다.
subscribe
O
map
중앙 메시징 서비스에서 이벤트를 구독합니다.
GraphQL의 Subscription
타입이나 WebSocket 프로토콜 등을 사용하지 않거나, 분산 시스템에 중앙 메시징 서비스를 제공 할 수 없는 경우엔 publish
, subscribe
커넥터를 구현하지 않아도 무관합니다.
요청 상태가 없는 커넥터입니다.
커넥터
어댑터에 위임
개요
map
X
Inline JavaScript Function String을 VM에서 해석하여 주어진 객체나 응답 객체를 변환합니다.
params
X
요청 페이로드에서 위의 타 커넥터들로 전달 할 객체를 생성합니다.
discover
O
분산 서비스의 업데이트나 종료를 감지하고, 노드, 서비스 API Schema, 액션 및 이벤트 구독, 발행 정보를 수집합니다.
health
O
분산 서비스 및 액션, 중앙 메시징 서비스의 상태 확인을 제공합니다.
reporter
O
출처 노드로 디버그 메세지를 전달합니다.
logger
O
Gateway의 로깅 인스턴스를 제공합니다.
Moleculer API Gateway는 아래 원칙을 기반으로 고안되었습니다.
분산 시스템안에서 유동적으로 작동합니다.
Persistence Layer를 갖지 않습니다.
"분산 서비스 -> API" 종속성을 최소화합니다.
서비스 API 스키마는 JSON 텍스트입니다.
분산 서비스 호출시 인증 등의 컨텍스트를 파라미터로 맵핑하도록 유도합니다.
확장 가능한 컴포넌트 패턴을 지향합니다.
프로토콜 플러그인은 서버, 미들웨어, 스키마, 핸들러의 모든 부분을 확장합니다.
접근 제어 정책은 프로토콜별 엔드포인트가 아닌 액션, 이벤트에 적용됩니다.
네트워킹 및 복원 패턴에 관여하지 않습니다.
분산 서비스와 API Gateway는 어댑터(Broker)로 연결됩니다.
분산 트랜잭션을 유도하거나 관여하지 않습니다.
아울러 분산 서비스 및 서비스 브로커에서 기대되는 패턴은 다음과 같습니다.
분산 서비스의 프로시저는 무상태를 지향합니다.
프로시저는 인증 컨텍스트를 고려하지 않습니다.
프로시저는 접근 제어를 고려하지 않습니다.
프로시저는 가능한 멱등성을 갖도록 고려됩니다.
서비스 브로커는 분산 시스템을 위한 복원 패턴을 구성합니다.
회로차단기
격벽
재시도
요청 큐
이하에서 서비스 API 스키마는 분산 환경의 부분적인 API 스키마를 의미합니다. Gateway API 스키마는 Gateway에서 통합된 API 스키마를 의미합니다.
서비스 API 스키마는 JSON 텍스트로 Gateway에 전달됩니다. 스키마 데이터의 직렬화 및 비직렬화는 MSA 라이브러리에 달렸습니다. 아래 예시에서는 Node.js 환경을 기준으로 서비스 API 스키마를 JavaScript 객체로 표기합니다.
REST API 맵핑에는 subscribe
를 제외한 call
, publish
, map
커넥터를 이용 할 수 있습니다.
basePath
를 기반으로 이하 REST 엔드포인트가 생성됩니다.
description
은 문서 생성시 활용되며 Markdown을 지원합니다 (옵션).
Call
GET /players/1
요청이 player.get
액션을 { id: 1 }
페이로드와 함께 호출하고 성공시 그 결과를 반환합니다.
depreacted
는 문서 생성시 활용됩니다 (옵션).
라우트 path를 구성하는 규칙은 를 참고 할 수 있습니다.
GET /players/me
요청이 player.get
액션을 { id: <인증 컨텍스트의 player.id> }
정보로부터 페이로드와 함께 호출하고 성공시 그 결과를 반환합니다.
Map
또는 map
커넥터 (Inline JavaScript Function String)를 통해 인증 컨텍스트의 player
객체를 바로 반환 할 수 있습니다. 이후에 다시 다루는 Inline JavaScript Function String은 API Gateway의 Node.js VM 샌드박스에서 해석됩니다.
Publish
POST /players/1
(body: { message: "blabla" }
) 요청은 player.message
이벤트를 { userId: id: <인증 컨텍스트의 player.id>, message: "blabla" }
페이로드와 함께 publish
하고 성공시 발송된 페이로드를 응답합니다.
Params
REST API의 params
맵핑에는 @path
, @body
, @query
, @context
객체를 이용 할 수 있습니다.
REST: {
basePath: "/players",
description: "player service REST API",
routes: [
{
method: "GET",
path: "/:id",
deprecated: false,
description: "Get player information by id",
call: {
action: "player.get",
params: {
id: "@path.id",
},
},
},
{
method: "GET",
path: "/me",
deprecated: false,
description: "Get player information of mine",
call: {
action: "player.get",
params: {
id: "@context.user.player.id",
},
},
},
{
method: "GET",
path: "/me",
deprecated: false,
description: "Get player information of mine",
map: `({ path, query, body, context }) => context.user.player`,
},
{
method: "POST",
path: "/message",
deprecated: false,
description: "Push notifications to all players",
publish: {
event: "player.message",
broadcast: false,
params: {
userId: "@context.user.player.id",
message: "@body.message",
},
},
},
],
},
// @body 객체 전체를 페이로드로 전달하거나 스트림을 전달 할 때 이용됩니다.
params: "@body",
// @ 문자열로 시작되지 않는 값들은 해석되지 않고 그대로 전달됩니다.
params: {
foo: "@path.foo", // will bar parsed
bar: "query.bar", // will be "query.bar"
zzz: ["any", { obj: "ject", can: "be", "use": 2 }],
},
// 항상 string 타입을 갖는 @query, @path 객체의 속성들에 한해서 타입을 boolean이나 number로 변환 할 수 있습니다.
params: {
foo: "@path.foo:number",
bar: "@query.bar:boolean",
},
Handling streaming request is up to delegator. To support streaming request, delegator should handle { createReadStream(): ReadableStream, ...meta: any }
params as their own way.
{
method: "POST",
action: "file.upload",
params: "@body.file",
}
When multipart/form-data request with 'file' field is mapped to above REST API schema. REST protocol plugin will delegate action call with params as below.
{
filename: string;
encoding: "utf8"|"7bit"|"base64"|...;
mimetype: string;
createReadStream(): ReadableStream;
}
{
typeDefs: `
extend type Mutation {
uploadFile(file: Upload): JSON
}
`,
resolvers: {
Mutation: {
uploadFile: {
call: "file.upload",
params: "@args.file",
},
},
},
}
When GraphQL multipart/form-data request is mapped to above GraphQL API schema. GraphQL protocol plugin will delegate action call with params as below.
{
filename: string;
encoding: "utf8"|"7bit"|"base64"|...;
mimetype: string;
createReadStream(): ReadableStream;
}
For WebSocket protocol, see 4.
Handling streaming response is up to protocol plugins. For REST protocol, any response from delegator like below will be handled as stream response with { "Content-Type": "application/octet-stream", "Transfer-Encoding": "chunked" }
headers.
{
createReadStream(): ReadableStream,
...,
}
For GraphQL protocol, above features just ignored.
For WebSocket protocol, see 4.
Also for REST protocol, can modify response with $headers
, status
properties in delegator response.
{
createReadStream(): ReadableStream,
$headers: {
"Content-Type": "text/html; charset=utf-8",
},
}
It is not only for the streaming response. below delegator response will also modify response headers.
{
$headers: {
"Status": "301 Moved Permanently",
"Location": "https://some.where.to.go.com",
},
$status: 302,
}
For GraphQL protocol, this feature is just ignored. For WebSocket protocol, this feature is just ignored.
WebSocket protocol supports bidirectional streaming. // TODO: documentation
MoleculerJS 어댑터로 API Gateway 함께와 아래의 피어 모듈을 활용 할 수 있습니다.
moleculer-iam - OIDC 및 Identity Provider를 제공하는 IAM 모듈, 인증 컨텍스트에 연동 가능
moleculer-file - 파일 업로드/다운로드/관리 모듈 (GCP Storage bucket backend / File System backend 지원)
moleculer-i18n - 국제화 데이터베이스 관리 및 조회 모듈 (MySQL/Maria DBMS 필요)
moleculer-console - 확장 가능한 Admin Console WebApp (React.js)
API document component 기본 제공 (moleculer-api 호환)
IAM component 기본 제공 (moleculer-iam 호환)
File management component 기본 제공 (moleculer-file 호환)
Translation component 기본 제공 (moleculer-i18n 호환)
API Gateway constructor options.
APIGatewayOptions type is a kind of container for all the subordinate components' options.
type APIGatewayOptions = {
brokers: RecursivePartial<ServiceBrokerOptions>[],
schema: RecursivePartial<SchemaRegistryOptions>,
server: RecursivePartial<APIServerOptions>,
logger: LoggerConstructorOptions,
} & RecursivePartial<APIGatewayOwnOptions>;
name
default
description
brokers
-
Service Broker constructor options. Can configure multiple brokers for a single gateway. Service Broker discovers remote services and works as a delegator for calling remote service procedures and also deals with central event messages.
schema
-
Schema Registry constructor options. Schema Registry handles the integration of remote service API schema and creates API handlers. Can disable or configure detailed options for each API Schema Plugins like GraphQL, REST and WebSocket, etc.
server
-
API Server constructor options. Can configure API Server update policy, server components (HTTP, WebSocket server) and network interface (HTTP, HTTPS) detailed options, middleware options and request context factory options.
logger
-
Global logger options. Currently logger is supported.
Options for the gateway itself rather inner components.
type APIGatewayOwnOptions = {
skipProcessEventRegistration: boolean;
};
name
default
description
skipProcessEventRegistration
false
Set true to not to set default handlers for process interrupt signals like SIGINT.
Service Broker options are consist of common properties and delegator specific properties. The common properties show below.
type ServiceBrokerOptions = {
registry: ServiceRegistryOptions;
batching: BatchingPoolOptions;
function: InlineFunctionOptions;
reporter: ReporterOptions;
log: {
event: boolean;
call: boolean;
},
} & ServiceBrokerDelegatorConstructorOptions;
name
default
description
registry
-
Options for the ServiceRegistry which collect remote services, available procedures and event types, etc.
batching
-
Options for batching feature which utilize for concurrent multiple procedure calls.
function
-
Options for inline function (JS function notation string) sandbox.
reporter
-
Options for remote service reporter instance which reports API integration status, error or logging in inline function sandbox, etc.
log.event
true
Enable logging event messages.
log.call
true
Enable logging remote procedure call.
type ServiceRegistryOptions = {
examples: {
processIntervalSeconds: number;
queueLimit: number;
limitPerActions: number;
limitPerEvents: number;
streamNotation: string;
omittedNotation: string;
omittedLimit: number;
redactedNotation: string;
redactedParamNameRegExps: RegExp[];
};
healthCheck: {
intervalSeconds: number;
};
};
name
default
description
examples
-
Options for the ServiceRegistry action (remote procedure), event example collecting feature.
examples.processIntervalSeconds
5
Consume example queue for every given intervals.
examples.queueLimit
50
Example queue size.
examples.limitPerActions
10
Maximum number of examples for a single action (remote procedure).
examples.limitPerEvents
10
Maximum number of examples for a single event.
examples.streamNotation
*STREAM*
Replace stream request and response of an example to given string.
examples.omittedNotation
*OMITTED*
Truncate example object's string property and append given suffix for...
examples.omittedLimit
100
The strings longer than given length.
examples.redactedNotation
*REDACTED*
React example object's string property to given string for...
examples.redactedParamNameRegExps
[
/password/i,
/secret/i,
/credential/i,
/key/i,
/token/i,
]
Matched strings with given regular expressions.
healthCheck
-
Options for the ServiceRegistry health check feature.
healthCheck.intervalSeconds
10
Health check for every given intervals.
type BatchingPoolOptions = {
batchingKey: (...args: any[]) => any;
entryKey: (batchingParams: any) => any;
failedEntryCheck: (entry: any) => boolean;
entriesLimit: number;
};
name
default
description
batchingKey
(hash function)
A keygen function for the key of same batching arguments. Create hash string with args object by default.
entryKey
(hash function)
A keygen function for the variable params of each batched entries.
failedEntryCheck
entry => entry && entry.batchingError
A function to determine whether each entries are failed or not in a batch response. By default, a remote procedure which supports batching should response { ..., batchingError: true }
object for failed entry.
entriesLimit
100
Maximum number of entries for a single batch.
type InlineFunctionOptions = {
util: {[key: string]: any};
};
name
default
description
util
{}
Any kind of object which can be accessed as global util
variable from inline functions.
type ReporterOptions = {
tableWidthZoomFactor: number;
};
name
default
description
tableWidthZoomFactor
1
A reporter sends a report which is consist of raw messages and a string that prints messages as a shape of a table. For that table, set a zoom factor of the width.
Specific options for the Service Broker Delegator. Can choose only one among supported delegators. Currently moleculer delegator is supported.
type ServiceBrokerDelegatorConstructorOptions = {
moleculer?: MoleculerServiceBrokerDelegatorOptions;
[otherDelegatorKey]?: any;
};
name
default
description
moleculer
-
Service Broker Delegator options.
moleculer delegator can be configured with moleculer broker own options and few extra options like below.
import * as Moleculer from "moleculer";
type MoleculerServiceBrokerDelegatorOptions = Moleculer.BrokerOptions & {
batchedCallTimeout: (itemCount: number) => number;
streamingCallTimeout: number;
streamingToStringEncoding: "ascii" | "utf8" | "utf-8" | "utf16le" | "ucs2" | "ucs-2" | "base64" | "latin1" | "binary" | "hex";
services: Moleculer.ServiceSchema[];
};
name
default
description
batchedCallTimeout
(return 5-60s based on item count)
A function to calculate the timeout options for a batched call.
streamingCallTimeout
3600000 (1 hour)
A timeout options for a streaming call (ms).
streamingToStringEncoding
base64
An encoding which is used to transform streaming data to buffer data. This is for the case that non-root property of payload is a readable stream. Because moleculer service broker can pipe only a single streaming data at once in a single params, a gateway needs to transform non-root streaming data to buffer data before proxy the action call. Try to check your moleculer service broker transporter options and this option for a malformed streaming data issue.
services
[]
Given Moleculer.ServiceSchema[]
would be registered on moleculer service broker started. This is for the testing convenience.
Service Registry options are consist of own options for the registry itself and Protocol, Policy, this two type of Plugin constructor options.
type SchemaRegistryOptions = {
maxVersions: number;
maxUnusedSeconds: number;
protocol: ProtocolPluginConstructorOptions,
policy: PolicyPluginConstructorOptions,
};
name
default
description
maxVersions
10
Maximum number of old versions for each branches.
maxUnusedSeconds
1800
Maximum unused duration until deleting non-master branches.
protocol
-
A ProtocolPlugin handles mapping Public API to calling internal services' procedure, publishing and subscribing event messages.
policy
-
A PolicyPlugin handles access controls (authorization) while calling internal services' procedure, publishing and subscribing event messages.
type ProtocolPluginConstructorOptions = {
GraphQL: RecursivePartial<GraphQLProtocolPluginOptions> | false;
REST: RecursivePartial<RESTProtocolPluginOptions> | false;
WebSocket: RecursivePartial<WebSocketProtocolPluginOptions> | false;
}
export type PolicyPluginConstructorOptions = {
scope: RecursivePartial<ScopePolicyPluginOptions> | false;
filter: RecursivePartial<FilterPolicyPluginOptions> | false;
};