worker-channel
Version:
A modern zero-dependency Worker communication and orchestration library
311 lines (309 loc) • 10.9 kB
TypeScript
export type ChannelWrite<DataMessages extends DataMessage> = {
[P in DataMessages["type"]]: (data: (DataMessages & {
type: P;
})["data"], transfer?: Transferable[]) => void;
};
export type ChannelRelease<DataMessages extends DataMessage> = {
[P in DataMessages["type"]]: () => void;
};
export type ChannelRead<DataMessages extends DataMessage> = {
[P in DataMessages["type"]]: () => Promise<(DataMessages & {
type: P;
})["data"]>;
};
export type ChannelReadAll<DataMessages extends DataMessage> = {
[P in DataMessages["type"]]: () => AsyncGenerator<(DataMessages & {
type: P;
})["data"]>;
};
export type NecessaryMessages<Write extends DataMessage, Read extends DataMessage> = {
type: "worker-channel:change-reader";
data: MessagePort;
} | {
type: "worker-channel:change-writer";
data: MessagePort;
} | {
type: "worker-channel:close-writer";
data: Write["type"];
} | {
type: "worker-channel:close-reader";
data: Read["type"];
} | {
type: "worker-channel:acknowledge";
data: boolean;
};
/** @public */
export type ChannelConnection = MessagePort | Worker | typeof globalThis;
/** Channel options */
export interface IChannelOptions {
/** Controls aborting the Channel. Basically the same as calling end. */
controller?: AbortController;
/** Where to write the messages to.
* Defaults to the UI/main thread.
* @default globalThis
*/
writeTo?: ChannelConnection;
/** Where to read the messages from.
* Defaults to the UI/main thread.
* @default globalThis
*/
readFrom?: ChannelConnection;
}
/** A message type should usually be denoted by a union from the consumer of this library.
* @public
*/
export type DataMessage<Data = any> = {
/** The channel to create, write and read from. */
type: string | number | symbol;
/** The data the channel writes or reads */
data: Data;
} | {
/** The channel to create, write and read from. */
type: string | number | symbol;
/** The data the channel writes or reads */
data?: Data;
};
export type InternalReadQueue<Read extends DataMessage> = Record<Read["type"], ReadableStreamDefaultReader<Read["data"]>>;
export type InternalWriteQueue<Write extends DataMessage> = Record<Write["type"], WritableStreamDefaultWriter<Write["data"]>>;
export type InternalReadWriteQueue<Read extends DataMessage, Write extends DataMessage> = Record<Read["type"] | Write["type"], TransformStream<Read["data"], Write["data"]>>;
export function consumeStream<T>(readable: ReadableStream<T>): AsyncGenerator<Awaited<T>>;
export function consumeReader<T>(reader: ReadableStreamDefaultReader<T>): AsyncGenerator<Awaited<T>>;
export class SleepAbortError extends Error {
constructor(message?: string);
}
export const sleep: (ms: number, controller?: AbortController) => Promise<unknown>;
export const Deferred: <Resolve, Reject>() => {
resolve: (value: Resolve | PromiseLike<Resolve>) => void;
reject: (reason?: Reject | undefined) => void;
promise: Promise<Resolve>;
};
export function isDefined<T>(x: T): x is NonNullable<T>;
export function writerIsClosed(writer: WritableStreamDefaultWriter): Promise<boolean>;
export abstract class Channel<Read extends DataMessage, Write extends DataMessage> {
/** @internal */
protected internalQueues: {
read?: InternalReadQueue<Read>;
write?: InternalWriteQueue<Write>;
transform?: InternalReadWriteQueue<Read, Write>;
};
/** Connect the readFrom or writeTo worker/port to either recieve from or send to to a different message port.
* @param target The worker/port to send the `connection` change to.
* @param action Whether you want to change the "writable" end or the "readable" end of the `target`
* @param connection The connection that should now be written to or read from.
*/
connect(target: "readFrom" | "writeTo", action: "change-reader" | "change-writer", connection: MessagePort): void;
constructor({ controller, writeTo, readFrom, }?: IChannelOptions);
/** @internal */
protected readonly controller?: IChannelOptions["controller"];
/** @internal */
protected writeTo: IChannelOptions["writeTo"];
/** @internal */
protected readFrom: IChannelOptions["readFrom"];
/** @internal */
protected readWriteSetup(): void;
/** @internal */
protected readSetup(): void;
/** @internal */
protected writeSetup(): void;
/** Propagate close on all writers of channel
*
* @example
*
* ```ts
* const writer = new WriteChannel();
*
* // closes the "string" channel.
* writer.close.writer.string();
* ```
*/
close: {
writer: ChannelRelease<Write>;
};
/** Propagate cancel on all readers
*
* @example
*
* ```ts
* const rChannel = new ReadChannel();
*
* // cancels the "string" channel.
* rChannel.cancel.reader.string();
* ```
*/
cancel: {
reader: ChannelRelease<Read>;
};
/** @internal */
protected _send<T extends Write["type"]>(type: T, data: (Write & {
type: T;
})["data"], transfer?: Transferable[]): void | undefined;
/** @internal */
protected _read<T extends Read["type"]>(type: T): Promise<Read["data"] | undefined>;
/** @internal */
protected _readAll<T extends Read["type"]>(type: T): AsyncGenerator<(Read & {
type: T;
})["data"]>;
/** @internal */
protected listen(): Promise<void>;
/** @internal */
protected unlisten(): Promise<void>;
/** @internal */
protected listener(ev: MessageEvent<NecessaryMessages<Read, Write>>): Promise<void>;
/** Starts the channel. Called automatically from the constructor,
* but if you ever `.end()` the channel this will start it again.
*/
start(): void;
/** Ends the communication of the channel.
* You can always restart the channel with `.start()`.
*/
end(): void;
}
/** Allows writing to a worker.
*
*
* @example
* An example worker script:
*
* ```ts
* // Setup the channel to be "string" (denoted by the type) with data of type string (denoted by data).
* type MyMessage = {type: "string", data: string};
* const rwChannel = new ReadWriteChannel<MyMessage, MyMessage>();
*
* // writes to the string channel:
* rwChannel.write.string("foo");
* ```
*/
export class WriteChannel<Write extends DataMessage> extends Channel<DataMessage<void>, Write> {
start(): void;
/** Write to the channel.
*
* @example
* An example worker script:
* ```ts
* // Setup the channel to be "string" (denoted by the type) with data of type string (denoted by data).
* type MyMessage = {type: "string", data: string};
* const rwChannel = new ReadWriteChannel<MyMessage, MyMessage>();
*
* // writes to the string channel:
* rwChannel.write.string("foo");
* ```
*/
readonly write: ChannelWrite<Write>;
}
/** Creates a readable channel.
*
* @example
* An example worker script:
*
* ```ts
* // Setup the channel to be "string" (denoted by the type) with data of type string (denoted by data).
* type MessageType = {type: "string", data: string};
* const rChannel = new ReadChannel<MessageType>();
*
* // Read a single item from "string" channel.
* console.log(await rChannel.read.string());
* // Read everything from the "string" channel.
* for await (const item of rChannel.readAll.string()) {
* console.log(item);
* }
* ```
* @public
*/
export class ReadChannel<Read extends DataMessage> extends Channel<Read, DataMessage<void>> {
start(): void;
/** Read from a channel.
*
* @example
* An example worker script:
*
* ```ts
* // Setup the channel to be "string" (denoted by the type) with data of type string (denoted by data).
* type MessageType = {type: "string", data: string};
* const rChannel = new ReadChannel<MessageType>();
*
* console.log(await rChannel.read.string())
* ```
*/
readonly read: ChannelRead<Read>;
/** Read everything from a channel.
*
* @example
* An example worker script:
*
* ```ts
*
* // Setup the channel to be "string" (denoted by the type) with data of type string (denoted by data).
* type MessageType = {type: "string", data: string};
* const rChannel = new ReadChannel<MessageType>();
*
* // Read everything from the "string" channel.
* for await (const item of rChannel.readAll.string()) {
* console.log(item);
* }
* ```
*/
readonly readAll: ChannelReadAll<Read>;
}
/** ReadWriteChannel is a readable and writable Channel.
* @example
*
* An example worker script:
*
* ```ts
* // Setup the channel to be "string" (denoted by the type) with data of type string (denoted by data).
* type MyMessage = {type: "string", data: string};
* const rwChannel = new ReadWriteChannel<MyMessage, MyMessage>();
*
* (async () => {
* // read each string from the 'string' channel (as declared by MyMessage).
* for await (const item of rwChannel.readAll.string()) {
* // write to the 'string' channel.
* rwChannel.write.string(worker);
* }
* })();
* ```
*/
export class ReadWriteChannel<Read extends DataMessage, Write extends DataMessage> extends Channel<Read, Write> {
start(): void;
/** Write to the channel.
*
* @example
* ```ts
* // Setup the channel to be "string" (denoted by the type) with data of type string (denoted by data).
* type MyMessage = {type: "string", data: string};
* const rwChannel = new ReadWriteChannel<MyMessage, MyMessage>();
*
* // writes to the string channel:
* rwChannel.write.string("foo");
* ```
*/
readonly write: ChannelWrite<Write>;
/** Read from the channel
*
* @example
* ```ts
* // Setup the channel to be "string" (denoted by the type) with data of type string (denoted by data).
* type MyMessage = {type: "string", data: string};
* const rwChannel = new ReadWriteChannel<MyMessage, MyMessage>();
*
* // Read data of type string from the "string" channel.
* const str = await rwChannel.read.string();
* ```
*/
readonly read: ChannelRead<Read>;
/** Read from the channel
*
* @example
* ```ts
* // Setup the channel to be "string" (denoted by the type) with data of type string (denoted by data).
* type MyMessage = {type: "string", data: string};
* const rwChannel = new ReadWriteChannel<MyMessage, MyMessage>();
*
* for await (const item of rwChannel.readAll.string()) {
* console.log(item);
* }
* ```
*/
readonly readAll: ChannelReadAll<Read>;
}
//# sourceMappingURL=index.d.ts.map