npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@burojs/field-types

v0.6.0

Published

Buro: default field types (string, number, ref, ...)

Readme

@burojs/field-types

The default field-type set for buro — the f.* builder you use inside defineResource.

Install

pnpm add @burojs/field-types @burojs/core

Usage

import { defineResource } from '@burojs/core';
import { f } from '@burojs/field-types';
import { users } from './users.js';

defineResource(
  'document',
  {
    fields: {
      id: f.number({ system: true }),
      title: f.string({ texts: 'Title', required: true }),
      status: f.enum(['draft', 'published'], { texts: 'Status' }),
      author: f.relation({ to: users, texts: 'Author' }),
      tags: f.relationMany({ to: 'tags', texts: 'Tags' }),
      published_at: f.dateTime({ texts: 'Published' }),
      config: f.code({ language: 'yaml' }),
      is_active: f.boolean({ texts: 'Active' }),
    },
  },
);

Each field carries its own label, validation and UI hints, so the list, show and form surfaces are all derived from one declaration.

Relations

f.relation (one key) and f.relationMany (an array of keys) name their target once, in to:

  • a resource handle — f.relation({ to: users }): typed, so a via hop past it and a by are checked against the target's fields;
  • a thunk — f.relation({ to: () => createOrderResource() }): for a resource declared later in the module, in another module that imports this one, or a factory. The thunk is called when the app first reads the resource's declaration (app.use), never inside defineResource, so two modules may relate to each other. A self-relation's thunk takes a return type, to: (): ResourceHandle => users, and the hop past it is then untyped in TypeScript (still checked at publish); between two mutually importing modules, annotate one side's thunk the same way. An error in the thunk is a rejected app.use naming the resource and the field;
  • a resource name — f.relation({ to: 'articles' }): legal for a target you cannot import, and untyped past the hop.
product: f.relation({ to: products, by: 'gtin', resolve: 'lazy' }), // stores the gtin; shows it, no lookup
owner: f.relation({ to: users }),                                    // eager by default: shows the title
tags: f.relationMany({ to: tags }),
  • by names the target field the key holds when it is not the id. It must be a declared, scalar, non-sensitive target field that admits eq and in, in the target's record DTO, and not its id field (data.idField ?? 'id'); otherwise it is relation.by-invalid at publish (and a TS error with a typed target). Every reader follows it: via hops, displays (one batched <by> in [...] read per column), the picker (it writes row[by]) and the list filter. Two target rows with the same key are never resolved to either: the display shows the key muted and logs a development error.
  • resolve governs displays only: 'eager' (the default) reads the target's title through the batched aggregator; 'lazy' reads nothing and shows the key — a link to the target's show page when the key is the id, plain text with by. A picker always resolves its selected value.

f.ref, f.refMany, f.children and f.relation's second argument are removed:

| Removed | Write instead | | --- | --- | | f.ref(target, opts) | f.relation({ to: target, ...opts }) | | f.refMany(target, opts) | f.relationMany({ to: target, ...opts }) | | f.children(target, { inline, fields, ... }) | no field: place relatedList(target) with where: [{ field: '<fk>', op: 'eq', value: l.ref('record', 'id') }] on the parent's show tree | | f.relation(opts, target), ui.to | f.relation({ to: target, ...opts }) |

Many-to-many links

A link keeps its keys in a junction resource instead of a column of the record. Declare it with through:

reservedCodes: f.relationMany({
  to: codes,
  through: {
    resource: documentCodes,   // the junction: a handle, a thunk or a name
    sourceKey: 'document_id',  // the junction field holding this record's key
    targetKey: 'code_id',      // the junction field holding the target's key
    where: [{ field: 'type', op: 'eq', value: 'reserved' }], // optional, literals only
  },
  preview: 'chips',
  providerOptions: { relationship: 'document_codes' },  // optional provider hint
}),
  • through — with a typed junction, sourceKey, targetKey and every where.field are checked against its fields. Because f.relationMany has two overloads (a column, a link), an invalid link reports its error on the call expression (TS2769, "No overload matches this call") rather than on the bad key. where holds literal values in the where vocabulary and applies to every read and filter of the link. At publish the junction must be a resource of the application whose sourceKey and targetKey are to-one relations to the owner and the target; see the JSON form for the checker codes. A link takes no by, nullable or default.
  • Separate (the default) or inline: true. A separate link is read apart from the record: place a panel of it with relatedList({ link: 'reservedCodes' }) (see the screens guide), and it is not shown in show or list modes on its own. preview: 'chips' also shows its keys as chips in show and list. inline: true shows them as chips with the record. Chips read the junction in batches for the visible rows, with any data provider. A record with more than 1000 junction rows shows "More than 1000 links" in place of its chips, and keys whose target the user may not see collapse into one "N not visible" chip.
  • Filtering. A link filters with has, hasAny, hasAll and hasNone (see the lists guide).

Limits of this version. Links are read-only. A link field is not a form input (inline links included), is never part of a create or update payload or of the record DTO, and is not part of the inferred record type (InferRecord). A separate link cannot be required (relation.through-required); required on an inline link is accepted and not yet enforced. through: 'provider' is refused.

Main exports

  • f / FieldBuilder / extendFieldBuilder — the field builder; build a resource's fields with it and pass them to @burojs/core's defineResource. An extended builder writes import { f as base } from '@burojs/field-types' and extendFieldBuilder(base, …). Includes string, number, boolean, date, dateTime, enum, relation, relationMany, code, richText, json and friends; every field takes in (the modes it appears in: planned and checked from P1a, honoured by forms and show from P1b).
  • fieldTypesPlugin — registers the set with a defineApp instance.
  • fromSchema / FromSchemaFieldDef — derive fields from an existing schema instead of hand-writing them.
  • isStandardSchema and StandardSchemaV1 — re-exported from @burojs/core: the guard detects a Standard Schema v1 validator (an object or, like an ArkType type, a function), so Zod/Valibot/ArkType can be passed to a field's validate with no runtime dependency on any of them.

License

MIT