can1357/oh-my-pi · error · OmpTypeError

thunk must return a Type (was ${typeof resolved})

Error message

thunk must return a Type (was ${typeof resolved})

What it means

Function definitions in omptype are thunks: zero-argument functions lazily producing a type/embeddable schema. parseDef invokes the thunk with Reflect.apply(def, undefined, []) and requires the return value to satisfy isEmbedded; anything else (string, number, plain object, undefined, class instance) triggers this error naming the typeof the bad return value.

Source

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

		}
		if (parsed.optional) {
			throw new OmpTypeError(`optional "?" marker is only valid on object property values`);
		}
		return parsed.ir;
	}
	if (Array.isArray(def)) {
		if (def.length === 3 && def[1] === "=") {
			throw new OmpTypeError("A default may only be specified for an object property or tuple element");
		}
		return parseArrayExpression(def, resolve);
	}
	if (def instanceof RegExp) return patternIR(def);
	if (def instanceof Date) return { k: "lit", v: def };
	if (isEmbedded(def)) return embed(def);
	if (typeof def === "function") {
		const resolved = Reflect.apply(def, undefined, []);
		if (!isEmbedded(resolved)) {
			throw new OmpTypeError(`thunk must return a Type (was ${typeof resolved})`);
		}
		return embed(resolved);
	}
	if (isObjectDefinition(def)) return parseObjectDefinition(def, resolve);
	throw new OmpTypeError(`unsupported definition ${String(def)} (was ${typeof def})`);
}

/** Whether `ir` needs no construction-time normalization or morph analysis. */
export function isSimpleIR(ir: IR): boolean {
	const cached = ir[kSimpleOwner] === ir ? ir[kSimple] : undefined;
	if (cached !== undefined) return cached;
	const simple = scanSimpleIR(ir);
	ir[kSimple] = simple;
	ir[kSimpleOwner] = ir;
	return simple;
}

function scanSimpleIR(ir: IR): boolean {

View on GitHub (pinned to 9690622007)

Solutions

  1. Return a Type from the thunk: () => type("string") rather than () => "string"
  2. Ensure lazy/async-loaded values are fully constructed types before returning
  3. Check memoization wrappers preserve the Type instance
  4. Inspect the reported typeof in the message to identify what was actually returned

Example fix

// before
const T = type(() => "string|number")
// after
const T = type(() => type("string|number"))
Defensive patterns

Strategy: type-guard

Validate before calling

function assertThunkReturnsType(fn) {
  if (typeof fn !== "function") return;
  const r = fn();
  if (r == null || typeof r !== "object")
    throw new Error("thunk must return a Type, not " + typeof r);
}

Type guard

const returnsType = (fn) => { try { const r = fn(); return r != null && typeof r === "object"; } catch { return false; } };

Try / catch

try { const T = type(thunk); } catch (e) {
  if (String(e.message).startsWith("thunk must return a Type")) {
    // wrap the thunk body's return in type(...) and retry
  } else throw e;
}

Prevention

When it happens

Trigger: Passing a function to type()/parseDef whose body returns something other than a Type/embeddable schema — e.g. returning a raw string definition instead of calling type() on it, returning undefined from a forgetful thunk, or a memoized getter returning a cached non-type value.

Common situations: Recursive type helpers where the thunk returns def data instead of a parsed type; lazy imports resolving to the wrong export; typos like () => "string" instead of () => type("string").

Related errors


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