UNPKG

zod-sockets

Version:

Socket.IO solution with I/O validation and the ability to generate AsyncAPI specification and a contract for consumers

702 lines (680 loc) 27.4 kB
import http, { IncomingMessage } from 'node:http'; import { RemoteSocket, Socket, Server } from 'socket.io'; import { z } from 'zod/v4'; import ts from 'typescript'; interface FlowCommons { /** @desc The URL to be used for obtaining refresh tokens. */ refreshUrl?: string; /** @desc A map between the scope name and a short description for it. */ availableScopes: Record<string, string>; } interface AuthHavingFlow { /** @desc The authorization URL to be used for this flow. */ authorizationUrl: string; } interface TokenHavingFlow { /** @desc The token URL to be used for this flow. */ tokenUrl: string; } interface OAuthFlowsObject { implicit?: FlowCommons & AuthHavingFlow; password?: FlowCommons & TokenHavingFlow; clientCredentials?: FlowCommons & TokenHavingFlow; authorizationCode?: FlowCommons & AuthHavingFlow & TokenHavingFlow; } interface HttpApiKeySecurity { type: "httpApiKey"; /** @desc The name of the header, query or cookie parameter to be used. */ name: string; in: "query" | "header" | "cookie"; } interface ApiKeySecurity { type: "apiKey"; in: "user" | "password"; } interface HttpSecurity { type: "http"; /** @link https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml */ scheme?: string; /** @example "Bearer" */ bearerFormat?: string; } interface ScopesHavingSecurity { /** @desc List of the needed scope names. An empty array means no scopes are needed. */ scopes?: string[]; } interface OAuth2Security extends ScopesHavingSecurity { type: "oauth2"; flows: OAuthFlowsObject; } interface OpenIdConnectSecurity extends ScopesHavingSecurity { type: "openIdConnect"; /** @desc OpenId Connect URL to discover OAuth2 configuration values */ openIdConnectUrl: string; } interface OtherSecurity { type: "userPassword" | "X509" | "symmetricEncryption" | "asymmetricEncryption" | "plain" | "scramSha256" | "scramSha512" | "gssapi"; } type SecuritySchemeObject = { description?: string; } & (HttpApiKeySecurity | ApiKeySecurity | HttpSecurity | OAuth2Security | OpenIdConnectSecurity | OtherSecurity); type EmptyObject = Record<string, never>; interface Distribution { join: (rooms: string | string[]) => Promise<void>; leave: (rooms: string | string[]) => Promise<void>; } type SomeRemoteSocket = RemoteSocket<Record<string, (...args: any[]) => void>, unknown>; interface RemoteClient<E extends EmissionMap, D extends z.ZodObject> extends Distribution { id: string; handshake: SomeRemoteSocket["handshake"]; rooms: string[]; getData: () => Readonly<Partial<z.infer<D>>>; emit: Emitter<E>; } interface Emission { schema: z.ZodTuple; ack?: z.ZodTuple; } type EmissionMap = Record<string, Emission>; type TupleOrTrue<T> = T extends z.ZodTuple ? T : z.ZodLiteral<true>; type TuplesOrTrue<T> = T extends z.ZodTuple ? z.ZodArray<T> : z.ZodLiteral<true>; type Emitter<E extends EmissionMap> = <K extends keyof E>(evt: K, ...args: z.input<E[K]["schema"]>) => Promise<z.output<TupleOrTrue<E[K]["ack"]>>>; type Broadcaster<E extends EmissionMap> = <K extends keyof E>(evt: K, ...args: z.input<E[K]["schema"]>) => Promise<z.output<TuplesOrTrue<E[K]["ack"]>>>; type RoomService<E extends EmissionMap, D extends z.ZodObject> = (rooms: string | string[]) => { /** * @desc Emits an event to all/others (depending on context) in the specified room(s) * @throws z.ZodError on validation * @throws Error on ack timeout * */ broadcast: Broadcaster<E>; getClients: () => Promise<RemoteClient<E, D>[]>; }; interface Client<E extends EmissionMap, D extends z.ZodObject> extends Distribution { /** @alias Socket.connected */ isConnected: () => boolean; /** @alias Socket.id */ id: Socket["id"]; handshake: Socket["handshake"]; /** * @desc When using express-session: * @example getRequest<express.Request>().session **/ getRequest: <T extends IncomingMessage = Socket["request"]>() => T; /** @desc Returns the list of the rooms the client in */ getRooms: () => string[]; /** * @desc Sends a new event to the client (this is not acknowledgement) * @throws z.ZodError on validation * @throws Error on ack timeout * */ emit: Emitter<E>; /** * @desc Emits to others * @throws z.ZodError on validation * @throws Error on ack timeout * */ broadcast: Broadcaster<E>; /** @desc Returns the client metadata according to the specified type or empty object */ getData: () => Readonly<Partial<z.infer<D>>>; /** * @desc Sets the client metadata according to the specified type * @throws z.ZodError on validation * */ setData: (value: z.infer<D>) => void; } /** * @desc Using module augmentation approach you can set the type of the actual logger used * @example declare module "zod-sockets" { interface LoggerOverrides extends winston.Logger {} } * @link https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation * */ interface LoggerOverrides { } /** @desc You can use any logger compatible with this type. */ type AbstractLogger = Record<"info" | "debug" | "warn" | "error", (message: string, meta?: any) => any> & LoggerOverrides; interface IndependentContext<E extends EmissionMap, D extends z.ZodObject> { logger: AbstractLogger; all: { /** @desc Returns the list of available rooms */ getRooms: () => string[]; /** @desc Returns the list of familiar clients */ getClients: () => Promise<RemoteClient<E, D>[]>; /** @desc Emits an event to everyone */ broadcast: Broadcaster<E>; }; /** @desc Provides room(s)-scope methods */ withRooms: RoomService<E, D>; } interface ClientContext<E extends EmissionMap, D extends z.ZodObject> extends IndependentContext<E, D> { /** @desc The sender of the incoming event */ client: Client<E, D>; } interface TracingContext<E extends EmissionMap, D extends z.ZodObject> extends ClientContext<E, D> { event: string; payload: unknown[]; } interface ErrorContext<E extends EmissionMap, D extends z.ZodObject> extends IndependentContext<E, D>, Partial<Pick<TracingContext<E, D>, "event" | "payload" | "client">> { error: Error; } interface ActionContext<IN, E extends EmissionMap, D extends z.ZodObject> extends ClientContext<E, D> { /** @desc Validated payload */ input: IN; } type Handler<CTX, OUT> = (params: CTX) => Promise<OUT>; interface Hooks<E extends EmissionMap, D extends z.ZodObject> { /** @desc The place for emitting events regardless receiving events */ onConnection: Handler<ClientContext<E, D>, void>; onDisconnect: Handler<ClientContext<E, D>, void>; onAnyIncoming: Handler<TracingContext<E, D>, void>; onAnyOutgoing: Handler<TracingContext<E, D>, void>; /** @desc The place for emitting events regardless clients activity */ onStartup: Handler<IndependentContext<E, D>, void>; /** @desc The place for handling errors, in particular validation errors of the incoming events */ onError: Handler<ErrorContext<E, D>, void>; } declare const rootNS = "/"; type RootNS = typeof rootNS; interface Namespace<E extends EmissionMap, D extends z.ZodObject> { /** @desc The events that the server can emit */ emission: E; /** @desc Handlers for some events in different contexts */ hooks: Partial<Hooks<E, D>>; /** @desc Schema of the client metadata in this namespace */ metadata: D; security: SecuritySchemeObject[]; } type Namespaces = Record<string, Namespace<EmissionMap, z.ZodObject>>; interface ConstructorOptions<NS extends Namespaces> { /** * @desc The acknowledgment awaiting timeout * @default 2000 * */ timeout?: number; /** * @desc You can disable the startup logo. * @default true */ startupLogo?: boolean; /** * @desc Define namespaces inline or consider using addNamespace() method * @default {} * @see Namespace * */ namespaces?: NS; security?: SecuritySchemeObject[]; } /** @todo consider using it for namespaces declaration only */ declare class Config<T extends Namespaces = EmptyObject> { constructor({ timeout, startupLogo, namespaces, security, }?: ConstructorOptions<T>); /** @default { path: "/", emission: {}, metadata: z.object({}), hooks: {}, examples: {}, security: [] } */ addNamespace<E extends EmissionMap = EmptyObject, D extends z.ZodObject = z.ZodObject<EmptyObject>, K extends string = RootNS>({ path, emission, metadata, hooks, security, }: Partial<Namespace<E, D>> & { path?: K; }): Config<Omit<T, K> & Record<K, Namespace<E, D>>>; } /** @desc Shorthand for single namespace config (root namespace only) */ declare const createSimpleConfig: <E extends EmissionMap, D extends z.ZodObject>({ startupLogo, timeout, security, emission, hooks, metadata, }?: Omit<ConstructorOptions<never>, "namespaces"> & Partial<Namespace<E, D>>) => Config<Omit<EmptyObject, "/"> & Record<"/", Namespace<E, D>>>; interface Commons<IN extends z.ZodTuple, NS extends Namespaces, K extends keyof NS> { /** @desc The incoming event payload validation schema (without or excluding acknowledgement) */ input: IN; /** * @desc The namespace this Action belongs to (optional) * @default "/" * */ ns?: K; /** @desc The incoming event name */ event: string; } interface ActionNoAckDef<IN extends z.ZodTuple, NS extends Namespaces, K extends keyof NS> extends Commons<IN, NS, K> { /** @desc No output schema => no returns => no acknowledgement */ handler: Handler<ActionContext<z.output<IN>, NS[K]["emission"], NS[K]["metadata"]>, void>; } interface ActionWithAckDef<IN extends z.ZodTuple, OUT extends z.ZodTuple, NS extends Namespaces, K extends keyof NS> extends Commons<IN, NS, K> { /** @desc The acknowledgement validation schema */ output: OUT; /** @desc The returns become an Acknowledgement */ handler: Handler<ActionContext<z.output<IN>, NS[K]["emission"], NS[K]["metadata"]>, z.input<OUT>>; } declare class ActionsFactory<NS extends Namespaces> { protected config: Config<NS>; constructor(config: Config<NS>); build<IN extends z.ZodTuple, OUT extends z.ZodTuple | undefined = undefined, K extends keyof NS = RootNS>(def: ActionNoAckDef<IN, NS, K> | ActionWithAckDef<IN, NonNullable<OUT>, NS, K>): Action<NS, IN, OUT>; } declare abstract class AbstractAction { abstract execute(params: { params: unknown[]; } & ClientContext<EmissionMap, z.ZodObject>): Promise<void>; } declare class Action<NS extends Namespaces, IN extends z.ZodTuple, OUT extends z.ZodTuple | undefined = undefined> extends AbstractAction { #private; constructor(action: ActionWithAckDef<IN, NonNullable<OUT>, NS, keyof NS> | ActionNoAckDef<IN, NS, keyof NS>); get event(): string; get namespace(): keyof NS; get inputSchema(): IN; get outputSchema(): OUT; execute({ params, logger, ...rest }: { params: unknown[]; } & ClientContext<EmissionMap, z.ZodObject>): Promise<void>; } declare const attachSockets: <NS extends Namespaces>({ io, actions, target, config: { namespaces, timeout, startupLogo }, logger: rootLogger, }: { /** * @desc The Socket.IO server * @example new Server() * */ io: Server; /** * @desc The array of handling rules for the incoming Socket.IO events * @example [ onPing ] * */ actions: AbstractAction[]; /** * @desc HTTP or HTTPS server to attach the sockets to * @example http.createServer().listen(8090) * */ target: http.Server; /** @desc The configuration describing the emission (outgoing events) */ config: Config<NS>; /** * @desc The instance of a logger * @default console * */ logger?: AbstractLogger; }) => Promise<Server>; interface IntegrationProps { config: Config<Namespaces>; actions: AbstractAction[]; /** * @desc When event has both .rest() and an acknowledgement, the "...rest" can not be placed in a middle. * @desc In this case, overloads are used to reflect variations on different number of the function arguments. * @default 3 * @example ( (cb) => void ) | ( (rest1, cb) => void ) | ( (rest1, rest2, cb) => void ) */ maxOverloads?: number; } declare const registryScopes: string[]; declare class Integration { #private; protected registry: Record<string, // namespace Record<(typeof registryScopes)[number], { event: string; node: ts.TypeNode; }[]>>; constructor({ config: { namespaces }, actions, maxOverloads, }: IntegrationProps); print(printerOptions?: ts.PrinterOptions): string; } /** @see https://github.com/asyncapi/bindings/tree/master/websockets */ declare namespace WS { /** @desc This object MUST NOT contain any properties. Its name is reserved for future use. */ interface Server { } /** * @desc When using WebSockets, the channel represents the connection. Unlike other protocols that support multiple * @desc virtual channels (topics, routing keys, etc.) per connection, WebSockets doesn't support virtual channels or, * @desc put it another way, there's only one channel and its characteristics are strongly related to the protocol * @desc used for the handshake, i.e., HTTP. */ interface Channel { /** * @desc The HTTP method to use when establishing the connection. Its value MUST be either GET or POST. * */ method: "GET" | "POST"; /** * @desc A Schema object containing the definitions for each query parameter. * @desc This schema MUST be of type object and have a properties key. */ query: SchemaObject | ReferenceObject; /** * @desc A Schema object containing the definitions of the HTTP headers to use when establishing the connection. * @desc This schema MUST be of type object and have a properties key. */ headers: SchemaObject | ReferenceObject; /** * @desc The version of this binding. If omitted, "latest" MUST be assumed. */ bindingVersion: "0.1.0"; } /** * @desc This object MUST NOT contain any properties. Its name is reserved for future use. * */ interface Operation { } /** @desc This object MUST NOT contain any properties. Its name is reserved for future use. */ interface Message { } } /** * @fileoverview AsyncAPI specification * @version 3.0.0 */ interface Bindings<T> { ws?: T; } /** @since 3.0.0 detached from OAS; added host, pathname, title, description, tags, externalDocs; changed security */ interface ServerObject { host: string; protocol: string; protocolVersion?: string; pathname?: string; title?: string; description?: string; variables?: Record<string, ServerVariableObject>; security?: Array<SecuritySchemeObject | ReferenceObject>; tags?: TagObject[]; externalDocs?: ExternalDocumentationObject; bindings?: Bindings<WS.Server>; } interface ContactObject { name?: string; url?: string; email?: string; } interface LicenseObject { name: string; url?: string; } /** @since 3.0.0 contains tags and externalDocs */ interface InfoObject { title: string; version: string; description?: string; termsOfService?: string; contact?: ContactObject; license?: LicenseObject; tags?: TagObject[]; externalDocs?: ExternalDocumentationObject; } /** @since 3.0.0 channels are optional, added operations */ interface AsyncApiObject { asyncapi: string; /** @desc URI or URN format */ id?: string; info: InfoObject; servers?: Record<string, ServerObject>; channels?: ChannelsObject; operations?: OperationsObject; components?: ComponentsObject; defaultContentType?: string; } /** * @since 3.0.0 An identifier for the described channel. The channelId value is case-sensitive. * */ type ChannelsObject = Record<string, ChannelObject>; interface ReferenceObject { $ref: string; } /** @since 3.0.0 renamed; added address, title, summary, messages, servers, tags, externalDocs; removed pubs/subs */ interface ChannelObject { /** @desc Typically the "topic name", "routing key", "event type", or "path". */ address?: string | null; title?: string; summary?: string; description?: string; messages?: MessagesObject; servers?: ReferenceObject[]; /** @desc Describes a map of parameters included in a channel name. */ parameters?: ParametersObject; tags?: TagObject[]; externalDocs?: ExternalDocumentationObject; /** @desc Map describing protocol-specific definitions for a channel. */ bindings?: Bindings<WS.Channel>; } interface ServerVariableObject { enum?: string[] | boolean[] | number[]; default: string | boolean | number; description?: string; examples?: string[]; } type SchemaObjectType = "integer" | "number" | "string" | "boolean" | "object" | "null" | "array"; /** * @desc DRAFT-07 * @link https://json-schema.org/specification-links#draft-7 * @link https://json-schema.org/draft-07/draft-handrews-json-schema-validation-01 * */ interface Draft07 { title?: string; type?: SchemaObjectType | SchemaObjectType[]; required?: string[]; multipleOf?: number; maximum?: number; exclusiveMaximum?: number; minimum?: number; exclusiveMinimum?: number; maxLength?: number; minLength?: number; pattern?: string; maxItems?: number; minItems?: number; uniqueItems?: boolean; maxProperties?: number; minProperties?: number; enum?: any[]; const?: any; examples?: any[]; readOnly?: boolean; writeOnly?: boolean; properties?: { [propertyName: string]: SchemaObject | ReferenceObject; }; patternProperties?: { [pattern: string]: SchemaObject | ReferenceObject; }; additionalProperties?: SchemaObject | ReferenceObject | boolean; additionalItems?: SchemaObject | ReferenceObject | boolean; items?: SchemaObject | ReferenceObject | [ SchemaObject | ReferenceObject, ...Array<SchemaObject | ReferenceObject> ]; propertyNames?: SchemaObject | ReferenceObject; contains?: SchemaObject; allOf?: (SchemaObject | ReferenceObject)[]; oneOf?: (SchemaObject | ReferenceObject)[]; anyOf?: (SchemaObject | ReferenceObject)[]; not?: SchemaObject | ReferenceObject; /** * @desc JSON Schema compliant Content-Type, optional when specified as a key of ContentObject * @example image/png */ contentMediaType?: string; /** * @desc Specifies the Content-Encoding for the schema, supports all encodings from RFC4648, and "quoted-printable" from RFC2045 * @override format * @see https://datatracker.ietf.org/doc/html/rfc4648 * @see https://datatracker.ietf.org/doc/html/rfc2045#section-6.7 * @example base64 */ contentEncoding?: string; } /** @link https://www.asyncapi.com/docs/reference/specification/v3.0.0#schemaObject */ interface SchemaObject extends Draft07 { description?: string; format?: "int32" | "int64" | "float" | "double" | "byte" | "binary" | "date" | "date-time" | "password" | string; default?: any; discriminator?: string; externalDocs?: ExternalDocumentationObject | ReferenceObject; deprecated?: boolean; } /** @since 3.0.0 added replies */ interface ComponentsObject { schemas?: Record<string, SchemaObject | ReferenceObject>; servers?: Record<string, ServerObject>; serverVariables?: Record<string, ServerVariableObject>; channels?: Record<string, ChannelObject>; messages?: Record<string, MessageObject>; securitySchemes?: Record<string, SecuritySchemeObject>; parameters?: Record<string, ParameterObject>; correlationIds?: Record<string, CorrelationIDObject>; operationTraits?: Record<string, OperationTraitObject>; messageTraits?: Record<string, MessageTraitObject>; replies?: Record<string, OperationReplyObject>; serverBindings?: Bindings<WS.Server>; channelBindings?: Bindings<WS.Channel>; operationBindings?: Bindings<WS.Operation>; messageBindings?: Bindings<WS.Message>; } /** @since 3.0.0 supports MultiFormatSchemaObject in payload */ interface MessageObject extends MessageTraitObject { payload?: SchemaObject | MultiFormatSchemaObject | ReferenceObject; traits?: MessageTraitObject | ReferenceObject; } /** @desc The key represents the message identifier. The messageId value is case-sensitive. */ type MessagesObject = Record<string, MessageObject | ReferenceObject>; /** @since 3.0.0 added action, channel, messages, reply */ interface OperationObject extends OperationTraitObject { action: "send" | "receive"; /** @desc A $ref pointer to the definition of the channel in which this operation is performed. */ channel: ReferenceObject; /** @desc A list of $ref pointers pointing to the supported Message Objects that can be processed by this operation */ messages?: ReferenceObject[]; reply?: OperationReplyObject | ReferenceObject; traits?: Record<string, OperationTraitObject>; } /** * @desc Describes the reply part that MAY be applied to an Operation Object. * @desc If an operation implements the request/reply pattern, the reply object represents the response message. * @since 3.0.0 new * */ interface OperationReplyObject { /** @desc Definition of the address that implementations MUST use for the reply. */ address?: OperationReplyAddressObject | ReferenceObject; /** @desc A $ref pointer to the definition of the channel in which this operation is performed. */ channel?: ReferenceObject; /** @desc A list of pointers to the supported Message Objects that can be processed by this operation as reply */ messages?: ReferenceObject[]; } /** * @desc An object that specifies where an operation has to send the reply. * @since 3.0.0 new * */ interface OperationReplyAddressObject { /** * @desc A runtime expression that specifies the location of the reply address. * @example $message.header#/replyTo * @example $message.payload#/messageId * @link https://www.asyncapi.com/docs/reference/specification/v3.0.0#runtimeExpression * */ location: string; description?: string; } /** * @desc The operation this application MUST implement. The field name (operationId) MUST be a string used to * @desc identify the operation in the document where it is defined, and its value is case-sensitive. * */ type OperationsObject = Record<string, OperationObject>; /** @since 3.0.0 operationId moved to OperationsObject; added title, security */ interface OperationTraitObject { title?: string; summary?: string; description?: string; security?: Array<SecuritySchemeObject | ReferenceObject>; tags?: TagObject[]; externalDocs?: ExternalDocumentationObject; bindings?: Bindings<WS.Operation>; } /** @since 3.0.0 messageId moved to MessagesObject, schemaFormat moved to MultiFormatSchemaObject */ interface MessageTraitObject { headers?: SchemaObject | MultiFormatSchemaObject; correlationId?: CorrelationIDObject; contentType?: string; name?: string; title?: string; summary?: string; description?: string; tags?: TagObject[]; externalDocs?: ExternalDocumentationObject; bindings?: Bindings<WS.Message>; examples?: MessageExampleObject[]; } /** * @desc Represents an example of a Message Object and MUST contain either headers and/or payload fields. * @since 3.0.0 new * */ interface MessageExampleObject { name?: string; summary?: string; headers?: Record<string, any>; payload?: any; } /** @since 3.0.0 new */ interface MultiFormatSchemaObject { /** * @desc A string containing the name of the schema format that is used to define the information. * @example application/vnd.aai.asyncapi+yaml;version=3.0.0 * @example application/schema+yaml;version=draft-07 * @example application/vnd.oai.openapi+yaml;version=3.0.0 * */ schemaFormat: string; schema: SchemaObject; } interface CorrelationIDObject { description?: string; /** * @desc A runtime expression that specifies the location of the correlation ID. * @example $message.header#/correlationId * @link https://www.asyncapi.com/docs/reference/specification/v3.0.0#runtimeExpression * */ location: string; } interface TagObject { name: string; description?: string; externalDocs?: ExternalDocumentationObject; } /** @since 3.0.0 partially extends SchemaObject; schema prop removed */ interface ParameterObject extends Pick<SchemaObject, "enum" | "default" | "description" | "examples"> { /** * @desc A runtime expression that specifies the location of the parameter value. * @link https://www.asyncapi.com/docs/reference/specification/v3.0.0#runtimeExpression * */ location?: string; } /** @desc Property pattern ^[A-Za-z0-9_\-]+$ */ type ParametersObject = Record<string, ParameterObject>; interface ExternalDocumentationObject { description?: string; url: string; } declare class AsyncApiBuilder { protected readonly document: AsyncApiObject; constructor(initial: Pick<AsyncApiObject, "info" | "id" | "defaultContentType">); addServer(name: string, server: ServerObject): this; addChannel(name: string, channel: ChannelObject): this; addOperation(name: string, operation: OperationObject): this; addSecurityScheme(name: string, schema: SecuritySchemeObject): this; addSchema(name: string, schema: SchemaObject | ReferenceObject): this; getSpec(): AsyncApiObject; getSpecAsJson(replacer?: (key: string, value: unknown) => unknown, space?: string | number): string; getSpecAsYaml(): string; } interface DocumentationParams { title: string; version: string; documentId?: string; description?: string; contact?: ContactObject; license?: LicenseObject; servers?: Record<string, { url: string; description?: string; }>; actions: AbstractAction[]; config: Config<Namespaces>; } declare class Documentation extends AsyncApiBuilder { #private; constructor({ actions, config: { namespaces, security: globalSecurity }, title, version, documentId, description, contact, license, servers, }: DocumentationParams); } /** @desc An error related to the input and output schemas declaration */ declare class IOSchemaError extends Error { name: string; } /** @desc An error of validating the incoming data */ declare class InputValidationError extends IOSchemaError { readonly cause: z.ZodError; name: string; constructor(cause: z.ZodError); } /** @desc An error of validating the outgoing data */ declare class OutputValidationError extends IOSchemaError { readonly cause: z.ZodError; name: string; constructor(cause: z.ZodError); } export { AbstractAction, type AbstractLogger, ActionsFactory, type ClientContext, Config, Documentation, type EmissionMap, InputValidationError, Integration, type LoggerOverrides, type Namespace, OutputValidationError, attachSockets, createSimpleConfig };