Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions .eslintignore

This file was deleted.

43 changes: 0 additions & 43 deletions .eslintrc

This file was deleted.

12 changes: 6 additions & 6 deletions .github/workflows/master-status.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:

strategy:
matrix:
node-version: [14.x, 16.x, 18.x]
node-version: [20.x, 22.x, 24.x]

steps:
- uses: actions/checkout@v1
Expand All @@ -22,10 +22,10 @@ jobs:
node-version: ${{ matrix.node-version }}
- name: npm install, build, and test
run: |
yarn
yarn run build
yarn run lint
yarn run test
yarn run bench
npm i
npm run build
npm run lint
npm run test
npm run bench
env:
CI: true
12 changes: 6 additions & 6 deletions .github/workflows/pr-validation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:

strategy:
matrix:
node-version: [18.x, 20.x]
node-version: [20.x, 22.x, 24.x]

steps:
- uses: actions/checkout@v1
Expand All @@ -22,10 +22,10 @@ jobs:
node-version: ${{ matrix.node-version }}
- name: npm install, build, and test
run: |
yarn
yarn run build
yarn run lint
yarn run test
yarn run bench
npm i
npm run build
npm run lint
npm run test
npm run bench
env:
CI: true
12 changes: 9 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,25 @@ packages/ipc/bin
packages/tcp/bin
packages/udp/bin
packages/ws/bin
packages/webrtc/bin

# distribution code
packages/kalm/dist
packages/ipc/dist
packages/tcp/dist
packages/udp/dist
packages/ws/dist

# publish-only files
packages/kalm/LICENSE
packages/ipc/LICENSE
packages/tcp/LICENSE
packages/udp/LICENSE
packages/ws/LICENSE
packages/webrtc/LICENSE

packages/kalm/CHANGELOG.md
packages/ipc/CHANGELOG.md
packages/tcp/CHANGELOG.md
packages/udp/CHANGELOG.md
packages/ws/CHANGELOG.md
packages/webrtc/CHANGELOG.md

packages/kalm/types.d.ts
26 changes: 25 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,33 @@
# Changelog

## [v7.0.0] - 2023-03-17
## [v8.0.0] - 2025-07-30

