UNPKG

worker-channel

Version:

A modern zero-dependency Worker communication and orchestration library

311 lines (309 loc) 10.9 kB
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