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

@com-fin/api-sdk

v0.1.8

Published

Typed API client, auth/error/retry layer, and TanStack Query hook factories for the Community Finance Platform, shared by com-fin-platform, com-fin-admin, and com-fin-member-app.

Readme

@com-fin/api-sdk

Shared, framework-agnostic TypeScript API client for the Community Finance Platform — one package consumed by com-fin-platform, com-fin-admin, and com-fin-member-app so all three apps get identical request/response types, auth handling, retry behaviour, error shapes, and query caching instead of each reimplementing it.

This package ships zero UI code and zero framework-specific code — no React components, nothing web-only or React-Native-only. It works from a Next.js app and an Expo app alike.

Layout

src/
├── generated/     orval output — typed request functions + response types.
│                  NEVER hand-edit; see "Regenerating" below.
├── client/        hand-written layer on top of generated code:
│                  http-client.ts             auth, retry/backoff, error normalization
│                  token-storage.interface.ts  storage contract consumer apps implement
│                  error-types.ts              ApiError hierarchy all three apps catch
├── hooks/         TanStack Query hook factories, one folder per domain
│                  (organisations, memberships, savings, rotation, meetings, payments)
└── schemas/       zod schemas mirroring the API's request DTOs, for client-side
                   form validation that matches server-side validation errors

Install (local development)

The three consumer apps are siblings of this repo on disk. Until this package is published, add it as a local path/workspace dependency:

// com-fin-platform/package.json (or com-fin-admin, com-fin-member-app)
{
  "dependencies": {
    "@com-fin/api-sdk": "file:../com-fin-api-sdk"
  }
}

npm workspaces / pnpm workspaces both resolve file: deps the same way — no extra config needed on the consumer side. After npm install, changes made here require the consumer app to reinstall (npm install) or, for a tighter inner loop, run this package's npm run dev (tsup --watch) alongside the consumer app's dev server, since file: deps are copied, not symlinked, by plain npm. If the team wants live symlinking during development, switch the three consumer repos + this one into an npm/pnpm workspace at a shared root — out of scope for this package alone to impose.

Publishing later (GitHub Packages)

No reason to version this independently yet — all four repos move together. Once that changes (e.g. mobile ships on a slower cadence than web), publish to GitHub Packages:

  1. publishConfig.registry is already set to https://npm.pkg.github.com in package.json.
  2. Flip "private": true to false.
  3. Each consumer repo needs an .npmrc scoping the @com-fin namespace to GitHub Packages:
    @com-fin:registry=https://npm.pkg.github.com
    //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
  4. npm version <major|minor|patch> && npm publish from CI on tag push (add a publish.yml workflow alongside .github/workflows/ci.yml when this day comes — CI here intentionally does not publish yet).
  5. Consumer package.jsons switch from file:../com-fin-api-sdk to a real semver range, e.g. "@com-fin/api-sdk": "^1.0.0".

Configuring the client

Call this once, at app startup, before any hook runs:

import { configureApiClient } from '@com-fin/api-sdk';
import { myAppTokenStorage } from './token-storage'; // your TokenStorage implementation

configureApiClient({
  baseUrl: process.env.EXPO_PUBLIC_API_URL ?? 'https://api.communityfinance.rw/api/v1',
  tokenStorage: myAppTokenStorage,
  onSessionExpired: () => router.replace('/login'),
});

Implementing TokenStorage

This package never assumes a storage mechanism — see src/client/token-storage.interface.ts. Each app supplies its own:

  • Web (com-fin-platform, com-fin-admin): an httpOnly-cookie-backed implementation if the API sets cookies, or a localStorage-backed one otherwise.
  • com-fin-member-app (Expo): an expo-secure-store-backed implementation (already an installed dependency there).
// example: expo-secure-store adapter
import * as SecureStore from 'expo-secure-store';
import type { TokenStorage, TokenPair } from '@com-fin/api-sdk';

export const secureStoreTokenStorage: TokenStorage = {
  async getTokens() {
    const raw = await SecureStore.getItemAsync('com-fin-tokens');
    return raw ? (JSON.parse(raw) as TokenPair) : null;
  },
  async setTokens(tokens) {
    await SecureStore.setItemAsync('com-fin-tokens', JSON.stringify(tokens));
  },
  async clearTokens() {
    await SecureStore.deleteItemAsync('com-fin-tokens');
  },
};

Errors

Every non-2xx response is normalized to an ApiError subclass (see src/client/error-types.ts) — ValidationError, AuthenticationError, AuthorizationError (with moduleKey set when a @RequiresModule gate rejected the request), NotFoundError, ConflictError, NetworkError. All three apps can render off the same error.code / error.message regardless of which one triggered it.

Using the hooks

import { useOrganisations, useRecordContribution } from '@com-fin/api-sdk';

const { data, isLoading } = useOrganisations();

const recordContribution = useRecordContribution(orgId);
recordContribution.mutate({ membershipId, amount: 5000, method: 'cash' });

Query keys are centralised per domain (e.g. organisationKeys in src/hooks/organisations) so mutations invalidate the right queries automatically — consumer apps don't hand-roll cache invalidation.

Regenerating the client

npm run generate          # regenerate src/generated from openapi.json
INPUT=http://localhost:3000/api/v1-json npm run generate   # ...or from a running com-fin-api instance

openapi.json is a committed snapshot. Until com-fin-api exposes a live spec endpoint, it's the source of truth and must be updated by hand to stay in sync with the real API as endpoints land; once com-fin-api ships @nestjs/swagger, replace this manual step with a spec:pull script that curls its /api/v1-json endpoint and overwrites openapi.json, then run npm run generate.

Never hand-edit anything under src/generated. npm run generate:check (also run in CI, see .github/workflows/ci.yml) regenerates into the working tree and fails the build if that produces a git diff — i.e. if openapi.json and the committed src/generated have drifted apart.

Why refresh bypasses codegen

src/client/http-client.ts calls POST /auth/refresh directly with a raw fetch, not through a generated function. Generated request functions all call through apiFetch (the orval mutator) for auth + retry — routing the refresh call through the same mutator would be self-referential (refreshing a token in order to refresh a token). It's the one intentional exception to "all requests go through src/generated."