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

@linklabjs/core

v0.1.3

Published

LinkLab core — semantic navigation graph engine

Readme

@linklabjs/core

The graph is the map. The Trail is the traveler.

"The Trail defines the path, the history, and the intention.
The graph knows the possibilities."

LinkLab associates two concepts:

  • The compiled graph — the map: entities, relations, optimal routes
  • The Trail — the traveler: navigation, context, history, intention

The map knows all paths. The traveler decides where to go — and by traveling, enriches the map.


The problem LinkLab solves

In every application, we write the same SQL joins by hand:

-- Get all actors in films directed by Nolan
SELECT people.*
FROM directors
  INNER JOIN credits ON directors.id = credits.personId AND credits.jobId = 2
  INNER JOIN movies  ON credits.movieId = movies.id
  INNER JOIN credits c2 ON movies.id = c2.movieId AND c2.jobId = 1
  INNER JOIN people  ON c2.personId = people.id
WHERE directors.name = 'Nolan'

With LinkLab:

cinema.directors('Nolan').movies.actors

LinkLab generates the SQL, finds the optimal path in the graph, and improves continuously from usage traces.


Installation

npm install @linklabjs/core

Quick start

import { Graph } from '@linklabjs/core'
import compiledGraph from './linklab/netflix/netflix.json'
import * as dataset from './data'

const graph = new Graph(compiledGraph, { dataset })
const netflix = graph.domain()

// Fluent navigation
const actors    = await netflix.movies(278).actors
const films     = await netflix.directors('Nolan').movies
const colleagues = await netflix.actors('DiCaprio').movies.directors

The result is a plain JavaScript array — map, filter, sort as usual:

const titles = await netflix.directors('Nolan').movies
  .then(films => films.filter(f => f.release_year > 2000))
  .then(films => films.map(f => f.title))
// ['Interstellar', 'Inception', 'The Dark Knight'...]

How it works

Your database or JSON files
    ↓  linklab build
{alias}.json          (compiled graph — precalculated routes)
    ↓  QueryEngine
SQL generated automatically
    ↓  NavigationEngine
Fluent API: cinema.directors('Nolan').movies.actors

linklab build is a CLI command from @linklabjs/cli. It produces the compiled graph that @linklabjs/core consumes at runtime.


Semantic views

When the same entity appears in multiple roles — actors, directors, writers all being people — LinkLab detects this at compile time and generates semantic views automatically:

netflix.movies(278).people      → everyone (all roles)
netflix.movies(278).actors      → actors only
netflix.movies(278).director    → director only
netflix.movies(278).writers     → writers only

people('Christopher Nolan').director and directors('Christopher Nolan') are equivalent — same entity, filtered by role. No separate endpoint to maintain.


API levels

Level 1  cinema.directors('Nolan').movies.actors
         → semantic facade, transparent, 80% of use cases

Level 2  graph.from('Pigalle').to('Alesia').path(Strategy.Shortest)
         → paths, strategies, Dijkstra

Level 3  graph.entities / .relations / .weights
         → introspection, debug, dashboards

Level 4  graph.weight(edge).set(value) / .compile()
         → metaprogramming, CalibrationJob

Level 1 — Semantic facade

new Graph(compiledGraph, options?)Graph

Main entry point. Builds a navigable graph.

import { Graph } from '@linklabjs/core'

const graph = new Graph(compiledGraph, {
  compiled?: CompiledGraph,           // precalculated routes
  dataset?:  Record<string, any[]>,   // JSON data in memory
  provider?: Provider,                // PostgresProvider for real database
})

graph.domain()DomainProxy

Returns the transparent semantic proxy (Level 1).

const cinema = graph.domain()

const cast   = await cinema.movies(278).people
const films  = await cinema.directors('Nolan').movies
const found  = await cinema.movies({ title: 'Inception' })

Level 2 — Pathfinding

graph.from(nodeId)PathBuilder

const builder = graph.from('Pigalle').to('Alesia')

builder.paths()                   // all paths — Shortest by default
builder.paths(Strategy.Comfort()) // +8 min per transfer
builder.path()                    // best path only
builder.links                     // subgraph between two nodes

Strategy

import { Strategy } from '@linklabjs/core'

Strategy.Shortest()       // minimal raw weight (default)
Strategy.Comfort()        // +8 min per transfer
Strategy.Custom(penalty)  // +penalty per transfer

Level 3 — Introspection

graph.entities   // GraphNode[]  — all nodes
graph.relations  // GraphEdge[]  — all edges
graph.schema     // Record<string, string>  — node types
graph.weights    // Map<string, number>  — current weights

Fastify plugin — REST + HATEOAS

