can1357/oh-my-pi · error · OmpTypeError

record keys must use a string schema

Error message

record keys must use a string schema

What it means

`omptype.record(keySchema, valueSchema)` requires the key schema to be a plain string schema (checked via `isStringKeyIR`); anything else (number keys, enums, unions, transforms) throws this OmpTypeError. Thrown from `record()` at packages/omptype/src/zod.ts:310.

Source

Thrown at packages/omptype/src/zod.ts:310

	return decorate(schemaFromIR<Values[number]>(type.enumerated(...values).ir));
};

export { enumSchema as enum };
export const union = <
	const Schemas extends readonly [ZodLikeSchema<unknown>, ZodLikeSchema<unknown>, ...ZodLikeSchema<unknown>[]],
>(
	schemas: Schemas,
): ZodLikeSchema<UnionOutput<Schemas>> =>
	decorate(schemaFromIR({ k: "union", members: schemas.map(schema => embed(schema)) }));
export const array = <Element>(element: ZodLikeSchema<Element>): ZodLikeSchema<Element[]> =>
	decorate(schemaFromIR({ k: "array", el: embed(element) }));
export const object = <const S extends Shape>(shape: S): ZodLikeSchema<Simplify<ObjectOutput<S>>> =>
	objectSchema(shape);
export const record = <Key extends string, Value>(
	keySchema: ZodLikeSchema<Key>,
	valueSchema: ZodLikeSchema<Value>,
): ZodLikeSchema<Record<string, Value>> => {
	if (!isStringKeyIR(keySchema.ir)) throw new OmpTypeError("record keys must use a string schema");
	const base = schemaFromIR<Record<string, Value>>({
		k: "object",
		props: [],
		index: embed(valueSchema),
		extras: "keep",
	});
	const checked = base.narrow((value, ctx: NarrowContext) => {
		for (const key in value) {
			if (keySchema(key) instanceof type.errors) return ctx.mustBe("a record with valid string keys");
		}
		return true;
	});
	return decorate(checked);
};
export const unknown = (): ZodLikeSchema<unknown> => decorate(schemaFromIR(type.unknown.ir));
export const any = (): ZodLikeSchema<unknown> => decorate(schemaFromIR(type.unknown.ir));
const nullSchema = (): ZodLikeSchema<null> => decorate(type.raw("null") as unknown as Decoratable<null>);
const undefinedSchema = (): ZodLikeSchema<undefined> =>

View on GitHub (pinned to 9690622007)

Solutions

  1. Use t.string() as the key schema
  2. If keys must be restricted to a known set, use t.object({...}) with those props instead of record()
  3. Wrap enum/number keys in a transformation: keep record keys as t.string() and validate values separately

Example fix

// before
omptype.record(omptype.enum(["a", "b"]), t.number())
// after
omptype.record(t.string(), t.number())
// or for fixed keys:
omptype.object({ a: t.number(), b: t.number() })
Defensive patterns

Strategy: type-guard

Validate before calling

function hasPlainStringKeys(schema: ZodLikeSchema<unknown>): boolean {
  return schema.ir.k === "string";
}
if (!hasPlainStringKeys(keySchema)) throw new Error("record key schema must be t.string()");

Type guard

function isPlainStringSchema(ir: { k: string }): ir is { k: "string" } {
  return ir.k === "string";
}

Try / catch

try {
  schema = omptype.record(keySchema, valueSchema);
} catch (err) {
  if (err instanceof OmpTypeError && err.message.includes("record keys")) {
    throw new Error("use t.string() for record keys; use t.object() for fixed/enum keys");
  } else throw err;
}

Prevention

When it happens

Trigger: `omptype.record(t.number(), value)`; `omptype.record(omptype.enum(["a","b"]), value)`; `omptype.record(t.string().max(3), value)` if the bound makes the key IR something other than a plain string schema accepted by isStringKeyIR.

Common situations: Porting Zod code where z.record(z.enum([...]), v) is legal but omptype restricts record keys to plain strings; wanting constrained keys — model those as t.object({...}) with named props instead; using t.literal(...) as key schema.

Related errors


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