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

@dataverse-kit/runtime

v1.0.0

Published

Framework-agnostic runtime helpers for Dynamics 365 / Dataverse: FetchXML parser+builder+OData converter, ConditionEvaluator (business-rule AND/OR groups with 18 operators), and DirtyTracker (ProForma-style change tracking).

Readme

@dataverse-kit/runtime

Framework-agnostic runtime helpers for Dynamics 365 / Dataverse. Zero React deps.

| Surface | Purpose | |---|---| | FetchXML | Parser, fluent builder, OData converter, LayoutXML utilities | | ConditionEvaluator | RuleConditionGroup evaluation with 18 FetchXML-style operators (nested AND/OR groups, field-to-field comparisons, dirty tracking) | | DirtyTracker | ProForma-style baseline change tracking with Dynamics-flavored coercion (null"", "5"5, "true"true) |

Install

npm install --save @dataverse-kit/runtime

DOMParser requirement

The FetchXML parser/builder/updaters call the browser-native DOMParser. That's available in:

  • Any modern browser (custom pages, web resources, PCF, dev servers)
  • Node 21+
  • Test environments configured with happy-dom or jsdom

On Node 18-20 you need a polyfill. The simplest pattern, used in this package's own test setup, is to register happy-dom as the global:

// vitest.config.ts (consumer)
import { defineConfig } from 'vitest/config';
export default defineConfig({
  test: { environment: 'happy-dom' },
});

The ConditionEvaluator and DirtyTracker surfaces are pure JS — they have no DOM dependency and run in any Node version.

FetchXML

import {
  parseFetchXml,
  FetchXmlBuilder,
  convertFilterTreeToOData,
  flattenFilterTree,
  buildLayoutXml,
  updateFetchXmlFilterTree,
  type FilterGroup,
} from '@dataverse-kit/runtime';

// Parse an existing query.
const parsed = parseFetchXml(view.fetchxml);
// { entityName: 'account', attributes: ['name', 'revenue'], filterTree: { type: 'and', conditions: [...], groups: [...] }, orders: [...], top: 50, ... }

// Build a new query.
const xml = new FetchXmlBuilder('account')
  .select('name', 'revenue')
  .where('statecode', 'eq', '0')
  .andWhere('revenue', 'gt', '100000')
  .orderBy('revenue', true)
  .top(50)
  .build();

// Convert FetchXML filters to OData $filter — preserves AND/OR grouping.
const odataFilter = parsed.filterTree && convertFilterTreeToOData(parsed.filterTree);
// "statecode eq 0 and (revenue gt 100000)"  — paren-wrap when grouped

// Need the flat condition list (e.g. for a flat-only UI)?
const flat = parsed.filterTree ? flattenFilterTree(parsed.filterTree) : [];

// Update an existing fetchxml string in place with a tree.
const tree: FilterGroup = {
  type: 'and',
  conditions: [{ attribute: 'statecode', operator: 'eq', value: '0' }],
  groups: [],
};
const withFilters = updateFetchXmlFilterTree(view.fetchxml, tree);

Operators supported by parseFetchXml / FetchXmlBuilder: eq, ne, gt/ge/lt/le, like, not-like, begins-with, ends-with, null, not-null, in, not-in, between, not-between, date keywords (today, yesterday, last-x-days, this-month, …), user/team keywords (eq-userid, eq-userteams), hierarchy (above, under, not-under).

OData conversion rules:

  • Polymorphic lookups (regardingobjectid, ownerid, customerid) are excluded (unsupported in OData)
  • Boolean fields (prefixed is/has/can/do/allow/enable/disable) convert 0/1 to true/false
  • like maps to contains() / startswith() / endswith() based on % position
  • in expands to an OR chain of eq comparisons

ConditionEvaluator

Evaluates business-rule condition trees against record data. The operator set is intentionally the same shape as FetchXML so consultants can carry one mental model across the platform.

import { evaluateConditionGroup, type RuleConditionGroup } from '@dataverse-kit/runtime';

const rule: RuleConditionGroup = {
  id: 'g1',
  logic: 'and',
  conditions: [
    { id: 'c1', source: 'field', fieldName: 'status', operator: 'eq', value: 'active' },
    {
      id: 'g2',
      logic: 'or',
      conditions: [
        { id: 'c2', source: 'field', fieldName: 'tier', operator: 'in', value: ['gold', 'platinum'] },
        { id: 'c3', source: 'field', fieldName: 'revenue', operator: 'gt', value: 1_000_000 },
      ],
    },
  ],
};

const shouldFire = evaluateConditionGroup(rule, {
  data: { status: 'active', tier: 'silver', revenue: 2_000_000 },
});
// true (status matches AND revenue gt 1M)

Supports: nested AND/OR groups, 18 operators (eq, ne, gt/ge/lt/le, like, not-like, begins-with, ends-with, null, not-null, between, in, not-in, contain-values, not-contain-values, changed, not-changed), four condition sources (field, data-source, form-state, calculated — the last is reserved), and field-to-field comparisons via compareToField.

For trace output (useful for debugging):

import { evaluateConditionGroupWithDetails } from '@dataverse-kit/runtime';

const trace = evaluateConditionGroupWithDetails(rule, context);
// { result: true, details: [{ conditionId, leftValue, operator, rightValue, result }, …] }

changed / not-changed operators read from context.originalData — pair them with DirtyTracker below.

DirtyTracker

import { DirtyTracker } from '@dataverse-kit/runtime';

const tracker = new DirtyTracker({ name: 'Contoso', revenue: 100_000 });

tracker.trackChange('name', 'Contoso Ltd');
tracker.trackChange('revenue', 100_000);     // back to baseline → not dirty

tracker.isFieldDirty('name');                 // true
tracker.isFieldDirty('revenue');              // false
tracker.getDirtyFields();                     // Set { 'name' }
tracker.getDirtyCount();                      // 1

// After a save, re-anchor:
tracker.setBaseline(updatedRecord);
tracker.getDirtyCount();                      // 0

Constructor accepts either a baseline record directly (the common case) or an options object for advanced cases:

// Restoring persisted state:
new DirtyTracker({ baseline: { name: 'X' }, initialDirtyFields: ['name'] });

Coercion semantics (matches the original ProForma pattern):

  • null / undefined / "" are all equivalent
  • Number ↔ string: "5" equals 5
  • Boolean ↔ string: "true" equals true

The same coercion is exposed as a standalone helper:

import { valuesEqual } from '@dataverse-kit/runtime';
valuesEqual(null, undefined);   // true
valuesEqual(5, '5');            // true
valuesEqual(true, 'false');     // false

Origin / sync notes

This package consolidates code that was previously duplicated:

  • FetchXML: originally vendored from msft/04-dynamics/tools/fetchxml/; that upstream was deleted once @dataverse-kit/odata and @dataverse-kit/views were re-pointed at this package, and runtime is now the canonical source.
  • ConditionEvaluator: ported from msft/03-frontend/dynamics-ui-kit/packages/dynamics-bpf-core/src/services/ConditionEvaluator.ts.
  • DirtyTracker: ported from msft/03-frontend/dynamics-ui-kit/apps/form-builder/src/engine/DirtyTracker.ts, generalized away from form-builder's RuleExecutionContext. 47 tests ported.

Downstream migration of the original copies is tracked in IMPLEMENTATION_STATUS.md.

Testing

npm test               # vitest (uses happy-dom for DOMParser)
npm run typecheck