Namespace Architecture
PineTS mimics Pine Script’s namespacing (ta., math., request.) through modular namespace classes attached to the Context.
Common Pattern: The Factory
Every function in a namespace is implemented using a factory pattern:
// Factory receives the Context
export function myFunc(context: any) {
// Returns the actual function to be called by user code
return (arg1, arg2, _callId) => {
// Logic accessing context...
};
}
The param() Function
Each namespace implements its own param() function. This is the interface between the transpiler and the runtime.
- Input: Raw value, Array, or Series.
- Output:
Seriesobject (usually), or raw value (forarraynamespace).
Why different param() implementations?
ta/math: Need to operate on Series. They wrap scalars in Series so.get(0)works universally.array: Operates on Pine Array Objects.param()returns the raw object/value, not a Series wrapper.request: Handles Tuples and needs the Parameter ID for caching secondary contexts.
Property-Like Access (Universal Method Pattern)
In Pine Script, namespace members can be accessed without parentheses:
// Pine Script - property-like access
tr1 = ta.tr // No parentheses
tr2 = ta.tr(true) // With parameter
pi = math.pi // Constant access
PineTS Unified Approach
PineTS simplifies this by implementing everything as methods. The transpiler automatically converts property access to method calls. This eliminates the complexity of maintaining separate getters and methods.
How It Works
- Implementation: All namespace members are implemented as methods (even constants):
// Indicator with optional parameter
export function tr(context: any) {
return (handle_na?: any) => {
const handleNa = handle_na !== undefined ? Series.from(handle_na).get(0) : true;
// ... calculation logic
};
}
// Constant (zero-parameter method)
export function pi(context: any) {
return () => Math.PI;
}
- Transpiler Transformation: When the transpiler encounters namespace member access without parentheses, it automatically adds them:
// User writes:
const tr = ta.tr;
const pi = math.pi;
// Transpiler converts to:
const tr = ta.tr();
const pi = math.pi();
- Explicit Calls: Methods called with parentheses work normally:
// User writes:
const tr = ta.tr(true);
// Transpiler passes through (with parameter wrapping):
const tr = ta.tr(ta.param(true, undefined, 'p0'), '_ta0');
Transpiler Logic
The transformation applies to direct namespace access for known Pine Script namespaces (ta, math, request, array, input):
// ✅ Transformed: Direct namespace access (all members)
ta.tr → ta.tr()
ta.ema → ta.ema()
math.pi → math.pi()
math.abs → math.abs()
// ✅ Not transformed: Already a call
ta.tr() → ta.tr()
ta.tr(true) → ta.tr(true)
// ✅ Not transformed: Variable assignment
const myTa = context.ta;
myTa.tr → myTa.tr (no transformation)
Benefits of This Approach
- Simplicity: Single implementation pattern for all namespace members
- No Special Cases: No need to distinguish between “getters” and “methods”
- Consistency: All namespace functions follow the same factory pattern
- Flexibility: Easy to add optional parameters to any function
- Maintainability: Less code, fewer edge cases to handle
- Performance: No overhead from property getters or descriptor lookups
Examples in PineTS
All namespace members work the same way:
// Indicators with optional parameters
const tr1 = ta.tr; // Auto-converted to ta.tr()
const tr2 = ta.tr(true); // With parameter
// Indicators with required parameters
const ema = ta.ema; // Auto-converted to ta.ema() (transpiler adds params)
const ema2 = ta.ema(close, 14); // With explicit parameters
// Constants (zero-parameter methods)
const pi = math.pi; // Auto-converted to math.pi()
const e = math.e; // Auto-converted to math.e()
Why Not JavaScript Getters?
Prior to this approach, PineTS used JavaScript getters for some namespace members. This created unnecessary complexity:
- ❌ Two different patterns to maintain (getters vs methods)
- ❌ Getters couldn’t accept optional parameters
- ❌ Required special handling in the namespace class
- ❌ More complex barrel file generation
The unified method approach solves all of these issues by treating everything the same way.
Specific Namespace Details
For implementation details of specific namespaces, see their source READMEs: