Skip to content

FlameFrame and Validation

Summary

FlameFrame<T> is a schema-validated wrapper around BareFrame. It lets you validate that data matches an expected type schema and separates valid rows from invalid ones.

JavaScript
const schema = {
  name: gaslamp.FlameGuards.isString,
  age: gaslamp.FlameGuards.isNumber,
};

const df = gaslamp.BareFrame.fromSheet(sheet);
const { passed, failed } = gaslamp.FlameFrame.from(df, schema);

// passed: only rows that match the schema
// failed: invalid rows with error messages

FlameFrame is not for data transformation. Use BareFrame for that, then validate the result.

Philosophy: Validation Is a Gate, Not a Transformer

In standard Google Apps Script, validation and transformation are mixed together:

JavaScript
// Standard GAS: validation mixed with processing
const results = [];
for (const row of data) {
  const age = parseInt(row.age, 10);
  if (isNaN(age)) {
    // handle error... but how? skip? throw? log?
  }
  // ... more processing ...
}

The problem: When do you validate? Before transformation? After? It's unclear, and errors get tangled with logic.

The Solution: Separate Validation from Transformation

gaslamp's approach:

  1. Transform using BareFrame (no validation)
  2. Validate using FlameFrame.from() (gate that separates good from bad)
  3. Handle the results (passed rows to next step, failed rows to error log)

Example workflow:

JavaScript
// Step 1: Read and transform
let df = gaslamp.BareFrame.fromSheet(sheet);
df = df.filter(row => row.get('archived') === false); // BareFrame operations
df = df.select(['name', 'age', 'email']);

// Step 2: Validate
const schema = {
  name: gaslamp.FlameGuards.isString,
  age: gaslamp.FlameGuards.isNumber,
  email: gaslamp.FlameGuards.isString,
};
const { passed, failed } = gaslamp.FlameFrame.from(df, schema);

// Step 3: Handle results
if (failed.length > 0) {
  console.error('Invalid rows:');
  console.error(failed.toString()); // shows invalid_message column
}

// Continue with validated data
const validated = passed;

How It Works: Validation Gates

The Input-Output Contract

When you call FlameFrame.from(df, schema):

  1. Input: A BareFrame with unknown data types
  2. Output: Two results:
  3. passed: A FlameFrame<T> containing only rows that passed all schema guards
  4. failed: A BareFrame containing invalid rows with an invalid_message column explaining what went wrong

What Is a Schema?

A schema is a map of column names to type guard functions:

JavaScript
const schema = {
  age: gaslamp.FlameGuards.isNumber,      // Guard: checks if value is a number
  name: gaslamp.FlameGuards.isString,     // Guard: checks if value is a string
  email: (val) => gaslamp.FlameGuards.isString(val) && val.includes('@'), // Custom guard
};

Each guard is called for every cell in its column. If any cell fails, the entire row is invalid.

Error Messages

When a row fails validation, failed includes an invalid_message column:

JavaScript
const { passed, failed } = gaslamp.FlameFrame.from(df, schema);

if (failed.length > 0) {
  // failed contains columns: [original columns] + invalid_message
  // Example invalid_message: "age: got string ('25') | email: got null (null)"
  console.error(failed.toString());
}

Error messages are human-readable and include:

  • Which field failed
  • What type was actually found
  • The preview of the problematic value

Design Insight: Why Not Validate Inside BareFrame?

You might ask: "Why not validate during transformation?"

Because:

  1. Separation of concerns
  2. BareFrame does transformation (fast, simple, no validation overhead)
  3. FlameFrame does validation (explicit gate, clear error handling)

  4. Clear error handling

  5. Transformation never fails (it just works with whatever data is there)
  6. Validation has a clear contract: "these rows are good, these are bad"

  7. Reusable gates

  8. Same schema can validate input AND output
  9. Easy to build validation chains

  10. Performance

  11. Don't pay validation cost unless you need it
  12. BareFrame operations stay fast

Example of reusing a schema:

JavaScript
const schema = { age: gaslamp.FlameGuards.isNumber, name: gaslamp.FlameGuards.isString };

// Validate on input
const { passed: valid1 } = gaslamp.FlameFrame.from(inputDf, schema);

// Transform
const transformed = valid1.filter(...).select(...);

// Re-validate output
const { passed: valid2, failed } = gaslamp.FlameFrame.from(transformed, schema);

Using Custom Guards

Beyond the predefined guards in FlameGuards, you can write custom validation:

JavaScript
// Custom inline guard
const schema = {
  age: (val) => typeof val === 'number' && val >= 0 && val <= 120,
  country: (val) => ['USA', 'Canada', 'Mexico'].includes(val),
};

const { passed, failed } = gaslamp.FlameFrame.from(df, schema);

Or use FlameWright factories for more complex rules:

JavaScript
// import { FlameWright } from 'gaslamp';

const schema = {
  age: gaslamp.FlameWright.inRange(0, 120),
  status: gaslamp.FlameWright.enumOf(['active', 'inactive']),
  email: gaslamp.FlameWright.isString, // isString with extra checks
};

Strict Mode: Fail Fast

By default, FlameFrame.from collects all errors. For fast-fail validation:

JavaScript
try {
  gaslamp.FlameFrame.from(df, schema, { strict: true });
} catch (e) {
  // Throws on the first invalid cell
  console.error(e.message); // "FlameFrame validation failed | row 0 | field 'age' | got string ('abc')"
}

Integration with the Workflow

FlameFrame fits into the larger data pipeline:

Text Only
GAS Sheet
BareFrame.fromSheet()     [raw data]
df.filter(...).select()   [transform with BareFrame]
FlameFrame.from(df, schema)  [validate]
{ passed, failed }        [split results]
    ├─ passed → next step [validated data]
    └─ failed → error log [what went wrong]

When to Use FlameFrame vs BareFrame

Scenario Use
Reading raw data from GAS BareFrame.fromSheet()
Transforming data (filter, select, groupBy) BareFrame methods
Ensuring data matches a schema FlameFrame.from()
Handling validation errors Split { passed, failed }
Fast analysis without validation BareFrame alone