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

@smartretraining/reapit-effect

v0.10.0

Published

Effect-native SDK for the Reapit Sales API (Agentbox), generated from the OpenAPI document with response schemas repaired against the live API.

Readme

@smartretraining/reapit-effect

Effect-native SDK for the Reapit Sales API (Agentbox), with exhaustive error typing.

bun add @smartretraining/reapit-effect effect

Usage

import * as Effect from "effect/Effect";
import * as FetchHttpClient from "effect/unstable/http/FetchHttpClient";
import * as Layer from "effect/Layer";
import * as Reapit from "@smartretraining/reapit-effect";

const program = Effect.gen(function* () {
  const page = yield* Reapit.getListings({ limit: 10, filterType: "Sale" });
  return page.listings;
});

Effect.runPromise(
  program.pipe(
    Effect.provide(Layer.merge(Reapit.CredentialsFromEnv, FetchHttpClient.layer)),
  ),
);

Credentials

REAPIT_CLIENT_ID and REAPIT_API_KEY are required; REAPIT_API_BASE_URL and REAPIT_API_VERSION ("1" or "2", default "2") are optional.

Reapit.CredentialsFromEnv;
Reapit.fromApiKey({ clientId: "...", apiKey: "..." });

API keys are IP-restricted. Reapit only accepts a key from the addresses registered against it, so a correct key from an unlisted address returns 401 Unauthorized with "Api Key does not exists".

Pagination

Collection operations expose .pages (stream of pages) and .items (stream of records) alongside the plain call:

import * as Stream from "effect/Stream";

// Every contact, one at a time.
Reapit.getContacts.items({ limit: 100 }).pipe(
  Stream.runForEach((contact) => Effect.log(contact.email)),
);

Reapit numbers pages with page/limit and answers with items (total record count), current, and last. The traversal stops on last rather than probing past the end.

Errors

Every operation's error channel is typed. Alongside the shared HTTP classes (NotFound, UnprocessableEntity, Unauthorized, …) there are three Reapit-specific ones:

| Error | Raised when | | --- | --- | | ReapitApiError | The API returned a structured { code, title, detail } failure. Carries every error the response listed, not just the first. | | ReapitVersionError | The version query parameter was rejected (code 300). | | UnknownReapitError | Nothing else matched. |

Reapit.getListing({ listingId: "12P0168" }).pipe(
  Effect.catch("NotFound", () => Effect.succeed(null)),
);

A note on types

Every scalar this API returns is a JSON string. Not just ids like "12P0168" — the page counters too:

{ "response": { "items": "6894", "current": "1", "last": "2298", "contacts": [ … ] } }

The vendor OpenAPI document declares items/current/last as integer. A schema built from that claim does not merely mislead — it fails to decode every real response. So the generated schemas type these as string, matching the wire. Convert at the edge:

const total = Number(page.items);

Booleans are the one exception; they arrive as real JSON booleans.

Filters

Reapit's filters are bracketed query parameters. They surface as camelCase — filter[memberId] is filterMemberId — and the bracketed form is what goes over the wire:

Reapit.getListings({ filterType: "Lease", filterMemberId: "1stf0142" });
// GET /listings?version=2&filter[type]=Lease&filter[memberId]=1stf0142

One trap worth knowing: /suburbs requires a filter and its own 422 message names them without the prefix ("Please specify one of postcode, suburbName, state or region"), but only the filter[...] forms are accepted — so it is filterState, not state.

How this package is generated

The vendor document is strong on the request side — it documents every filter, and there are many — and unusable on the response side: only 9 of its 141 component schemas are referenced from any path, so payloads type as unknown[].

Rather than trust it, the response schemas are derived from the API itself:

scripts/capture-samples.ts   → calls each readable endpoint, records STRUCTURE
                               ONLY into specs/observed.json (no values, no
                               examples — nothing that could carry personal
                               data). Raw bodies land in .samples/, gitignored.
scripts/build-spec.ts        → vendor request side + observed response side
                               → specs/openapi.json
scripts/convert.ts           → OpenAPI → Smithy (.generated-specs/reapit.json)
scripts/generate.ts          → Smithy → src/services/reapit.ts

Regenerate without touching the API (specs/observed.json is committed):

bun run convert && bun run generate

Re-observe the API when it changes:

bun run specs:capture

Coverage

26 operations across contacts, listings, enquiries, offices, staff, search requirements, subscriptions, and the lookup lists. Response schemas for 19 of them are derived from observed responses; the remaining 7 are the write paths (createContact, updateContact, createEnquiry, createSearchRequirement, updateSearchRequirement, deleteSearchRequirement, updateContactSubscriptions), whose schemas come from the vendor document — they cannot be observed without creating records.

Their request encoding is covered: the tests issue a PUT and a DELETE against ids that do not exist, which proves the body and path label are transmitted and the failure maps to a typed error, without writing anything. Their response schemas remain unverified against a successful call.

The live API also exposes roughly twenty endpoints absent from the vendor document entirely (/tasks, /notes, /offers, /leads, /inspections, /projects, /appointments, /webhook-subscriptions, …). They answer 400 "The method GET is not allowed" rather than 404, so they exist and are reachable — but only via verbs we cannot discover by reading.

They are not documented by Reapit either, which is worth recording so the question is not reopened. Reapit serves its own Swagger UI at /docs?client_id=…&version=…, rendered from /docs/swagger.yaml. That document was compared against specs/openapi.vendor.json operation by operation: identical path sets, identical parameter names, identical enum cardinalities — 0 of 26 operations differ. Fetching the version=1 document too produced the same path set. So the vendor copy here is a faithful, complete rendering of everything Reapit publishes for this API key, and the missing endpoints appear in neither version.

Discovering them would need either request shapes from Reapit directly, or probing writes with deliberately invalid bodies and reading the validation errors back. Neither is guesswork-free, so nothing is modelled here rather than shipping types nobody has verified.

Releasing

The @smartretraining packages share a single version line and ship together under one v<version> tag, mirroring how the upstream packages are released. That is why this package's first published version is 0.4.0 rather than 0.1.0 — it joins rex-effect on its existing line.

Releases run from .github/workflows/release-smartretraining.yml using npm trusted publishing (OIDC), so no npm token exists in the repository or on any developer machine, and every tarball carries a provenance attestation linking it to the commit and workflow run that produced it. The version is bumped in an ordinary reviewed commit; the workflow refuses to publish if the packages disagree on it or if it is already on the registry.

Testing

The suite runs against a live account:

REAPIT_CLIENT_ID=... REAPIT_API_KEY=... bun run test

The tests are read-only — nothing is created, updated, or deleted.