{"id":14978188,"url":"https://github.com/robintail/zod-sockets","last_synced_at":"2026-04-02T18:56:04.479Z","repository":{"id":221038844,"uuid":"753278748","full_name":"RobinTail/zod-sockets","owner":"RobinTail","description":"Socket.IO solution with I/O validation and the ability to generate AsyncAPI specification and a contract for consumers.","archived":false,"fork":false,"pushed_at":"2025-09-30T05:03:07.000Z","size":3292,"stargazers_count":98,"open_issues_count":1,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-10-03T20:44:44.870Z","etag":null,"topics":["async-api","asyncapi","asyncapi-specification","asyncapi-tools","nodejs","server","socket","socket-io","sockets","typescript-library","validation","websocket","websockets","zod"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/zod-sockets","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/RobinTail.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"RobinTail","buy_me_a_coffee":"robintail"}},"created_at":"2024-02-05T20:08:07.000Z","updated_at":"2025-10-01T06:06:01.000Z","dependencies_parsed_at":"2024-03-28T20:41:26.820Z","dependency_job_id":"75c65f7f-f521-4aef-bd89-472df588d832","html_url":"https://github.com/RobinTail/zod-sockets","commit_stats":{"total_commits":490,"total_committers":4,"mean_commits":122.5,"dds":0.5163265306122449,"last_synced_commit":"f0a8dbb130abed6061d6bdf7c3aeb1fc4228bfb5"},"previous_names":["robintail/zod-sockets"],"tags_count":64,"template":false,"template_full_name":null,"purl":"pkg:github/RobinTail/zod-sockets","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RobinTail%2Fzod-sockets","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RobinTail%2Fzod-sockets/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RobinTail%2Fzod-sockets/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RobinTail%2Fzod-sockets/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RobinTail","download_url":"https://codeload.github.com/RobinTail/zod-sockets/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RobinTail%2Fzod-sockets/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279000780,"owners_count":26082851,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-08T02:00:06.501Z","response_time":56,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["async-api","asyncapi","asyncapi-specification","asyncapi-tools","nodejs","server","socket","socket-io","sockets","typescript-library","validation","websocket","websockets","zod"],"created_at":"2024-09-24T13:57:01.232Z","updated_at":"2026-04-02T18:56:04.451Z","avatar_url":"https://github.com/RobinTail.png","language":"TypeScript","funding_links":["https://github.com/sponsors/RobinTail","https://buymeacoffee.com/robintail"],"categories":[],"sub_categories":[],"readme":"# Zod Sockets\n\n[![coverage](https://coveralls.io/repos/github/RobinTail/zod-sockets/badge.svg)](https://coveralls.io/github/RobinTail/zod-sockets)\n[![AsyncAPI Validation](https://github.com/RobinTail/zod-sockets/actions/workflows/async-api-validation.yml/badge.svg)](https://github.com/RobinTail/zod-sockets/actions/workflows/async-api-validation.yml)\n![NPM Downloads](https://img.shields.io/npm/dw/zod-sockets)\n![NPM License](https://img.shields.io/npm/l/zod-sockets)\n\n**Socket.IO solution with I/O validation and the ability to generate AsyncAPI specification and a contract for consumers.**\n\n# How it works\n\n[Demo Chat](https://github.com/RobinTail/chat)\n\n## Technologies\n\n- [Typescript](https://www.typescriptlang.org/) first.\n- Sockets — [Socket.IO](https://socket.io/), using [WebSocket](https://github.com/websockets/ws) for transport.\n- Schema validation — [Zod 4.x](https://github.com/colinhacks/zod).\n- Generating documentation according to [AsyncAPI 3.0](https://www.asyncapi.com) specification.\n- Generating client side types — inspired by [zod-to-ts](https://github.com/sachinraja/zod-to-ts).\n- Supports any logger having `info()`, `debug()`, `error()` and `warn()` methods.\n\n## Concept\n\nThe library distinguishes between incoming and outgoing events. The first are called Actions, and the second — Emission.\nEmission is configured first, representing the schemas for validating the outgoing data, as well as optionally received\nacknowledgements. Based on this configuration, an Actions Factory is created, where Actions are produced that have\nschemas for checking the incoming data and an optionally sent acknowledgement, and a handler. This handler is aware of\nthe Emission types and is equipped with the emission and broadcasting methods, while its returns become an\nacknowledgement for the Action. This configuration is used to validate the input and output data against the specified\nschemas, it can be exported to frontend side, thus ensuring that the established contract is followed.\n\n![Workflow Diagram](flow.svg)\n\n# Quick start\n\n## Installation\n\nInstall the package and its peer dependencies.\n\n```shell\npnpm add zod-sockets zod socket.io typescript\n```\n\n## Set up config\n\n```typescript\nimport { createSimpleConfig } from \"zod-sockets\";\n\nconst config = createSimpleConfig(); // shorthand for root namespace only\n```\n\n## Create a factory\n\n```typescript\nimport { ActionsFactory } from \"zod-sockets\";\n\nconst actionsFactory = new ActionsFactory(config);\n```\n\n## Create an action\n\n```typescript\nimport { z } from \"zod\";\n\nconst onPing = actionsFactory.build({\n  event: \"ping\",\n  input: z.tuple([]).rest(z.unknown()),\n  output: z.tuple([z.literal(\"pong\")]).rest(z.unknown()),\n  handler: async ({ input }) =\u003e [\"pong\", ...input] as const,\n});\n```\n\n## Create a server\n\n```typescript\nimport http from \"node:http\";\nimport { Server } from \"socket.io\";\nimport { attachSockets } from \"zod-sockets\";\n\nattachSockets({\n  /** @see https://socket.io/docs/v4/server-options/ */\n  io: new Server(),\n  config: config,\n  actions: [onPing],\n  target: http.createServer().listen(8090),\n});\n```\n\n## Try it\n\nStart the application and execute the following [command](https://socket.io/docs/v4/troubleshooting-connection-issues/):\n\n```shell\ncurl \"http://localhost:8090/socket.io/?EIO=4\u0026transport=polling\"\n```\n\nThe expected response should be similar to:\n\n```json\n{\n  \"sid\": \"***\",\n  \"upgrades\": [\"websocket\"],\n  \"pingInterval\": 25000,\n  \"pingTimeout\": 20000,\n  \"maxPayload\": 1000000\n}\n```\n\nThen consider using [Postman](https://learning.postman.com/docs/sending-requests/websocket/create-a-socketio-request/)\nfor sending the `ping` event to `ws://localhost:8090` with acknowledgement.\n\n# Basic features\n\n## Emission\n\nThe outgoing events should be configured using `z.tuple()` schemas. Those tuples describe the types of the arguments\nsupplied to the `Socket::emit()` method, excluding an optional acknowledgment, which has its own optional schema being\na callback function having ordered arguments. The schemas may also have transformations. This declaration establishes\nthe constraints on your further implementation as well as payload validation and helps to avoid mistakes during the\ndevelopment. Consider the following examples of two outgoing events, with and without acknowledgment:\n\n```typescript\nimport { z } from \"zod\";\nimport { createSimpleConfig } from \"zod-sockets\";\n\nconst config = createSimpleConfig({\n  emission: {\n    // enabling Socket::emit(\"chat\", \"message\", { from: \"someone\" })\n    chat: {\n      schema: z.tuple([z.string(), z.object({ from: z.string() })]),\n    },\n    // enabling Socket::emit(\"secret\", \"message\", ([readAt]: [Date]) =\u003e {})\n    secret: {\n      schema: z.tuple([z.string()]),\n      ack: z.tuple([z.iso.datetime().transform((str) =\u003e new Date(str))]),\n    },\n  },\n});\n```\n\n## Server compatibility\n\n### With HTTP(S)\n\nYou can attach the sockets server to any HTTP or HTTPS server created using native Node.js methods.\n\n```typescript\nimport { createServer } from \"node:http\"; // or node:https\nimport { attachSockets } from \"zod-sockets\";\n\nattachSockets({ target: createServer().listen(port) });\n```\n\n### With Express\n\nFor using with Express.js, supply the `app` as an argument of the `createServer()` (avoid ~~`app.listen()`~~).\n\n```typescript\nimport express from \"express\";\nimport { createServer } from \"node:http\"; // or node:https\nimport { attachSockets } from \"zod-sockets\";\n\nconst app = express();\nattachSockets({ target: createServer(app).listen(port) });\n```\n\n### With Express Zod API\n\nFor using with `express-zod-api`, take the `httpServer` or `httpsServer` returned by the `createServer()` method and\nassign it to the `target` property.\n\n```typescript\nimport { createServer } from \"express-zod-api\";\nimport { attachSockets } from \"zod-sockets\";\n\nconst { servers } = await createServer();\nattachSockets({ target: servers.pop()! });\n```\n\n## Logger compatibility\n\n### Customizing logger\n\nThe library supports any logger having `info()`, `debug()`, `error()` and\n`warn()` methods. For example, `pino` logger with `pino-pretty` extension:\n\n```typescript\nimport pino, { Logger } from \"pino\";\nimport { attachSockets } from \"zod-sockets\";\n\nconst logger = pino({\n  transport: {\n    target: \"pino-pretty\",\n    options: { colorize: true },\n  },\n});\nattachSockets({ logger });\n\n// Setting the type of logger used\ndeclare module \"zod-sockets\" {\n  interface LoggerOverrides extends Logger {}\n}\n```\n\n### With Express Zod API\n\nIf you're using `express-zod-api`, you can reuse the same logger from the returns of the `createServer()` method.\n\n```typescript\nimport { createServer, BuiltinLogger } from \"express-zod-api\";\nimport { attachSockets } from \"zod-sockets\";\n\nconst { logger } = await createServer();\nattachSockets({ logger });\n\n// Setting the type of logger used\ndeclare module \"zod-sockets\" {\n  interface LoggerOverrides extends BuiltinLogger {}\n}\n```\n\n## Receiving events\n\n### Making actions\n\nActions (the declarations of incoming events) are produced on an `ActionFactory` which is an entity made aware of the\nEmission types (possible outgoing events) by supplying the config to its constructor. This architecture enables you to\nkeep the produced Actions in separate self-descriptive files.\n\n```typescript\nimport { ActionsFactory } from \"zod-sockets\";\n\nconst actionsFactory = new ActionsFactory(config);\n```\n\nProduce actions using the `build()` method accepting an object having the incoming `event` name, the `input` schema for\nits payload (excluding acknowledgment) and a `handler`, which is a function where you place your implementation for\nhandling the event. The argument of the `handler` in an object having several handy entities, the most important of\nthem is `input` property, being the validated event payload:\n\n```typescript\nconst onChat = actionsFactory.build({\n  // ns: \"/\", // optional, root namespace is default\n  event: \"chat\",\n  input: z.tuple([z.string()]),\n  handler: async ({ input: [message], client, all, withRooms, logger }) =\u003e {\n    /* your implementation here */\n    // typeof message === \"string\"\n  },\n});\n```\n\n### Acknowledgements\n\nActions may also have acknowledgements that are basically direct and immediate responses to the one that sent the\nincoming event. Acknowledgement is acquired from the returns of the `handler` and being validated against additionally\nspecified `output` schema. When the number of payload arguments is flexible, you can use `rest()` method of\n`z.tuple()`. When the data type is not important at all, consider describing it using `z.unknown()`. When using\n`z.literal()`, Typescript may assume the type of the actually returned value more loose, therefore the `as const`\nexpression might be required. The following example illustrates an action acknowledging \"ping\" event with \"pong\" and\nan echo of the received payload:\n\n```typescript\nconst onPing = actionsFactory.build({\n  event: \"ping\",\n  input: z.tuple([]).rest(z.unknown()),\n  output: z.tuple([z.literal(\"pong\")]).rest(z.unknown()),\n  handler: async ({ input }) =\u003e [\"pong\" as const, ...input],\n});\n```\n\n## Dispatching events\n\n### In Action context\n\nThe Emission awareness of the `ActionsFactory` enables you to emit and broadcast other events due to receiving the\nincoming event. Depending on your application's needs and architecture, you can choose different ways to send events.\nThe emission methods have constraints on emission types declared in the configuration. The `input` is available for\nprocessing the validated payload of the Action.\n\n```typescript\nactionsFactory.build({\n  handler: async ({ input, client, withRooms, all }) =\u003e {\n    // sending to the sender of the received event:\n    await client.emit(\"event\", ...payload);\n    // sending to everyone except the client:\n    await client.broadcast(\"event\", ...payload);\n    // sending to everyone except the client in a room:\n    withRooms(\"room1\").broadcast(\"event\", ...payload);\n    // sending to everyone except the client within several rooms:\n    withRooms([\"room1\", \"room2\"]).broadcast(\"event\", ...payload);\n    // sending to everyone everywhere including the client:\n    all.broadcast(\"event\", ...payload);\n  },\n});\n```\n\n### In Client context\n\nThe previous example illustrated the events dispatching due to or in a context of an incoming event. But you can also\nemit events regardless the incoming ones by setting the `onConnection` property within `hooks` of the config, which\nhas a similar interface except `input` and fires for every connected client:\n\n```typescript\nimport { createSimpleConfig } from \"zod-sockets\";\n\nconst config = createSimpleConfig({\n  // emission: { ... },\n  hooks: {\n    onConnection: async ({ client, withRooms, all }) =\u003e {\n      /* your implementation here */\n    },\n  },\n});\n```\n\n### Independent context\n\nMoreover, you can emit events regardless the client activity at all by setting the `onStartup` property within `hooks`\nof the config. The implementation may have a `setInterval()` for recurring emission.\n\n```typescript\nimport { createSimpleConfig } from \"zod-sockets\";\n\nconst config = createSimpleConfig({\n  hooks: {\n    onStartup: async ({ all, withRooms }) =\u003e {\n      // sending to everyone in a room:\n      withRooms(\"room1\").broadcast(\"event\", ...payload);\n      // sending to everyone within several rooms:\n      withRooms([\"room1\", \"room2\"]).broadcast(\"event\", ...payload);\n      // sending to everyone everywhere:\n      all.broadcast(\"event\", ...payload);\n      // sending to some particular user by familiar id:\n      (await all.getClients())\n        .find(({ id }) =\u003e id === \"someId\")\n        ?.emit(\"event\", ...payload);\n    },\n  },\n});\n```\n\n## Handling errors\n\n### Error context\n\nYou can configure the `onError` hook for handling errors of various natures.\nThe library currently provides two classes of proprietary errors:\n`InputValidationError` and `OutputValidationError` (for Action acknowledgments).\nThe hook is intended to be generic, so some of its arguments are optional.\nThe following example shows how to emit an outgoing `error` event when the\nincoming event data is invalid.\n\n```typescript\nimport { createSimpleConfig, InputValidationError } from \"zod-sockets\";\n\nconst config = createSimpleConfig({\n  emission: {\n    error: {\n      schema: z.tuple([\n        z.string().describe(\"name\"),\n        z.string().describe(\"message\"),\n      ]),\n    },\n  },\n  hooks: {\n    onError: async ({ error, event, payload, client, logger }) =\u003e {\n      logger.error(event ? `${event} handling error` : \"Error\", error);\n      if (error instanceof InputValidationError \u0026\u0026 client) {\n        try {\n          await client.emit(\"error\", error.name, error.message);\n        } catch {} // no errors inside this hook\n      }\n    },\n  },\n});\n```\n\n### Emission errors\n\nEvery usage of `.emit()` and `.broadcast()` methods can potentially throw\na `ZodError` on validation or an `Error` on timeout. Those errors are not\nhandled by the library yet, not wrapped and not delegated to the `onError` hook,\nso they have to be handled in place using `try..catch` approach.\n\n## Rooms\n\n### Available rooms\n\nRooms are the server side concept. Initially, each newly connected Client is located within a room having the same\nidentifier as the Client itself. The list of available rooms is accessible via the `getRooms()` method of the `all`\nhandler's argument.\n\n```typescript\nconst handler = async ({ all, logger }) =\u003e {\n  logger.debug(\"All rooms\", all.getRooms());\n};\n```\n\n### Distribution\n\nThe `client` argument of a handler (of a Client or an Action context) provides methods `join()` and `leave()` in order\nto distribute the clients to rooms. Those methods accept a single or multiple room identifiers and _may_ be async\ndepending on adapter, therefore consider calling them with `await` anyway.\n\n```typescript\nconst handler = async ({ client }) =\u003e {\n  await client.leave([\"room2\", \"room3\"]);\n  await client.join(\"room1\");\n};\n```\n\n### Who is where\n\nRegardless the context, each handler has `withRooms()` argument accepting a single or multiple rooms identifiers. The\nmethod returns an object providing the `getClients()` async method, returning an array of clients within those rooms.\nThose clients are also equipped with distribution methods `join()` and `leave()`.\n\n```typescript\nconst handler = async ({ withRooms }) =\u003e {\n  await withRooms(\"room1\").getClients();\n  await withRooms([\"room1\", \"room2\"]).getClients();\n};\n```\n\nAlternatively, you can request `getClients()` method of the `all` argument, which returns an array of all familiar\nclients having `rooms` property, being an array of the room identifiers that client is located.\n\n```typescript\nconst handler = async ({ all, logger }) =\u003e {\n  const clients = await all.getClients();\n  for (const client of clients) {\n    logger.debug(`${client.id} is within`, client.rooms);\n  }\n};\n```\n\n### Subscriptions\n\nIn order to implement a subscription service you can utilize the rooms feature and make two Actions: for\n[subscribing](example/actions/subscribe.ts) and [unsubscribing](example/actions/unsubscribe.ts). Handlers of those\nActions can simply do `client.join()` and `client.leave()` in order to address the client to/from a certain room. A\nsimple `setInterval()` function within an [Independent Context](#independent-context) (`onStartup` hook) can broadcast\nto those who are in that room. Here is a simplified example:\n\n```ts\nimport { createServer } from \"express-zod-api\";\nimport { attachSockets, createSimpleConfig, ActionsFactory } from \"zod-sockets\";\nimport { Server } from \"socket.io\";\nimport { z } from \"zod\";\n\nconst { logger, servers } = await createServer();\n\nconst config = createSimpleConfig({\n  emission: {\n    time: {\n      schema: z.tuple([\n        z\n          .date() // constraints\n          .transform((date) =\u003e date.toISOString())\n          .pipe(z.iso.datetime()),\n      ]),\n    },\n  },\n  hooks: {\n    onStartup: async ({ withRooms }) =\u003e {\n      setInterval(() =\u003e {\n        withRooms(\"subscribers\").broadcast(\"time\", new Date());\n      }, 1000);\n    },\n  },\n});\n\nconst factory = new ActionsFactory(config);\nawait attachSockets({\n  config,\n  logger,\n  io: new Server(),\n  target: servers.pop()!,\n  actions: [\n    factory.build({\n      event: \"subscribe\",\n      input: z.tuple([]),\n      handler: async ({ client }) =\u003e client.join(\"subscribers\"),\n    }),\n    factory.build({\n      event: \"unsubscribe\",\n      input: z.tuple([]),\n      handler: async ({ client }) =\u003e client.leave(\"subscribers\"),\n    }),\n  ],\n});\n```\n\n## Metadata\n\nMetadata is a custom object-based structure for reading and storing additional information about the clients.\nInitially it is an empty object.\n\n### Defining constraints\n\nYou can specify the schema of the `metadata` in config.\nPlease avoid transformations in those schemas since they are not going to be applied.\n\n```typescript\nimport { z } from \"zod\";\nimport { createSimpleConfig } from \"zod-sockets\";\n\nconst config = createSimpleConfig({\n  metadata: z.object({\n    /** @desc Number of messages sent to the chat */\n    msgCount: z.number().int(),\n  }),\n});\n```\n\n### Reading\n\nIn every context you can read the client's metadata using the `getData()` method.\nSince the presence of the data is not guaranteed, the method returns an `Partial\u003c\u003e` object of the specified schema.\n\n```typescript\nconst handler = async ({ client, withRooms }) =\u003e {\n  client.getData();\n  withRooms(\"room1\")\n    .getClients()\n    .map((someone) =\u003e someone.getData());\n};\n```\n\n### Writing\n\nWithin a client context you can use `setData()` method to store the metadata on the client. The method provides type\nassistance of its argument and may throw `ZodError` if it does not pass the validation against the specified schema.\n\n```typescript\nconst handler = async ({ client }) =\u003e {\n  client.setData({ msgCount: 4 });\n};\n```\n\n## Namespaces\n\nNamespaces allow you to separate incoming and outgoing events into groups, in which events can have the same name, but\ndifferent essence, payload and handlers. For using namespaces replace the `createSimpleConfig()` method with\n`new Config()`, then use its `.addNamespace()` method for each namespace. Namespaces may have `emission`, `hooks`\nand `metadata`. Read the Socket.IO [documentation on namespaces](https://socket.io/docs/v4/namespaces/).\n\n```typescript\nimport { Config } from \"zod-sockets\";\n\nconst config = new Config()\n  .addNamespace({\n    // The namespace \"/public\"\n    emission: { chat: { schema } },\n    hooks: {\n      onStartup: () =\u003e {},\n      onConnection: () =\u003e {},\n      onDisconnect: () =\u003e {},\n      onAnyIncoming: () =\u003e {},\n      onAnyOutgoing: () =\u003e {},\n      onError: () =\u003e {},\n    },\n    metadata: z.object({ msgCount: z.number().int() }),\n  })\n  .addNamespace({\n    path: \"private\", // The namespace \"/private\" has no emission\n  });\n```\n\n# Integration\n\n## Exporting types for frontend\n\nIn order to establish constraints for events on the client side you can generate their Typescript definitions.\n\n```typescript\nimport { Integration } from \"zod-sockets\";\n\nconst integration = new Integration({ config, actions });\nconst typescriptCode = integration.print(); // write this to a file\n```\n\nCheck out [the generated example](example/example-client.ts).\n\nYou can adjust the naming of the produced functional arguments by applying the `.describe()` method to the schemas.\n\nThere is also a special handling for the cases when event has both `.rest()` on the payload schema and an\nacknowledgement, resulting in producing overloads, because acknowledgement has to go after `...rest` which is\nprohibited. You can adjust the number of the those overloads by using the `maxOverloads` option of the `Integration`\nconstructor. The default is `3`.\n\nThen on the frontend side you can create a strictly typed Socket.IO client.\n\n```typescript\nimport { io } from \"socket.io-client\";\nimport { Root } from \"./generated/backend-types.ts\"; // the generated file\n\nconst socket: Root.Socket = io(Root.path);\n```\n\nAlternatively, you can avoid installing and importing `socket.io-client` module by making a\n[standalone build](https://socket.io/docs/v4/client-installation/#standalone-build) having\n[`serveClient` option](https://socket.io/docs/v4/server-options/#serveclient) configured on the server.\n\n## Generating documentation\n\nYou can generate the AsyncAPI specification of your API and write it into a file, that can be used as the documentation:\n\n```typescript\nimport { Documentation } from \"zod-sockets\";\n\nconst yamlString = new Documentation({\n  config,\n  actions,\n  version: \"1.2.3\",\n  title: \"Example APP\",\n  servers: { example: { url: \"https://example.com/socket.io\" } },\n}).getSpecAsYaml();\n```\n\nSee the example of the generated documentation [on GitHub](example/example-documentation.yaml) or\n[open in Studio](https://studio.asyncapi.com/?url=https://raw.githubusercontent.com/RobinTail/zod-sockets/main/example/example-documentation.yaml).\n\n### Adding examples to the documentation\n\nYou can add examples to a schema using its `.meta()` method:\n\n```ts\nimport { createSimpleConfig, ActionsFactory } from \"zod-sockets\";\n\n// Examples for outgoing events (emission)\nconst config = createSimpleConfig({\n  emission: {\n    event1: { schema: schema.meta({ examples: [\"example payload\"] }) },\n    event2: {\n      schema,\n      ack: ack.meta({ examples: [\"example acknowledgement\"] }),\n    },\n  },\n});\n\n// Examples for incoming event (action)\nconst factory = new ActionsFactory(config);\nconst action = factory.build({\n  input: payloadSchema.meta({ examples: [\"example payload\"] }),\n  output: ackSchema.meta({ examples: [\"example acknowledgement\"] }),\n});\n```\n\n### Adding security schemas to the documentation\n\nYou can describe the security schemas for the generated documentation both on\nserver and namespace levels.\n\n```ts\n// Single namespace\nimport { createSimpleConfig } from \"zod-sockets\";\n\nconst config = createSimpleConfig({\n  security: [\n    {\n      type: \"httpApiKey\",\n      description: \"Server security schema\",\n      in: \"header\",\n      name: \"X-Api-Key\",\n    },\n  ],\n});\n```\n\n```ts\n// Multiple namespaces\nimport { Config } from \"zod-sockets\";\n\nconst config = new Config({\n  security: [\n    {\n      type: \"httpApiKey\",\n      description: \"Server security schema\",\n      in: \"header\",\n      name: \"X-Api-Key\",\n    },\n  ],\n}).addNamespace({\n  security: [\n    {\n      type: \"userPassword\",\n      description: \"Namespace security schema\",\n    },\n  ],\n});\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frobintail%2Fzod-sockets","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frobintail%2Fzod-sockets","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frobintail%2Fzod-sockets/lists"}