Overview
The TypeScript transform lets you execute custom TypeScript (or JavaScript) code on each record in your pipeline. This is useful for:- Complex data transformations not supported by SQL
- Custom business logic with type safety
- Data parsing and formatting
- Conditional transformations based on complex rules
- Expanding one input row into many output rows, or filtering rows out
Configuration
Parameters
string
required
Must be
scriptstring
required
The source or transform to read data from
string
required
One of
typescript, ts, javascript, or js. TypeScript values are
transpiled to JavaScript at pipeline build time; plain JavaScript is passed
through unchanged.string
required
The column that uniquely identifies each row
object
Mapping of output field name to Arrow type. Required whenever
invoke
returns a shape different from the input — any field you return that isn’t
declared here will be dropped. If omitted, the output schema is inherited
from the input. Supported types: string (alias for utf8), int8,
int16, int32, int64, uint8, uint16, uint32, uint64, float16,
float32, float64, boolean, binary, date32, date64, timestamp,
time32, time64, duration, interval, null.string
required
Your TypeScript code. Must define a top-level
invoke(data) function that
receives a single record and returns one of: the transformed record object,
null to filter the record out, or an array of record objects to expand one
input row into many output rows (empty array emits nothing).integer
default:"4"
Number of sandboxed script instances that process rows in parallel. Higher
values improve throughput on CPU-bound scripts at the cost of more memory.
Use
1 if your script relies on rows being processed in order.integer
default:"0"
Minimum number of rows to accumulate before invoking the script. Smaller
upstream batches are combined until this threshold is reached, which reduces
per-call overhead on high-volume streams with tiny batches.
0 disables
accumulation and processes each batch immediately.Script structure
Your script must define a top-levelinvoke function that:
- Accepts a single
dataparameter (a plain JS object representing one row) - Returns one of:
- A record object — the transformed row
null— filter this row out of the output- An array of record objects — expand this input row into many output rows (return
[]to emit nothing, includenullentries in the array to skip specific rows)
- Can return a different shape than the input when using the
schemaconfiguration
_gs_op column (insert/update/delete marker) is automatically copied from the input row to each output row, so your script does not need to set it.
Basic example
Declare aschema whenever invoke adds new fields. Without one, Turbo infers the output schema from the input and any new keys you return are dropped.
Filtering records
Returnnull to filter out records that don’t match your criteria:
Custom output schema
If you omit theschema field, the output schema is inherited from the input. Any new fields you add in invoke will be dropped because they aren’t in the schema. Declare a schema whenever your output differs from the input:
Input/output format
Thedata parameter is a plain JavaScript object with your record’s fields. Values use native JS types — strings stay strings, integers/floats become numbers, booleans stay booleans, list columns become arrays, struct columns become nested objects.
Expanding one row into many
Return an array of objects frominvoke to emit multiple output rows for a single input row. This is useful for unpacking nested arrays or cross-joining with a lookup list. Returning [] drops the row entirely; null entries inside the array are skipped.
Examples
Example: Type-safe value formatting
Convert wei to ETH with TypeScript type safety:Example: Parse JSON fields
Extract data from JSON strings with type safety:Example: Complex conditional logic
Apply different transformations based on conditions:Example: String manipulation
Clean and format text data:Example: Array and object manipulation
Work with complex data structures:TypeScript features
Type safety benefits
TypeScript provides:- Compile-time type checking: Catch errors before deployment
- IntelliSense: Better IDE autocomplete and suggestions
- Refactoring support: Safer code changes
- Self-documenting code: Types serve as inline documentation
Supported TypeScript features
Type Annotations
Type Annotations
- Interface definitions - Type aliases - Union and intersection types -
Generic types - Optional properties (
?) - Readonly properties
Modern JavaScript
Modern JavaScript
- All ES6+ features (arrow functions, destructuring, spread operator) -
JSON.parse()andJSON.stringify()-Mathobject (Math.floor, Math.random, etc.) -Dateobject - String methods (split, substring, replace, etc.) - Array methods (map, filter, reduce, etc.) - Object methods (Object.keys, Object.values, etc.) - BigInt for large number handling
Type Guards
Type Guards
typeofchecks -instanceofchecks - Custom type predicates - Discriminated unions
Not available
What is available
The runtime is a QuickJS interpreter running inside WebAssembly. In addition to standard ES2020 syntax, you can use:JSON.parse/JSON.stringifyMath,Date,BigInt,RegExp,Map,Set- All
String,Array, andObjectprototype methods console.log/console.error— writes to the pipeline’s stderr log (visible viagoldsky turbo logs, but not queryable from your sink)
Error Handling
Always include error handling in your scripts:Performance tuning
Each transform exposes two optional knobs for throughput:parallelism and batch_size.
Parallelism
Controls how many sandboxed script instances process rows in parallel. Each instance handles a slice of the incoming batch.- Default:
4 - Higher values: More concurrency, proportionally more memory
1: Sequential processing — use this when your script depends on row order
Batch size
Controls how many rows are accumulated beforeinvoke is called. Smaller upstream batches are combined until the threshold is reached, which reduces per-call overhead.
- Default:
0(disabled — each upstream batch is processed immediately) - Higher values: Better throughput on high-volume streams with tiny batches
- Trade-off: Higher values increase end-to-end latency as rows wait to accumulate
Example
When to tune these parameters
Performance considerations
Execution Speed
Execution Speed
- TypeScript is transpiled to JavaScript once when the pipeline starts (no per-record transpile cost) - Each row is executed inside a QuickJS interpreter, which is significantly slower than native SQL transforms - Every record is evaluated individually - Keep scripts simple and avoid expensive per-row work (regex compilation, JSON.parse on huge blobs, allocating large temporary objects, etc.)
Memory Usage
Memory Usage
- Scripts run in a sandboxed environment with limited memory - Avoid
creating large data structures - Process records one at a time, don’t
accumulate state - Clean up temporary variables - Higher
parallelismvalues increase memory usage proportionally
Optimization Tips
Optimization Tips
- Pre-define types and interfaces outside the function - Use built-in
methods (Array.map, filter) instead of manual loops - Avoid nested loops and
recursive functions - Cache frequently accessed values in variables - Use
parallelismandbatch_sizeto tune throughput for your workload
Debugging
Add debug fields
console.log output goes to the pipeline’s stderr log and is not visible in your sink. To inspect intermediate values in the data you actually ship, add debug fields to the returned record:
Test locally
Before deploying, test your logic in a TypeScript playground or Node.js:Best practices
1
Use SQL when possible
SQL transforms are faster and more efficient. Only use TypeScript for logic that SQL cannot express.
2
Define clear types
Define interfaces for your input and output types:
3
Use null to filter records
Return
null to filter out records that don’t match your criteria:4
Handle null and undefined
Always check for null/undefined values:
5
Use type guards
Validate data types at runtime: