#!/usr/bin/env node /** * Fetches kinds.json from the nostr library and generates TypeScript definitions * Run: node scripts/fetch-kinds.js */ import { writeFileSync } from 'fs'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const KINDS_URL = 'https://git.mleku.dev/mleku/nostr/raw/branch/main/encoders/kind/kinds.json'; async function fetchKinds() { console.log(`Fetching kinds from ${KINDS_URL}...`); const response = await fetch(KINDS_URL); if (!response.ok) { throw new Error(`Failed to fetch kinds.json: ${response.status} ${response.statusText}`); } const data = await response.json(); console.log(`Fetched ${Object.keys(data.kinds).length} kinds (version: ${data.version})`); return data; } function generateTypeScript(data) { const kinds = []; for (const [kindNum, info] of Object.entries(data.kinds)) { const k = parseInt(kindNum, 10); // Determine classification let classification = 'regular'; if (info.classification) { classification = info.classification; } else if (k === 0 || k === 3 || (k >= data.ranges.replaceable.start && k < data.ranges.replaceable.end)) { classification = 'replaceable'; } else if (k >= data.ranges.parameterized.start && k <= data.ranges.parameterized.end) { classification = 'parameterized'; } else if (k >= data.ranges.ephemeral.start && k < data.ranges.ephemeral.end) { classification = 'ephemeral'; } kinds.push({ kind: k, name: info.name, description: info.description, nip: info.nip || null, classification, deprecated: info.deprecated || false, spec: info.spec || null }); } // Sort by kind number kinds.sort((a, b) => a.kind - b.kind); return `/** * Nostr Event Kinds Database * Auto-generated from ${KINDS_URL} * Version: ${data.version} * Source: ${data.source} * * DO NOT EDIT - This file is auto-generated by scripts/fetch-kinds.js */ export interface KindInfo { kind: number; name: string; description: string; nip: string | null; classification: 'regular' | 'replaceable' | 'ephemeral' | 'parameterized'; deprecated: boolean; spec: string | null; } export interface KindRanges { regular: { start: number; end: number; description: string }; replaceable: { start: number; end: number; description: string }; ephemeral: { start: number; end: number; description: string }; parameterized: { start: number; end: number; description: string }; } export const EVENT_KINDS: KindInfo[] = ${JSON.stringify(kinds, null, 2)}; export const KIND_RANGES: KindRanges = ${JSON.stringify(data.ranges, null, 2)}; export const PRIVILEGED_KINDS: number[] = ${JSON.stringify(data.privileged)}; export const DIRECTORY_KINDS: number[] = ${JSON.stringify(data.directory)}; export const KIND_ALIASES: Record = ${JSON.stringify(data.aliases, null, 2)}; // Lookup map for fast access const kindMap = new Map(EVENT_KINDS.map(k => [k.kind, k])); export function getKindInfo(kind: number): KindInfo | undefined { return kindMap.get(kind); } export function getKindName(kind: number): string { const info = kindMap.get(kind); return info ? info.name : \`Kind \${kind}\`; } export function isReplaceable(kind: number): boolean { if (kind === 0 || kind === 3) return true; return kind >= KIND_RANGES.replaceable.start && kind < KIND_RANGES.replaceable.end; } export function isEphemeral(kind: number): boolean { return kind >= KIND_RANGES.ephemeral.start && kind < KIND_RANGES.ephemeral.end; } export function isParameterized(kind: number): boolean { return kind >= KIND_RANGES.parameterized.start && kind <= KIND_RANGES.parameterized.end; } export function isPrivileged(kind: number): boolean { return PRIVILEGED_KINDS.includes(kind); } export function isDirectoryKind(kind: number): boolean { return DIRECTORY_KINDS.includes(kind); } `; } async function main() { try { const data = await fetchKinds(); const ts = generateTypeScript(data); // Write to common library const outPath = join(__dirname, '..', 'projects', 'common', 'src', 'lib', 'constants', 'event-kinds.ts'); writeFileSync(outPath, ts); console.log(`Generated ${outPath} with ${Object.keys(data.kinds).length} kinds`); } catch (error) { console.error('Error:', error.message); process.exit(1); } } main();