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

rallyorm

v2.0.3

Published

ORM for Rally API. Simplifies integration and management of Rally entities in Node.js with modern async/await patterns.

Readme

RallyORM

CI Tests Coverage npm version npm downloads Node.js TypeScript License: MIT

RallyORM is a TypeScript library for working with the Rally (Broadcom Rally / CA Agile Central) Web Services API using typed models, repositories, and async/await.

It gives you a higher-level API over Rally entities such as UserStory, Defect, Task, Project, Iteration, Release, TestCase, and more, plus a generator that turns your own workspace's type definitions (custom fields included) into typed models.

The package is ESM-only and targets Node.js applications.

Installation

Requirements:

  • Node.js 18 or newer
npm install rallyorm

What It Includes

  • RallyClient for low-level API access: retries, rate-limit handling, request queueing, write guards
  • RallyDataSource for typed repositories
  • RallyRepository<T> for common read and write operations
  • eager and lazy relationship loading for Rally references, including polymorphic collections
  • typed Rally models generated from Rally's own type definitions
  • npx rallyorm generate to produce models for your workspace, custom fields included
  • optional progress telemetry for long-running loads
  • helpers for Rally custom fields

Main API

  • RallyDataSource is the usual entry point
  • repository getters such as ds.userStories, ds.defects, ds.tasks, ds.projects and ds.testCases expose typed repositories
  • find, findBy, findAllBy, findOne, findOneBy, count, exists, and save cover the most common repository flows
  • select picks the fields to fetch and the relationships to eager-load in one list
  • RallyClient is available when you need lower-level control

Quick Start

import { RallyDataSource } from 'rallyorm';

const ds = new RallyDataSource({
  apiKey: process.env.RALLY_API_KEY as string,
  baseUrl: process.env.RALLY_BASE_URL,
  workspace: process.env.RALLY_WORKSPACE,
  readOnly: true
});

const stories = await ds.userStories.find({
  query: '(ScheduleState = "In-Progress")',
  select: ['FormattedID', 'Name', 'Owner.DisplayName'],
  maxResults: 10
});

for (const story of stories) {
  console.log(story.FormattedID, story.Name, story.Owner?.DisplayName);
}

