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

@longsightgroup/oneroster

v0.3.0

Published

Faithful OneRoster 1.2 for TypeScript — CSV and portable REST contracts, consumers, and provider helpers.

Readme

OneRoster for TypeScript

Parse, validate, and write OneRoster CSV packages. Use versioned clients and provider routes for OneRoster REST APIs.

The package uses standard Web APIs and runs in Node, Deno, Cloudflare Workers, Hono, and browser-compatible bundlers.

Install

pnpm add @longsightgroup/oneroster

How the package is organized

| Import | Use when | | -------------------------------- | -------------------------------------------------------- | | @longsightgroup/oneroster | CSV ZIP packages for rostering, gradebook, and resources | | @longsightgroup/oneroster/v1p2 | OneRoster 1.2 REST (normative target) | | @longsightgroup/oneroster/v1p1 | OneRoster 1.1 REST compatibility |

Import REST APIs from the version you need. The v1.1 and v1.2 entry points have separate types and behavior, with no cross-version fallback.

New to the REST API? Start with the OneRoster REST guide, then use the generated operation catalog to find every client call, HTTP path, query option, and OAuth scope.

CSV support follows the corrected OneRoster CSV Binding 1.2.1. Packages still declare oneroster.version,1.2 in manifest.csv. The 1.2.1 number identifies the CSV document correction level, not a separate manifest or REST version.

CSV: parse a package

parseAndValidateOneRosterCsvFullZip accepts ZIP bytes and returns a Result. A successful result contains typed records. An expected data failure contains structured diagnostics instead of throwing.

import { parseAndValidateOneRosterCsvFullZip } from "@longsightgroup/oneroster";

const result = parseAndValidateOneRosterCsvFullZip(zipBytes, {
  referenceMode: "allRows",
});

if (result._tag === "err") {
  for (const d of result.error) {
    console.error(d.code, d.fileName, d.rowNumber, d.field, d.message);
  }
} else {
  const { users, classes, enrollments } = result.value.fullPackage.rosteringPackage;
  console.log(users.length, classes.length, enrollments.length);
}

Round-trip a validated package back to normalized ZIP bytes:

import {
  parseAndValidateOneRosterCsvFullZip,
  writeOneRosterCsvFullZip,
} from "@longsightgroup/oneroster";

const parsed = parseAndValidateOneRosterCsvFullZip(zipBytes);
if (parsed._tag === "ok") {
  const written = writeOneRosterCsvFullZip(parsed.value.fullPackage);
}

For separate CSV files, use parseAndValidateOneRosterCsvFullFiles. Use parseOneRosterCsvZip when you need the lower-level ZIP parsing boundary.

REST: read roster data (1.2)

Create a client with a service base URL and an accessTokenProvider. Read methods encode query parameters and parse each response. Use collectAll* or iterateAll* to follow pages.

import {
  createOneRosterV1p2FilterClause,
  createOneRosterV1p2RosteringClient,
} from "@longsightgroup/oneroster/v1p2";

const filter = createOneRosterV1p2FilterClause("status", "=", "active");
if (filter._tag !== "ok") throw new Error("bad filter");

const client = createOneRosterV1p2RosteringClient({
  serviceBaseUrls: {
    rostering: "https://sis.example/ims/oneroster/rostering/v1p2",
  },
  accessTokenProvider: (scopes, signal) => yourTokenService(scopes, signal),
});

if (client._tag === "ok") {
  const page = await client.value.getAllUsers({
    query: { limit: 100, filter: filter.value },
    signal: AbortSignal.timeout(30_000),
  });

  if (page._tag === "ok") {
    for (const user of page.value.items) {
      console.log(user.sourcedId, user.givenName, user.familyName);
    }
  }

  // The caller sets limits for full traversal.
  const all = await client.value.collectAllUsers({ maxPages: 50, maxItems: 10_000 });

  // Lazy traversal uses the same link and offset rules without materializing every item.
  for await (const result of client.value.iterateAllUsers({
    maxPages: 50,
    maxItems: 10_000,
    signal: AbortSignal.timeout(30_000),
  })) {
    if (result._tag === "err") {
      console.error(result.error._tag);
      break;
    }
    processPage(result.value);
  }
}

