Skip to content

@neurowire/core

The format authority for Neurowire (version 0.7.0). It owns the canonical model (zod schemas), the output serializers, mesh and construct types, OPML export, and the pure helpers for merging, filtering, refining, diffing, and id generation. Pure: no network, no DOM, only zod as a runtime dependency.

bash
npm install @neurowire/core

Dependency direction

core <- ingest <- taps <- (cli, api, web). Core never imports from any other Neurowire package, which keeps it format-pure and dependency-light. HTML is deliberately not a core format: it lives in @neurowire/web.

Model

The canonical feed model. Every parser produces it; every serializer consumes it. It carries list-metadata only (no full article bodies). See the model concept.

Types

ts
interface Person {
  name: string
  url?: string
  email?: string
}

interface NeurowireEntry {
  id: string
  title: string
  link: string
  published?: string
  updated?: string
  summary?: string
  authors?: Person[]
  tags?: string[]
  source?: { name?: string; url?: string }
}

interface NeurowireFeed {
  id: string
  title: string
  home?: string
  self?: string
  updated: string
  authors?: Person[]
  generator?: { name: string; version?: string }
  entries: NeurowireEntry[]
}

NeurowireEntry fields

FieldTypeDescription
idstringStable identifier (real GUID or synthetic urn:nwf: id).
titlestringArticle title.
linkstringArticle URL.
publishedstring?ISO 8601 publication date.
updatedstring?ISO 8601 last-modified date.
summarystring?Plain-text summary or excerpt.
authorsPerson[]?Article authors.
tagsstring[]?Free-form tags or categories.
source{ name?, url? }?Originating source (set during a merge).

NeurowireFeed fields

FieldTypeDescription
idstringStable feed identifier.
titlestringFeed title.
homestring?The site's home page URL.
selfstring?The feed's own canonical URL.
updatedstringISO 8601 timestamp of the most recent change.
authorsPerson[]?Feed-level authors.
generator{ name, version? }?Generator stamp.
entriesNeurowireEntry[]The articles.

Schemas and validators

ts
const PersonSchema: z.ZodType<Person>
const EntrySchema: z.ZodType<NeurowireEntry>
const FeedSchema: z.ZodType<NeurowireFeed>

function parseNeurowireFeed(data: unknown): NeurowireFeed
ExportDescription
PersonSchemaZod schema for a Person.
EntrySchemaZod schema for a NeurowireEntry.
FeedSchemaZod schema for a NeurowireFeed.
parseNeurowireFeed(data)Validate an unknown value into a NeurowireFeed. Throws ZodError on invalid input.

Constants

ts
const GENERATOR: { name: 'Neurowire'; version: '0.1.0' }

The generator stamp written into feeds Neurowire produces.

Serializers

Turn a NeurowireFeed into a wire format. Registered in FORMATS / MEDIA_TYPES / EXTENSIONS and dispatched by serialize. See output formats.

Dispatch

ts
const FORMATS: readonly ['atom', 'rss', 'json', 'md', 'nwf']
type Format = 'atom' | 'rss' | 'json' | 'md' | 'nwf'

const MEDIA_TYPES: Record<Format, string>
const EXTENSIONS: Record<Format, string>

function isFormat(value: string): value is Format
function serialize(feed: NeurowireFeed, format: Format): string
ExportDescription
FORMATSThe supported output format ids, in order.
FormatUnion type of the format ids.
MEDIA_TYPESThe Content-Type for each format.
EXTENSIONSThe file extension for each format (atom and rss both map to xml).
isFormat(value)Type guard: is the string one of FORMATS?
serialize(feed, format)Serialize a feed to the requested format.

MEDIA_TYPES values:

FormatContent-TypeExtension
nwftext/x-neurowire; charset=utf-8nwf
atomapplication/atom+xml; charset=utf-8xml
rssapplication/rss+xml; charset=utf-8xml
jsonapplication/feed+json; charset=utf-8json
mdtext/markdown; charset=utf-8md

Atom

ts
function toAtom(feed: NeurowireFeed): string

Serialize a feed to an Atom 1.0 document.

RSS

ts
function toRss(feed: NeurowireFeed): string
function toRfc822(iso: string): string | undefined
ExportDescription
toRss(feed)Serialize a feed to an RSS 2.0 document.
toRfc822(iso)Format an ISO-ish date string as an RFC 822 date in GMT (e.g. Wed, 02 Oct 2024 13:00:00 GMT). Returns undefined when unparseable.

JSON Feed

ts
function toJsonFeed(feed: NeurowireFeed): string
function toJsonFeedObject(feed: NeurowireFeed): JsonFeedDocument

interface JsonFeedDocument {
  version: string
  title: string
  home_page_url?: string
  feed_url?: string
  authors?: { name: string; url?: string }[]
  items: {
    id: string
    url: string
    title: string
    summary?: string
    date_published?: string
    date_modified?: string
    authors?: { name: string; url?: string }[]
    tags?: string[]
  }[]
}
ExportDescription
toJsonFeed(feed)Serialize a feed to a JSON Feed 1.1 string.
toJsonFeedObject(feed)Build the JSON Feed 1.1 document as a plain object.
JsonFeedDocumentThe shape of a JSON Feed 1.1 document.

Markdown

ts
function toMarkdown(feed: NeurowireFeed): string

Serialize a feed to a Markdown document (one ### heading per entry).

NWF

The compact, line-oriented Neurowire Feed format. See the README for the full spec.

ts
function toNwf(feed: NeurowireFeed): string
function fromNwf(text: string): NeurowireFeed
function validateNwf(text: string): NwfValidation

interface NwfIssue {
  line: number
  message: string
}

interface NwfValidation {
  valid: boolean
  errors: NwfIssue[]
  warnings: NwfIssue[]
  feed?: NeurowireFeed
}
ExportDescription
toNwf(feed)Serialize a feed to NWF (interned authors/tags/sources, relative links, delta timestamps). Does not carry generator.
fromNwf(text)Parse an NWF document back into a NeurowireFeed.
validateNwf(text)Validate an NWF document and return line-numbered diagnostics, plus the parsed feed when there are no errors.
NwfIssueOne diagnostic: a 1-based line and a message.
NwfValidationValidation result: valid, errors, warnings, and (on success) feed.

Mesh and construct types

The data model for bundling sources. See meshes and constructs.

Mesh

ts
interface MeshSource {
  name: string
  url: string
}

interface Mesh {
  name: string
  sources: MeshSource[]
}

const MeshSourceSchema: z.ZodType<MeshSource>
const MeshSchema: z.ZodType<Mesh>

function parseMesh(data: unknown): Mesh

A Mesh is a named bundle of sources that fetch and merge into one feed.

ExportDescription
MeshSource / MeshSourceSchemaOne source: a display name and a url (feed or website).
Mesh / MeshSchemaA named bundle of sources.
parseMesh(data)Validate an unknown value into a Mesh. Throws ZodError.

Construct

ts
interface ConstructRef {
  ref: string
}

type ConstructMember = ConstructRef | Mesh

interface Construct {
  name: string
  meshes: ConstructMember[]
}

const ConstructRefSchema: z.ZodType<ConstructRef>
const ConstructMemberSchema: z.ZodType<ConstructMember>
const ConstructSchema: z.ZodType<Construct>

function parseConstruct(data: unknown): Construct
function isConstructRef(member: ConstructMember): member is ConstructRef

A Construct is a named bundle of meshes (a "repo" of feeds). A member is either an inline Mesh or a ConstructRef pointing at a mesh by name. A bare string in the input is shorthand for { ref: string }, so a published list can stay terse: ["ai-news", "security"].

ExportDescription
ConstructRef / ConstructRefSchemaA reference to a mesh by name, resolved at fetch time.
ConstructMember / ConstructMemberSchemaOne member: an inline Mesh, a ConstructRef, or a bare string (transformed to { ref }).
Construct / ConstructSchemaA named bundle of meshes.
parseConstruct(data)Validate an unknown value into a Construct. Throws ZodError.
isConstructRef(member)Type guard: is a resolved member a ConstructRef?

OPML export

ts
function meshToOpml(mesh: Mesh): string
function constructToOpml(construct: Construct): string
ExportDescription
meshToOpml(mesh)Serialize a mesh to an OPML 2.0 subscription list: one <outline> per source.
constructToOpml(construct)Serialize a construct to a two-level OPML 2.0 document: one category <outline> per mesh, each wrapping its sources. Reference members emit an empty category outline.

TIP

Parsing OPML the other way (OPML to mesh) lives in @neurowire/ingest as opmlToMesh, since it depends on the XML parser.

Merge

ts
interface MergePart {
  feed: NeurowireFeed
  source?: { name?: string; url?: string }
}

interface MergeOptions {
  id?: string
  limit?: number
}

function mergeFeeds(
  title: string,
  parts: MergePart[],
  options?: MergeOptions,
): NeurowireFeed

Merge several feeds into one named feed. Each entry is tagged with its source (falling back to the source feed's own title/home), entries are de-duplicated by link, sorted newest first, and capped by an optional limit.

Param / fieldTypeDescription
titlestringThe merged feed's title.
partsMergePart[]The feeds to merge plus their source labels.
options.idstring?Stable id for the merged feed. Defaults to a urn derived from the title.
options.limitnumber?Keep only the newest N entries.

Filter

Pure, deterministic entry filtering by field and pattern.

ts
type FilterField = 'title' | 'summary' | 'source' | 'author' | 'tag'

interface FilterRule {
  field: FilterField
  pattern: string
  regex?: boolean
}

interface FilterSpec {
  include?: FilterRule[]
  exclude?: FilterRule[]
}

function matchRule(entry: NeurowireEntry, rule: FilterRule): boolean
function filterEntries(feed: NeurowireFeed, spec: FilterSpec): NeurowireFeed
ExportDescription
FilterFieldThe entry field a rule matches against.
FilterRuleA field, a pattern, and an optional regex flag (substring match otherwise). Matching is case-insensitive.
FilterSpecAn include set and an exclude set of rules.
matchRule(entry, rule)Does any of the entry's values for the rule's field match?
filterEntries(feed, spec)Keep entries that match at least one include rule (or there are none) and match no exclude rule. An empty spec returns the feed unchanged.

Refine

Pure, deterministic time-window filtering, sorting, and limiting. Callers pass now (epoch ms) into resolveWindow, keeping the module side-effect free.

ts
type SortKey = 'date' | 'title' | 'source'
type SortOrder = 'asc' | 'desc'

interface SelectOptions {
  from?: number
  to?: number
  sort?: SortKey
  order?: SortOrder
  limit?: number
}

interface WindowSpec {
  since?: string
  maxAge?: string
  today?: boolean
  thisWeek?: boolean
  between?: [string, string]
}

interface TimeWindow {
  from?: number
  to?: number
}

function parseDuration(value: string): number | undefined
function resolveWindow(spec: WindowSpec, now: number): TimeWindow
function selectEntries(feed: NeurowireFeed, opts: SelectOptions): NeurowireFeed
ExportDescription
SortKey / SortOrderSort field and direction.
SelectOptionsEpoch-ms from/to bounds, a sort, an order, and a limit.
WindowSpecA high-level window: since/maxAge duration, today, thisWeek, or an explicit between pair.
TimeWindowConcrete from/to epoch-ms bounds.
parseDuration(value)Parse a duration like "24h", "90m", or "7d" into milliseconds, or undefined.
resolveWindow(spec, now)Turn a WindowSpec into epoch-ms bounds, relative to now. Precedence: between > today > thisWeek > since/maxAge.
selectEntries(feed, opts)Apply a date window, an optional sort, and an optional limit. Date sorts default to newest-first; title and source sorts default to A-Z. Undated entries are dropped only when a date bound is set.

Diff

Pure dedup helpers for watch-style polling. The seen-state lives entirely in the calling app layer.

ts
function entryKey(entry: NeurowireEntry): string
function newEntries(feed: NeurowireFeed, seen: Iterable<string>): NeurowireEntry[]
ExportDescription
entryKey(entry)Stable identity for dedup: the entry id when present, else its link.
newEntries(feed, seen)The feed's entries whose entryKey is not in seen, in original order. seen is any iterable of keys, read once.

Id helpers

Stable, content-derived entry ids. Pure and dependency-free (no node:crypto).

ts
function hashHex(input: string): string
function stableId(link: string, title: string): string
ExportDescription
hashHex(input)FNV-1a (64-bit) hash of input, as a fixed 16-char lowercase hex string.
stableId(link, title)A deterministic synthetic id derived from an entry's link and title. Shape: urn:nwf:<16-char hex>.