apiKey is required. workspace and baseUrl are optional (baseUrl defaults to https://rally1.rallydev.com/slm/webservice/v2.0; use https://eu1.rallydev.com/slm/webservice/v2.0 for EU tenants).

Basic Usage

Read Data

const defects = await ds.defects.find({
  query: '(State = "Open")',
  select: ['FormattedID', 'Name', 'Priority'],
  maxResults: 20
});

const defect = await ds.defects.findOne('123456');

You can also work directly with model classes when that is clearer for the calling code:

import { UserStory } from 'rallyorm';

const repo = ds.getRepository(UserStory);
const story = await repo.findOne('123456');

Filter With Repository Helpers

findBy and findAllBy build Rally queries from plain objects when that reads better than writing raw query strings.

const inProgressStories = await ds.userStories.findBy({
  where: {
    ScheduleState: 'In-Progress',
    Project: { ObjectID: 12345 }
  },
  select: ['FormattedID', 'Name', 'ScheduleState'],
  order: 'LastUpdateDate desc'
});

const hasOpenDefects = await ds.defects.exists({
  State: 'Open',
  Project: { ObjectID: 12345 }
});

Supported helper operators include $eq, $ne, $gt, $gte, $lt, $lte, $contains, $in, $and, and $or.

Use findAllBy when you want RallyORM to page through the full result set automatically.

const allProjectStories = await ds.userStories.findAllBy({
  where: { Project: { ObjectID: 12345 } },
  select: ['FormattedID', 'Name'],
  pagesize: 200
});

RallyORM warns through the client logger when a where field is not filterable or an order field is not sortable according to the model metadata, so typos surface early.

Select Fields and Relationships

select is the single list that drives both the WSAPI fetch and relationship eager-loading. It accepts an array or a comma-delimited string.

const stories = await ds.userStories.find({
  select: ['FormattedID', 'Name', 'Project.Name', 'Owner.DisplayName'],
  maxResults: 10
});

console.log(stories[0].Project.Name);
console.log(stories[0].Owner.DisplayName);
  • A plain name ('Name') is fetched as a scalar field. If it is a relationship ('Tasks'), the related records are eager-loaded with their default fields.
  • A dot path ('Owner.DisplayName') fetches Owner and eager-loads it with DisplayName. Paths can go as deep as relationshipLoaderOptions.maxDepth allows ('Iteration.Project.Name').
  • '*' fetches every field of the top-level entity (fetch=true) and disables eager-loading for that query.

Polymorphic collections such as TestSet.WorkProducts mix several entity types. Add a type filter in square brackets to restrict nested loading to one of them:

const testSets = await ds.testSets.find({
  select: ['Name', 'WorkProducts[HierarchicalRequirement].TestCases.Name']
});

The whole WorkProducts collection is still loaded; TestCases are only loaded for the work products that are user stories. Only the bracket syntax is treated as a filter, so a relation named like an entity type ('Iteration.Project.Name') is always a plain relation chain.

When a relationship is eager-loaded, RallyORM exposes it as the corresponding typed model when the model exists in the registry.

With models that declare typed properties (the ones emitted by npx rallyorm generate, see below), pass the list as const and the selected top-level fields become required in the result type while everything else keeps its optional declaration:

const [story] = await ds.userStories.find({
  select: ['FormattedID', 'Name'] as const,
  maxResults: 1
});

story.Name;        // string — selected
story.Description; // string | undefined — not selected

The built-in models type every field as any, so this narrowing only kicks in once you use generated models.

Lazy Relationships

Rally only returns the fields you fetch, and naming a relationship in select eager-loads it. A relationship therefore arrives as a bare reference — exposed as a LazyLink — when the record is read without a field list (findOne(id) returns every field, relationships included, as references) or with select: ['*']. Call load() to dereference it on demand:

const story = await ds.userStories.findOne('123456');   // every field, relationships as references

const projectLink = story?.Project;                     // LazyLink { _ref, _refObjectName, … }
const project = await projectLink?.load();              // one GET, returns the typed Project model

console.log(project?.Name);

This is useful when you want a lightweight first read and only dereference some related entities later. When you already know which relationships you need, select: ['Project.Name'] is cheaper: it batches the loads instead of issuing one request per load().

Write Data

Write operations are disabled by default.

Enable them explicitly when you create the data source:

const ds = new RallyDataSource({
  apiKey: process.env.RALLY_API_KEY as string,
  workspace: process.env.RALLY_WORKSPACE,
  allowCreate: true,
  allowUpdate: true,
  allowDelete: true
});

Create or update through save:

const defect = await ds.defects.save({
  Name: 'Example defect',
  Description: 'Created with RallyORM'
});

defect.Name = 'Updated defect title';
await ds.defects.save(defect);

Entities track their own changes, so saving an existing entity only sends the fields that changed. Fields flagged readOnly in the model metadata (FormattedID, CreationDate, roll-ups, …) are stripped from the payload automatically.

For production usage, keep writes disabled unless the process really needs them.

String-based tag writes are strict. If RallyORM cannot resolve or create every requested tag, the write fails instead of silently dropping tags from the payload.

Validation is opt-in: entity.validate() checks required fields, types, lengths, ranges and allowed values against the model metadata and entity.getErrors() lists what failed. Nothing is validated implicitly on save, so workspaces with customised dropdown values keep working with the built-in models.

Relationship Loading Limits

Relationship hydration is bounded to protect callers from runaway graphs.

  • relationshipLoaderOptions.maxDepth controls how many nested levels RallyORM will traverse. Default is 5.
  • relationshipLoaderOptions.maxCacheEntries controls the in-memory relationship reference cache size. Default is 5000.
  • relationshipLoaderOptions.collectionConcurrency controls how many collection references are resolved in parallel. Default is 10.
  • relationshipLoaderOptions.inverseQueryChunkSize controls how many parent references are packed into one inverse query. Default is 50.
const ds = new RallyDataSource({
  apiKey: process.env.RALLY_API_KEY as string,
  relationshipLoaderOptions: {
    maxDepth: 3,
    maxCacheEntries: 1000
  }
});

When the configured relationship depth is reached, RallyORM stops descending further and logs a warning through the configured client logger.

Progress Telemetry

Large findAllBy calls and deep relationship loads can take a while. Enable telemetry: true to render live progress bars in the terminal, or pass onProgress to receive the same events programmatically.

const ds = new RallyDataSource({
  apiKey: process.env.RALLY_API_KEY as string,
  telemetry: true,
  onProgress: event => {
    // event.operation: 'query' | 'relationship' | 'collection'
    // event.current / event.total, plus entityType, relationshipName and level
  }
});

Polymorphic relationship loads report one bar for the whole relation plus one sub-bar per source entity type.

Custom Fields

Rally custom fields usually start with c_.

import { UserStory, createCustomFieldAccessor } from 'rallyorm';

type StoryFields = {
  c_MyField?: string;
};

class MyStory extends UserStory {
  get customFields(): StoryFields {
    return createCustomFieldAccessor<StoryFields>(this);
  }
}

const story = new MyStory({ Name: 'Custom field example' });
story.customFields.c_MyField = 'Hello';

For full typing of your workspace's custom fields, generate models instead (see below).

Package Exports

  • rallyorm exposes the main client, data source, repositories, models, LazyLink, the error classes and the public types (IRallyClientConfig, IFindOptions, SelectResult, IRallyProgressEvent, …)
  • rallyorm/utils exposes helper utilities for custom fields

Model Generator

The built-in models cover the standard Rally types. npx rallyorm generate reads the type definitions of your workspace and emits TypeScript models that include your custom fields, your dropdown values and your portfolio item hierarchy.

npx rallyorm generate --api-key=$RALLY_API_KEY --workspace=123456789 --output=./src/rally-models

Options:

  • --api-key=<key> — Rally API key (prompted when omitted)
  • --workspace=<id> — workspace ObjectID (prompted when omitted)
  • --output=<dir> — output directory, default ./src/models/generated
  • --base-url=<url> — WSAPI base URL for non-US tenants
  • --include=Defect,HierarchicalRequirement,... — generate only these types
  • --base-import=<specifier> — import specifier for the RallyORM base classes, default rallyorm

Generated files use .js relative import specifiers so they work directly with Node.js ESM after compilation. The output includes an index.ts that exports every model, a GENERATED_MODELS array, and a GeneratedRallyDataSource with typed getters bound to the generated classes.

GeneratedRallyDataSource is the recommended path when you want IDE autocomplete for workspace-specific and custom fields:

import { GeneratedRallyDataSource } from './src/rally-models/index.js';

const ds = new GeneratedRallyDataSource({
  apiKey: process.env.RALLY_API_KEY as string,
  workspace: process.env.RALLY_WORKSPACE
});

const testCases = await ds.testCases.findAllBy({
  select: ['FormattedID', 'Name', 'c_MyCustomField']
});

If you prefer to stay on the base datasource, register the generated classes with models. They override the built-in models with the same entity type, and the built-in getters (ds.defects, ds.userStories, …) return them:

import { RallyDataSource } from 'rallyorm';
import { GENERATED_MODELS } from './src/rally-models/index.js';

const ds = new RallyDataSource({
  apiKey: process.env.RALLY_API_KEY as string,
  models: GENERATED_MODELS
});

Migrating From 1.x

  • fetch and include query options were merged into select. Replace fetch: ['Name', 'Project'], include: 'Project.Name' with select: ['Name', 'Project.Name']. Passing the old keys logs a warning and they are ignored.
  • models: 'generated' was removed from RallyDataSource; pass the GENERATED_MODELS array instead.
  • The set of built-in models is now generated from Rally type definitions and covers the standard artifact, test, timebox, portfolio and organisation types. Anything else (builds, changesets, capacity planning, …) is available through npx rallyorm generate.

Contributing

Bug reports and pull requests are welcome. See CONTRIBUTING.md for the development, testing and release workflow.

License

MIT