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.
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:
// 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:
- Transform using
BareFrame(no validation) - Validate using
FlameFrame.from()(gate that separates good from bad) - Handle the results (passed rows to next step, failed rows to error log)
Example workflow:
// 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):
- Input: A
BareFramewith unknown data types - Output: Two results:
passed: AFlameFrame<T>containing only rows that passed all schema guardsfailed: ABareFramecontaining invalid rows with aninvalid_messagecolumn explaining what went wrong
What Is a Schema?¶
A schema is a map of column names to type guard functions:
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:
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:
- Separation of concerns
BareFramedoes transformation (fast, simple, no validation overhead)-
FlameFramedoes validation (explicit gate, clear error handling) -
Clear error handling
- Transformation never fails (it just works with whatever data is there)
-
Validation has a clear contract: "these rows are good, these are bad"
-
Reusable gates
- Same schema can validate input AND output
-
Easy to build validation chains
-
Performance
- Don't pay validation cost unless you need it
- BareFrame operations stay fast
Example of reusing a schema:
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:
// 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:
// 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:
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:
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 |
Related¶
- BareFrame-First Design — Transformation operations
- Cell and Primitive Types — Understanding what types can be validated
- FlameWright (future guide) — Deep dive into type guards and factories