Skip to content

FlameWright: Runtime Type Validation

FlameWright (and FlameGuards) is the runtime type validation system in gaslamp. Since Google Apps Script does not support TypeScript at runtime, type checking must happen in code — and FlameWright is the tool for that.

Combined with FlameFrame, you can validate every column in a DataFrame in one step — and when invalids are found, errors tells you exactly which row and field caused the problem. FlameWright is most powerful when used with FlameFrame, but it also works standalone on any value in your code.

JavaScript
// With FlameFrame: validate DataFrame columns
const { passed, failed } = gaslamp.FlameFrame.from(df, {
  name: gaslamp.FlameGuards.isString,
  age:  gaslamp.FlameGuards.isNumber,
});

// Standalone: validate any value
if (!gaslamp.FlameGuards.isNumber(value)) {
  throw new Error("Expected a number");
}

For applying a schema to DataFrame columns, see FlameFrame and Schema Validation.


Core Concept: Flame Guards

A Flame guard is a function that takes an unknown value and returns true or false.

JavaScript
const isString = (value) => typeof value === "string";

isString("hello"); // true
isString(42);      // false

In GAS, use the predefined guards from FlameGuards or build your own with FlameWright.


FlameGuards — Predefined Guards

FlameGuards provides ready-to-use guards for common types.

Primitive guards

JavaScript
gaslamp.FlameGuards.isString("hello");      // true
gaslamp.FlameGuards.isNumber(42);           // true — excludes NaN
gaslamp.FlameGuards.isBoolean(true);        // true
gaslamp.FlameGuards.isNull(null);           // true
gaslamp.FlameGuards.isUndefined(undefined); // true

Object guards

JavaScript
gaslamp.FlameGuards.isValidDate(new Date());          // true
gaslamp.FlameGuards.isArray([1, 2, 3]);               // true
gaslamp.FlameGuards.isMap(new Map());                 // true
gaslamp.FlameGuards.isPlainObject({ name: "Alice" }); // true

GAS sheet value guards

Sheet cells often contain empty strings or mixed types. These guards handle the most common cases.

JavaScript
gaslamp.FlameGuards.isEmptyString("");         // true — blank cell
gaslamp.FlameGuards.isStringOrNumber("Alice"); // true — typical cell value
gaslamp.FlameGuards.isStringOrNumber(42);      // true — NaN also passes (use isNumber to exclude it)
gaslamp.FlameGuards.isTable([[1, 2], [3, 4]]); // true — getValues() output

FlameWright — Parameterized Validators

FlameWright builds validators for more complex types.

arrayOf — typed array

JavaScript
const isStringArray = gaslamp.FlameWright.arrayOf(gaslamp.FlameGuards.isString);
isStringArray(["Alice", "Bob"]); // true
isStringArray(["Alice", 42]);    // false

enumOf — allowed values

JavaScript
const isStatus = gaslamp.FlameWright.enumOf(["active", "inactive"]);
isStatus("active");   // true
isStatus("deleted");  // false

inRange — number range

JavaScript
const isPercent  = gaslamp.FlameWright.inRange(0, 100);
const isPositive = gaslamp.FlameWright.inRange(0);

isPercent(50);   // true
isPercent(101);  // false
isPositive(999); // true

nullable — allow null

JavaScript
const nullableString = gaslamp.FlameWright.nullable(gaslamp.FlameGuards.isString);
nullableString(null);  // true — blank cell may come in as null
nullableString("abc"); // true
nullableString(42);    // false

anyOf — union type

JavaScript
const isStringOrDate = gaslamp.FlameWright.anyOf([
  gaslamp.FlameGuards.isString,
  gaslamp.FlameGuards.isValidDate,
]);
isStringOrDate("2024-01-01"); // true
isStringOrDate(new Date());   // true
isStringOrDate(42);           // false

fromSchema — object validator

JavaScript
const isUser = gaslamp.FlameWright.fromSchema({
  name: gaslamp.FlameGuards.isString,
  age:  gaslamp.FlameGuards.isNumber,
});
isUser({ name: "Alice", age: 30 }); // true
isUser({ name: "Bob" });            // false — age missing

partial — optional fields

partial wraps each field to also accept undefined, then pass to fromSchema:

