Examples/Custom Serializer

Custom Serializer

Shows how to implement and use a custom serializer for message encoding. This is useful for using binary formats like MessagePack, adding compression, or wrapping messages with metadata. Both host and worker must use the same serializer.

Custom Serializer

Shows how to implement and use a custom serializer for message encoding. This is useful for using binary formats like MessagePack, adding compression, or wrapping messages with metadata. Both host and worker must use the same serializer.

Additional Files

messages.ts
/**
 * Shared message definitions for the custom serializer example
 */

import { DefineMessages } from 'isolated-workers';

/**
 * Message types for the serializer example
 */
export type Messages = DefineMessages<{
  echo: {
    payload: { data: string };
    result: { echoed: string; serializer: string };
  };
}>;
host.ts
/**
 * Custom Serializer Example - Host (Client) Side
 *
 * This example demonstrates how to use a custom serializer for
 * message encoding/decoding. Both host and worker must use the
 * same serializer class.
 */

import { createWorker } from 'isolated-workers';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
import type { Messages } from './messages.js';
import { verboseSerializer } from './serializer.js';

const __dirname = dirname(fileURLToPath(import.meta.url));

async function main() {
  console.log('Starting custom serializer example...\n');
  console.log('Using:', verboseSerializer.constructor.name);
  console.log(
    'Terminator:',
    JSON.stringify(verboseSerializer.terminator),
    '\n'
  );

  const worker = await createWorker<Messages>({
    script: join(__dirname, 'worker.ts'),
    timeout: 10000,
    serializer: verboseSerializer,
  });

  console.log(`Worker spawned with PID: ${worker.pid}\n`);

  try {
    // Send a message using our custom serializer
    console.log('Sending message with custom serialization...');
    const result = await worker.send('echo', {
      data: 'Hello, Custom Serializer!',
    });

    console.log('\nResult received:');
    console.log('  Echoed:', result.echoed);
    console.log('  Worker serializer:', result.serializer);
  } finally {
    await worker.close();
    console.log('\nWorker closed successfully');
  }
}

main().catch((err) => {
  console.error('Error:', err);
  process.exit(1);
});
worker.ts
/**
 * Custom Serializer Example - Worker (Server) Side
 *
 * The worker must use the same serializer as the host.
 */

import { startWorkerServer, Handlers } from 'isolated-workers';
import type { Messages } from './messages.js';
import { verboseSerializer } from './serializer.js';

const handlers: Handlers<Messages> = {
  echo: ({ data }) => {
    console.log(`Worker received: "${data}"`);
    return {
      echoed: data.toUpperCase(),
      serializer: verboseSerializer.constructor.name,
    };
  },
};

async function main() {
  console.log('Worker starting with custom serializer...');
  console.log('Using:', verboseSerializer.constructor.name);

  const server = await startWorkerServer(handlers, {
    serializer: verboseSerializer,
  });

  console.log('Worker ready');

  process.on('SIGTERM', async () => {
    await server.stop();
    process.exit(0);
  });
}

main().catch((err) => {
  console.error('Worker error:', err);
  process.exit(1);
});
serializer.ts
/**
 * Custom Serializer Implementation
 *
 * This example shows how to create a custom serializer.
 * A real use case might be using MessagePack, Protocol Buffers,
 * or adding compression/encryption.
 *
 * IMPORTANT: The serializer class must be named (not anonymous)
 * for mismatch detection to work properly.
 */

import { Serializer } from 'isolated-workers';

/**
 * A verbose JSON serializer that adds metadata to each message.
 * This is for demonstration - in production you might use a more
 * efficient binary format.
 */
export class VerboseJsonSerializer extends Serializer {
  private static messageCount = 0;

  serialize<T>(data: T): string {
    VerboseJsonSerializer.messageCount++;
    const wrapped = {
      _serializer: 'VerboseJsonSerializer',
      _messageId: VerboseJsonSerializer.messageCount,
      _timestamp: Date.now(),
      data,
    };
    return JSON.stringify(wrapped);
  }

  deserialize<T>(input: string | Uint8Array): T {
    const str =
      typeof input === 'string' ? input : new TextDecoder().decode(input);
    const wrapped = JSON.parse(str) as {
      _serializer: string;
      _messageId: number;
      _timestamp: number;
      data: T;
    };

    // Log metadata (in real code, you might use this for debugging/tracing)
    console.log(
      `[SERIALIZER] Message #${wrapped._messageId} from ${wrapped._serializer}`
    );

    return wrapped.data;
  }

  // Custom terminator - using double newline to avoid conflicts
  terminator = '\n\n';
}

// Export a singleton instance for convenience
export const verboseSerializer = new VerboseJsonSerializer();

Running the Example

Run the example

bash
pnpm run:custom-serializer