Serialisation
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
Type | Supported | Example | Output | Notes |
---|---|---|---|---|
Number | Yes | 5 |
5 |
All numeric types supported |
String | Yes | "hello" |
"hello" |
|
Boolean | Yes | true |
true |
|
Null | Yes | null |
null |
|
Undefined | Yes | undefined |
undefined |
|
BigInt | Yes | 5n |
5n |
Large integers supported |
Set | Yes | new Set([1, 2, 3]) |
new Set([1, 2, 3]) |
Preserves Set semantics |
Map | Yes | new Map([["foo", "bar"]]) |
new Map([["foo", "bar"]]) |
Preserves Map semantics |
Typed Array | Yes | new Uint8Array([1, 2, 3]) |
new Uint8Array([1, 2, 3]) |
All TypedArray variants supported |
Object | Yes | { foo: "bar" } |
{ foo: "bar" } |
Must be non-cyclic |
Date | Yes | new Date(2025, 04, 22) |
new Date(1747852200000) |
Serialised as Unix timestamp in milliseconds |
Regular Expression | Yes | /foo/g , new RegExp("foo", "g") |
/foo/g |
Preserved as RegExp literal |
Promise | Yes | Promise.resolve("hello") |
"hello" |
Automatically resolved |
Function | Yes | () => "hello" |
() => "hello" |
Closures must be handled carefully [1] |
Class Instance | Yes | new MyClass() |
{ foo: "bar" } |
Only enumerable own properties are preserved |
Class constructor | Yes | class { static x = 5; constructor() { this.foo = "bar"; } } |
class { static x = 5; constructor() { this.foo = "bar"; } } |
Closures must be handled carefully [1] |
Symbol | No | Symbol("foo") |
TypeError | Symbol 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:
- Only enumerable own properties are preserved
- Prototype chain is not serialised
- 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
- Avoid Cyclic References: Ensure objects don't contain circular references
- Handle Closures Carefully: Be explicit about what values functions need access to
- Consider Class Serialisation: Remember that class instances lose their prototype chain
- Use Typed Arrays: For binary data, prefer TypedArrays over regular arrays
- Handle Promises: Remember that promises are automatically resolved
Common Pitfalls
- Symbol Usage: Don't use Symbols in comptime expressions
- Environment Variables: Don't rely on runtime environment variables
- Class Methods: Remember that class methods are not preserved
- Cyclic References: Watch out for circular references in objects
- Closure Scope: Ensure all required values are available at compile time
Troubleshooting
If you encounter serialisation issues:
- Check if the type is supported (refer to the table above)
- Ensure no cyclic references exist in your objects
- Verify that all required values are available at compile time
- For functions, check that all closure values are properly accessible
- For class instances, verify that you're only using enumerable own properties