Cell and Primitive Types¶
Summary¶
gaslamp defines two type aliases to represent spreadsheet values:
Primitive— Safe, normalized values (string,number,boolean,null)Cell— Raw values as received from GAS (includesDateobjects)
These types separate concerns: what GAS gives us (raw) from what we work with internally (safe). This boundary makes data handling predictable and safe.
Philosophy: Accept Reality at the Boundary¶
The core idea: Don't pretend GAS data is clean. Accept it as-is at the boundary, then normalize internally.
Why this matters:
- GAS returns different types for different values (
strings,numbers,Datesas objects) - You can't predict types until runtime
- Fighting this is futile; accepting it is liberating
The design says: "Here's where raw data enters (boundary = Cell).
Here's where we work safely (internal = Primitive)."
The Problem: GAS Data Types Are Unpredictable¶
In standard Google Apps Script:
// What type is this?
const value = sheet.getRange("A1").getValue();
GAS returns:
- Text as strings
- Numbers as numbers
- Dates as JavaScript
Dateobjects - Empty cells as empty strings
The trouble: You can't safely write code that assumes anything about the type. You can't validate. You can't compute with confidence. Errors only appear at runtime.
The Solution: Separate Raw and Normalized¶
gaslamp draws a clear line:
At the boundary (reading from GAS):
type Cell = Primitive | Date;
Accept exactly what GAS gives you.
Date is there because GAS returns Date objects.
Inside DataFrame (working with data):
type Primitive = string | number | boolean | null;
Work only with normalized values. Type-safe. Predictable. Testable.
When to Use Each Type¶
| Scenario | Type | Why |
|---|---|---|
| Reading from GAS sheets | Cell |
This is what GAS gives you |
| DataFrame columns | Primitive |
Normalized and type-safe |
| Writing to JSON or APIs | Primitive |
Universal serialization |
| User-facing function arguments | Primitive |
Clear contract: "I expect numbers, not Dates" |
Design Insight: Why Not Date in Primitive?¶
You might ask: "Why not just include Date in Primitive?"
Because:
- Dates are special. JSON can't serialize them natively. Spreadsheets don't have a "date type" — they have numbers formatted as dates.
- Normalization happens at the boundary. When you read a date from GAS, you decide: convert to ISO string? Keep as timestamp? This decision belongs at the edge, not in the core.
- Clarity. When a function accepts
Primitive, you know it's type-safe. When it acceptsCell, you know you're at the raw boundary.
Extending This Design¶
If GAS adds new return types in the future, the pattern scales easily:
// Example: if GAS ever returns Set objects
type Cell = Primitive | Date | Set<any>;
// All DataFrame operations continue working because the
// architecture was built around: "Cell = what GAS gives us"
The philosophy survives; only the implementation adapts.
Related¶
- Data Orientation Conversions — How to transform between
PrimitiveandCell - BareFrame-First Design — How DataFrames use these types internally