Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@quilted/threads

Package Overview
Dependencies
Maintainers
1
Versions
79
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@quilted/threads - npm Package Compare versions

Comparing version 0.0.0-preview-20240216173826 to 0.0.0-preview-20240225030354

2

build/typescript/index.d.ts

@@ -7,3 +7,3 @@ export { retain, release, StackFrame, isMemoryManageable } from './memory.ts';

export { createThreadAbortSignal, acceptThreadAbortSignal, type ThreadAbortSignal, } from './abort-signal.ts';
export type { Thread, ThreadTarget, ThreadCallable, ThreadCallableFunction, ThreadSafeArgument, ThreadSafeReturnType, ThreadSafeReturnValueType, ThreadEncoder, ThreadEncoderApi, ThreadEncodable, AnyFunction, } from './types.ts';
export type { Thread, ThreadTarget, ThreadEncoder, ThreadEncoderApi, ThreadEncodable, AnyFunction, } from './types.ts';
//# sourceMappingURL=index.d.ts.map

@@ -14,3 +14,3 @@ import { type ThreadOptions } from './target.ts';

*/
export declare function createThreadFromBroadcastChannel<Self = Record<string, never>, Target = Record<string, never>>(channel: BroadcastChannel, options?: ThreadOptions<Self, Target>): import("../types.ts").ThreadCallable<Target>;
export declare function createThreadFromBroadcastChannel<Self = Record<string, never>, Target = Record<string, never>>(channel: BroadcastChannel, options?: ThreadOptions<Self, Target>): import("../types.ts").Thread<Target>;
//# sourceMappingURL=broadcast-channel.d.ts.map

@@ -24,3 +24,3 @@ import { type ThreadOptions } from '../target.ts';

targetOrigin?: string;
}): import("../../types.ts").ThreadCallable<Target>;
}): import("../../types.ts").Thread<Target>;
//# sourceMappingURL=iframe.d.ts.map

@@ -23,3 +23,3 @@ import { type ThreadOptions } from '../target.ts';

targetOrigin?: string;
}): import("../../types.ts").ThreadCallable<Target>;
}): import("../../types.ts").Thread<Target>;
//# sourceMappingURL=nested.d.ts.map

@@ -20,3 +20,3 @@ import { type ThreadOptions } from './target.ts';

*/
export declare function createThreadFromMessagePort<Self = Record<string, never>, Target = Record<string, never>>(port: MessagePort, options?: ThreadOptions<Self, Target>): import("../types.ts").ThreadCallable<Target>;
export declare function createThreadFromMessagePort<Self = Record<string, never>, Target = Record<string, never>>(port: MessagePort, options?: ThreadOptions<Self, Target>): import("../types.ts").Thread<Target>;
//# sourceMappingURL=message-port.d.ts.map

@@ -14,3 +14,3 @@ import { type ThreadOptions } from './target.ts';

*/
export declare function createThreadFromBrowserWebSocket<Self = Record<string, never>, Target = Record<string, never>>(websocket: WebSocket, options?: ThreadOptions<Self, Target>): import("../types.ts").ThreadCallable<Target>;
export declare function createThreadFromBrowserWebSocket<Self = Record<string, never>, Target = Record<string, never>>(websocket: WebSocket, options?: ThreadOptions<Self, Target>): import("../types.ts").Thread<Target>;
//# sourceMappingURL=web-socket-browser.d.ts.map

@@ -21,3 +21,3 @@ import { type ThreadOptions } from './target.ts';

*/
export declare function createThreadFromWebWorker<Self = Record<string, never>, Target = Record<string, never>>(worker: Worker, options?: ThreadOptions<Self, Target>): import("../types.ts").ThreadCallable<Target>;
export declare function createThreadFromWebWorker<Self = Record<string, never>, Target = Record<string, never>>(worker: Worker, options?: ThreadOptions<Self, Target>): import("../types.ts").Thread<Target>;
//# sourceMappingURL=web-worker.d.ts.map

@@ -9,3 +9,5 @@ import type { RELEASE_METHOD, RETAIN_METHOD, ENCODE_METHOD, RETAINED_BY } from './constants.ts';

*/
export type Thread<Target> = ThreadCallable<Target>;
export type Thread<Target> = {
[K in keyof Target]: Target[K] extends (...args: any[]) => infer ReturnType ? ReturnType extends Promise<any> | AsyncGenerator<any, any, any> ? Target[K] : never : never;
};
/**

@@ -35,37 +37,6 @@ * An object backing a `Thread` that provides the message-passing interface

/**
* A function type that can be called over a thread. It is the same as defining a
* normal function type, but with the additional restriction that the function must
* always return an asynchronous value (either a promise or an async generator). Additionally,
* all arguments to that function must also be thread-callable
*/
export interface ThreadCallableFunction<Args extends any[], ReturnType> {
(...args: ThreadSafeArgument<Args>): ThreadSafeReturnType<ReturnType>;
}
/**
* A mapped object type that takes an object with methods, and converts it into the
* an object with the same methods that can be called over a thread.
*/
export type ThreadCallable<T> = {
[K in keyof T]: T[K] extends (...args: infer Args) => infer ReturnType ? ThreadCallableFunction<Args, ReturnType> : never;
};
export type MaybePromise<T> = T extends Promise<any> ? T : T | Promise<T>;
/**
* Converts the return type of a function into the type it will be when
* passed over a thread.
*/
export type ThreadSafeReturnType<T> = T extends AsyncGenerator<infer T, infer R, infer N> ? AsyncGenerator<ThreadSafeReturnValueType<T>, ThreadSafeReturnValueType<R>, ThreadSafeReturnValueType<N>> : T extends Generator<infer T, infer R, infer N> ? Generator<ThreadSafeReturnValueType<T>, ThreadSafeReturnValueType<R>, ThreadSafeReturnValueType<N>> | AsyncGenerator<ThreadSafeReturnValueType<T>, ThreadSafeReturnValueType<R>, ThreadSafeReturnValueType<N>> : T extends Promise<infer U> ? Promise<ThreadSafeReturnValueType<U>> : T extends infer U | Promise<infer U> ? Promise<ThreadSafeReturnValueType<U>> : Promise<ThreadSafeReturnValueType<T>>;
/**
* Converts an object into the type it will be when passed over a thread.
*/
export type ThreadSafeReturnValueType<T> = T extends (...args: infer Args) => infer ReturnType ? ThreadCallableFunction<Args, ReturnType> : T extends (infer ArrayElement)[] ? ThreadSafeReturnValueType<ArrayElement>[] : T extends readonly (infer ArrayElement)[] ? readonly ThreadSafeReturnValueType<ArrayElement>[] : T extends Set<infer U> ? Set<ThreadSafeReturnValueType<U>> : T extends Map<infer K, infer U> ? Map<K, ThreadSafeReturnValueType<U>> : T extends object ? {
[K in keyof T]: ThreadSafeReturnValueType<T[K]>;
} : T;
/**
* Converts an object into the type it could be if accepted as an argument to a function
* called over a thread.
*/
export type ThreadSafeArgument<T> = T extends (...args: infer Args) => infer TypeReturned ? TypeReturned extends Promise<any> ? (...args: Args) => TypeReturned : TypeReturned extends AsyncGenerator<any, any, any> ? (...args: Args) => TypeReturned : TypeReturned extends Generator<infer T, infer R, infer N> ? (...args: Args) => AsyncGenerator<T, R, N> : TypeReturned extends boolean ? (...args: Args) => boolean | Promise<boolean> : (...args: Args) => TypeReturned | Promise<TypeReturned> : {
[K in keyof T]: ThreadSafeArgument<T[K]>;
};
/**
* An object that can retain a reference to a `MemoryManageable` object.

@@ -72,0 +43,0 @@ */

# @quilted/threads
## 0.0.0-preview-20240216173826
## 0.0.0-preview-20240225030354
### Patch Changes
- Simplify thread helper types
- Clean up type casting during thread creation

@@ -13,2 +15,4 @@

