Nostr-aware code generator that reads schemata's compiled JSON schemas and produces typed code for 13 languages. Schemata-codegen includes tag tuple types, kind event interfaces, runtime validators, kind metadata, and error messages.
Unlike the validator approach, no "heavy" validator is needed (e.g. AJV etc.), and code is run natively. For the real world practitioner, this means that schemata-codegen can be deployed to production, and not just constrained to CI.
The above said, schemata-codegen is complementary to the schemata validator approach.
| Repo | Role |
|---|---|
| schemata | The schemas themselves (YAML source, compiled JSON dist/) |
| schemata-{rs,py,go,...} | Data packages — embed the compiled JSON for a given language |
| schemata-validator-{rs,py,go,...} | Runtime validators — pass/fail a JSON blob against a schema (AJV, jsonschema, etc.) |
| schemata-codegen | Code generator — produces typed language constructs from the schemas |
The data packages give you access to raw schemas. The validators check whether a JSON blob conforms to a schema (pass/fail — useful for conformance testing, which is what sherlock does against live relay data). The codegen produces typed language constructs — so when you're writing code that builds or reads tags, the compiler catches structural mistakes (wrong position, missing field, bad marker value) that string[][] silently accepts.
| Phase | schemata-js-ajv | schemata-codegen |
|---|---|---|
| Write code (authoring) | Nothing / no types | IDE autocomplete, type errors at compile time |
| Build (compile) | Nothing | Type checker catches wrong field names, missing tags |
| Construct event (runtime) | Nothing until event is finished | Lightweight tag validator can check before signing |
| Validate finished event | Full JSON Schema validation via AJV | Could also validate, but AJV is more thorough |
| Display to user | Cryptic error paths | KIND_NAMES for UI, human-friendly error messages |
Every existing generator (quicktype, typify, datamodel-codegen, Zod 4) fails on schemata's schemas. The features that make schemata valuable — tag tuple structure via items-as-array, contains, if/then, oneOf + allOf composition — are exactly what every generator chokes on. See findings.md for the full assessment.
schemata-codegen takes a different approach: instead of generically parsing JSON Schema, it pattern-matches against the specific structural shapes schemata uses and fails loudly on anything unrecognized.
npm install
npm run build
# Generate all outputs:
node dist/index.js --schemas ../schemata/dist --all
# Or pick specific outputs:
node dist/index.js --schemas ../schemata/dist --out tags.d.ts --kinds kinds.d.ts --validators validators.ts| Flag | Output | Description |
|---|---|---|
--out |
tags.d.ts |
Tag tuple types (always generated) |
--kinds |
kinds.d.ts |
Kind event interfaces with literal discriminants |
--validators |
validators.ts |
Zero-dependency runtime tag validators |
--registry |
kind-registry.ts |
Kind metadata (name, NIP, required tags, category) |
--errors |
error-messages.ts |
Human-friendly error messages from schema errorMessage fields |
--ajv-schemas |
ajv-schemas/ |
AJV-ready JSON schemas (see AJV schemas below) |
| Flag | Output | API option |
|---|---|---|
--c-validators |
validators.c + validators.h |
--c-api generic|nostrdb |
--rust-validators |
validators.rs |
--rust-api generic|nostr|nostrdb |
--go-validators |
validators.go |
|
--python-validators |
validators.py |
|
--kotlin-validators |
Validators.kt |
|
--java-validators |
SchemataValidators.java |
|
--swift-validators |
Validators.swift |
|
--dart-validators |
validators.dart |
|
--php-validators |
validators.php |
|
--csharp-validators |
Validators.cs |
|
--cpp-validators |
validators.hpp |
|
--ruby-validators |
validators.rb |
| Flag | Description |
|---|---|
--all |
Generate all outputs |
--dump-plan |
Dump ValidatorAction[] plan as JSON |
No generated outputs are committed. Run --all or individual flags to produce them locally. Tests verify correctness by generating and compiling the outputs during CI.
156 tag schemas classified into 5 patterns:
// fixed_tuple (96 tags) — exact length, typed positions
export type AmountTag = readonly ["amount", string];
export type EmojiTag = readonly ["emoji", string, string];
// optional_trailing (8 tags) — union of valid lengths
export type RTag =
| readonly ["r", string]
| readonly ["r", string, "read" | "write"];
// open_tail (46 tags) — typed prefix, rest string[]
export type TTag = readonly ["t", string, ...string[]];
// discriminated_union (2 tags) — oneOf with named variants
export type ETag = ETagVariant1 | ETagVariant2;
// structured_metadata (4 tags) — open-tail with contains constraints
export type ImetaTag = readonly ["imeta", string, ...string[]];
// Runtime: must contain entries matching: ^url https?://\S+$, ^m (image/...)$177 per-kind interfaces with literal kind discriminants, a NostrEvent discriminated union, and KNOWN_KINDS mapping.
export interface Kind10002Event {
readonly kind: 10002;
readonly content: string;
readonly tags: string[][];
// ...
}
export type NostrEvent = Kind0Event | Kind1Event | ... | Kind40000Event;136 kind-level validators and 3 tag-level validators. Zero dependencies — plain functions on string[][]. Works in any TypeScript/JavaScript environment without AJV. The same 136 validators are also generated for C, Rust, Go, Python, Kotlin, Java, Swift, Dart, PHP, C#, C++, and Ruby.
import { validateKind9735Tags, validateKindTags } from './validators.js';
const errors = validateKind9735Tags(event.tags);
// Or dispatch by kind number:
const errors = validateKindTags(event.kind, event.tags);Structured metadata for all 177 kinds — kind number, NIP, human-readable name, required tags, category.
import { KIND_NAMES, KIND_REGISTRY } from './kind-registry.js';
KIND_NAMES[9735] // "Zap Receipt (NIP-57)"
KIND_REGISTRY[9735].requiredTags // ["p", "bolt11", "description"]Human-friendly error messages extracted from schema errorMessage fields. Base field messages (shared across all kinds) and per-kind messages.
import { BASE_ERROR_MESSAGES, KIND_ERROR_MESSAGES } from './error-messages.js';
BASE_ERROR_MESSAGES["pubkey"] // "pubkey must be a secp256k1 public key"
KIND_ERROR_MESSAGES[7] // [{ keyword: "...", message: "kind must equal 7" }, ...]Pre-processed JSON schemas with nested $schema, $id, and errorMessage fields stripped at build time. These can be passed directly to ajv.compile() without runtime preprocessing.
Consumers who use AJV (sherlock, nostr-watch, nostria) can generate these once and load them instead of stripping fields at runtime.
node dist/index.js --schemas ../schemata/dist --ajv-schemas ./ajv-schemas
# Then in your code:
import kind7Schema from './ajv-schemas/kind-7.json';
const validate = ajv.compile(kind7Schema); // No preprocessing neededAfter generating outputs (see Usage above), the validators and kind registry work directly with nostr-tools — no wrapper needed. Both kind (number) and tags (string[][]) match what codegen expects:
import type { Event } from 'nostr-tools';
import { validateKindTags } from './validators.js';
import { KIND_NAMES } from './kind-registry.js';
function handleEvent(event: Event) {
const errors = validateKindTags(event.kind, event.tags);
if (errors.length > 0) {
console.warn(`Invalid ${KIND_NAMES[event.kind]}:`, errors);
return;
}
// event.tags validated — safe to process
}Same pattern with NDK. NDKEvent.tags is string[][] and NDKEvent.kind is NDKKind | number — both compatible with codegen's validators:
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { validateKindTags } from './validators.js';
import { KIND_NAMES } from './kind-registry.js';
function handleEvent(event: NDKEvent) {
const errors = validateKindTags(event.kind, event.tags);
if (errors.length > 0) {
console.warn(`Invalid ${KIND_NAMES[event.kind]}:`, errors);
return;
}
// event.tags validated — safe to process
}- PATTERNS.md — catalog of 69+ regex patterns with codegen classification and pipeline docs
- schemata — source YAML schemas and compiled JSON dist/
- sherlock — schema conformance scanner using codegen outputs
npm test # 317 tests — extraction, emission, tsc compilation, runtime validation