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

@reso-standards/reso-client

v0.2.2

Published

RESO Client SDK for TypeScript — OData 4.01 URI builder, CRUD helpers, CSDL metadata validation, and query parsing.

Readme

RESO Client SDK

OData 4.01 client SDK for TypeScript. Provides URI building, CRUD helpers, CSDL metadata parsing and validation, query option validation, and response parsing.

User Guide – a task-oriented walkthrough with realistic examples.

Install

This package is not on npm yet. Install from the reso-tools monorepo on GitHub:

git clone https://github.com/RESOStandards/reso-tools.git
cd reso-tools/reso-client
npm install

The preinstall hook automatically builds sibling packages this one depends on (odata-expression-parser). To bootstrap every package at once, run npm run bootstrap from the repo root instead.

To consume the SDK from another project, link it locally with npm link or use a file: dependency.

Quick Start

import {
  createClient,
  createEntity,
  readEntity,
  queryEntities,
  buildUri,
  isODataCollection,
} from "@reso-standards/reso-client";

// Create a client with OAuth2 Client Credentials
const client = await createClient({
  baseUrl: "http://localhost:8080",
  auth: {
    mode: "client_credentials",
    clientId: "my-client-id",
    clientSecret: "my-client-secret",
    tokenUrl: "https://auth.example.com/oauth/token",
    scope: "api",
  },
});

// Create a record
const created = await createEntity(client, "Property", {
  ListPrice: 250000,
  City: "Austin",
  BedroomsTotal: 3,
}, { prefer: "representation" });

// Read a record by key
const record = await readEntity(client, "Property", "ABC123");

// Query with $filter, $select, $orderby, $top
const response = await queryEntities(client, "Property", {
  $filter: "ListPrice gt 200000 and City eq 'Austin'",
  $select: "ListPrice,City,BedroomsTotal",
  $orderby: "ListPrice desc",
  $top: 10,
});

if (response.status === 200 && isODataCollection(response.body)) {
  console.log(`Found ${response.body.value.length} properties`);
}

Features

Authentication

The client supports two authentication modes.

OAuth2 Client Credentials

Tokens are managed automatically – fetched on first use, refreshed proactively at 90% of TTL, and retried once on 401 responses.

const client = await createClient({
  baseUrl: "http://localhost:8080",
  auth: {
    mode: "client_credentials",
    clientId: "my-client-id",
    clientSecret: "my-client-secret",
    tokenUrl: "https://auth.example.com/oauth/token",
    scope: "api",                  // optional
    defaultExpiresIn: 3600,        // optional, default 1 hour
    credentialTransport: "body",   // optional: "body" (default), "header", "query"
  },
});

Credential transport options:

  • body (default): Client ID and secret sent as form parameters
  • header: Basic auth header (Authorization: Basic base64(id:secret))
  • query: Client ID and secret appended as URL query parameters

Bearer Token

For static tokens or when managing token lifecycle externally.

const client = await createClient({
  baseUrl: "http://localhost:8080",
  auth: { mode: "token", authToken: "my-bearer-token" },
});

Environment Variables

Build auth configuration from environment variables – no code changes needed to switch between token and client credentials.

import { authConfigFromEnv, configFromEnv } from "@reso-standards/reso-client";

// Auth config only
const auth = authConfigFromEnv();
const client = await createClient({ baseUrl: "http://localhost:8080", auth });

// Full config (base URL + auth)
const config = configFromEnv();
const client = await createClient(config);

| Variable | Description | |----------|-------------| | RESO_BASE_URL | Server base URL (required for configFromEnv) | | RESO_AUTH_TOKEN | Bearer token (token mode) | | RESO_CLIENT_ID | OAuth2 client ID (client credentials mode) | | RESO_CLIENT_SECRET | OAuth2 client secret | | RESO_TOKEN_URI | OAuth2 token endpoint URL | | RESO_SCOPE | OAuth2 scope (optional) | | RESO_EXPIRES_IN | Default token TTL in seconds (optional, default 3600) |

When RESO_CLIENT_ID, RESO_CLIENT_SECRET and RESO_TOKEN_URI are all set, client credentials mode is used. Otherwise falls back to RESO_AUTH_TOKEN.

Using a .env file (Node 22+):

# .env
RESO_BASE_URL=http://localhost:8080
RESO_CLIENT_ID=my-client-id
RESO_CLIENT_SECRET=my-client-secret
RESO_TOKEN_URI=https://auth.example.com/oauth/token
RESO_SCOPE=api
node --env-file=.env script.js

URI Builder

Chainable functional API for constructing OData URLs with system query options.

import { buildUri } from "@reso-standards/reso-client";

