Serialisation

September 11, 2025 ยท View on GitHub

comptime.ts supports serialisation of various JavaScript values during compile-time evaluation. This document details which types can be safely serialised and their specific behaviors.

const number = comptime(5);
const set = comptime(new Set([1, 2, 3]));
const debug = comptime(process.env.DEBUG ? (x: string) => console.log(x) : () => {});

Compiles to:

const number = 5;
const set = new Set([1, 2, 3]);
const debug = x => console.log(x);

Supported Types Reference

TypeSupportedExampleOutputNotes
NumberYes55All numeric types supported
StringYes"hello""hello"
BooleanYestruetrue
NullYesnullnull
UndefinedYesundefinedundefined
BigIntYes5n5nLarge integers supported
SetYesnew Set([1, 2, 3])new Set([1, 2, 3])Preserves Set semantics
MapYesnew Map([["foo", "bar"]])new Map([["foo", "bar"]])Preserves Map semantics
Typed ArrayYesnew Uint8Array([1, 2, 3])new Uint8Array([1, 2, 3])All TypedArray variants supported
ObjectYes{ foo: "bar" }{ foo: "bar" }Must be non-cyclic
DateYesnew Date(2025, 04, 22)new Date(1747852200000)Serialised as Unix timestamp in milliseconds
Regular ExpressionYes/foo/g, new RegExp("foo", "g")/foo/gPreserved as RegExp literal
PromiseYesPromise.resolve("hello")"hello"Automatically resolved
FunctionYes() => "hello"() => "hello"Closures must be handled carefully [1]
Class InstanceYesnew MyClass(){ foo: "bar" }Only enumerable own properties are preserved
Class constructorYesclass { static x = 5; constructor() { this.foo = "bar"; } }class { static x = 5; constructor() { this.foo = "bar"; } }Closures must be handled carefully [1]
SymbolNoSymbol("foo")TypeErrorSymbol identity is not transferable from comptime to runtime

[1] See "Function Serialisation" section below

Promise Handling

Promises are automatically resolved during serialisation:

// This:
const result = comptime(Promise.resolve(42));

// Becomes:
const result = 42;

If your expression returns a promise, we recommend wrapping it in the comptime function to provide the correct types.

import { readFile } from "fs/promises" with { type: "comptime" };
const result = readFile("file.txt", "utf-8"); // inferred as Promise<string>, but is actually a string

Since readFile is imported as a comptime function, the call is awaited and replaced with the file contents. To correctly type the result, you can use the comptime function to wrap the expression.

import { readFile } from "fs/promises" with { type: "comptime" };
import { comptime } from "comptime.ts" with { type: "comptime" };
const result = comptime(readFile("file.txt", "utf-8")); // inferred as string

This is of course, zero-cost, since the comptime call is also compiled away.

Function Serialisation

Functions are serialised with their source code preserved. However, you must ensure that all values normally accessible in the function's scope are also available after serialisation. The best practice is to always take your function's dependencies as parameters.

Example:

const debug = comptime(process.env.DEBUG ? (x: string) => console.log(x) : () => {});

debug("Initialising app...");

Compiles to (assuming process.env.DEBUG is true):

const debug = (x) => console.log(x);

debug("Initialising app...");

Class Instance Serialisation

Class instances are serialised as plain objects, with some limitations:

  1. Only enumerable own properties are preserved
  2. Prototype chain is not serialised
  3. Methods are not preserved unless explicitly defined as own properties

Example:

class MyClass {
	constructor() {
		this.foo = "bar";
	}
	method() {
		/* ... */
	}
}

// This:
const instance = comptime(new MyClass());

// Becomes:
const instance = { foo: "bar" };

Conversely, if you try to serialise a Class constructor, comptime.ts will serialise it into the class definition.

const MyClass = comptime(
	class {
		static x = 5;
		constructor() {
			this.foo = "bar";
		}
	},
);

Compiles to:

const MyClass = class {
	static x = 5;
	constructor() {
		this.foo = "bar";
	}
};

Best Practices

  1. Avoid Cyclic References: Ensure objects don't contain circular references
  2. Handle Closures Carefully: Be explicit about what values functions need access to
  3. Consider Class Serialisation: Remember that class instances lose their prototype chain
  4. Use Typed Arrays: For binary data, prefer TypedArrays over regular arrays
  5. Handle Promises: Remember that promises are automatically resolved

Common Pitfalls

  1. Symbol Usage: Don't use Symbols in comptime expressions
  2. Environment Variables: Don't rely on runtime environment variables
  3. Class Methods: Remember that class methods are not preserved
  4. Cyclic References: Watch out for circular references in objects
  5. Closure Scope: Ensure all required values are available at compile time

Troubleshooting

If you encounter serialisation issues:

  1. Check if the type is supported (refer to the table above)
  2. Ensure no cyclic references exist in your objects
  3. Verify that all required values are available at compile time
  4. For functions, check that all closure values are properly accessible
  5. For class instances, verify that you're only using enumerable own properties