- [`3a573a8d`](https://github.com/lemonmade/quilt/commit/3a573a8db9978749323691eadae530397ed606f5) Thanks [@lemonmade](https://github.com/lemonmade)! - Simplify thread helper types
- [`79ff7af7`](https://github.com/lemonmade/quilt/commit/79ff7af7cc4e594a86efc0302f1ddfdc309fdb65) Thanks [@lemonmade](https://github.com/lemonmade)! - Clean up type casting during thread creation

@@ -15,0 +19,0 @@

@@ -5,3 +5,3 @@ {

"type": "module",
"version": "0.0.0-preview-20240216173826",
"version": "0.0.0-preview-20240225030354",
"license": "MIT",

@@ -8,0 +8,0 @@ "engines": {

@@ -72,3 +72,5 @@ # `@quilted/threads`

expose: {
add(a: number, b: number) {
// In reality, you’d usually implement a more computationally-expensive
// function here!
async add(a: number, b: number) {
return a + b;

@@ -111,7 +113,8 @@ },

Not all types of arguments are supported for functions proxied via message passing by `@quilted/threads`. Only the following simple types can be used:
Not all types of arguments are supported for functions proxied via message passing by `@quilted/threads`. Only the following types can be used:
- Strings, numbers, `true`, `false`, `null`, and `undefined`
- Objects whose keys and values are all simple types
- Arrays whose values are all simple types
- `RegExp`, `Date`, `ArrayBuffer`, and `URL` instances
- Objects whose keys and values are all acceptable types
- Arrays, `Map`s, and `Set`s whose values are acceptable types
- Functions, but they will become asynchronous when proxied, and all functions accepted by arguments in those functions, or returned as part of return values, will have the same argument limitations (also note the [memory management implications of functions](#memory-management) detailed below)

@@ -122,3 +125,2 @@

- No `WeakMap` or `WeakSet`
- No `ArrayBuffer` or typed arrays
- Instances of classes will transfer, but only their own properties — that is, properties on their prototype chain **will not** be transferred (additionally, no effort is made to preserve `instanceof` or similar checks on the transferred value)

@@ -125,0 +127,0 @@

@@ -121,4 +121,5 @@ import {ENCODE_METHOD} from '../constants.ts';

// TODO: avoid this if using a `structuredClone` postMessage-ing object?
if (value instanceof RegExp) {
const result = [{[REGEXP]: [value.source, value.flags]}];
const result = {[REGEXP]: [value.source, value.flags]};
const fullResult: EncodeResult = [result, transferables];

@@ -130,3 +131,3 @@ seen.set(value, fullResult);

if (value instanceof URL) {
const result = [{[URL_ID]: value.href}];
const result = {[URL_ID]: value.href};
const fullResult: EncodeResult = [result, transferables];

@@ -138,3 +139,3 @@ seen.set(value, fullResult);

if (value instanceof Date) {
const result = [{[DATE]: value.toISOString()}];
const result = {[DATE]: value.toISOString()};
const fullResult: EncodeResult = [result, transferables];

@@ -149,3 +150,3 @@ seen.set(value, fullResult);

});
const result = [{[MAP]: entries}];
const result = {[MAP]: entries};
const fullResult: EncodeResult = [result, transferables];

@@ -158,3 +159,3 @@ seen.set(value, fullResult);

const entries = [...value].map((entry) => encodeValue(entry));
const result = [{[SET]: entries}];
const result = {[SET]: entries};
const fullResult: EncodeResult = [result, transferables];

@@ -287,2 +288,6 @@ seen.set(value, fullResult);

if (!isBasicObject(value)) {
return value;
}
const result: Record<string | symbol, any> = {};

@@ -289,0 +294,0 @@

@@ -28,7 +28,2 @@ export {retain, release, StackFrame, isMemoryManageable} from './memory.ts';

ThreadTarget,
ThreadCallable,
ThreadCallableFunction,
ThreadSafeArgument,
ThreadSafeReturnType,
ThreadSafeReturnValueType,
ThreadEncoder,

@@ -35,0 +30,0 @@ ThreadEncoderApi,

import type {
Thread,
ThreadTarget,
ThreadCallable,
ThreadEncoder,

@@ -123,3 +122,3 @@ ThreadEncoderApi,

const call = createCallable<Target>(handlerForCall, callable);
const call = createCallable<Thread<Target>>(handlerForCall, callable);

@@ -418,3 +417,3 @@ const encoderApi: ThreadEncoderApi = {

callable?: (keyof T)[],
): ThreadCallable<T> {
): T {
let call: any;

@@ -421,0 +420,0 @@

@@ -1,3 +0,3 @@

import {describe, it, expect} from 'vitest';
import {createThreadFromMessagePort, type ThreadCallable} from '../index.ts';
import {describe, it, expect, vi} from 'vitest';
import {createThreadFromMessagePort} from '../index.ts';
import {MessageChannel} from './utilities.ts';

@@ -7,31 +7,31 @@

it('calls the exposed API over a message channel', async () => {
interface EndpointApi {
hello(): string;
interface ThreadAPI {
hello(): Promise<string>;
}
const {port1, port2} = new MessageChannel();
const threadOne = createThreadFromMessagePort<
const thread1 = createThreadFromMessagePort<
Record<string, never>,
EndpointApi
ThreadAPI
>(port1);
createThreadFromMessagePort<EndpointApi>(port2, {
expose: {hello: () => 'world'},
createThreadFromMessagePort<ThreadAPI>(port2, {
expose: {hello: async () => 'world'},
});
expect(await threadOne.hello()).toBe('world');
expect(await thread1.hello()).toBe('world');
});
it('proxies function calls', async () => {
type EndpointApi = ThreadCallable<{
greet(getName: () => string): string;
}>;
interface ThreadAPI {
greet(getName: () => Promise<string>): Promise<string>;
}
const {port1, port2} = new MessageChannel();
const threadOne = createThreadFromMessagePort<
const thread1 = createThreadFromMessagePort<
Record<string, never>,
EndpointApi
ThreadAPI
>(port1);
createThreadFromMessagePort<EndpointApi>(port2, {
createThreadFromMessagePort<ThreadAPI>(port2, {
expose: {

@@ -42,14 +42,14 @@ greet: async (getName) => `Hello, ${await getName()}!`,

expect(await threadOne.greet(() => 'Chris')).toBe('Hello, Chris!');
expect(await thread1.greet(async () => 'Chris')).toBe('Hello, Chris!');
});
it('proxies generators', async () => {
type EndpointApi = ThreadCallable<{
iterate(): Generator<number, void, void>;
}>;
interface ThreadAPI {
iterate(): AsyncGenerator<number, void, void>;
}
const {port1, port2} = new MessageChannel();
const threadOne = createThreadFromMessagePort<
const thread1 = createThreadFromMessagePort<
Record<string, never>,
EndpointApi
ThreadAPI
>(port1);

@@ -60,5 +60,5 @@

createThreadFromMessagePort<EndpointApi>(port2, {
createThreadFromMessagePort<ThreadAPI>(port2, {
expose: {
*iterate() {
async *iterate() {
while (yielded < 5) {

@@ -71,3 +71,3 @@ yield ++yielded;

for await (const value of threadOne.iterate()) {
for await (const value of thread1.iterate()) {
expect(value).toBe(++expected);

@@ -78,3 +78,3 @@ }

it('proxies async generators', async () => {
interface EndpointApi {
interface ThreadAPI {
iterate(): AsyncGenerator<number, void, void>;

@@ -84,5 +84,5 @@ }

const {port1, port2} = new MessageChannel();
const threadOne = createThreadFromMessagePort<
const thread1 = createThreadFromMessagePort<
Record<string, never>,
EndpointApi
ThreadAPI
>(port1);

@@ -93,3 +93,3 @@

createThreadFromMessagePort<EndpointApi>(port2, {
createThreadFromMessagePort<ThreadAPI>(port2, {
expose: {

@@ -104,3 +104,3 @@ async *iterate() {

for await (const value of threadOne.iterate()) {
for await (const value of thread1.iterate()) {
expect(value).toBe(++expected);

@@ -110,5 +110,87 @@ }

it('proxies basic JavaScript types', async () => {
interface OneOfEverything {
string: string;
number: number;
boolean: boolean;
null: null;
undefined: undefined;
set: Set<any>;
map: Map<any, any>;
regexp: RegExp;
date: Date;
url: URL;
array: any[];
error: Error;
}
interface ThreadAPI {
oneOfEverything(options: OneOfEverything): Promise<void>;
}
const {port1, port2} = new MessageChannel();
const thread1 = createThreadFromMessagePort<
Record<string, never>,
ThreadAPI
>(port1);
const spy = vi.fn();
createThreadFromMessagePort<ThreadAPI>(port2, {
expose: {
oneOfEverything: spy,
},
});
const oneOfEverything: OneOfEverything = {
string: 'string',
number: 42,
boolean: true,
null: null,
undefined: undefined,
set: new Set([1, 2, 3]),
map: new Map([
['one', 1],
['two', 2],
]),
regexp: /regexp/,
date: new Date(),
url: new URL('https://example.com'),
array: [1, 2, 3],
error: new Error('error'),
};
await thread1.oneOfEverything(oneOfEverything);
expect(spy).toHaveBeenCalledWith(oneOfEverything);
});
it('proxies complex object types', async () => {
interface ThreadAPI {
respondWithArray(array: Uint8Array): Promise<Uint8Array>;
}
const {port1, port2} = new MessageChannel();
const thread1 = createThreadFromMessagePort<
Record<string, never>,
ThreadAPI
>(port1);
createThreadFromMessagePort<ThreadAPI>(port2, {
expose: {
async respondWithArray(array) {
return array;
},
},
});
const array = new Uint8Array([1, 2, 3, 4, 5]);
const response = await thread1.respondWithArray(array);
expect(response).toBeInstanceOf(Uint8Array);
expect(response).toStrictEqual(array);
});
it('throws errors when calling methods on terminated threads', async () => {
interface EndpointApi {
greet(): string;
interface ThreadAPI {
greet(): Promise<string>;
}

@@ -119,10 +201,10 @@

const {port1, port2} = new MessageChannel();
const threadOne = createThreadFromMessagePort<
const thread1 = createThreadFromMessagePort<
Record<string, never>,
EndpointApi
ThreadAPI
>(port1, {signal: abort.signal});
createThreadFromMessagePort<EndpointApi>(port2, {
createThreadFromMessagePort<ThreadAPI>(port2, {
expose: {
greet: () => 'Hello, world!',
greet: async () => 'Hello, world!',
},

@@ -133,9 +215,9 @@ });

await expect(threadOne.greet()).rejects.toBeInstanceOf(Error);
await expect(thread1.greet()).rejects.toBeInstanceOf(Error);
});
it('rejects all in-flight requests when a thread terminates', async () => {
type EndpointApi = ThreadCallable<{
greet(): string;
}>;
interface ThreadAPI {
greet(): Promise<string>;
}

@@ -145,8 +227,8 @@ const abort = new AbortController();

const {port1, port2} = new MessageChannel();
const threadOne = createThreadFromMessagePort<
const thread1 = createThreadFromMessagePort<
Record<string, never>,
EndpointApi
ThreadAPI
>(port1, {signal: abort.signal});
createThreadFromMessagePort<EndpointApi>(port2, {
createThreadFromMessagePort<ThreadAPI>(port2, {
expose: {

@@ -157,3 +239,3 @@ greet: () => new Promise(() => {}),

const result = threadOne.greet();
const result = thread1.greet();

@@ -166,5 +248,5 @@ abort.abort();

it('rejects all in-flight requests when a target thread terminates', async () => {
type EndpointApi = ThreadCallable<{
greet(): string;
}>;
interface ThreadAPI {
greet(): Promise<string>;
}

@@ -174,8 +256,8 @@ const abort = new AbortController();

const {port1, port2} = new MessageChannel();
const threadOne = createThreadFromMessagePort<
const thread1 = createThreadFromMessagePort<
Record<string, never>,
EndpointApi
ThreadAPI
>(port1);
createThreadFromMessagePort<EndpointApi>(port2, {
createThreadFromMessagePort<ThreadAPI>(port2, {
signal: abort.signal,

@@ -187,3 +269,3 @@ expose: {

const result = threadOne.greet();
const result = thread1.greet();

@@ -190,0 +272,0 @@ abort.abort();

@@ -15,3 +15,9 @@ import type {

*/
export type Thread<Target> = ThreadCallable<Target>;
export type Thread<Target> = {
[K in keyof Target]: Target[K] extends (...args: any[]) => infer ReturnType
? ReturnType extends Promise<any> | AsyncGenerator<any, any, any>
? Target[K]
: never
: never;
};

@@ -42,93 +48,7 @@ /**

/**
* A function type that can be called over a thread. It is the same as defining a
* normal function type, but with the additional restriction that the function must
* always return an asynchronous value (either a promise or an async generator). Additionally,
* all arguments to that function must also be thread-callable
*/
export interface ThreadCallableFunction<Args extends any[], ReturnType> {
(...args: ThreadSafeArgument<Args>): ThreadSafeReturnType<ReturnType>;
}
/**
* A mapped object type that takes an object with methods, and converts it into the
* an object with the same methods that can be called over a thread.
*/
export type ThreadCallable<T> = {
[K in keyof T]: T[K] extends (...args: infer Args) => infer ReturnType
? ThreadCallableFunction<Args, ReturnType>
: never;
};
export type MaybePromise<T> = T extends Promise<any> ? T : T | Promise<T>;
/**
* Converts the return type of a function into the type it will be when
* passed over a thread.
*/
export type ThreadSafeReturnType<T> = T extends AsyncGenerator<
infer T,
infer R,
infer N
>
? AsyncGenerator<
ThreadSafeReturnValueType<T>,
ThreadSafeReturnValueType<R>,
ThreadSafeReturnValueType<N>
>
: T extends Generator<infer T, infer R, infer N>
?
| Generator<
ThreadSafeReturnValueType<T>,
ThreadSafeReturnValueType<R>,
ThreadSafeReturnValueType<N>
>
| AsyncGenerator<
ThreadSafeReturnValueType<T>,
ThreadSafeReturnValueType<R>,
ThreadSafeReturnValueType<N>
>
: T extends Promise<infer U>
? Promise<ThreadSafeReturnValueType<U>>
: T extends infer U | Promise<infer U>
? Promise<ThreadSafeReturnValueType<U>>
: Promise<ThreadSafeReturnValueType<T>>;
/**
* Converts an object into the type it will be when passed over a thread.
*/
export type ThreadSafeReturnValueType<T> = T extends (
...args: infer Args
) => infer ReturnType
? ThreadCallableFunction<Args, ReturnType>
: T extends (infer ArrayElement)[]
? ThreadSafeReturnValueType<ArrayElement>[]
: T extends readonly (infer ArrayElement)[]
? readonly ThreadSafeReturnValueType<ArrayElement>[]
: T extends Set<infer U>
? Set<ThreadSafeReturnValueType<U>>
: T extends Map<infer K, infer U>
? Map<K, ThreadSafeReturnValueType<U>>
: T extends object
? {[K in keyof T]: ThreadSafeReturnValueType<T[K]>}
: T;
/**
* Converts an object into the type it could be if accepted as an argument to a function
* called over a thread.
*/
export type ThreadSafeArgument<T> = T extends (
...args: infer Args
) => infer TypeReturned
? TypeReturned extends Promise<any>
? (...args: Args) => TypeReturned
: TypeReturned extends AsyncGenerator<any, any, any>
? (...args: Args) => TypeReturned
: TypeReturned extends Generator<infer T, infer R, infer N>
? (...args: Args) => AsyncGenerator<T, R, N>
: TypeReturned extends boolean
? (...args: Args) => boolean | Promise<boolean>
: (...args: Args) => TypeReturned | Promise<TypeReturned>
: {[K in keyof T]: ThreadSafeArgument<T[K]>};
/**
* An object that can retain a reference to a `MemoryManageable` object.

@@ -135,0 +55,0 @@ */

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc