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

@cesar-richard/git-connector-sdk

v1.45.0

Published

TypeScript SDK for the git-connector v1 API (work items + iterations aggregated from GitHub/GitLab). Version published on npm tracks server releases.

Readme

@cesar-richard/git-connector-sdk

npm version license

TypeScript SDK for git-connector — a server that aggregates GitHub + GitLab activity (PRs, MRs, issues, commits, comments, reviews, CI runs) and exposes a unified, ticket-correlated read API.

This SDK is a thin, fully-typed wrapper around the server's /v1/* HTTP API, generated from its OpenAPI spec via openapi-fetch.

Version policy: the SDK version always tracks the git-connector server release it was generated against. Pinning the same major+minor as your server is recommended.


Install

npm install @cesar-richard/git-connector-sdk
# or
pnpm add @cesar-richard/git-connector-sdk
# or
bun add @cesar-richard/git-connector-sdk

Quick start

import { createGitConnectorClient } from "@cesar-richard/git-connector-sdk";

const client = createGitConnectorClient({
  baseUrl: "https://git-connector.example.com",
  token: process.env.GIT_CONNECTOR_TOKEN!, // gck_…
});

const { data, error } = await client.GET("/v1/work-items", {
  params: { query: { state: "open", iteration: "current" } },
});

if (error) throw new Error(`git-connector error: ${JSON.stringify(error)}`);

for (const item of data.items) {
  console.log(`${item.source}/${item.projectKey}#${item.number} — ${item.title}`);
  for (const pr of item.linkedActivities) {
    console.log(`  ↳ ${pr.type.toUpperCase()} ${pr.url} (${pr.state})`);
  }
}

The client is fully typed: request params, query strings, and response bodies are all derived from the server's OpenAPI schema. Hover any field in your editor to see its shape.

Authentication

The SDK sends Authorization: Bearer <token> on every request. Mint a token from the git-connector control UI or via:

curl -X POST https://git-connector.example.com/admin/api-keys \
     -H "Content-Type: application/json" \
     -d '{"name":"my-integration"}'
# → { "token": "gck_…", "id": 12, "name": "my-integration", "createdAt": "…" }

Tokens with the gck_ prefix are immutable once minted; revoke and reissue via the same endpoint.


Activity tracking / CRA workflow

The /v1/* surface is designed to power activity reports (CRA, billable-time sheets, "what did I do this month"). The primitives below let you slice your data per-user, per-day, per-type.

What did I do today?

const today = new Date().toISOString().slice(0, 10);
const { data } = await client.GET("/v1/events", {
  params: { query: { from: today, to: today, user: "cesar" } },
});
for (const e of data.events) {
  console.log(`[${e.day}] ${e.type} — ${e.title} (${e.repo})`);
}

List my open PRs / MRs

const { data } = await client.GET("/v1/activities", {
  params: {
    query: { author: "cesar", state: "open,draft", type: "pr,mr" },
  },
});
for (const a of data.items) {
  console.log(`${a.source}/${a.repo}#${a.number} — ${a.title} (${a.state})`);
}

List issues I opened

const { data } = await client.GET("/v1/work-items", {
  params: { query: { author: "cesar", state: "open" } },
});

Incremental polling (every 5 minutes)

Use ?since=<ISO> to fetch only events newer than your last cursor:

let cursor = "2026-05-18T00:00:00Z";
const { data } = await client.GET("/v1/events", {
  params: {
    query: { from: "2026-05-01", to: "2026-05-31", since: cursor },
  },
});
if (data.events.length > 0) {
  cursor = data.events[data.events.length - 1].occurredAt;
}

Monthly CRA: count events per day

const { data } = await client.GET("/v1/events", {
  params: {
    query: {
      from: "2026-05-01",
      to: "2026-05-31",
      user: "cesar",
      type: "commit,review,comment,state-transition",
    },
  },
});
const byDay: Record<string, number> = {};
for (const e of data.events) {
  byDay[e.day] = (byDay[e.day] ?? 0) + 1;
}

Reliability of the author field

author (on Activity and Event) is the GitHub or GitLab account login (e.g. cesar, never César Richard). If the underlying provider payload doesn't expose a login — rare cases like a git commit by an email not linked to any account — author is null. The git-config commit-author name is never used for author; it lives in meta.commitAuthorName when available, for display purposes only.

Filters like ?author=cesar (on /v1/activities and /v1/work-items) and ?user=cesar (on /v1/events) match the login exactly (case-insensitive). They do NOT fuzzy-match display names. This is a deliberate design choice for auditable activity tracking.

GitLab resolver: commits ingested via polling are resolved server-side by mapping commit.author_email → GL user login via the GitLab Users API. The result is cached (default TTL 90 days). If the email cannot be resolved (anonymous commit, email not associated with any GL user, private email on a self-hosted instance), author stays null and the git-config name lives in meta.commitAuthorName. Webhook-ingested GL commits use the pusher's username directly (no resolver needed).

Structured author access via authorResolved: commit events also carry an authorResolved blob with {login, name, email} (each nullable) — a single typed accessor that aggregates everything we know about the author:

for (const e of data.events) {
  if (e.type !== "commit") continue;
  const { login, name, email } = e.authorResolved ?? {};
  console.log(`${login ?? "anonymous"} (${name ?? "?"} <${email ?? "?"}>)`);
}

Available on every commit event regardless of provider or ingestion path. event.author is always equal to event.authorResolved?.login.

User resolution on work-items (assigneesResolved, authorResolved, reviewerResolved)

Beyond commit events, /v1/work-items exposes resolved user identities on work-item assignees, linked-activity authors, and reviewers. Each is a ResolvedUser shape:

type ResolvedUser = {
  id: string;     // "gl:<user_id>" or "gh:<login>" — stable across login renames
  login: string;
  name: string | null;
  email: string | null;
};

The id is stable: two references to the same GitLab user retain the same gl:<user_id> even after the user's username changes (a common pattern in self-hosted GitLab where matricule-based logins get rewritten to friendly names later). GitHub usernames don't typically change in a way that affects this, so id is gh:<login> for GitHub users.

const { data } = await client.GET("/v1/work-items");
for (const wi of data.items) {
  for (const a of wi.assigneesResolved ?? []) {
    console.log(`${wi.title} assigned to ${a.name ?? a.login} (id=${a.id})`);
  }
  for (const pr of wi.linkedActivities) {
    if (pr.authorResolved) {
      console.log(`PR ${pr.url} by ${pr.authorResolved.name ?? pr.authorResolved.login}`);
    }
    for (const rev of pr.reviews.entries) {
      console.log(`  review by ${rev.reviewerResolved?.name ?? rev.reviewer}`);
    }
  }
}

Link traceability (linkedActivities[].linkSource)

Each entry in linkedActivities carries an optional linkSource object with the raw hintSource persisted on the link row and a human-readable kind derived from it (body-ref, title-ref, pr-comment-ref, issue-comment-ref, unknown). Consumers can use this to surface WHY a link was created — useful for debugging missing or unexpected links in the Explorer UI.

for (const pr of wi.linkedActivities) {
  if (pr.linkSource) {
    console.log(`  ↳ ${pr.url} linked via ${pr.linkSource.kind} (${pr.linkSource.hintSource})`);
  }
}

GitLab resolution uses a 90-day cache (configurable via GITLAB_USER_CACHE_TTL_DAYS).

Issue ↔ PR/MR link detection patterns

WorkItem.linkedActivities is populated by scanning PR/MR bodies, commit messages, notes, AND issue comments for references to PR/MR numbers or URLs. The recognized patterns are:

Canonical (in PR/MR body / commits / notes — forward direction):

  • Fixes #1234, Fix #1234, Closes #1234, Close #1234, Resolves #1234, Resolve #1234 (English short refs).
  • https://github.com/<owner>/<repo>/issues/<n> or https://<gitlab>/.../-/issues/<n> (full URLs).
  • <owner>/<repo>#<n> (cross-repo GitHub).
  • <alias>#<n> and <alias>!<n> (project-alias cross-repo).
  • Short refs #<n> and !<n> (resolved against the active provider/project).

Issue-comment reverse direction (hint_source="issue_comment"):

The above PR/MR URLs and short refs are all recognized in issue comments as evidence that the cited PR/MR is linked to the issue.

Additionally, the following explicit phrase prefixes are recognized, case-insensitively, to catch informal "this issue was delivered" comments:

| Phrase | Example | Behavior | |---|---|---| | Fixed by …, Fixed in … | Fixed by 1214, Fixed by https://…/pull/1214 | Treat the cited ref as a linked PR/MR | | Fixes by …, Fix by … | Fix by #99 | Same | | Resolved by …, Resolves by …, Resolve by … | Resolved by 42 | Same | | Closed by …, Closes by …, Close by … | Closed by !99 | Same | | FR: Corrigé par …, Corrigée par …, Corrige par … | Corrigé par 42 | Same | | FR: Résolu par …, Resolu par … | Résolu par !99 | Same | | FR: Fermé par …, Ferme par … | Fermé par #1214 | Same | | FR: … dans <n> variant | Corrigé dans 42 | Same |

The phrase prefix lets the SDK detect refs even when the number is bare (no # or ! prefix), e.g. Fixed by 1214 instead of Fixed by #1214.

Code blocks (fenced ``` and inline `) are stripped before scanning.

PR review status (reviewStatus)

Each LinkedActivity carries an optional reviewStatus field with 8 possible values, computed server-side from the PR's state, draft flag, requested reviewers, and review history:

| Value | Meaning | |---|---| | draft | PR is a draft — not yet ready for review | | open-no-review | Open, no reviewer requested | | open-awaiting-my-review | Open, you (the viewer) are a requested reviewer | | open-awaiting-other-review | Open, waiting on someone else's review | | open-changes-requested | Open, at least one reviewer requested changes (and that verdict hasn't been superseded by an approval) | | open-approved | Open and approved, no outstanding change requests | | merged | Merged | | closed | Closed without merging |

Pass ?viewer=<login> (case-insensitive) on GET /v1/work-items or GET /v1/work-items/{source}/{projectKey}/{number} to drive the open-awaiting-my-review vs open-awaiting-other-review split. Without viewer, awaiting-review PRs always show as open-awaiting-other-review.

const { data } = await client.GET("/v1/work-items", {
  params: { query: { state: "open", viewer: "cesar" } },
});
for (const wi of data.items) {
  for (const pr of wi.linkedActivities) {
    if (pr.reviewStatus === "open-awaiting-my-review") {
      console.log(`Needs your review: ${pr.url}`);
    }
  }
}

API surface

The /v1/* API exposes five resources: work items, iterations, work-item details, the unified events stream, and raw activities.

GET /v1/work-items — list aggregated work items

Issues from GitHub + GitLab, with linked PRs/MRs resolved server-side via ticket-reference correlation. Filter by state, iteration (id or "current"), assignee, label, source.

const { data } = await client.GET("/v1/work-items", {
  params: {
    query: {
      state: "open",
      iteration: "current",
      assignee: "alice",
    },
  },
});
// data: { items: WorkItem[]; total: number; limit: number }

Each WorkItem includes its linkedActivities (PRs/MRs with full enrichment: reviews summary, unresolved threads, volume, status history, reviewRequestedAt, mergedAt).

GET /v1/work-items/{source}/{projectKey}/{number} — detail

const { data } = await client.GET(
  "/v1/work-items/{source}/{projectKey}/{number}",
  { params: { path: { source: "gitlab", projectKey: "acme/ops", number: 42 } } },
);

GET /v1/iterations — list iterations

const { data } = await client.GET("/v1/iterations");
// data: IterationWithCount[] — each carries workItemCount across all linked WorkItems

GET /v1/events — unified activity stream

Daily-bucketed (Paris-TZ) chronological event stream. Mixes commits, comments, reviews, state-transitions, and CI runs across both providers. Cursor-paginated.

const { data } = await client.GET("/v1/events", {
  params: {
    query: {
      from: "2026-05-01",
      to: "2026-05-17",
      type: "ci-run,review", // CSV
      user: "alice",
      limit: "200",
    },
  },
});
// data: { events: GitEvent[]; total: number; nextCursor: string | null }

// Paginate:
if (data.nextCursor) {
  const { data: next } = await client.GET("/v1/events", {
    params: { query: { from: "2026-05-01", to: "2026-05-17", cursor: data.nextCursor } },
  });
}

Event types in the stream:

| type | Source | parentActivityId | Notable meta keys | |--------|--------|--------------------|---------------------| | commit | Default branch | null | fullMessage | | comment | Issue/PR/MR notes | resolves to PR/MR/issue | body | | review | GH PR review / GL MR approval | resolves to PR/MR | state, reviewer | | state-transition | Derived from activity state diff | resolves to PR/MR/issue | from, to | | ci-run | GH Actions job / GL pipeline job | resolves to PR/MR via head SHA | runId, jobName, conclusion, durationSec, sha, branch |


Schema as a separate import

If you need the raw OpenAPI types without the client (e.g. for codegen or for non-openapi-fetch consumers), import directly:

import type { components } from "@cesar-richard/git-connector-sdk/schema";

type WorkItem = components["schemas"]["WorkItem"];
type GitEvent = components["schemas"]["GitEvent"];

The full OpenAPI document is also published in the package — see openapi.json for the contract.

Error handling

openapi-fetch never throws on HTTP-level errors — it returns { data, error, response }. Always check error before using data:

const { data, error, response } = await client.GET("/v1/work-items");
if (error) {
  if (response.status === 401) throw new Error("Missing/invalid gck_ token");
  if (response.status === 403) throw new Error("Token revoked");
  throw new Error(`git-connector ${response.status}: ${JSON.stringify(error)}`);
}
// data is non-null and fully typed from here

Network errors (DNS, timeout, connection refused) reject the promise as usual.

Custom fetch

Pass your own fetch implementation to wire up retries, telemetry, or a non-global fetch (e.g. inside Cloudflare Workers):

import { fetch as undiciFetch } from "undici";

const client = createGitConnectorClient({
  baseUrl: "https://git-connector.example.com",
  token: process.env.GIT_CONNECTOR_TOKEN!,
  fetch: undiciFetch,
});

Compatibility

  • Node ≥ 18 (uses global fetch).
  • Bun ≥ 1.0.
  • Modern browsers (no Node-only APIs).
  • ESM only.

Related

  • Server / control UI: git-connector
  • OpenAPI spec: ships with this package as openapi.json — generated server-side, the SDK types derive from it via openapi-typescript.
  • Releases: SDK and server release in lockstep via semantic-release on every merge to main.

License

MIT