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

@speclynx/apidom-datamodel

v1.12.2

Published

Data model primitives for ApiDOM.

Downloads

44

Readme

@speclynx/apidom-datamodel

@speclynx/apidom-datamodel provides the foundational data model primitives for ApiDOM. It contains the core element classes, namespace system, and serialization utilities that form the basis for all ApiDOM structures.

The data model is based on SpecLynx-flavored Refract specification 0.7.0, a recursive data structure for expressing complex structures, relationships, and metadata.

Installation

You can install this package via npm CLI by running the following command:

 $ npm install @speclynx/apidom-datamodel

Primitive Elements

Primitive elements are the building blocks of ApiDOM. Each element has:

  • element - Type identifier (e.g., 'string', 'object', 'array')
  • content - The element's value
  • meta - Metadata (id, classes, title, description, links)
  • attributes - Element-specific properties

Element

Base class that all ApiDOM elements extend.

import { Element } from '@speclynx/apidom-datamodel';

const element = new Element('content');
element.element = 'custom';
element.meta.set('id', 'unique-id');
element.attributes.set('attr', 'value');

Additionally, convenience attributes are exposed on every element as shortcuts for common meta properties:

  • id (StringElement) - Shortcut for .meta.get('id')
  • classes (ArrayElement) - Shortcut for .meta.get('classes')
  • title (StringElement) - Shortcut for .meta.get('title')
  • description (StringElement) - Shortcut for .meta.get('description')
  • links (ArrayElement) - Shortcut for .meta.get('links')
import { StringElement } from '@speclynx/apidom-datamodel';

const element = new StringElement('hello');
element.id = 'greeting';
element.title = 'A greeting';
element.classes = ['important', 'message'];

element.id.toValue(); // => 'greeting'
element.title.toValue(); // => 'A greeting'
element.classes.toValue(); // => ['important', 'message']

StringElement

Represents a string value.

import { StringElement } from '@speclynx/apidom-datamodel';

const str = new StringElement('hello');
str.toValue(); // => 'hello'
str.length; // => 5

NumberElement

Represents a numeric value.

import { NumberElement } from '@speclynx/apidom-datamodel';

const num = new NumberElement(42);
num.toValue(); // => 42

BooleanElement

Represents a boolean value.

import { BooleanElement } from '@speclynx/apidom-datamodel';

const bool = new BooleanElement(true);
bool.toValue(); // => true

NullElement

Represents a null value.

import { NullElement } from '@speclynx/apidom-datamodel';

const nil = new NullElement();
nil.toValue(); // => null

ArrayElement

Represents an ordered collection of elements.

import { ArrayElement } from '@speclynx/apidom-datamodel';

const arr = new ArrayElement([1, 2, 3]);
arr.length; // => 3
arr.get(0).toValue(); // => 1
arr.push(4);
arr.map((item) => item.toValue()); // => [1, 2, 3, 4]
arr.filter((item) => item.toValue() > 2); // => ArraySlice with 3, 4

ObjectElement

Represents a collection of key-value pairs.

import { ObjectElement } from '@speclynx/apidom-datamodel';

const obj = new ObjectElement({ name: 'John', age: 30 });
obj.get('name').toValue(); // => 'John'
obj.set('email', '[email protected]');
obj.keys(); // => ['name', 'age', 'email']
obj.values(); // => ['John', 30, '[email protected]']
obj.hasKey('name'); // => true

ObjectElement supports generics for typed key-value pairs:

import { ObjectElement, StringElement, NumberElement } from '@speclynx/apidom-datamodel';

const typed = new ObjectElement<StringElement, NumberElement>({ a: 1, b: 2 });

MemberElement

Represents a key-value pair within an ObjectElement.

import { MemberElement } from '@speclynx/apidom-datamodel';

const member = new MemberElement('key', 'value');
member.key.toValue(); // => 'key'
member.value.toValue(); // => 'value'

Higher-Order Elements

RefElement

Represents a reference to another element by ID.

import { RefElement, StringElement } from '@speclynx/apidom-datamodel';

const target = new StringElement('referenced');
target.id = 'target-id';

const ref = new RefElement('target-id');
ref.toValue(); // => 'target-id'

LinkElement

Represents a hyperlink.

import { LinkElement } from '@speclynx/apidom-datamodel';

const link = new LinkElement();
link.relation = 'self';
link.href = 'https://example.com';

Extending Elements

You can create custom element types by extending the primitive elements. This is useful for creating semantic elements that represent specific data structures.

Creating a Custom Element

import { ObjectElement, StringElement } from '@speclynx/apidom-datamodel';

class PersonElement extends ObjectElement {
  constructor(content, meta, attributes) {
    super(content, meta, attributes);
    this.element = 'person';
  }

  get name() {
    return this.get('name');
  }

  set name(value) {
    this.set('name', value);
  }

  get email() {
    return this.get('email');
  }

  set email(value) {
    this.set('email', value);
  }
}

const person = new PersonElement({ name: 'John', email: '[email protected]' });
person.element; // => 'person'
person.name.toValue(); // => 'John'
person.email.toValue(); // => '[email protected]'

Registering Custom Elements

To use custom elements with serialization and namespace features, register them:

import { Namespace } from '@speclynx/apidom-datamodel';

const namespace = new Namespace();
namespace.register('person', PersonElement);