JavaScript
const partialSchema = gaslamp.FlameWright.partial({
  name: gaslamp.FlameGuards.isString,
  age:  gaslamp.FlameGuards.isNumber,
});
const isPartialUser = gaslamp.FlameWright.fromSchema(partialSchema);
isPartialUser({ name: "Alice" }); // true — age is optional
isPartialUser({});                // true — all fields optional
isPartialUser({ name: 123 });    // false — name must still be a string

extend — merge schemas

JavaScript
const baseSchema = { name: gaslamp.FlameGuards.isString };
const extraSchema = { age: gaslamp.FlameGuards.isNumber };

const isUser = gaslamp.FlameWright.fromSchema(
  gaslamp.FlameWright.extend(baseSchema, extraSchema)
);
isUser({ name: "Alice", age: 30 }); // true

FlameGaslets — GAS Object Guards

GAS Sheet and Spreadsheet objects cannot be reliably checked with instanceof across script boundaries. FlameGaslets uses duck-typing instead.

JavaScript
function processInput(value) {
  if (gaslamp.FlameGaslets.isSheet(value)) {
    const df = gaslamp.BareFrame.fromSheet(value);
    console.log(df.length);
    return;
  }
  if (gaslamp.FlameGaslets.isSpreadsheet(value)) {
    const sheet = value.getActiveSheet();
    console.log(sheet.getName());
    return;
  }
  throw new Error("Expected a Sheet or Spreadsheet object");
}

Validation Helpers

ensure — validate and return typed value

Use ensure when you need the validated value assigned to a variable. Throws TypeError with message "ensure failed | @<at> | "<label>" | got: <value>".

JavaScript
function processRow(row) {
  const name = gaslamp.ensure(
    row.get("name"),
    gaslamp.FlameGuards.isString,
    { at: "processRow", label: "name" },
  );
  // Throws: TypeError: ensure failed | @processRow | "name" | got: 123
  console.log(name.toUpperCase()); // name is guaranteed to be a string
}

assert — narrow type in-place

Use assert when you want to narrow the type of an existing variable. Throws TypeError with message "assert failed | @<at> | "<label>" | got: <value>".

JavaScript
function calcTotal(price) {
  gaslamp.assert(
    price,
    gaslamp.FlameGuards.isNumber,
    { at: "calcTotal", label: "price" },
  );
  // Throws: TypeError: assert failed | @calcTotal | "price" | got: "abc"
  return price * 1.1; // price is guaranteed to be a number
}

explain — field-level error messages

Use explain to get human-readable error messages per field. Useful for logging validation problems when processing rows.

JavaScript
// Field present but wrong type → "invalid"
const errors = gaslamp.explain(
  { name: 123, age: 30 },
  { name: gaslamp.FlameGuards.isString, age: gaslamp.FlameGuards.isNumber },
);
// ["name: invalid (123)"]

// Field missing entirely → "missing"
const errors2 = gaslamp.explain(
  { name: "Alice" },
  { name: gaslamp.FlameGuards.isString, age: gaslamp.FlameGuards.isNumber },
);
// ["age: missing"]

if (errors.length > 0) {
  console.error("Validation failed: " + errors.join(", "));
}

Practical Workflow

Validating function arguments

Use guards to protect any function from unexpected input:

JavaScript
function calculateDiscount(price, rate) {
  gaslamp.assert(price, gaslamp.FlameWright.inRange(0), { at: "calculateDiscount", label: "price" });
  gaslamp.assert(rate,  gaslamp.FlameWright.inRange(0, 1), { at: "calculateDiscount", label: "rate" });
  return price * (1 - rate);
}

Validating a GAS form submission

Validate user input from a GAS form before processing:

JavaScript
function onFormSubmit(e) {
  const response = {
    name:  e.values[1],
    email: e.values[2],
    age:   Number(e.values[3]),
  };

  const schema = {
    name:  gaslamp.FlameGuards.isString,
    email: gaslamp.FlameGuards.isString,
    age:   gaslamp.FlameWright.inRange(0, 120),
  };

  const errors = gaslamp.explain(response, schema);

  if (errors.length > 0) {
    console.error("Invalid submission: " + errors.join(", "));
    return;
  }

  // Safe to use response.name, response.email, response.age here
  console.log(`Received from ${response.name}`);
}

Next Steps