API reference@evolu/commonWorker › MessageChannel

Defined in: packages/common/src/Worker.ts:226

Typed, disposable MessageChannel.

Creates two entangled ports: keep one and transfer the other (e.g., to a SharedWorker via postMessage with transfer). Messages sent to one port are received by the other.

Messages are queued until onMessage is assigned, enabling safe async initialization. The sender can post messages immediately while the receiver performs async setup — no manual buffering required.

For one-way communication, omit Output (defaults to never).

Transfer a channel port to a SharedWorker and use the retained port immediately:

import {
  assertEqual,
  createMessageChannel,
  createMessagePort,
  createSharedWorker,
  type NativeMessagePort,
} from "@evolu/common";

interface Query {
  readonly text: string;
}
interface QueryResult {
  readonly rows: ReadonlyArray<string>;
}
interface ConnectToEvolu {
  readonly port: NativeMessagePort<QueryResult, Query>;
}

const response = Promise.withResolvers<QueryResult>();
using sharedWorker = createSharedWorker<ConnectToEvolu>((self) => {
  self.onConnect = (controlPort) => {
    controlPort.onMessage = ({ port }) => {
      const workerQueries = createMessagePort<QueryResult, Query>(port);
      workerQueries.onMessage = (query) => {
        workerQueries.postMessage({ rows: [query.text] });
        workerQueries[Symbol.dispose]();
      };
    };
  };
});
using queryChannel = createMessageChannel<Query, QueryResult>();
queryChannel.port1.onMessage = response.resolve;

// The main thread retains port1 and transfers the worker-facing port2.
sharedWorker.port.postMessage({ port: queryChannel.port2.native }, [
  queryChannel.port2.native,
]);
queryChannel.port1.postMessage({ text: "all todos" });

assertEqual(await response.promise, { rows: ["all todos"] });

On the worker side, attach the handler after asynchronous initialization; messages sent earlier remain queued:

import {
  assertEqual,
  createMessageChannel,
  createMessagePort,
} from "@evolu/common";

interface Query {
  readonly text: string;
}

using channel = createMessageChannel<Query>();
using workerPort = createMessagePort<never, Query>(channel.port2.native);
const received: Array<Query> = [];
const allReceived = Promise.withResolvers<void>();
// The main thread can send several messages while the worker initializes.
channel.port1.postMessage({ text: "all todos" });
channel.port1.postMessage({ text: "completed todos" });

await Promise.resolve();
workerPort.onMessage = (query) => {
  received.push(query);
  if (received.length === 2) allReceived.resolve();
};
await allReceived.promise;

assertEqual(received, [{ text: "all todos" }, { text: "completed todos" }]);

See

https://developer.mozilla.org/en-US/docs/Web/API/MessageChannel

Extends

Extended by

Methods

[dispose]()

dispose: void;

Defined in: node_modules/@typescript/old/lib/lib.esnext.disposable.d.ts:34

Inherited from

Disposable.[dispose]

Properties

port1

readonly port1: MessagePort<Input, Output>;

Defined in: packages/common/src/Worker.ts:228

The first port of the channel.


port2

readonly port2: MessagePort<Output, Input>;

Defined in: packages/common/src/Worker.ts:231

The second port of the channel.