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

@tilewise/source-github

v0.1.2

Published

**A GitHub repository's issues as a resource: listed and filtered through GitHub's issue search, read and written through its issues API.**

Readme

🐙 @tilewise/source-github

A GitHub repository's issues as a resource: listed and filtered through GitHub's issue search, read and written through its issues API.

GitHub Octokit API version Enterprise

GitHub's issue search is the one list that filters by more than a state, so the resource's queries go there, each filter said as search qualifiers. A read, a create and a change go through the issues API. GitHub serves less than the contract allows, and the metadata says so field by field: a client only ever offers what GitHub answers exactly.

✨ Highlights

  • 🔎 Filters as qualifiers: all joins by a space and any by OR (GitHub's advanced search), each value quoted as a qualifier takes it.
  • 🧹 Simplified as it's built: a condition every issue meets says nothing, and one no issue can meet asks GitHub nothing at all.
  • 📜 Typed from GitHub's own spec: every route's request and answer through @octokit/types.
  • 🏢 GitHub Enterprise: one baseUrl, and the API version on every request.

🔄 Where each operation goes

flowchart LR
  query["POST /issues/query"] --> clause["filters.ts<br/>a qualifier per condition"]
  clause --> text["search-text.ts<br/>repo:… is:issue …"]
  text --> search[("GET /search/issues")]
  read["GET · POST · PATCH<br/>/issues/:number"] --> api[("issues API")]

  classDef tilewise fill:#0F766E,stroke:#115E59,color:#fff
  classDef gh fill:#24292F,stroke:#57606A,color:#fff
  classDef route fill:#6366F1,stroke:#4338CA,color:#fff
  class query,read route
  class clause,text tilewise
  class search,api gh

🚀 Quick start

import { router, serve } from '@tilewise/source';
import { githubIssues } from '@tilewise/source-github';

const { definition, source } = githubIssues({
  repo: 'tilewise/app',
  token: process.env.GITHUB_TOKEN, // reads issues; writes them for create and update
});

const route = router([serve(definition, source)]);

On GitHub Enterprise, give its API:

githubIssues({
  repo: 'team/app',
  token,
  baseUrl: 'https://github.example.com/api/v3',
});

🔎 A filter becomes a search

The query a client sends:

// Assigned to ada, or to no one
const adaOrNobody = {
  any: [
    { field: 'assignees', op: 'in', value: ['ada'] },
    { field: 'assignees', op: 'empty' },
  ],
};

await issues.query({
  filter: {
    all: [
      { field: 'state', op: 'equals', value: 'open' },
      { field: 'labels', op: 'in', value: ['bug', 'good first issue'] },
      adaOrNobody,
      { field: 'comments', op: 'gte', value: 2 },
      { field: 'createdAt', op: 'inLast', value: 7 },
      { field: 'author', op: 'notIn', value: ['bot'] },
    ],
  },
  sort: [{ field: 'updatedAt', dir: 'asc' }],
  page: { skip: 20, take: 10 },
});

…and what GitHub is asked (shown unencoded):

GET /search/issues
  ?q=repo:tilewise/app is:issue is:open label:"bug","good first issue" (assignee:ada OR no:assignee) comments:>=2 created:>2026-10-07T12:00:00Z -author:bot
  &advanced_search=true&sort=updated&order=asc&page=3&per_page=10

🧮 What each field filters by

Each field's operators are those its search qualifiers say exactly:

| Field | Filters | Qualifier | | ------------------------ | ------------------------------------------ | --------------------- | | state | is, is not, in, none of, empty | is:open | | locked | is, in, none of, empty | is:locked | | labels | has any of, has none of, empty | label:"a","b" | | assignees | has any of, has none of, empty | assignee:ada | | author, milestone | is, is not, in, none of; a milestone empty | author:ada | | comments, reactions | is, is not, in, none of, the ranges | comments:>3 | | createdAt, updatedAt | the ranges, in the last, not in the last | created:>2026-10-07 | | closedAt | the ranges, in the last | closed:<=2026-10-01 |

A query sorts by createdAt or updatedAt, one at a time. A page is a whole page of at most 100, and a search's first 1000 issues are all it answers, so it counts no more.

[!NOTE] GitHub matches a login and a label whatever their case, where the contract's is doesn't; a value picked from the rows matches either way.

✍️ Writes

| Field | Create | Update | | ----------- | -------- | -------- | | title | required | required | | body | ✅ | ✅ | | state | | required | | labels | ✅ | ✅ | | assignees | ✅ | ✅ |

const issue = await issues.create({
  values: { title: 'Crash on start', labels: ['bug'] },
});

await issues.update(issue.number, {
  values: { title: 'Crash on start', state: 'closed' },
});

A value GitHub refuses (its 422) answers 400 invalid with GitHub's own words, and a missing issue (404, or 410 when deleted) is a 404.

⚙️ Options

| Option | Default | What | | ---------- | ------------------------ | -------------------------------------------- | | repo | | 'owner/name' | | token | none | reads the issues; writes them for the writes | | resource | 'issues' | the resource's name, its path under /api/ | | baseUrl | https://api.github.com | GitHub Enterprise's API | | fetch | the global one | the fetch Octokit calls, for a test's double |

🚫 What it leaves out

[!WARNING]

  • Search: GitHub matches whole words, and the contract's search is any piece of text.
  • Sorting by comments or reactions: GitHub breaks their ties in its own order, not by number.
  • Deleting an issue, which its REST API doesn't do.
  • Pull requests, which the issues API answers too: they aren't rows.

🗂️ Layout

src/
├── index.ts              the package's exports
├── types/
│   ├── issue.ts          an issue and a search's page, from Octokit's types
│   ├── request.ts        a query's part in GitHub's words
│   └── body.ts           a write's body
├── client/
│   └── client.ts         Octokit, and GitHub's errors as ours
├── search/
│   ├── clause.ts         a filter as qualifiers
│   └── search-text.ts    a clause as the search's text
└── issues/
    ├── issues.ts         githubIssues
    ├── definition.ts     an issue's fields
    ├── row.ts            an issue as our row
    ├── filters.ts        each field's operators
    └── issues.test.ts    the adapter over a fake fetch

🔑 Key files

  • issues/issues.ts: the source itself. Its query goes to the search with the repository's scope, its read, create and update to the issues API, and its metadata says exactly how far GitHub goes.
  • issues/filters.ts: each field's operators as the qualifier that says it exactly: a value named (author:ada), compared (comments:>3), or an instant to the second.
  • search/clause.ts: a filter as a tree of qualifiers, simplified as it's joined, so true and false conditions vanish or short-circuit before GitHub is asked.
  • search/search-text.ts: the clause as the search's text, within GitHub's limits of five operators and 256 characters.
  • client/client.ts: Octokit with the token, the base URL and the API version, and GitHub's answers as ours: a missing issue is none, a refused value 400 invalid.

🧱 Built on

| Library | Used for | | --------------------------------------------------------------------- | ------------------------------------------------------------ | | @octokit/core | each request: its token, its base URL, the API version | | @octokit/types | each route's request and answer, from GitHub's OpenAPI spec | | @octokit/request-error | what a refused request throws, its status and GitHub's words |