import Fastify from 'fastify'
import { linklabPlugin } from '@linklabjs/core'

const app = Fastify()

await app.register(linklabPlugin, {
  graph:      compiledGraph,
  prefix:     '/api',
  dataLoader: { provider: postgresProvider },
  onEngine:   (engine, req) => {
    engine.hooks.on('access.check', async (ctx) => {
      if (!req.user) return { cancelled: true, reason: 'unauthenticated' }
    })
  }
})

// These routes work automatically — no configuration:
// GET /api/movies
// GET /api/movies/278
// GET /api/movies/278/people
// GET /api/directors/2/movies

Response includes _links generated from the graph:

{
  "id": 504,
  "name": "Tim Robbins",
  "_links": {
    "self":    { "href": "/api/movies/278/people/504" },
    "up":      { "href": "/api/movies/278" },
    "movies":  { "href": "/api/movies/278/people/504/movies" },
    "credits": { "href": "/api/movies/278/people/504/credits" }
  }
}

Low-level API

QueryEngine

import { QueryEngine } from '@linklabjs/core'

const engine = new QueryEngine(compiledGraph)

engine.getRoute(from, to)                        // RouteInfo
engine.generateSQL(options: QueryOptions)         // string — readable SQL
engine.executeInMemory(options, dataset)          // any[] — JSON execution
engine.generateJSONPipeline(options)              // object — debug
interface QueryOptions {
  from:      string
  to:        string
  filters?:  Record<string, any>   // WHERE conditions
  semantic?: string                // semantic view label — ex: 'actor'
}

PathFinder

import { PathFinder } from '@linklabjs/core'

const finder = new PathFinder(graph)

finder.findShortestPath(from, to)           // PathDetails | null
finder.findAllPaths(from, to, maxPaths?)    // Path[]
finder.hasPath(from, to)                    // boolean
finder.getReachableNodes(from, maxDepth?)   // Set<string>
finder.getPathWeight(path)                  // number
finder.getStats()                           // { nodes, edges, avgDegree }

GraphCompiler

import { GraphCompiler } from '@linklabjs/core'

const compiler = new GraphCompiler({
  weightThreshold?: number,   // pruning threshold (default: 1000)
  keepFallbacks?:   boolean,  // keep alternative routes
  maxFallbacks?:    number,   // max alternatives per route
})

compiler.compile(graph, metrics): CompiledGraph

Core types

interface GraphNode {
  id:      string
  type:    string
  label?:  string
  [key: string]: any
}

interface GraphEdge {
  from:     string
  to:       string
  weight:   number
  name?:    string
  via?:     string
  metadata?: Record<string, any>
}

interface CompiledGraph {
  version:     string
  compiledAt:  string
  nodes:       GraphNode[]
  routes:      RouteInfo[]
}

interface RouteInfo {
  from:      string
  to:        string
  semantic?: boolean
  label?:    string
  primary: {
    path:    string[]
    edges:   RouteStep[]
    weight:  number
    joins:   number
  }
  fallbacks: RouteInfo['primary'][]
}

interface Provider {
  query<T>(sql: string, params?: any[]): Promise<T[]>
  close(): Promise<void>
}

Recommended imports

import {
  Graph,
  Strategy,
  PathFinder,
  QueryEngine,
  GraphCompiler,
  NavigationEngine,
  linklabPlugin,
} from '@linklabjs/core'

import type {
  GraphNode,
  GraphEdge,
  CompiledGraph,
  RouteInfo,
  QueryOptions,
} from '@linklabjs/core'

Examples

| Example | Source | Demonstrates | |---------|--------|-------------| | dvdrental | PostgreSQL | FK relations, semantic views, full pipeline | | netflix | JSON | Pivot detection, semantic views (actors/directors/writers) | | cinema | JSON | Minimal graph, REPL starting point | | metro | GTFS open data | Dijkstra, real RATP weights, strategies | | musicians | Manual | Cycles, minHops, via filter |

See the examples folder.


Custom formatters

Extend BaseFormatter to transform raw navigation results into domain-readable output:

import { BaseFormatter } from '@linklabjs/core'
import type { NavigationPath } from '@linklabjs/core'

export class MyFormatter extends BaseFormatter {
  format(path: NavigationPath): string {
    return [
      `Path: ${path.nodes.join(' → ')}`,
      `Weight: ${path.totalWeight}`,
    ].join('\n')
  }
}

Not an ORM

LinkLab does not map tables to objects. It does not manage migrations. It does not hide your SQL.

It compiles a navigation graph from your existing schema and resolves paths through it. The generated SQL is readable — visible in the REPL and in QueryEngine.generateSQL().



License

MIT — Charley Simon