HDS Model

The HDSModel loads the HDS data model and exposes it through lazy-loaded helper classes for items, streams, authorizations, event types, and datasources.

Initialization

const HDSLib = require('hds-lib');

// Optional: override service URL
HDSLib.settings.setServiceInfoURL('https://demo.datasafe.dev/reg/service/info');

// Initialize once at app startup
await HDSLib.initHDSModel();

// Use anywhere in your code
const model = HDSLib.getHDSModel();

initHDSModel() fetches the service info, extracts the model URL from assets, and loads the definitions. The model data is frozen (immutable) after loading.

Manual loading

const model = new HDSLib.HDSModel('https://model.datasafe.dev/pack.json');
await model.load();

Overload

model.load(url, overload), model.loadFromObject(data, overload) and initHDSModel({ overload }) take an HDSModelOverload (ts/HDSModel/HDSModel-Overload.ts) that is validated, then merged before the model is frozen. An overload may add items, streams, eventTypes, settings, datasources and appStreams, and refine the label, description, repeatable, reminder and devNotes of an existing item. It may not change an existing item’s type, streamId, eventType, variations, options, composite, datasource, converter-engine or unitConversions, nor an existing stream’s parentId, role or clientData: the load throws an HDSLibError listing each one.

Restating one of those fields with exactly the canonical value is accepted, for items as for streams. The comparison is on the JSON serialization, so object key order counts: { value: 0, label: … } and { label: …, value: 0 } are different. This lets an app keep an item in its overlay after the same definition becomes canonical.

Properties

Property Type Description
isLoaded boolean Whether model data has been loaded
modelData object Raw model JSON (frozen)
assets object Service-info assets map (e.g., datasource URLs)

Items Definitions — model.itemsDefs

Items are the atomic data definitions in the HDS model (e.g., “body-weight”, “profile-name”).

model.itemsDefs.getAll()

Returns all item definitions.

const allItems = model.itemsDefs.getAll();
// Array of HDSItemDef instances

model.itemsDefs.forKey(key)

Retrieve an item definition by its unique key.

const weight = model.itemsDefs.forKey('body-weight');
weight.key;          // "body-weight"
weight.label;        // "Weight" (localized)
weight.description;  // Localized description
weight.eventTypes;   // ["mass/kg", "mass/lb"]
weight.repeatable;   // "unlimited"
weight.reminder;     // ReminderConfig or null

model.itemsDefs.forEvent(event)

Find the item definition matching an event’s streamId and type.

const event = { streamIds: ['body-weight', 'dummy'], type: 'mass/kg' };
const itemDef = model.itemsDefs.forEvent(event);
itemDef.key; // "body-weight"

HDSItemDef properties

Property Type Description
key string Unique identifier (e.g., body-weight)
data object Raw definition from model JSON
label string Localized display name
description string Localized description
eventTypes string[] Supported event types
repeatable string How many times item can be collected (default: "unlimited")
reminder ReminderConfig \| null Reminder configuration

HDSItemDef.eventTemplate()

Returns a template for creating events for this item.

const template = weight.eventTemplate();
// { streamIds: ['body-weight'], type: 'mass/kg' }

Streams — model.streams

Streams are the hierarchical containers where events are stored. The HDS model defines a standard stream hierarchy.

Stream hierarchy

graph TD
    Root["(root)"]
    Root --> profile[profile]
    Root --> body[body]
    Root --> lifestyle[lifestyle]
    Root --> apps[applications]

    profile --> pname[profile-name]
    profile --> pdob[profile-date-of-birth]
    profile --> psurname[profile-surname]

    body --> bweight[body-weight]
    body --> bheight[body-height]
    body --> bvulva[body-vulva]
    bvulva --> bvm[body-vulva-mucus]
    bvm --> bvmi[body-vulva-mucus-inspect]
    bvm --> bvms[body-vulva-mucus-stretch]

This is a simplified view. The actual hierarchy is defined by the loaded data model.

model.streams.getNecessaryListForItems(itemKeys, params?)

Get the list of streams that need to be created to store a set of items.

Parameters:

const itemKeys = [
  'profile-name',
  'profile-date-of-birth',
  'body-vulva-mucus-stretch',
  'profile-surname'
];

const streams = model.streams.getNecessaryListForItems(itemKeys);
// Returns ordered list of streams to create:
// [
//   { id: 'profile', name: 'Profile', parentId: null },
//   { id: 'profile-name', name: 'Name', parentId: 'profile' },
//   { id: 'profile-date-of-birth', name: 'Date of Birth', parentId: 'profile' },
//   { id: 'profile-surname', name: 'Surname', parentId: 'profile' },
//   { id: 'body', name: 'Body', parentId: null },
//   { id: 'body-vulva', name: 'Vulva', parentId: 'body' },
//   { id: 'body-vulva-mucus', name: 'Mucus', parentId: 'body-vulva' },
//   { id: 'body-vulva-mucus-stretch', name: 'Stretch', parentId: 'body-vulva-mucus' }
// ]

With known existing streams:

const streams = model.streams.getNecessaryListForItems(itemKeys, {
  knowExistingStreamsIds: ['profile']
});
// Skips creating 'profile' stream, still creates children

model.streams.getDataById(streamId)

Retrieve the raw model definition for a stream.

const data = model.streams.getDataById('body-weight');

model.streams.getParentsIds(streamId)

Get ordered list of parent stream IDs from root to immediate parent.

const parents = model.streams.getParentsIds('body-vulva-mucus-stretch');
// ['body', 'body-vulva', 'body-vulva-mucus']

Authorizations — model.authorizations

Generate permission request sets from item keys. Computes the minimal set of stream permissions needed.

model.authorizations.forItemKeys(itemKeys, options?)

Options:

const itemKeys = [
  'body-vulva-mucus-inspect',
  'profile-name',
  'profile-date-of-birth',
  'body-vulva-mucus-stretch',
  'profile-surname'
];

const options = {
  preRequest: [
    { streamId: 'profile' },
    { streamId: 'app-test', defaultName: 'App test', level: 'write' }
  ]
};

const auths = model.authorizations.forItemKeys(itemKeys, options);
// [
//   { streamId: 'profile', defaultName: 'Profile', level: 'read' },
//   { streamId: 'app-test', defaultName: 'App test', level: 'write' },
//   { streamId: 'body-vulva-mucus-inspect', defaultName: 'Inspect', level: 'read' },
//   { streamId: 'body-vulva-mucus-stretch', defaultName: 'Stretch', level: 'read' }
// ]

Optimization logic:

AuthorizationRequestItem

{
  streamId: string;
  level: string;       // 'read', 'manage', 'contribute', 'writeOnly'
  defaultName: string; // Localized stream display name
}

Event Types — model.eventTypes

Access event type metadata from the model.

model.eventTypes.getEventTypeDefinition(eventType)

const def = model.eventTypes.getEventTypeDefinition('mass/kg');

model.eventTypes.getEventTypeExtra(eventType)

const extra = model.eventTypes.getEventTypeExtra('mass/kg');

model.eventTypes.getEventTypeSymbol(eventType)

Returns the symbol/icon associated with an event type, or null.

const symbol = model.eventTypes.getEventTypeSymbol('mass/kg');

Datasources — model.datasources

External data sources defined in the model (e.g., medication databases).

model.datasources.getAll()

const all = model.datasources.getAll();
// Array of HDSDatasourceDef

model.datasources.forKey(key)

const medSource = model.datasources.forKey('medication');

HDSDatasourceDef properties

Property Type Description
key string Datasource identifier
label string Localized label
description string Localized description
endpoint string Resolved URL (handles assetKey://path protocol)
queryParam string Query parameter name (e.g., q)
minQueryLength number Minimum query string length
resultKey string JSON path to results in API response
displayFields object { label, description } — fields for display
valueFields string[] Fields to extract as values

Endpoint resolution

Datasource endpoints can use a special protocol that resolves through service-info assets:

"datasets://medication"
  → assets['datasets'] + '/medication'
  → "https://datasets.datasafe.dev/medication"

Direct HTTP URLs are used as-is.