- Supporting TypeScript 6:
- Supported
typescript versions: ^5.3.3 || ^6.0.2
- The
onDisconnect hook is now equipped with reason property on its argument:
- The default hook logs that reason;
- This feature was suggested and implemented by @Miodec.
- Fixed
withRooms().getClients() to return remote clients correctly in action handlers:
- The method used to return an empty array due to wrong configuration;
- The issue was found, reported and fixed by @simwai.
- Supported Node.js version:
^20.19.0 || ^22.12.0 || ^24.0.0:
- Pure ESM distribution: all these versions support
require(ESM) syntax;
- Supported Zod version:
^4.1.0:
- Imports may be changed from
zod/v4 to just zod;
- Dropped support of examples that are given as
example property of .meta() argument;
- Dropped support of examples given within an object-based value of
examples property of .meta() argument;
- Dropped support for depicting
z.date() without transformations:
- Use either
z.iso.datetime().transform((str) => new Date(str)) for input schema,
- Or
z.date().transform((date) => date.toISOString()).pipe(z.iso.datetime()) for output schema.
- Fixed the missing
code property in the custom function schema validator.
- Updating documentation to reflect the new way to specify examples.
- Supporting
zod@^4.0.5:
- Imports may be changed from
zod/v4 to just zod;
- Compatibility adjustment for Zod 3.25.75.
- Switched to Zod 4:
- 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;
- Properties assigned with
z.any() or z.unknown() schema are now typed as required:
- 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.
- import { z } from "zod";
+ import { z } from "zod/v4";
createSimpleConfig({
emission: {
event: {
- schema: z.tuple([z.string()]),
+ schema: z.tuple([z.string().meta({ examples: ["test"] })]),
},
},
- examples: {
- event: {
- payload: ["test"],
- },
- },
});
const action = actionsFactory
- .build({ input: z.tuple([z.string()]) })
+ .build({ input: z.tuple([z.string().meta({ examples: ["test"] })]) })
- .example("input", ["test"]);
- Drop support for Node 18 (end of life);
- The deprecated
serializer property on the Integration constructor argument removed.
- Naming of circular types is now numeric:
- Deprecated
serializer property on the Integration constructor argument (no longer used).
- type Type2048581c137c5b2130eb860e3ae37da196dfc25b = {
+ type Type1 = {
title: string;
- features: Type2048581c137c5b2130eb860e3ae37da196dfc25b;
+ features: Type1;
}[];
- Documentation update on compatibility with Express Zod API v21;
- Tested compatibility with Express v5;
- Removed redundant
event argument for Action::execute().
- 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:
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
}
},
},
});
- Technical update due to improved builder configuration:
- Fixed missing
node: protocol in the imports of core modules in the distributed javascript files.
- Breaking changes:
- Minimum supported versions:
- For Node.js: 18.18.0, 20.9.0, 22.0.0;
- For
zod: 3.23.0.
- Ability to describe security schemas on the server and namespace level:
- These security schemas go directly to the generated documentation.
// Single namespace
import { createSimpleConfig } from "zod-sockets";
const config = createSimpleConfig({
security: [
{
type: "httpApiKey",
description: "Server security schema",
in: "header",
name: "X-Api-Key",
},
],
});
// 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",
},
],
});
- First production-ready release having stable public API.
- Moved
logger from configuration to attachRouting():
- This simplifies reusing logger instance when running along with
express-zod-api.
// before:
import { createSimpleConfig, Config } from "zod-sockets";
createSimpleConfig({ logger });
// or
new Config({ logger });
// after:
import { attachSockets } from "zod-sockets";
attachSockets({ config });
- Added
client.getRequest() method (proxy for Socket::request).
- 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>).
- Added
handshake property to the client objects:
- 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.
// sending to someone knowing their id:
(await all.getClients())
.find(({ id }) => id === "someId")
?.emit("event", ...payload);
- Changed runtime dependency: replaced
chalk with ansis.
- Major improvement to the generated documentation: depicting payloads as actual tuples they are.
- 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().
- 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.
- Featuring examples in the generated documentation:
- Describe
Action examples using its .example() method;
- Describe Emission examples using
examples property in namespace config.
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"]);
- Minor adjustments to the documentation.
- 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.
// 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 */
});
- 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.
- 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).
- Increasing AsyncAPI version to 2.6.0 in the generated documentation;
- Human-readable identifiers for channels, operations and messages in the generated documentation.
- Fix: marked tuple items as required.
- 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;
- See the example of the generated documentation here.
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();
- 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.
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" });
- Ensuring that the namespace in the generated client is named the same as it's declared on backend;
- Using
chalk v5 in runtime.
- 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.
import { Integration } from "zod-sockets";
new Integration({ config, actions }).print(); // typescript code
- Minor adjustments and cleanup.
- Introducing namespaces feature.
- See the Namespaces documentation.
- 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().
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.
- Upgrading dependencies and improving the documentation.
- Adding
join() and leave() methods to RemoteClient (the ones returned by getClients()).
- 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).
- 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.
- 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().
- Ability to interact with the client's metadata:
getData<T>() and setData<T>() methods.
- Using
io.of("/") for both all.getRooms() and all.getClients().
- 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).
- Adding
getRooms() and withRooms() providing join(), leave() and broadcast() methods to the Action handler.
- Concept description and a workflow diagram.
- Moved
logger from attachSockets() to createConfig() argument.
- Fixed module exports.
- Unit and integration tests.
createSocketsConfig() renamed to createConfig().
- Ensure emitting the declared events only.
- Generic implementation for
emit() and broadcast().
- Added broadcasting feature.
- Delegated emission error handling to user.
- 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.