Problem
Today codegen generates validators that check whether tags are correct after construction. But the bugs happen during construction. Real-world example from nostr-watch's Kind30166.ts:
// BUG: checks 'open' instead of 'read' — copy-paste error
if (read && typeof open === 'number' && read > 0) {
tags.push(['rtt-read', String(Math.round(read))]);
}
No amount of post-hoc validation prevents this — it's a logic error in manually written tag construction code. The validator would only tell you the rtt-read tag is missing from the output, not why.
Proposal
Generate builder functions that take typed data and produce correct tags:
// Generated builder — correct by construction
export function buildKind30166Tags(data: {
relay: string;
network?: "clearnet" | "tor" | "i2p" | "loki";
rttOpen?: number;
rttRead?: number; // no chance of checking the wrong variable
rttWrite?: number;
supportedNips?: number[];
requirements?: { auth?: boolean; payment?: boolean; pow?: boolean; ssl?: boolean };
software?: string;
geohash?: string;
}): string[][] {
const tags: string[][] = [];
tags.push(["d", data.relay]);
if (data.network) tags.push(["n", data.network]);
if (data.rttOpen != null) tags.push(["rtt-open", String(Math.round(data.rttOpen))]);
if (data.rttRead != null) tags.push(["rtt-read", String(Math.round(data.rttRead))]);
// ... etc
return tags;
}
This inverts the direction: validators go tags → errors, builders go data → tags. The schema has enough information for both — tag names, positions, value constraints, required vs optional.
Where the data comes from
The validator planner (plan-validators.ts) already extracts ValidatorAction[] per kind — required tags, conditional tags, value patterns. A builder emitter would consume the same action plan but emit construction code instead of checking code.
Design considerations
- Required vs optional tags: Schema
contains constraints mark required tags. Others are optional (only emitted if data is provided).
- Value formatting: Numeric values need
String(), enums need type narrowing, patterns need validation at the builder boundary.
- Multi-language: Like validators, builders could be generated for multiple languages using the same action plan.
- Relationship to validators: Builders guarantee structural correctness. Validators remain useful for checking events received from the wire (where you can't trust the builder was used).
Effort: Medium-High | Impact: Highest
This is the strongest value proposition for app devs — shifting from "validate what you built" to "build it right the first time."
Problem
Today codegen generates validators that check whether tags are correct after construction. But the bugs happen during construction. Real-world example from nostr-watch's Kind30166.ts:
No amount of post-hoc validation prevents this — it's a logic error in manually written tag construction code. The validator would only tell you the
rtt-readtag is missing from the output, not why.Proposal
Generate builder functions that take typed data and produce correct tags:
This inverts the direction: validators go
tags → errors, builders godata → tags. The schema has enough information for both — tag names, positions, value constraints, required vs optional.Where the data comes from
The validator planner (
plan-validators.ts) already extractsValidatorAction[]per kind — required tags, conditional tags, value patterns. A builder emitter would consume the same action plan but emit construction code instead of checking code.Design considerations
containsconstraints mark required tags. Others are optional (only emitted if data is provided).String(), enums need type narrowing, patterns need validation at the builder boundary.Effort: Medium-High | Impact: Highest
This is the strongest value proposition for app devs — shifting from "validate what you built" to "build it right the first time."