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

@xenosystem/data-core

v0.1.0

Published

The shared data contract for the XENO data-panel family — XenoResultSet down, XenoQuery up — plus the pure pipeline (filter, sort, group, aggregate, paginate, format, pivot). Zero dependencies, zero DOM.

Readme

@xenosystem/data-core

XenoResultSet flows down, XenoQuery flows up. One contract for the whole data-panel family, plus the pure pipeline that realises it.

Six panels — table · connectors · fields · chart · metrics · calendar — and every host resolver speak exactly this vocabulary. That is what makes connectors → table → chart wireable at all: all four ports carry the same two shapes.

Zero dependencies. Zero DOM. Zero React. Importable from a panel, a host, a worker, or a test.

The contract was invented twice before it was written down

sheets' SQLEngine.SQLResult and DatabaseConnector.DatabaseQueryResult are field-for-field identical ({columns, rows, rowCount, executionTime, error?}) — convergent invention inside one repo. This package is that shape, widened by a 23-surface survey and hardened against the twelve landmines in XENO DATA PANELS - UNION SPEC.md §6.

Install

npm install @xenosystem/data-core

Quickstart — a host resolver

import { runQuery } from '@xenosystem/data-core'

// A creative app answering a panel's queryRequest from its own engine.
host.on('queryRequest', ({ requestId, query }) => {
  const result = runQuery(engine.allRows(), query, { columns: engine.columns, rev: engine.rev })
  host.emit('result', { requestId, result })
})

That is the whole seam. The panel never reads the network; the host never renders a cell.

What the types enforce

Several fields exist specifically to make a shipped bug unrepresentable:

| Field | Prevents | |---|---| | XenoRow.id (mandatory) | positional row identity — sort/filter/page a positional result and selection, editing and drill-down all target the wrong row | | XenoColumn.id ≠ name | resolving a column by display name; a JOIN with two same-named columns silently picks the first | | XenoAggregate.totalFn | grand totals that always sum — an average column whose total is the sum of the averages | | XenoColumn.type | comparators chosen by runtime typeof, which degrade a numeric column to lexicographic on one stray string | | XenoWindow.timeZone (mandatory) | UTC day-bucketing that puts a 23:00-local event on the wrong day | | XenoGroup.key: XenoScalar[] | group-key collisions — a real value "No Value" merging into the empty bucket | | XenoFilterOp.relative | hardcoded 7/30/90-day tabs, which is what one product shipped for want of this primitive | | XenoValueFormat.nullText | a hardcoded empty-display word that a real value can collide with |

Two of those are wrong answers currently on screen in shipping products. Each has a named regression test in test/landmines.test.ts.

The pipeline

filter → sort → group → aggregate → paginate → (format at render)

Stage order is not negotiable. Aggregating after paging computes the page's average, not the column's. Grouping after paging invents groups that do not exist. And totals always come from the full filtered set — the page is a viewport, not a population.

| Stage | Module | The decision | |---|---|---| | filter | filter.ts | One operator enum over four incompatible vocabularies. relative stores {unit, n, anchor} as data, so a saved query still means what it said tomorrow. An empty group passes — a half-authored filter must not blank the table. | | sort | compare.ts | One comparator pass, not N stable passes. Comparators come from column.type. Nulls are placed (last by default, in both directions), not compared. Documented tiebreak on row.id so paging can never repeat one row and drop another. | | group | group.ts | Structured XenoScalar[] keys with a control-character-delimited internal serialization; null is a sentinel, never a word. Date buckets go through the zone; numeric buckets are fixed-width. | | aggregate | aggregate.ts | 17 functions incl. percentiles (R-7 / PERCENTILE.INC, stated because definitions differ) and sample stdev/var (n-1). Non-numeric cells are excluded, never counted as 0. | | totals | aggregate.ts | totalFn ?? fn, computed from the rows — never rolled up from the group results. DECOMPOSABLE_AGGREGATES names the exact set for which a roll-up would have been safe. | | pivot | pivot.ts | Each (column-key × measure) becomes a synthetic leaf column, so a grid reads group.aggregates[column.id] with no pivot-specific lookup. This is why pivot is a view mode and not a sixth panel. | | paginate | pipeline.ts | Last. Clamps nonsense input rather than returning garbage. | | format | format.ts | Presentation only — never fed back into a comparator. (A grid that sorts formatted strings puts '1,000' before '900'.) |

Time is where two products got a wrong answer

A date is a calendar day with no instant — '2026-03-15' is the 15th everywhere, and applying a zone to it is a bug. A datetime is an instant, which only becomes a day once a zone is applied. That distinction is why the two types are separate, and time.ts honours it:

bucketKey(parseTime('2026-03-15T00:30:00+02:00', 'datetime')!, 'day', 'Europe/Bucharest')  // '2026-03-15'
bucketKey(parseTime('2026-03-15T00:30:00+02:00', 'datetime')!, 'day', 'UTC')               // '2026-03-14'  ← the bug
bucketKey(parseTime('2026-03-15', 'date')!, 'day', 'America/Los_Angeles')                  // '2026-03-15'  ← never shifts

Calendar shifts also clamp rather than overflow: one month before 31 March is 28 February, not 3 March. (Found by a test written against this package, not against the survey.)

The data-access seam

queryRequest out / result in, correlated by a mandatory requestId — the panel keeps only the newest outstanding id and drops stale results. Without it a slow page-1 response lands after page-2 and the grid silently shows the wrong page.

Two resolvers, one contract. In a creative app the host answers from its own engine; in xeno-apps the connectors panel is the resolver, wired in the user's graph. Neither the table nor the chart can tell the difference — which is the property that makes this family serve both goals at once.

Consequence: table, chart, metrics, calendar and fields declare storage.local and nothing else. connectors is the sole capability holder.

Scripts

npm run typecheck   # tsc --noEmit
npm test            # vitest run   (106 tests)
npm run build       # tsup → dist (esm + cjs + d.ts)

See INTEGRATION.md and examples/ (a node-runnable resolver).