Skip to content

nostrability/schemata-codegen

Repository files navigation

schemata-codegen

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.

How it fits in the schemata ecosystem

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.

Direct comparison schemata-codegen vs schemata-validator approach

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

Why not use an existing JSON Schema code generator?

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.

Usage

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

CLI flags

TypeScript outputs

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)

Multi-language validators

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

Other

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.

Generated outputs

Tag tuple types (tags.d.ts)

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/...)$

Kind event interfaces (kinds.d.ts)

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;

Runtime validators (validators.ts)

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);

Kind registry (kind-registry.ts)

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"]

Error messages (error-messages.ts)

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" }, ...]

AJV-ready schemas (ajv-schemas/)

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 needed

Using with nostr-tools

After 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
}

Using with NDK

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
}

See Also

  • 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

Tests

npm test    # 317 tests — extraction, emission, tsc compilation, runtime validation

About

Nostr code generator for schemata schemas: typed tag tuples, kind interfaces, runtime validators, and error messages.

Topics

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

Watchers

Forks

Packages

 
 
 

Contributors