arrakis-js
Version:
Arrakis Javascript client library
148 lines (147 loc) • 5.84 kB
JavaScript
// Copyright (c) 2022, California Institute of Technology and contributors
//
// You should have received a copy of the licensing terms for this
// software included in the file "LICENSE" located in the top-level
// directory of this package. If you did not, you can view a copy at
// https://git.ligo.org/ngdd/arrakis-js/-/raw/main/LICENSE
import { DEFAULT_ARRAKIS_SERVER } from "./constants";
import * as Arrow from "apache-arrow";
import { Channel } from "./channel";
import { SeriesBlock } from "./block";
/**
* Count channels matching a set of conditions
*
* @param {string} [pattern] - Channel pattern to match channels with, using regular expressions
* @param {string|Array<string>} [dataType] - If set, find all channels with these data types
* @param {number} [minRate] - Minimum sample rate for channels
* @param {number} [maxRate] - Maximum sample rate for channels
* @param {string|Array<string>} [publisher] - If set, find all channels associated with these publishers
* @returns {Promise<number>} A promise that resolves to the number of channels matching query
*/
export async function count(pattern, dataType, minRate, maxRate, publisher) {
const response = await fetch(`${DEFAULT_ARRAKIS_SERVER}/count`, {
method: "POST",
body: JSON.stringify({ pattern, dataType, minRate, maxRate, publisher }),
}).then((res) => res.json());
return response.count;
}
/**
* Find channels matching a set of conditions and stream results via Server-Sent Events (SSE)
*
* @param {string} [pattern] - Channel pattern to match channels with, using regular expressions
* @param {string|Array<string>} [dataType] - If set, find all channels with these data types
* @param {number} [minRate] - Minimum sample rate for channels
* @param {number} [maxRate] - Maximum sample rate for channels
* @param {string|Array<string>} [publisher] - If set, find all channels associated with these publishers
* @returns {AsyncGenerator<Channel>} An async generator that yields channel objects as they are received via SSE
*/
export async function* find(pattern, dataType, minRate, maxRate, publisher) {
// Build query parameters
const params = new URLSearchParams();
if (pattern)
params.append("pattern", pattern);
if (dataType) {
if (Array.isArray(dataType)) {
params.append("dataType", dataType.join(","));
}
else {
params.append("dataType", dataType);
}
}
if (minRate)
params.append("minRate", minRate.toString());
if (maxRate)
params.append("maxRate", maxRate.toString());
if (publisher) {
if (Array.isArray(publisher)) {
params.append("publisher", publisher.join(","));
}
else {
params.append("publisher", publisher);
}
}
const findUrl = `${DEFAULT_ARRAKIS_SERVER}/find?${params.toString()}`;
let queue = Array();
let resolveNext;
let done = false;
const eventSource = new EventSource(findUrl);
// Fulfill the promise once the next channel
// is received, inside onmessage
eventSource.onmessage = (e) => {
const message = JSON.parse(e.data);
if (message.done) {
done = true;
eventSource.close();
return;
}
if (message.channel) {
queue.push(Channel.fromJson(message.channel));
if (resolveNext) {
// if there is a promise waiting,
// fulfill it with the first channel in the queue
resolveNext(queue.shift());
// reset the promise callback
resolveNext = undefined;
}
}
};
eventSource.onerror = (err) => {
console.error("SSE connection error:", err);
eventSource.close();
done = true;
};
try {
while (!done || queue.length > 0) {
// yield if there are channels in the queue
if (queue.length > 0) {
yield queue.shift();
}
else {
// otherwise, wait for next channel with a promise
yield new Promise((resolve) => {
resolveNext = resolve;
});
}
}
}
finally {
eventSource.close();
}
}
/**
* Fetch timeseries data for a list of channels within a time range
*
* @param {string[]} channels - A list of channels to request
* @param {number} start - GPS start time, in seconds
* @param {number} end - GPS end time, in seconds
* @returns {Promise<SeriesBlock>} A promise that resolves to a SeriesBlock containing the requested data
*/
export async function* stream(channels, start, end) {
// Create a stream reader for Arrow record batches
const response = await fetch("http://localhost:8000/stream", {
method: "POST",
body: JSON.stringify({ channels, start, end }),
});
if (!response.ok || !response.body)
throw new Error("Failed to fetch data");
const reader = await Arrow.RecordBatchStreamReader.from(response.body);
for await (const batch of reader) {
yield SeriesBlock.fromBatch(batch);
}
}
/**
* Get channel metadata for channels requested
*
* @param {string[]} channels - List of channels to request
* @returns {Promise<Record<string, Channel>>} A promise that resolves to a mapping of channel names to channel metadata
*/
export async function describe(channels) {
const params = new URLSearchParams();
params.append("channels", channels.join(","));
const response = await fetch(`${DEFAULT_ARRAKIS_SERVER}/describe?${params.toString()}`).then((res) => res.json());
const result = {};
for (const [name, data] of Object.entries(response)) {
result[name] = Channel.fromJson(data);
}
return result;
}