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

adya-ras-connect

v0.2.3

Published

Mount the RAS integration surface into your Express app: SSO handoff, record sync, and deliverable return.

Readme

adya-ras-connect

Mount the RAS integration surface into your Express app. Three functions of your code; the protocol is ours.

npm install adya-ras-connect
import express from 'express'
import { rasConnect, paginateByWatermark } from 'adya-ras-connect'

const app = express()
app.use(express.json({ limit: '32mb' }))   // deliverables arrive as base64

app.use('/ras', rasConnect({
  integrationKey: process.env.RAS_INTEGRATION_KEY!,

  // Who is signed in? Your auth, your call. Return null to refuse.
  resolveUser: async (req) => {
    const user = await currentUser(req)
    if (!user?.canUseRas) return null
    return {
      user_id: String(user.id),
      email: user.email,
      name: user.name,
      company_id: user.orgId ?? null,
    }
  },

  // A page of your records for RAS to index and analyse.
  // `>=` and the helper are both load-bearing — see Pagination below.
  listRecords: async ({ userId, since, limit }) => {
    const rows = await db.records.find({
      owner: userId,
      ...(since ? { updated_at: { $gte: new Date(since) } } : {}),
    }).sort({ updated_at: 1 }).limit(limit * 2)

    return paginateByWatermark(rows, {
      limit,
      updatedAt: (r) => r.updated_at.toISOString(),
    })
  },

  // A finished report coming back. Already decoded and hash-verified.
  onDeliverable: async (d) => {
    await storage.put(`ras/${d.deliverable_id}`, d.content)
    await db.deliverables.insert({
      id: d.deliverable_id, user: d.user_id, record: d.record_id,
      title: d.title, verdict: d.review_verdict,
    })
  },
}))

That's the integration.

What you get

| route | who calls it | auth | |---|---|---| | POST /handoff/code | your frontend | your session — never the integration key | | POST /handoff/redeem | RAS | x-integration-key | | GET /records | RAS | x-integration-key | | POST /deliverables | RAS | x-integration-key |

The two auth paths are disjoint on purpose. /handoff/code sits behind your own login because a real person is present. The other three carry no user at all — they are background jobs — so they authenticate as a machine.

Direction is fixed: RAS always calls you. You never call RAS, and you need no outbound credential. You hold one shared secret and that is the whole surface.

The SDK handles: 32-byte code entropy, two-minute expiry, single-use consumption, hashed-at-rest code storage, timing-safe key comparison, payload validation and bounds, base64 decoding, SHA-256 content verification, pagination envelopes, and the error shapes RAS expects.

The handoff, end to end

  1. Your frontend asks for a code — POST /ras/handoff/code, carrying your session.
  2. You get { data: { code, expires_in_ms } }.
  3. Redirect the browser to RAS with it: https://ras.example.com/handoff?code=...
  4. RAS redeems it against you server-to-server and gets the claims.
  5. RAS provisions a workspace and signs the scientist in.

The user's password never leaves your system. RAS never sees a credential of yours beyond the shared key, and re-checks entitlement with you at redeem time — so access revoked between the click and the redeem is honoured.

Pass anything you like as run_context when minting; RAS hands it back to the workspace untouched, which is how "open RAS on this record" works.

Options

| option | required | notes | |---|---|---| | integrationKey | ✅ | Must match what RAS holds for you. Min 16 chars; validated at construction, not at first request. | | resolveUser | ✅ | Return null for "not signed in" and "not entitled" — they must be indistinguishable to the caller. | | listRecords | ✅ | Sort ascending by your update timestamp and honour limit. | | onDeliverable | | Omit and /deliverables returns 501. | | codeStore | | Defaults to in-process. See below. | | codeTtlMs | | Default 120000. | | logger | | { warn, error }. Defaults to silence. |

Running more than one instance

The default code store is an in-process Map. A code minted on one instance cannot be redeemed on another, so behind a load balancer the handoff fails for a fraction of users — intermittently, which reads as a bug rather than a misconfiguration.

If you run more than one process, supply a shared store:

codeStore: {
  async put(hash, entry) {
    await redis.set(`ras:${hash}`, JSON.stringify(entry), 'PX', 120_000)
  },
  async take(hash) {
    // MUST be atomic. GETDEL, not GET then DEL.
    const raw = await redis.getdel(`ras:${hash}`)
    return raw ? JSON.parse(raw) : null
  },
}

take being atomic is what makes a code single-use. A read-then-delete implementation lets two concurrent redemptions both succeed, which turns a leaked code into a working one.

Pagination — read this one

This is the only part of the integration that is easy to get wrong, and the failure is silent: records simply never arrive, and both sides report success. A client that hand-rolled it in testing lost 25% of its data before anyone noticed.

Use the helper and you cannot hit it:

import { rasConnect, paginateByWatermark } from 'adya-ras-connect'

listRecords: async ({ userId, since, limit }) => {
  const rows = await db.shipments.find({
    owner: userId,
    ...(since ? { updated_at: { $gte: new Date(since) } } : {}),  // >= not >
  }).sort({ updated_at: 1 }).limit(limit * 2)                     // over-fetch

  return paginateByWatermark(rows, {
    limit,
    updatedAt: (r) => r.updated_at.toISOString(),
  })
}

Why, if you would rather write it yourself

watermark is the last-modified time of the newest record you hand over. RAS stores it and sends it back as since.

Records that share a timestamp are the problem — anything written in the same tick, by a batch job, an overnight import, or a bulk edit. Two rules:

1. Filter with >=, never >. With a strict >, every record sharing the last timestamp in a page is skipped for ever: the next request asks for rows strictly newer than a timestamp they are equal to. Re-offering boundary records costs nothing — RAS upserts by id and dedups documents on content hash, so a record it has already seen is a no-op.

2. Take the watermark from the last row you RETURN, not the newest row you fetched. Truncate to limit first, then read the timestamp off the final row of the truncated page. Anything you trimmed is strictly newer, so it comes back on the next page.

Rule 2 bites hardest when you merge sources. Query two tables, cap each at limit, and they reach different points in time; take the maximum across both and the watermark jumps past unread rows in the slower one, which then fall below since permanently.

Also

  • Return a real watermark whenever the page is non-empty. null makes RAS reuse the previous one and re-read the same window indefinitely.
  • Honour limit. Returning more than asked is not harmful, but returning 2 × limit because you capped two queries separately is a symptom of rule 2.
  • Sort ascending by your timestamp. An unsorted page truncated to limit drops arbitrary records rather than the newest.

Response shapes

Success is { "data": ... }; failure is { "error": { "code", "message" } }.

| code | status | meaning | |---|---|---| | unauthorized | 401 | integration key missing or wrong | | forbidden | 403 | resolveUser returned null | | invalid_request | 400 | payload failed validation | | code_invalid | 400 | code absent, expired, or already used | | hash_mismatch | 400 | decoded bytes don't match content_hash | | not_implemented | 501 | no onDeliverable configured |

Note on /runs

GET /runs is served as an alias of /records for RAS deployments predating the rename. New integrations should ignore it; it will be removed once no registered platform depends on it.