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

trajectory-rollup

v0.1.0

Published

Build deterministic, boundary-aware cell and edge rollups from trajectory observations.

Readme

trajectory-rollup

trajectory-rollup turns ordered position observations into deterministic cell and directed-edge summaries. Session boundaries, idle gaps, spatial jumps, exact distinct membership, late data, and window closure are explicit parts of the contract.

The package has no runtime dependencies and supports both import and require.

Why it exists

Batch mobility jobs often hide important policy in SQL: which timestamp owns a transition, whether an equality is inside an idle threshold, how negative coordinates map to cells, and whether daily distinct counts can be added. This package makes those choices testable and versioned.

Core example

import { rollupTrajectory } from "trajectory-rollup";

const result = rollupTrajectory(
  [
    {
      kind: "position",
      actorKey: "actor-a",
      eventTimeMs: 1_000,
      orderKey: "001",
      space: "alpha",
      x: -1,
      z: 5,
    },
    {
      kind: "position",
      actorKey: "actor-a",
      eventTimeMs: 2_000,
      orderKey: "002",
      space: "alpha",
      x: 12,
      z: 5,
    },
  ],
  {
    window: { startMs: 0, endMs: 10_000 },
    regionSize: 10,
    idleGapMs: 5_000,
    maxJumpDistance: 1_000,
    actorToken: (actorKey) => `opaque:${actorKey}`,
  }
);

The first point belongs to cell (-1, 0): cell coordinates use mathematical floor, including for negative positions. An edge belongs to the window containing its origin observation. A destination may therefore fall just beyond the window end when it is within the configured idle look-ahead.

Ordering and boundaries

Records are grouped by actorKey and sorted by (eventTimeMs, orderKey). Reusing the same tuple is an error because silently choosing a predecessor would make the result input-order dependent. session-start and session-end records break continuity. A gap equal to idleGapMs remains continuous; a larger gap begins a new segment.

Changing space or exceeding maxJumpDistance produces a teleport-like edge rather than a normal trip. Space-changing edges retain both space (the origin) and toSpace (the destination), so coordinates from different spaces cannot collapse into one key. sumDistance adds Euclidean distance only within one coordinate space; it is zero for a space-changing edge rather than inventing a metric between unrelated frames. Same-cell observations contribute cell samples but do not create an edge.

Exact distinct counts

Every cell and edge contains an ordered set of opaque membership tokens. mergeTrajectoryRollups unions those tokens and recomputes the count; it never adds per-part distinct counts. The actorToken callback should return a stable, non-reversible token suitable for the caller's privacy model. This package does not provide anonymity or k-anonymity.

Parts with different profile IDs cannot be merged. The profile captures algorithm version, region size, idle threshold, jump threshold, time source, and attribution policy.

With no target window, every part must describe the same interval; this is useful for merging shards. To combine daily parts into a monthly result, pass the monthly window. The unique part intervals must cover it exactly without gaps or partial overlaps. Identical intervals may repeat for shards, and exact memberships are unioned across every part. The caller must supply every record-disjoint shard exactly once; exact interval coverage cannot detect a missing or repeated shard, and repeated scalar inputs would be added twice.

Window closure and late data

planClosableWindows marks a window closable only after the source watermark reaches:

window end + idle gap + allowed lateness + ingestion flush delay

Late observations or ranges can mark every affected origin window dirty, including the preceding idle-look-ahead interval. Recompute dirty windows from source records; do not incrementally subtract an unknown prior predecessor.

PostgreSQL

The trajectory-rollup/postgres entrypoint accepts a complete rollup (including diagnostics) and a structural query client, with no dependency on a particular PostgreSQL driver. Its revision executor uses one client for BEGIN through COMMIT, takes a transaction-scoped lock, stages a new revision, replaces one window/profile revision, writes exact memberships, and rolls back on error. When recomputing a dirty window, pass the marker's observed dirtyGeneration. The transaction deletes that marker only when the generation still matches, so a concurrent late-data update remains queued. Marker writers must increment generation on every conflict; omitting dirtyGeneration leaves the marker untouched. Generated schema and table identifiers are rejected when they exceed PostgreSQL's standard 63-byte identifier limit, avoiding silent server-side truncation and collisions.

See design, service adapter, and prior art for the boundaries of the package.

Limitations

  • Input coordinates are planar. Supply a projection before calling the package.
  • Opaque membership tokens can still be personal data depending on how they are made and retained.
  • A watermark is a caller assertion about source completeness, not a guarantee created here.
  • PostgreSQL execution is atomic within one database transaction; it is not atomic with external queues, object stores, or caches.

Development

npm install
npm run check

Licensed under the ISC License.