// Now PersonElement will be used when deserializing 'person' elements
const serialiser = namespace.serialiser;
const refracted = { element: 'person', content: [] };
const element = serialiser.deserialise(refracted); // => PersonElement

Refraction

The refract function converts JavaScript values to ApiDOM elements.

import { refract } from '@speclynx/apidom-datamodel';

refract('hello'); // => StringElement('hello')
refract(42); // => NumberElement(42)
refract(true); // => BooleanElement(true)
refract(null); // => NullElement()
refract([1, 2, 3]); // => ArrayElement([NumberElement(1), NumberElement(2), NumberElement(3)])
refract([1, undefined, 3]); // => ArrayElement([NumberElement(1), NullElement(), NumberElement(3)])
refract({ a: 'b' }); // => ObjectElement({ a: StringElement('b') })
refract({ a: undefined }); // => ObjectElement({ a: undefined }) - undefined values are preserved

Namespace

The Namespace class provides a registry for element classes and handles element detection and conversion.

import { Namespace } from '@speclynx/apidom-datamodel';

const namespace = new Namespace();

// Convert JavaScript values to elements
namespace.toElement('hello'); // => StringElement('hello')
namespace.toElement(42); // => NumberElement(42)

// Register custom element classes
class CustomElement extends namespace.Element {
  element = 'custom';
}
namespace.register('custom', CustomElement);

// Get element class by name
const ElementClass = namespace.getElementClass('custom'); // => CustomElement

Extending Namespace

You can register custom detection functions:

import { Namespace, Element } from '@speclynx/apidom-datamodel';

class DateElement extends Element {
  element = 'date';
}

const namespace = new Namespace();
namespace.register('date', DateElement);
namespace.detect((value) => value instanceof Date, DateElement);

namespace.toElement(new Date()); // => DateElement

Creating Namespace Plugins

It is possible to create plugin modules that define elements for custom namespaces. Plugin modules should export a namespace function that takes an options object containing an existing namespace to which you can add your elements:

import { Namespace } from '@speclynx/apidom-datamodel';

// Define your plugin module (normally done in a separate file)
const plugin = {
  namespace: (options) => {
    const base = options.base;
    const ArrayElement = base.getElementClass('array');

    // Register custom elements
    base.register('category', ArrayElement);

    return base;
  }
};

// Create namespace and load the plugin
const namespace = new Namespace();
namespace.use(plugin);

// Now 'category' element is available
const CategoryElement = namespace.getElementClass('category');

Plugins can also define a load function for additional initialization:

const plugin = {
  namespace: (options) => {
    // Register elements
  },
  load: (options) => {
    // Additional initialization after namespace setup
  }
};

Serialization

The namespace provides serialization methods:

import { Namespace, ObjectElement } from '@speclynx/apidom-datamodel';

const namespace = new Namespace();
const obj = new ObjectElement({ key: 'value' });

// Serialize to refract format
const refracted = namespace.toRefract(obj);
// => { element: 'object', content: [...] }

// Deserialize from refract format
const element = namespace.fromRefract(refracted);
// => ObjectElement({ key: 'value' })

JSON Serialiser

Direct serialization without namespace:

import { JSONSerialiser, ObjectElement } from '@speclynx/apidom-datamodel';

const serialiser = new JSONSerialiser();
const obj = new ObjectElement({ a: 1 });

// Serialize
const json = serialiser.serialise(obj);
// => { element: 'object', content: [...] }

// Deserialize
const element = serialiser.deserialise(json);
// => ObjectElement({ a: 1 })

Element Operations

Cloning

Elements can be deeply cloned:

import { ObjectElement } from '@speclynx/apidom-datamodel';

const original = new ObjectElement({ nested: { value: 1 } });
const cloned = original.clone();

cloned.get('nested').set('value', 2);
original.get('nested').get('value').toValue(); // => 1 (unchanged)

Freezing

Elements can be frozen for immutability. Freezing also sets up parent references:

import { ObjectElement } from '@speclynx/apidom-datamodel';

const obj = new ObjectElement({ a: 1 });
obj.freeze();

obj.isFrozen; // => true
obj.get('a').parent === obj.getMember('a'); // => true

Equality

Elements support deep equality checking:

import { ArrayElement } from '@speclynx/apidom-datamodel';

const arr1 = new ArrayElement([1, 2, 3]);
const arr2 = new ArrayElement([1, 2, 3]);

arr1.equals([1, 2, 3]); // => true
arr1.equals(arr2.toValue()); // => true

Creating References

Elements with IDs can create references to themselves:

import { ObjectElement } from '@speclynx/apidom-datamodel';

const obj = new ObjectElement({ data: 'value' });
obj.id = 'my-object';

const ref = obj.toRef();
ref.toValue(); // => 'my-object'

ObjectSlice

ObjectSlice is a utility for working with filtered object members:

import { ObjectElement } from '@speclynx/apidom-datamodel';

const obj = new ObjectElement({ a: 1, b: 2, c: 3 });
const slice = obj.filter((value) => value.toValue() > 1);

slice.keys(); // => ['b', 'c']
slice.values(); // => [2, 3]

KeyValuePair

Internal structure used by MemberElement to store key-value pairs:

import { KeyValuePair, StringElement, NumberElement } from '@speclynx/apidom-datamodel';

const pair = new KeyValuePair();
pair.key = new StringElement('name');
pair.value = new NumberElement(42);

pair.clone(); // creates a deep copy