can1357/oh-my-pi · error · OmpTypeError

optional key ${String(key)} cannot specify a default

Error message

optional key ${String(key)} cannot specify a default

What it means

A property marked optional with the "?" marker cannot also declare a default value — the two are mutually exclusive in omptype's IR (optional means the key may be absent; a default means it is always materialized). parseObjectDefinition throws this when a parsed property has both opt and hasDefault set.

Source

Thrown at packages/omptype/src/ir.ts:1431

			prop = {
				key,
				opt,
				val: isObjectDefinition(val) ? parseObjectDefinition(val, resolve) : parseDef(val, resolve),
			};
		}
		if (key !== originalKey) {
			if (!spreadKeys?.has(key) && props.some(candidate => candidate.key === key)) {
				throw new OmpTypeError(`duplicate object key ${String(key)}`);
			}
			if (normalizedKey === undefined) normalizedKey = key;
			else {
				normalizedKeys ??= [normalizedKey];
				normalizedKeys.push(key);
			}
		} else if (!spreadKeys?.has(key) && (key === normalizedKey || normalizedKeys?.includes(key))) {
			throw new OmpTypeError(`duplicate object key ${String(key)}`);
		}
		if (opt && prop.hasDefault) throw new OmpTypeError(`optional key ${String(key)} cannot specify a default`);
		if (simple && (prop.hasDefault || !isSimpleIR(prop.val))) simple = false;
		addObjectProp(props, spreadKeys, prop);
	}
	for (const key of Object.getOwnPropertySymbols(def)) {
		if (!Object.prototype.propertyIsEnumerable.call(def, key)) continue;
		const val = def[key];
		let prop: PropIR;
		if (typeof val === "string") {
			const parsed = parseStringDef(val, resolve);
			prop = { key, opt: parsed.optional, val: parsed.ir };
			if (parsed.hasDefault) {
				prop.def = parsed.def;
				prop.hasDefault = true;
			}
		} else if (Array.isArray(val) && val.length === 2 && val[1] === "?") {
			prop = { key, opt: true, val: parseDef(val[0], resolve) };
		} else if (Array.isArray(val) && val.length === 3 && val[1] === "=") {
			prop = {

View on GitHub (pinned to 9690622007)

Solutions

  1. Drop the "?" marker and keep the default (the default already makes the key optional in effect)
  2. Keep "?" and remove the default
  3. If the field should always exist with a fallback, use the default-only form

Example fix

// before
type({ "port?": ["=", "8080"] })
// after
type({ port: ["=", "8080"] }) // default implies optionality
Defensive patterns

Strategy: validation

Validate before calling

function assertOptionalWithoutDefault(key, val) {
  const isOptional = key.endsWith("?") || key.endsWith("?:");
  const hasDefault = (Array.isArray(val) && val.length === 3 && val[1] === "=") || val?.hasDefault === true;
  if (isOptional && hasDefault) throw new Error(`optional key ${key} cannot also have a default`);
}

Try / catch

try { const T = type(def); } catch (e) {
  if (String(e.message).startsWith("optional key")) {
    // drop either the "?" marker or the default, then retry
  } else throw e;
}

Prevention

When it happens

Trigger: Defining a property like "id?:" with a defaulted value form ([key, "=", value] or an embedded type with hasDefault) in parseObjectDefinition, i.e. opt === true while prop.hasDefault is true.

Common situations: Adding "?" to a property that previously had a default; auto-generating schemas from TS types where optional fields also carry defaults; misunderstanding that "?" plus default are redundant in ArkType-like DSLs.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/1393b78db7dafc9e. Report an issue: GitHub.