Skip to content

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 (includes Date objects)

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, Dates as 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:

JavaScript
// What type is this?
const value = sheet.getRange("A1").getValue();

GAS returns:

  • Text as strings
  • Numbers as numbers
  • Dates as JavaScript Date objects
  • 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):

TypeScript
type Cell = Primitive | Date;

Accept exactly what GAS gives you. Date is there because GAS returns Date objects.

Inside DataFrame (working with data):

TypeScript
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:

  1. Dates are special. JSON can't serialize them natively. Spreadsheets don't have a "date type" — they have numbers formatted as dates.
  2. 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.
  3. Clarity. When a function accepts Primitive, you know it's type-safe. When it accepts Cell, you know you're at the raw boundary.

Extending This Design

If GAS adds new return types in the future, the pattern scales easily:

TypeScript
// 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.