const url = buildUri("http://localhost:8080", "Property")
  .key("ABC123")
  .select("ListPrice", "City")
  .filter("ListPrice gt 200000")
  .expand("Media")
  .orderby("ListPrice desc")
  .top(10)
  .skip(20)
  .count()
  .build();

// Compound keys
const url2 = buildUri("http://localhost:8080", "OrderItem")
  .compoundKey({ OrderId: "O1", ItemId: "I1" })
  .build();

Supported query options: $select, $filter, $orderby, $top, $skip, $count, $expand, $search, $compute, $format.

CRUD Helpers

High-level functions that compose the URI builder and HTTP client.

import {
  createEntity,
  readEntity,
  updateEntity,
  replaceEntity,
  deleteEntity,
  queryEntities,
} from "@reso-standards/reso-client";

// POST /{Resource}
await createEntity(client, "Property", body, { prefer: "representation" });

// GET /{Resource}('{key}')
await readEntity(client, "Property", "ABC123");

// PATCH /{Resource}('{key}')
await updateEntity(client, "Property", "ABC123", patch, {
  prefer: "representation",
  ifMatch: '"etag-value"',
});

// PUT /{Resource}('{key}')
await replaceEntity(client, "Property", "ABC123", fullBody);

// DELETE /{Resource}('{key}')
await deleteEntity(client, "Property", "ABC123");

// GET /{Resource}?$filter=...&$top=...
await queryEntities(client, "Property", {
  $filter: "City eq 'Austin'",
  $top: 25,
});

CSDL Metadata Parsing

Parse and validate EDMX/CSDL XML metadata documents. The parser, validator, and Csdl* types live in @reso-standards/reso-metadata-utils; reso-client provides the metadata fetchers.

import { fetchAndParseMetadata } from "@reso-standards/reso-client";
import { parseCsdlXml, validateCsdl, getEntityType } from "@reso-standards/reso-metadata-utils";

// Fetch and parse from a server
const schema = await fetchAndParseMetadata("http://localhost:8080", "my-token");

// Or parse raw XML
const schema = parseCsdlXml(xmlString);

// Look up entity types
const propertyType = getEntityType(schema, "Property");

// Validate the schema
const result = validateCsdl(schema);
if (!result.valid) {
  console.log("Validation errors:", result.errors);
}

Parsed types include: CsdlEntityType, CsdlProperty, CsdlNavigationProperty, CsdlComplexType, CsdlEnumType, CsdlEntityContainer, CsdlEntitySet, CsdlSingleton, CsdlAction, CsdlFunction, and more.

Query Validation

Validate query options against CSDL metadata to catch errors before sending requests.

import { validateQueryOptions } from "@reso-standards/reso-client";

const result = validateQueryOptions(
  {
    $select: "ListPrice,City,NonExistentField",
    $filter: "ListPrice gt 200000",
    $orderby: "ListPrice desc",
    $top: 10,
  },
  entityType,
);

if (!result.valid) {
  console.log("Invalid query options:", result.errors);
}

Validates $select and $orderby field references against the entity type, $filter property references by walking the parsed AST (using @reso-standards/odata-expression-parser), and $top/$skip value constraints.

Response Parsing

Extract annotations, detect errors, and handle paging.

import {
  extractAnnotations,
  extractEntityData,
  isODataError,
  parseODataError,
  isODataCollection,
  getNextLink,
  followAllPages,
} from "@reso-standards/reso-client";

// Extract @odata annotations
const annotations = extractAnnotations(entity);
// → { context, id, editLink, etag }

// Strip annotations to get raw data
const data = extractEntityData(entity);

// Check for OData errors
if (isODataError(response.body)) {
  const error = parseODataError(response);
  console.log(error.error.message);
}

// Auto-page through all results
const allEntities = await followAllPages(client, firstResponse);

Filter Parser (Re-exported)

The parseFilter function and AST types from @reso-standards/odata-expression-parser are re-exported for convenience.

import { parseFilter } from "@reso-standards/reso-client";

const ast = parseFilter("ListPrice gt 200000 and City eq 'Austin'");

Examples

Runnable examples are in the examples/ directory. Each requires the reference server running at http://localhost:8080.

| Example | Description | |---------|-------------| | fetch-property.ts | Read a Property by key | | query-with-filter.ts | Query with $filter, $select, $orderby, $top | | create-and-update.ts | Create a Property, update it, read it back | | validate-metadata.ts | Fetch and validate CSDL metadata | | oauth-flow.ts | OAuth2 Client Credentials flow |

# Start the reference server first
cd ../reso-reference-server && docker compose up -d

# Run an example with bearer token
RESO_AUTH_TOKEN=test npx tsx examples/query-with-filter.ts

# Run with client credentials via .env file
node --env-file=.env npx tsx examples/oauth-flow.ts

Development

npm install
npm run build
npm test        # 118 tests

License

See LICENSE in the repository root.