TypeScript checks field projections and narrows the result. For example, pass query: { fields: ["sourcedId", "givenName"] } to request those fields.

Filter combination has a different return type in each version. combineOneRosterV1p1Filters returns the combined filter directly to maintain compatibility with the v1.1 API. combineOneRosterV1p2Filters returns a Result. Both functions accept validated clauses and an "AND" | "OR" join, so callers should follow the return type from their versioned import.

The 1.1 and 1.2 clients support optional read retries. A retry policy must be bounded, applies only to GET, observes Retry-After, and passes the request's abort signal to backoff waits. Without retryPolicy, each operation makes one request. Tests and hosts with their own clock can provide retryClock: { nowMilliseconds }. Other clients use the system clock.

const resilientClient = createOneRosterV1p2RosteringClient({
  serviceBaseUrls: {
    rostering: "https://sis.example/ims/oneroster/rostering/v1p2",
  },
  accessTokenProvider: (scopes, signal) => yourTokenService(scopes, signal),
  retryPolicy: {
    maxAttempts: 3,
    maxElapsedMilliseconds: 15_000,
    statusCodes: [429, 502, 503],
    retryConnectionErrors: true,
  },
});

REST: pass back grades (1.2)

Gradebook writes do not require an options object. To cancel a slow request after 30 seconds, pass { signal: AbortSignal.timeout(30_000) }. An AbortSignal is the standard Web API value that tells fetch when to stop a request. POST, PUT, and DELETE each make one authenticated request, and the client never retries writes automatically.

import { createOneRosterV1p2GradebookClient } from "@longsightgroup/oneroster/v1p2";

const client = createOneRosterV1p2GradebookClient({
  serviceBaseUrls: {
    gradebook: "https://sis.example/ims/oneroster/gradebook/v1p2",
  },
  accessTokenProvider: (scopes, signal) => yourTokenService(scopes, signal),
});

if (client._tag === "ok") {
  await client.value.putResult(result.sourcedId, result);
  const check = await client.value.getResult(result.sourcedId);
}

Assessment Results Profile clients use the gradebook base URL and are exported from @longsightgroup/oneroster/v1p2. Resources has a separate client. To build a OneRoster provider, use createOneRosterV1p2ProviderRouter. Supply authentication and persistence implementations; the router accepts Request and returns Response.

REST: authorize legacy 1.1 requests

OneRoster 1.1 always requires an explicit request authorizer. Hosts can keep injecting their own implementation, or deliberately select the portable OAuth 1.0a HMAC-SHA1 helper. The helper reads no environment variables and performs no authentication probing.

import {
  createOneRosterV1p1OAuth1Authorizer,
  createOneRosterV1p1RosteringClient,
} from "@longsightgroup/oneroster/v1p1";

const authorizer = createOneRosterV1p1OAuth1Authorizer({
  credentials: {
    consumerKey: yourConsumerKey,
    consumerSecret: yourConsumerSecret,
    token: yourToken,
    tokenSecret: yourTokenSecret,
  },
});

if (authorizer._tag === "ok") {
  const client = createOneRosterV1p1RosteringClient({
    baseUrl: "https://sis.example/ims/oneroster/v1p1",
    authorizer: authorizer.value,
  });
}

What's implemented

| Area | Status | | ------------------------------------------------- | ------ | | CSV ZIP intake + manifest | Done | | Rostering, gradebook, resources CSV tables | Done | | CSV write + record builders | Done | | Reference and duplicate-ID validation | Done | | Official CSV Rostering reference gate (719 cases) | Done | | OneRoster 1.2 REST consumers | Done | | OneRoster 1.1 REST compatibility | Done | | Provider router (framework-neutral) | Done | | OpenAPI parity gate (pnpm run rest:spec-check) | Done |

The only runtime dependency is fflate, which parses ZIP containers. Its types are not part of the public API.

Development

pnpm run check    # format, lint, typecheck, tests
pnpm run build    # emit dist/
pnpm run csv:rostering-cert-check # verify all official bulk and delta Rostering cases

Conformance-oriented checks also include pnpm run test:portability, pnpm run compatibility:v1p1, pnpm run csv:rostering-cert-check, and pnpm run rest:spec-check. The CSV and REST specification checks download SHA-pinned official artifacts into ignored .specs/.