can1357/oh-my-pi · error · OmpTypeError
ParseError: A mutable default value must be specified as a f
Error message
ParseError: A mutable default value must be specified as a factory
What it means
omptype forbids static (non-factory) default values that are mutable objects, because a single shared object instance would be reused across every parse and mutations would leak between results. `rejectMutableStaticDefault` throws this ParseError when `.default(obj)` or an object-literal default receives a non-null object that is not a Date. Null and Date are exempt.
Source
Thrown at packages/omptype/src/type.ts:628
const error = errors[0];
let heading = label;
for (let index = 0; index < error.path.length; index++) {
const segment = error.path[index];
if (typeof segment === "number") {
if (label === "Default" && index === 0) heading = "Default value";
heading += ` at [${segment}]`;
} else if (label === "Default" && index === 0) {
heading += ` ${String(segment)}`;
} else {
heading += `.${String(segment)}`;
}
}
throw new OmpTypeError(`ParseError: ${heading} ${error.problem}`);
}
function rejectMutableStaticDefault(value: unknown): void {
if (value !== null && typeof value === "object" && !(value instanceof Date)) {
throw new OmpTypeError("ParseError: A mutable default value must be specified as a factory");
}
}
function normalizeDefaults(ir: IR, seen = new WeakSet<object>()): void {
if (seen.has(ir)) return;
seen.add(ir);
switch (ir.k) {
case "object":
for (const prop of ir.props) {
normalizeDefaults(prop.val, seen);
if (!prop.hasDefault || prop.defValidated) continue;
let candidate: unknown;
let factory = false;
if (prop.defFactory && typeof prop.def === "function") {
candidate = prop.def();
factory = true;
} else {
rejectMutableStaticDefault(prop.def);View on GitHub (pinned to 9690622007)
Solutions
- Wrap the default in a factory: `.default(() => [])`.
- Use `.default(() => ({ a: 1 }))` for object defaults.
- If an intentionally shared immutable default is needed, freeze it — but prefer factories.
Example fix
// before
const T = type({ tags: 'string[]' }).default({ tags: [] }); // throws
// after
const T = type({ tags: 'string[]' }).default(() => ({ tags: [] })); Defensive patterns
Strategy: validation
Validate before calling
function safeDefault(T, value) {
if (value !== null && typeof value === 'object' && !(value instanceof Date)) {
return T.default(() => value);
}
return T.default(value);
} Type guard
function isMutableStaticDefault(v) {
return v !== null && typeof v === 'object' && !(v instanceof Date);
} Try / catch
try {
const T = type({ tags: 'string[]' }).default([]);
} catch (e) {
if (e instanceof OmpTypeError && e.message.includes('must be specified as a factory')) {
// retry with factory or fix call site
} else throw e;
} Prevention
- Habit: always use `.default(() => ...)` for arrays and objects, even single-use.
- Never inline `[]` or `{}` as static defaults.
- Search codebases for `.default({` and `.default([` during reviews.
When it happens
Trigger: `type({...}).default({ a: 1 })`, `type('string[]').default([])`, or an object property declared with `[]`/`{}` as a static default via `normalizeDefaults`.
Common situations: Defaults for arrays/objects copied from other schema libraries (like plain JSON defaults); users unaware that static object defaults create shared-state bugs.
Related errors
- ParseError: ${heading} ${error.problem}
- transparent (brush_parser::BindingParseError)
- ${destination} returned invalid JSON
- Schema produced no default value
- No hashline sections found in input.
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/df680af9a392e8f4.
Report an issue: GitHub.