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

@nimir/references

v1.0.0

Published

Nested resource references resolver for typescript

Readme

@nimir/references - Type-safe nested reference resolver for resource graphs

npm version npm downloads bundle size license

Define sources (how to fetch resources), then resolve arbitrary payloads by declaring which fields are references. Its all type safe.

Install

pnpm install @nimir/references
npm install @nimir/references
yarn add @nimir/references
deno install npm:@nimir/references
bun install npm:@nimir/references

Features

  • defineReferences(builder) — create a typed reference resolver with named sources.
  • refs.inline(data, config) — resolve references in a payload, return a cloned result.
  • refs.fn(fn, config) — wrap an async function; auto-resolve references in its return value.
  • refs.invalidate(source, ids?) — clear cache entries for a source.
  • refs.restore() — hydrate sources from persistent cache.
  • refs.clear() — invalidate all sources.
  • Batch and list source modes with inflight deduplication.
  • Pluggable caching: in-memory, IndexedDB (idb-keyval), Redis.
  • React integration via refs.hook and refs.use.
  • Nested resolution up to 10 levels deep.
  • Null-safe: missing IDs resolve to null.

Quick start

import { defineReferences } from '@nimir/references';

type Faculty = { id: string; name: string };
type Branch = { id: string; facultyId: string };

const references = defineReferences(c => ({
  Faculty: c.source<Faculty>({
    batch: async ids => fetchFaculties(ids),
  }),
  Branch: c.source<Branch>({
    batch: async ids => fetchBranches(ids),
  }),
}));

const result = await references.inline(
  { branchId: 'b1' as string | null },
  {
    fields: {
      branchId: { source: 'Branch', fields: { facultyId: 'Faculty' } },
    },
  },
);

// result.branchIdT  → Branch | null
// result.branchIdT.facultyIdT → Faculty | null

How resolution works

Fields config

Resolution is driven by a fields object that mirrors the shape of your data:

  • Direct ref: { userId: 'User' }
  • Direct ref array: { userIds: 'User' } (where userIds is Array<string | null | undefined>)
  • Nested ref: { branchId: { source: 'Branch', fields: { facultyId: 'Faculty' } } }
  • Structural nesting (into objects/arrays without creating a reference):
    • { profile: { avatarFileId: 'File' } }
    • { items: { productId: 'Product' } } for items: Array<{ productId: ... }>

Output shape (T / Ts)

For a field x:

  • If x is a single ref ID (string | null | undefined), the resolved value is added at xT.
  • If x is an array of ref IDs (Array<string | null | undefined>), the resolved values are added at xTs.

The original ID fields stay as-is; the library returns a cloned object (no mutation).

Null / missing semantics

  • If the ID is null/undefined, the corresponding xT / xTs[i] is null.
  • If the ID is present but not returned by the source, it resolves to null.

Define sources

defineReferences takes a builder callback. Each entry becomes a named source usable in fields.

Sources can be configured in two modes:

batch — fetch by IDs

Fetches only requested IDs. Supports batching, inflight deduplication, and negative caching.

import { defineReferences } from '@nimir/references';

type User = { id: string; email: string };

const references = defineReferences(c => ({
  User: c.source<User>({
    batch: async ids => fetchUsers(ids),
    // batchSize: 200,       (max IDs per batch call, default 200)
    // ttlMs: 60_000,        (cache TTL in ms, default 4 hours)
    // keyBy: u => u.id,     (ID extractor, default item.id)
    // cache: ReferenceCache.new(createMemoryCache()),
  }),
}));

list — fetch all

Fetches a full collection and resolves from it. Refreshes on TTL expiry.

const references = defineReferences(c => ({
  Role: c.source<Role>({
    list: async () => fetchAllRoles(),
    // ttlMs: 60_000,
    // keyBy: r => r.id,
    // cache: ReferenceCache.new(createMemoryCache()),
  }),
}));

Caching

The source layer supports persistent caching via ReferenceCache.

import { ReferenceCache } from '@nimir/references';
import { createMemoryCache } from '@nimir/references/in-memory';

type User = { id: string; email: string };

const cache = ReferenceCache.new<User>(createMemoryCache());

const references = defineReferences(c => ({
  User: c.source<User>({
    batch: ids => fetchUsers(ids),
    cache,
    ttlMs: 5 * 60_000,
  }),
}));

IndexedDB (via idb-keyval)

Optional adapter — the core package stays runtime-agnostic.

import { ReferenceCache } from '@nimir/references';
import { createIdbKeyvalCache } from '@nimir/references/idb-keyval';

const cache = ReferenceCache.new<User>(createIdbKeyvalCache({ database: 'my-app', table: 'references' }));

Redis

Generic adapter — bring any Redis client (ioredis, redis, @upstash/redis, etc.).

import { ReferenceCache } from '@nimir/references';
import { createRedisCache } from '@nimir/references/redis';
import Redis from 'ioredis';

const cache = ReferenceCache.new<User>(createRedisCache({ client: new Redis(), prefix: 'my-app:refs:' }));

Node API

  • defineReferences((builder) => ({ ...sources })) — create a Refs instance.
  • refs.inline(data, { fields, transform? }) — resolve references in data, return a cloned result.
  • refs.fn(fn, { fields, transform? }) — wrap an async function; resolves references in its return value.
  • refs.invalidate(sourceName, ids?) — clear in-memory + persistent cache entries for one source.
  • refs.restore() — eagerly hydrate all sources from persistent cache.
  • refs.clear() — invalidate all sources.

React API

Extends base node with React specific functions:

  • refs.hook(useQuery, { fields, transform? }) — wrap a data hook; returns { result, status, fetchStatus, error, invalidate }.
  • refs.use(data, { fields, transform? }) — resolve inline data reactively.
import { defineReferences } from '@nimir/references/react';

const refs = defineReferences(c => ({ ... }));

// Wrap a hook — returns { result, status, fetchStatus, error, invalidate }
const useTicket = refs.hook(useGetTicket, { fields: { assigneeId: 'User' } });

// Or resolve inline data
const resolved = refs.use(data, { fields: { assigneeId: 'User' } });

Caveats

  • Depth limit — resolution is bounded at 10 levels to prevent infinite loops on circular configs.
  • Unknown sources — referencing a source name that doesn't exist at runtime is silently skipped.