UNPKG

zod-sockets

Version:

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

567 lines (428 loc) 18.3 kB
# Changelog ## Version 4 ### v4.1.2 - Fixed the missing `code` property in the custom function schema validator. ### v4.1.1 - Updating documentation to reflect the new way to specify examples. ### v4.1.0 - Supporting `zod@^4.0.5`: - Imports may be changed from `zod/v4` to just `zod`; ### v4.0.1 - Compatibility adjustment for Zod 3.25.75. ### v4.0.0 - Switched to Zod 4: - Minimum supported version: 3.25.56; - All imports must be changed to "zod/v4"; - Read the [Explanation of the versioning strategy](https://github.com/colinhacks/zod/issues/4371); - Changes to `Documentation`: - Generating Documentation is mostly delegated to Zod 4 `z.toJSONSchema()`; - Zod Sockets implements some overrides and improvements to fit it into AsyncAPI 3.0.0 that extends JSON Schema; - Feat: supporting circular/recursive schemas: https://zod.dev/api#recursive-objects; - Changes to `Integration`: - The `optionalPropStyle` option removed from `Integration` class constructor: - Use `.optional()` to add question mark to the object property as well as `undefined` to its type; - Use `.or(z.undefined())` to add `undefined` to the type of the object property; - See the [reasoning](https://x.com/colinhacks/status/1919292504861491252); - Properties assigned with `z.any()` or `z.unknown()` schema are now typed as required: - Read the [details here](https://v4.zod.dev/v4/changelog#changes-zunknown-optionality); - Added types generation for `z.never()`, `z.void()` and `z.unknown()` schemas; - The fallback type for unsupported schemas and unclear transformations in response changed from `any` to `unknown`; - Supporting `z.templateLiteral()` and `z.nonoptional()` schemas; - Method `Actions::example()` removed — use the `.meta({ examples })` method of its schema; - Property `examples` removed from the argument of `createSimpleConfig()` and `Config::addNamespace()`: - use the `.meta({ examples })` method of the corresponding schema; - The property `originalError` renamed to `cause` for `InputValidationError` and `OutputValidationError`. ```diff - import { z } from "zod"; + import { z } from "zod/v4"; ``` ```diff createSimpleConfig({ emission: { event: { - schema: z.tuple([z.string()]), + schema: z.tuple([z.string().meta({ examples: ["test"] })]), }, }, - examples: { - event: { - payload: ["test"], - }, - }, }); ``` ```diff const action = actionsFactory - .build({ input: z.tuple([z.string()]) }) + .build({ input: z.tuple([z.string().meta({ examples: ["test"] })]) }) - .example("input", ["test"]); ``` ## Version 3 ### v3.0.0 - Drop support for Node 18 (end of life); - The deprecated `serializer` property on the `Integration` constructor argument removed. ## Version 2 ### v2.3.0 - Supporting Node 24. ### v2.2.0 - Naming of circular types is now numeric: - Deprecated `serializer` property on the `Integration` constructor argument (no longer used). ```diff - type Type2048581c137c5b2130eb860e3ae37da196dfc25b = { + type Type1 = { title: string; - features: Type2048581c137c5b2130eb860e3ae37da196dfc25b; + features: Type1; }[]; ``` ### v2.1.1 - Documentation update on compatibility with Express Zod API v21; - Tested compatibility with Express v5; - Removed redundant `event` argument for `Action::execute()`. ### v2.1.0 - Featuring `onError` hook for handling errors of various natures: - The hook is intended to be generic, so some of its arguments are optional; - Exposing error classes: `InputValidationError` and `OutputValidationError` (for Action acknowledgments); - The following example shows how to emit an outgoing `error` event when the incoming event data is invalid: ```typescript import { createSimpleConfig, InputValidationError } from "zod-sockets"; const config = createSimpleConfig({ emission: { error: { schema: z.tuple([ z.string().describe("name"), z.string().describe("message"), ]), }, }, hooks: { onError: async ({ error, event, payload, client, logger }) => { logger.error(event ? `${event} handling error` : "Error", error); if (error instanceof InputValidationError && client) { try { await client.emit("error", error.name, error.message); } catch {} // no errors inside this hook } }, }, }); ``` ### v2.0.1 - Technical update due to improved builder configuration: - Fixed missing `node:` protocol in the imports of core modules in the distributed javascript files. ### v2.0.0 - **Breaking changes**: - Minimum supported versions: - For Node.js: 18.18.0, 20.9.0, 22.0.0; - For `zod`: 3.23.0. ## Version 1 ### v1.2.0 - Ability to describe security schemas on the server and namespace level: - These security schemas go directly to the generated documentation. ```ts // Single namespace import { createSimpleConfig } from "zod-sockets"; const config = createSimpleConfig({ security: [ { type: "httpApiKey", description: "Server security schema", in: "header", name: "X-Api-Key", }, ], }); ``` ```ts // Multiple namespaces import { Config } from "zod-sockets"; const config = new Config({ security: [ { type: "httpApiKey", description: "Server security schema", in: "header", name: "X-Api-Key", }, ], }).addNamespace({ security: [ { type: "userPassword", description: "Namespace security schema", }, ], }); ``` ### v1.1.0 - Supporting Node 22. ### v1.0.0 - First production-ready release having stable public API. ## Version 0 ### v0.20.0 - Moved `logger` from configuration to `attachRouting()`: - This simplifies reusing logger instance when running along with `express-zod-api`. ```ts // before: import { createSimpleConfig, Config } from "zod-sockets"; createSimpleConfig({ logger }); // or new Config({ logger }); // after: import { attachSockets } from "zod-sockets"; attachSockets({ config }); ``` ### v0.19.0 - Added `client.getRequest()` method (proxy for `Socket::request`). ### v0.18.0 - Fixed possibly invalid values of `type` property when depicting `z.literal()`, `z.enum()` and `z.nativeEnum()`; - Added depicting of `z.tuple().rest()` when used in a nested level of the schemas; - Upgraded all dependencies; - Consistent typing of the `Namespace` properties; - Client distribution methods `join()` and `leave()` made async (always return `Promise<void>`). ### v0.17.0 - Added `handshake` property to the client objects: - See the Socket.IO documentation on handshake: https://socket.io/docs/v4/server-socket-instance/#sockethandshake ### v0.16.0 - Added `.emit()` method to the clients returned by `getClients()` method of `all` or `withRoom()`; - Improved types for `getData()` in the for the clients returned by `getClients()` method; - Better example of a subscription service using rooms. ```ts // sending to someone knowing their id: (await all.getClients()) .find(({ id }) => id === "someId") ?.emit("event", ...payload); ``` ### v0.15.1 - Changed runtime dependency: replaced `chalk` with `ansis`. ### v0.15.0 - Major improvement to the generated documentation: depicting payloads as actual tuples they are. ### v0.14.2 - Detaching from OpenAPI: - Reducing dependencies; - AsyncAPI 3.0.0 stricter compliance; - Extending from JSON Schema Draft-07 with several proprietary features of AsyncAPI standard. - Several adjustments made in this regard: - `discriminator` field changed to `string`; - using `const` field for `z.literal()`. ### v0.14.1 - Fixed broken publishing workflow (broken release). - The following versions and deprecated: 0.14.0, 0.13.1, 0.13.0, 0.12.0, 0.11.3, 0.11.2. ### v0.14.0 - Featuring examples in the generated documentation: - Describe `Action` examples using its `.example()` method; - Describe Emission examples using `examples` property in namespace config. ```ts import { createSimpleConfig, ActionsFactory } from "zod-sockets"; // Examples for outgoing events (emission) const config = createSimpleConfig({ emission: { event1: { schema }, event2: { schema, ack }, }, examples: { event1: { schema: ["example payload"] }, // single example event2: [ // multiple examples { schema: ["example payload"], ack: ["example acknowledgement"] }, { schema: ["example payload"], ack: ["example acknowledgement"] }, ], }, }); // Examples for incoming event (action) const factory = new ActionsFactory(config); const action = factory .build({ input: payloadSchema, output: ackSchema, }) .example("input", ["example payload"]) .example("output", ["example acknowledgement"]); ``` ### v0.13.1 - Minor adjustments to the documentation. ### v0.13.0 - Config creation changes aim to improve the clarity and make it easier to begin using this library for the first time; - Easier config for a simple applications: - Replacing `createConfig()` with `createSimpleConfig()` - for a single namespace (root namespace only). - Making namespaces opt-in feature: - Use the exposed `new Config()` and its `.addNamespace()` method of each namespace; - Fallbacks removed from `Config::constructor` — it creates no namespaces by default, but `addNamespace` creates root namespace when `path` prop is omitted; - See the migration advice below. ```ts // if using the root namespace only: import { createSimpleConfig } from "zod-sockets"; const simpleConfig = createSimpleConfig({ /* logger, timeout, emission, hooks, metadata */ }); // if using namespaces other than "/": import { Config } from "zod-sockets"; const config = new Config({ logger, timeout }) .addNamespace({ path: "ns1", /* emission, hooks, metadata */ }) .addNamespace({ path: "ns2", /* emission, hooks, metadata */ }); ``` ### v0.12.0 - Switching to AsyncAPI version 3.0.0 for generating documentation: - Channel identifiers are human-readable again thanks to the dedicated `address` property; - Server URL is deconstructed into `protocol`, `host` and `pathname`; - The featured `operations` are detached from `channels`; - Custom protocols are no longer supported, therefore changing `socket.io` to `ws`, channel bindings remain; - For the Socket.IO acknowledgements using the featured `reply` schema instead of the message bindings; - In this regard, new composition implies a dedicated operation per message; - Several other adjustments according to [Release notes](https://www.asyncapi.com/blog/release-notes-3.0.0). ### v0.11.3 - Fixed the server `protocol` in the generated documentation (taking from the supplied server URL); - Meanwhile, the server `url` in the generated documentation has no protocol prefix now; - Reverted channel identifiers to actual namespaces in the generated documentation (according to AsyncAPI spec). ### v0.11.2 - Increasing AsyncAPI version to 2.6.0 in the generated documentation; - Human-readable identifiers for channels, operations and messages in the generated documentation. ### v0.11.1 - Fix: marked tuple items as required. ### v0.11.0 - Featuring `Documentation` class: - Ability to generate the documentation of your Socket.IO-based application according to AsyncAPI standard; - Using a custom protocol `socket.io` that extends WebSockets bindings for describing acknowledgements and handshake; - Compliance with AsyncAPI version 2.5.0 so far (will be increased later); - Following features are not supported yet: - Examples, - `z.lazy()` and handling of circular references, - References and component-based composition of the document, - Informative errors. - Since AsyncAPI does not yet support `prefixItems` feature for describing tuples, those are depicted as objects having numeric properties. I found it acceptable at the moment because [Arrays are Objects](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array); - See the example of the generated documentation [here](example/example-documentation.yaml). ```typescript import { Documentation } from "zod-sockets"; const yamlString = new Documentation({ config, actions, version: "1.2.3", title: "Example APP", servers: { example: { url: "https://example.com/socket.io" } }, }).getSpecAsYaml(); ``` ### v0.10.0 - Important changes to configuration: - The `createConfig()` method now returns an instance of `Config` class providing `addNamespace()` method; - The `addNamespace()` method becomes a primary approach for the namespace-first configuration; - By default, `createConfig()` creates an empty root namespace (having `/` path); - Namespaces consist of optional `emission`, `hooks` and `metadata`; - Therefore, the declaration of namespaces is moved from being under `emission` to the top level; - Hooks are moved from the argument of `attachSockets()` into the one of `addNamespace()`. - Metadata is now a schema-based property of namespace: - No need to declare its interface; - Instead, `metadata` property of namespace should be assigned with an object-based schema; - The default schema for metadata is `z.object({}).strip()` — an empty object; - Methods `getData()` and `setData()` of the client context no longer require a type argument; - The `setData()` method performs validation and can throw `ZodError`; - Transformations are not allowed in the schema of metadata. ```ts import { createConfig } from "zod-sockets"; const before = createConfig({ emission: { // The namespace "/public" public: {}, // The namespace "/private" private: {}, }, }); const after = createConfig() // this makes root namespace "/" .addNamespace({ path: "public" }) .addNamespace({ path: "private" }); ``` ### v0.9.1 - Ensuring that the namespace in the generated client is named the same as it's declared on backend; - Using `chalk` v5 in runtime. ### v0.9.0 - New peer dependency required: `typescript`. - Featuring `Integration` class: - It provides an ability to export the event definitions into a Typescript file for using on the frontend side; - For better naming of the functional arguments consider using `.describe()` method of the schemas; - There is also a special handling for the cases when event has both `.rest()` on the payload and an acknowledgement; - See the [example of the generated code](example/example-client.ts). ```typescript import { Integration } from "zod-sockets"; new Integration({ config, actions }).print(); // typescript code ``` ### v0.8.1 - Minor adjustments and cleanup. ### v0.8.0 - Introducing namespaces feature. - See the [Namespaces documentation](https://socket.io/docs/v4/namespaces/). - The default namespace is a root namespace `/`. - The namespaces can be declared within `emission` property of the `createConfig()` argument. - The `ActionsFactory::build()` method now accepts optional property `ns`. - The `hooks` property of the `attachSockets()` method now accepts handlers for each namespace (optional). - Breaking changes: - `ActionMap` type removed; - Instead, the `ActionsFactory::build()` method now requires the `event` property of its argument; - Meanwhile, `actions` supplied to `attachSockets()` method now has to be an array of the produced actions. - The following properties of the `attachSockets()` argument must now be wrapped into `hooks`: - `onConnection()`, `onDisconnect()`, `onAnyIncoming()`, `onAnyOutgoing()`, `onStartup()`. ### v0.7.0 - `onAny()` property of `attachSockets()` argument renamed to `onAnyIncoming()`, having `event` and `payload` arguments; - Introducing `onAnyOutgoing()`, having the same interface; - Startup logo added; - Some more refactoring. ### v0.6.2 - Upgrading dependencies and improving the documentation. ### v0.6.1 - Adding `join()` and `leave()` methods to `RemoteClient` (the ones returned by `getClients()`). ### v0.6.0 - Restoring the `all` argument of the Action handler (removed in v0.4.0), but now it works as expected, by providing: - `getRooms()` — all available rooms, - `getClients()` — all familiar clients, - `broadcast()` — sends an event to everyone. - Describing the basic features in the documentation (Readme). ### v0.5.0 - Introducing `onStartup` option for `attachSockets()` method: - Ability to interact with rooms regardless of incoming events. - `join()` and `leave()` methods are moved from `withRooms()` to `client`. - `attachSockets()` became async. ### v0.4.0 - Reverted some changed made in v0.3.0: removed `all` argument from the Action handler: - The nested `broadcast` method moved to `client` argument, - The nested `getRooms()` renamed to `getAllRooms()`, - The nested `getClients()` renamed to `getAllClients()`. ### v0.3.2 - Ability to interact with the client's metadata: `getData<T>()` and `setData<T>()` methods. ### v0.3.1 - Using `io.of("/")` for both `all.getRooms()` and `all.getClients()`. ### v0.3.0 - New argument for the Action handler: `all` having methods: - `broadcast()` (moved); - `getRooms()` — returns all the available rooms; - `getClients()` — returns all the familiar clients. - The argument `emit()` of the Action handler moved into `client` one. - The argument `withRooms()` of the Action handler now also provides the `getClients()` method (clients in the rooms). ### v0.2.3 - Adding `getRooms()` and `withRooms()` providing `join()`, `leave()` and `broadcast()` methods to the Action handler. ### v0.2.2 - Adjusting documentation. ### v0.2.1 - Concept description and a workflow diagram. ### v0.2.0 - Moved `logger` from `attachSockets()` to `createConfig()` argument. ### v0.1.0 - Fixed module exports. - Unit and integration tests. - `createSocketsConfig()` renamed to `createConfig()`. ### v0.0.3 - Ensure emitting the declared events only. - Generic implementation for `emit()` and `broadcast()`. ### v0.0.2 - Added broadcasting feature. - Delegated emission error handling to user. ### v0.0.1 - First draft of the idea originally implemented as a feature for `express-zod-api`. - Capable to handle incoming events handling payloads validated by `zod` schemas and acknowledge them. - Can emit events having validated payloads and receive acknowledgements.