clockworklabs/SpacetimeDB · error · TypeError
could not serialize result: object had neither a `ok` nor an
Error message
could not serialize result: object had neither a `ok` nor an `err` field
What it means
Sum types whose two variants are named exactly 'ok' (index 0) and 'err' (index 1) get a specialized serializer: it writes discriminant byte 0/1 plus the payload field. At serialize time it probes the value with 'ok' in value / 'err' in value; a value with neither field (null, {}, arrays, or mis-cased keys like Ok/Err) throws this TypeError.
Source
Thrown at crates/bindings-typescript/src/lib/algebraic_type.ts:672
) {
const serializeOk = AlgebraicType.makeSerializer(
ty.variants[0].algebraicType,
typespace
);
const serializeErr = AlgebraicType.makeSerializer(
ty.variants[0].algebraicType,
typespace
);
return (writer, value) => {
if ('ok' in value) {
writer.writeU8(0);
serializeOk(writer, value.ok);
} else if ('err' in value) {
writer.writeU8(1);
serializeErr(writer, value.err);
} else {
throw new TypeError(
'could not serialize result: object had neither a `ok` nor an `err` field'
);
}
};
} else {
let serializer = SERIALIZERS.get(ty);
if (serializer != null) return serializer;
const serializers: Record<string, Serializer<any>> = {};
const body = `\
switch (value.tag) {
${ty.variants
.map(
({ name }, i) => `\
case ${JSON.stringify(name!)}:
writer.writeByte(${i});
return this.${name!}(writer, value.value);`View on GitHub (pinned to 524b4487d9)
Solutions
- Wrap the payload: serialize { ok: value } or { err: value }
- Verify the variant names in the module schema are exactly ok/err (the bindings special-case those spellings)
- Type-check values before insert/reducer calls so unwrapped or null payloads fail in your code, not inside the serializer
Example fix
// before
reducers.set_result(payload); // payload = 'done' or undefined -> throws
// after
reducers.set_result({ ok: payload });
// or
reducers.set_result({ err: 'not permitted' }); Defensive patterns
Strategy: type-guard
Validate before calling
function isResultLike(v: unknown): boolean {
return typeof v === 'object' && v !== null && ('ok' in v || 'err' in v);
}
// if (!isResultLike(value)) throw new TypeError('expected { ok } or { err }'); Type guard
type ResultLike<T = unknown, E = unknown> = { ok: T } | { err: E };
function isResultLike<T, E>(v: unknown): v is ResultLike<T, E> {
return typeof v === 'object' && v !== null && ('ok' in v || 'err' in v);
} Prevention
- Model Result values in TS as a discriminated union so the compiler rejects unwrapped payloads
- Double-check the module's variant spellings are exactly ok/err when the TS side uses the special-case path
- Validate external (JSON) input at the boundary before it reaches insert/reducer calls
When it happens
Trigger: Serializing a Result-shaped sum value that is null/undefined, {}, uses different casing ({ Ok: x } or { Err: e }), or passes the payload unwrapped (value instead of { ok: value }) - e.g. inserting into a Result-typed column or calling a reducer with a Result argument.
Common situations: Module schema declares the variants with different casing than the TS bindings expect (exactly 'ok'/'err'); JSON payloads where the wrapper object was dropped; writing a default/undefined value to a Result column.
Related errors
- Could not serialize sum type; unknown tag ${value.tag}
- cannot serialize refs without a typespace
- cannot deserialize refs without a typespace
- Cannot extract table name from query
- Missing type name for ${typeBuilder.constructor.name ?? 'Typ
AI-assisted analysis of clockworklabs/SpacetimeDB@524b4487d9 (2026-08-16).
Data as JSON: /api/errors/f948691288947a28.
Report an issue: GitHub.