Named Time Series Values

  • A time series entry consists of a timestamp, one or more values, and an optional tag.
    Each value can be given a name to indicate what it represents, such as "Temperature", "Humidity", "Pressure", etc.

  • Referring to these values by their names in time series methods (such as append, get, etc.)
    makes your code more readable and easier to manage.

  • In order for the Studio to present the time series values by their names, as can be seen here,
    you need to register the named values on the server.

  • In this page:


Named values

  • Many time series are populated with multiple values for each measurement.
    For example, each GPS measurement in a route-tracking time series would include at least two values:
    latitude and longitude.

  • You can ease the management of multi-value time series by -

    • Naming time series values in custom classes.
    • Calling time series methods with your custom types to address and manage values by name.

Define time series class with named values

To define a class with named values, add the static property TIME_SERIES_VALUES to the class.
E.g.:

class RoutePoint {
    
    // Add the following static param:
    static TIME_SERIES_VALUES = ["latitude", "longitude"];

    // The Latitude and Longitude properties will contain the time series entry values.
    // The names for these values will be "latitude" and "longitude" respectively.
    
    constructor(
        latitude = 0,
        longitude = 0
    ) {
        Object.assign(this, {
            latitude,
            longitude
        });
    }
}

The class can then be used by time series methods like append:

const baseTime = new Date();
const oneHour = 60 * 60 * 1000;
let nextHour = new Date(baseTime.getTime() + oneHour);

const tsf = session.timeSeriesFor("users/john", "RoutePoints", RoutePoint);

const routePoint = new RoutePoint();
routePoint.latitude = 40.712776;
routePoint.longitude = -74.005974;

// Append coordinates using the routePoint object
tsf.append(nextHour, routePoint, "devices/Navigator");

await session.saveChanges();

Examples

In this example, we define a StockPrice class and use it when appending StockPrice entries.

class StockPrice {

    // Define the names for the entry values
    static TIME_SERIES_VALUES = ["open", "close", "high", "low", "volume"];

    constructor(
        open = 0,
        close = 0,
        high = 0,
        low = 0,
        volume = 0
    ) {
        Object.assign(this, {
            open,
            close,
            high,
            low,
            volume
        });
    }
}

const session = documentStore.openSession();
await session.store(new User("John"), "users/john");

// Get an instance of 'timeSeriesFor', pass:
// * the document ID
// * the time series name
// * the class that will hold the entry's values
const tsf = session.timeSeriesFor("users/john", "StockPrices", StockPrice);

const optionalTag = "companies/kitchenAppliances";
const baseTime = new Date();
baseTime.setUTCHours(0);
const oneDay = 24 * 60 * 60 * 1000;

// Provide the multiple values via the StockPrice class
const price1 = new StockPrice();
price1.open = 52;
price1.close = 54;
price1.high = 63.5;
price1.low = 51.4;
price1.volume = 9824;

// Call 'append' with the custom StockPrice class
let nextDay = new Date(baseTime.getTime() + oneDay);
tsf.append(nextDay, price1, optionalTag);

const price2 = new StockPrice();
price2.open = 54;
price2.close = 55;
price2.high = 61.5;
price2.low = 49.4;
price2.volume = 8400;

nextDay = new Date(baseTime.getTime() + oneDay * 2);
tsf.append(nextDay, price2, optionalTag);

const price3 = new StockPrice();
price3.open = 55;
price3.close = 57;
price3.high = 65.5;
price3.low = 50;
price3.volume = 9020;

nextDay = new Date(baseTime.getTime() + oneDay * 3);
tsf.append(nextDay, price3, optionalTag);

await session.saveChanges();

In this example, we get StockPrice values by name and check whether a stock's closing-time prices are ascending over time.

let goingUp = false;

const allEntries = await session
    .timeSeriesFor("users/john", "StockPrices")
    .get();

// Call 'asTypedEntry' to be able to access the entry's values by their names
// Pass the class type (StockPrice)
const typedEntry1 = allEntries[0].asTypedEntry(StockPrice);

// Access the entry value by its StockPrice class property name (close)
const closePriceDay1 = typedEntry1.value.close;

const typedEntry2 = allEntries[1].asTypedEntry(StockPrice);
const closePriceDay2 = typedEntry2.value.close;

const typedEntry3 = allEntries[2].asTypedEntry(StockPrice);
const closePriceDay3 = typedEntry3.value.close;

// Check if the stock's closing price is rising
if ((closePriceDay2 > closePriceDay1) && (closePriceDay3 > closePriceDay2)) {
    goingUp = true;
}

In this query, we use the custom StockPrice type so we can address trade Volume by name.

const oneDay = 24 * 60 * 60 * 1000;
const startTime = new Date();
const endTime = new Date(startTime.getTime() + 3 * oneDay);

// Note: the 'where' clause must come after the 'between' clause
const tsQueryText = `
    from StockPrices
    between $start and $end
    where Tag == "AppleTech"`;

const query = session.query({ collection: "companies" })
    .whereEquals("address.city", "New York")
    .selectTimeSeries(b => b.raw(tsQueryText), TimeSeriesRawResult)
    .addParameter("start", startTime)
    .addParameter("end", endTime);

// Execute the query:
const results = await query.all();

// Access entries results:
const tsEntries = results[0].results;

// Call 'asTypedEntry' to be able to access the entry's values by their names
// Pass the class type (StockPrice)
const volumeDay1 = tsEntries[0].asTypedEntry(StockPrice).value.volume;
const volumeDay2 = tsEntries[1].asTypedEntry(StockPrice).value.volume;
const volumeDay3 = tsEntries[2].asTypedEntry(StockPrice).value.volume;
from "companies"
where address.city = $p0
select timeseries(
    from StockPrices
    between $start and $end
    where Tag == "AppleTech")
{
   "p0":"New York",
   "start":"2024-06-04T06:02:39.826Z",
   "end":"2024-06-07T06:02:39.826Z"
}

Register time series named values

Registering a custom time series type on the server stores this information in the database record.
This allows the Studio to present time series values by name when you view and manage them.


Usage

To register a time series type, call documentStore.timeSeries.register, e.g.:

// Register the named values for the 'StockPrices' series on the server
await documentStore.timeSeries.register("Users",
    "StockPrices", ["open", "close", "high", "low", "volume"]);


The time series entries will be listed in the Studio under their corresponding named values:

"Time series entries"

Time series entries with named values


The named values can be managed from the Time Series Settings View in the Studio:

"Time series settings view"

The time series settings view


Syntax

// Available overloads:
// ====================

register(collection, name, valueNames);
register(collectionClass, name, valueNames);
register(collectionClass, timeSeriesEntryClass);
register(collectionClass, timeSeriesEntryClass, name); 


Parameter Type Description
collection string The time series collection name
name string Time series name
valueNames string[] Names to register (name per value)
collectionClass object The collection class
timeSeriesEntryClass object The custom time series entry class