commit [#](https://github.com/kalm/kalm.js/commits)

### Breaking changes

- Updated bundling for greater compatibility (exports *may* behave differently)
- Deprecated the WebRTC Transport (too convoluted to fit the Kalm model)
- Changed the signature of the `frame` event handler from `(frame: RawFrame, payloadBytes: number)` to `({ body: RawFrame, payloadBytes: number})` to ensure all event handlers only have one arguments.

### Minor changes

- Added support for the new native WS APIs in Node 22 and later
- Removed yarn from the toolchain. There's no reason to keep it now that NPM workspaces are more mature.
- Deprecated the `agent` property for the WS Transport
- Migrated the underlying Node EventEmitter to the cross-platform EventTarget system. A translation layer should keep end-user code intact.
- Fixed server connections not getting cleaned up
- Removed empty channels from frame payloads, saving bandwidth
- Changed the subscribe handler's second argument name from `frame` to `context`, to reduce confusion with its nested `frame` property.
- Fixed missing UDP client `connect` event.
- Removed the potentially misleading argument in the `connect` event since it only exposes the unbound socket.
- Bumped engines requirement to Node 20.x


## [v7.0.0] - 2023-03-17

commit [99a3ab9](https://github.com/kalm/kalm.js/commit/99a3ab9c495f6f50d7d4b4a0f478a213cc0ce484)

### Major changes

- Standardized parameter names and expected behavior
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Copyright 2023 Frederic Charette
Copyright 2025 Frederic Charette

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
Expand Down
22 changes: 22 additions & 0 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import eslint from '@eslint/js';
import { globalIgnores } from 'eslint/config';
import tseslint from 'typescript-eslint';
import jestConfig from 'eslint-plugin-jest';
import spacing from '@stylistic/eslint-plugin';

export default tseslint.config(
eslint.configs.recommended,
tseslint.configs.recommended,
jestConfig.configs['flat/recommended'],
spacing.configs.recommended,
{
rules: {
'@stylistic/semi': [2, 'always'],
'@typescript-eslint/no-explicit-any': 0,
'@typescript-eslint/no-require-imports': 1,
'jest/no-done-callback': 0,
'jest/no-conditional-expect': 0,
},
},
globalIgnores(['**/bin', '**/dist', '**/*.js']),
);
27 changes: 27 additions & 0 deletions examples/binary_compression/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Binary messages and compression example

This example shows how to send binary messages and how to add compression.

# Requirements

- The clients can run in either the browser or in Node.js.
- The server must run in a Node.js environment.
- NPM or other package manager to install `kalm`, `@kalm/ws` and the compression library of your choice. In this example, we are using `snappy`.

# Testing

Launch the server first:

```
node ./server.js
```

It should log that the server is ready to receive new connections. At this stage, launch any number of clients:

```
node ./client.js
```

The clients should connect to the server and send an "hello world!" message.

In turn, the server will both respond to that client: "hello from the server!" and broadcast to all clients "A new client has connected!"
62 changes: 62 additions & 0 deletions examples/binary_compression/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { connect, routines } from 'kalm';
import ws from '@kalm/ws';
import { compressSync, uncompress } from 'snappy';

/**
* Creates a kalm client that uses the Websocket transport.
* It will attempt to connect to a server located at the address '0.0.0.0' on port 3938.
*
* The realtime routine will emit messages as soon as possible. While this is ideal in a few scenarios, for instance when the client does not send a lot of events,
* it does not leverage the benefits of buffering and may cause slowdowns if many messages are sent rapidly.
*
* To ensure that the message sent is not coerced into JSON, we must pass the `json:false` parameter.
*/
const client = connect({
transport: ws({}),
json: false,
port: 3938,
host: '0.0.0.0',
routine: routines.realtime(),
});

/**
* An example interface for messages sent between client and server
*/
type MyCustomPayload = {
message: string
};

/**
* To confirm that the client has succesfully connected to the server, listen to the 'connect' event.
*/
client.on('connect', () => {
/**
* Once a client has connected, we subscribe to messages sent on the "foo" channel.
*/
client.subscribe('foo', (body: Buffer, context) => {
/**
* When we receive a message on the foo channel, we also receive information about the frame and context.
* We'll need to decompress the buffer to read it.
*
* body: <Buffer>
* context: {
* client: <Client>,
* frame: {
* channel: "foo",
* id: 1,
* messageIndex: 1,
* payloadBytes: 22,
* payloadMessages: 1,
* }
* }
*/
uncompress(body).then((decompressedMessage) => {
console.log('Server event', JSON.parse(decompressedMessage.toString()) as MyCustomPayload, context);
});
});

/**
* To send messages from the client to the server, simply `write` to the desired channel.
*/
client.write('foo', compressSync(Buffer.from(JSON.stringify({ message: 'hello world!' }))));
});
79 changes: 79 additions & 0 deletions examples/binary_compression/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { listen, routines } from 'kalm';
import ws from '@kalm/ws';
import { compressSync, uncompress } from 'snappy';

/**
* Creates a kalm server that uses the Websocket transport.
* It is bound to local IP 0.0.0.0 and listens on port 3938.
*
* The tick routine will emit messages to clients at a frequency no higher than 5hz, or no shorter than 20ms
*
* This is a common setup for relaying information to multiple connected clients that all send information rapidly.
*
* To ensure that the message sent is not coerced into JSON, we must pass the `json:false` parameter.
*/
const provider = listen({
transport: ws(),
port: 3938,
json: false,
routine: routines.tick({ hz: 5 }),
host: '0.0.0.0',
});

/**
* An example interface for messages sent between client and server
*/
type MyCustomPayload = {
message: string
};

/**
* First, the server must listen for connection events
*/
provider.on('connection', (client) => {
/**
* Once a client has connected, we subscribe to messages sent on the "foo" channel.
*/
client.subscribe('foo', (body: Buffer, context) => {
/**
* When we receive a message on the foo channel, we also receive information about the frame and context.
* We'll need to decompress the buffer to read it.
*
* body: <Buffer>
* context: {
* client: <Client>,
* frame: {
* channel: "foo",
* id: 1,
* messageIndex: 1,
* payloadBytes: 12,
* payloadMessages: 1,
* }
* }
*/
uncompress(body).then((decompressedMessage) => {
console.log('Client event', JSON.parse(decompressedMessage.toString()) as MyCustomPayload, context);
});
});

/**
* To send messages from the server to the newly connected client, simply `write` to the desired channel.
*/
client.write('foo', compressSync(Buffer.from(JSON.stringify({
message: 'hello from the server!',
} as MyCustomPayload))));

/**
* To send a message to all connected clients, for example to announce that a new client has connected, you may use the `broadcast` function. Again, it is important to specify which channel to use.
*/
provider.broadcast('foo', compressSync(Buffer.from(JSON.stringify({
message: 'A new client has connected!',
} as MyCustomPayload))));
});

/**
* The `ready` event lets you know that the server is ready to receive connections
*/
provider.on('ready', () => {
console.log('The server is now listening on port 3938');
});
Loading