Skip to content

Commit

Permalink
Add custom serialize and deserialize options to both thread seria…
Browse files Browse the repository at this point in the history
…lization implementations
  • Loading branch information
lemonmade committed Aug 26, 2024
1 parent b5ca2bd commit f2463ec
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .changeset/seven-baboons-fetch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@quilted/threads': minor
---

Add custom `serialize` and `deserialize` options to both thread serialization implementations
28 changes: 28 additions & 0 deletions packages/threads/source/Thread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,34 @@ export interface ThreadFunctions {
deserialize(id: string, thread: AnyThread): AnyFunction;
}

/**
* Options to customize the creation of a `ThreadSerialization` instance.
*/
export interface ThreadSerializationOptions {
/**
* A custom function to run when serializing values. If this function returns `undefined`,
* the default serialization will be used. You can also call the second argument to this function
* to apply the default serialization to subsets of the value.
*/
serialize?(
value: object,
defaultSerialize: (value: unknown) => unknown,
thread: AnyThread,
transferable?: any[],
): any | undefined;

/**
* A custom function to run when deserializing values. If this function returns `undefined`,
* the default deserialization will be used. You can also call the second argument to this function
* to apply the default deserialization to subsets of the value.
*/
deserialize?(
value: object,
defaultDeserialize: (value: unknown) => unknown,
thread: AnyThread,
): any | undefined;
}

/**
* Options to customize the creation of a `Thread` instance.
*/
Expand Down
1 change: 1 addition & 0 deletions packages/threads/source/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export {
type ThreadFunctions,
type ThreadSerialization,
type ThreadMessageMap,
type ThreadSerializationOptions,
} from './Thread.ts';
export {ThreadBroadcastChannel} from './threads/ThreadBroadcastChannel.ts';
export {ThreadBrowserWebSocket} from './threads/ThreadBrowserWebSocket.ts';
Expand Down
50 changes: 48 additions & 2 deletions packages/threads/source/serialization/ThreadSerializationJSON.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import type {ThreadSerializable} from '../types.ts';
import type {AnyThread, ThreadSerialization} from '../Thread.ts';
import type {
AnyThread,
ThreadSerialization,
ThreadSerializationOptions,
} from '../Thread.ts';
import {SERIALIZE_METHOD, TRANSFERABLE} from '../constants.ts';

import {isBasicObject, isIterator} from './shared.ts';
Expand Down Expand Up @@ -29,6 +33,14 @@ const UINT32_ARRAY = '_@u32';
* - `Uint8Array`
*/
export class ThreadSerializationJSON implements ThreadSerialization {
readonly #customSerializer?: ThreadSerializationOptions['serialize'];
readonly #customDeserializer?: ThreadSerializationOptions['deserialize'];

constructor(options?: ThreadSerializationOptions) {
this.#customSerializer = options?.serialize;
this.#customDeserializer = options?.deserialize;
}

/**
* Serializes a value into a JSON-compatible format that can be transferred between threads.
*/
Expand All @@ -41,6 +53,7 @@ export class ThreadSerializationJSON implements ThreadSerialization {
thread: AnyThread,
transferable?: any[],
seen = new Map<unknown, unknown>(),
isApplyingDefault = false,
): any {
if (value == null) return value;

Expand All @@ -50,6 +63,21 @@ export class ThreadSerializationJSON implements ThreadSerialization {
seen.set(value, undefined);

if (typeof value === 'object') {
if (this.#customSerializer && !isApplyingDefault) {
const customValue = this.#customSerializer(
value,
(value) =>
this.#serializeInternal(value, thread, transferable, seen, true),
thread,
transferable,
);

if (customValue !== undefined) {
seen.set(value, customValue);
return customValue;
}
}

if ((value as any)[TRANSFERABLE]) {
transferable?.push(value as any);
seen.set(value, value);
Expand Down Expand Up @@ -188,8 +216,26 @@ export class ThreadSerializationJSON implements ThreadSerialization {
return this.#deserializeInternal(value, thread);
}

#deserializeInternal(value: unknown, thread: AnyThread): any {
#deserializeInternal(
value: unknown,
thread: AnyThread,
isApplyingDefault = false,
): any {
if (value == null) return value;

if (typeof value === 'object') {
if (this.#customDeserializer && !isApplyingDefault) {
const customValue = this.#customDeserializer(
value,
(value) => this.#deserializeInternal(value, thread, true),
thread,
);

if (customValue !== undefined) {
return customValue;
}
}

if (value == null) {
return value as any;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import type {ThreadSerializable} from '../types.ts';
import type {AnyThread, ThreadSerialization} from '../Thread.ts';
import type {
AnyThread,
ThreadSerialization,
ThreadSerializationOptions,
} from '../Thread.ts';
import {SERIALIZE_METHOD, TRANSFERABLE} from '../constants.ts';

import {isBasicObject, isIterator} from './shared.ts';
Expand All @@ -18,6 +22,14 @@ const ASYNC_ITERATOR = '_@i';
* memory management system by setting the `garbageCollection: 'weak-ref'` option.
*/
export class ThreadSerializationStructuredClone implements ThreadSerialization {
readonly #customSerializer?: ThreadSerializationOptions['serialize'];
readonly #customDeserializer?: ThreadSerializationOptions['deserialize'];

constructor(options?: ThreadSerializationOptions) {
this.#customSerializer = options?.serialize;
this.#customDeserializer = options?.deserialize;
}

/**
* Serializes a value into a structured cloning-compatible format that can be transferred between threads.
*/
Expand All @@ -30,6 +42,7 @@ export class ThreadSerializationStructuredClone implements ThreadSerialization {
thread: AnyThread,
transferable?: any[],
seen = new Map<unknown, unknown>(),
isApplyingDefault = false,
): any {
if (value == null) return value;

Expand All @@ -39,6 +52,21 @@ export class ThreadSerializationStructuredClone implements ThreadSerialization {
seen.set(value, undefined);

if (typeof value === 'object') {
if (this.#customSerializer && !isApplyingDefault) {
const customValue = this.#customSerializer(
value,
(value) =>
this.#serializeInternal(value, thread, transferable, seen, true),
thread,
transferable,
);

if (customValue !== undefined) {
seen.set(value, customValue);
return customValue;
}
}

if ((value as any)[TRANSFERABLE]) {
transferable?.push(value as any);
seen.set(value, value);
Expand Down Expand Up @@ -129,8 +157,26 @@ export class ThreadSerializationStructuredClone implements ThreadSerialization {
return this.#deserializeInternal(value, thread);
}

#deserializeInternal(value: unknown, thread: AnyThread): any {
#deserializeInternal(
value: unknown,
thread: AnyThread,
isApplyingDefault = false,
): any {
if (value == null) return value;

if (typeof value === 'object') {
if (this.#customDeserializer && !isApplyingDefault) {
const customValue = this.#customDeserializer(
value,
(value) => this.#deserializeInternal(value, thread, true),
thread,
);

if (customValue !== undefined) {
return customValue;
}
}

if (value == null) {
return value as any;
}
Expand Down

0 comments on commit f2463ec

Please sign in to comment.