reduct-js
Version:
ReductStore Client SDK for Javascript/NodeJS/Typescript
156 lines (155 loc) • 4.97 kB
JavaScript
import { APIError } from "./APIError";
/**
* Represents a batch of records for writing
*/
export var BatchType;
(function (BatchType) {
BatchType[BatchType["WRITE"] = 0] = "WRITE";
BatchType[BatchType["UPDATE"] = 1] = "UPDATE";
BatchType[BatchType["REMOVE"] = 2] = "REMOVE";
})(BatchType || (BatchType = {}));
export class Batch {
constructor(bucketName, entryName, httpClient, type) {
this.bucketName = bucketName;
this.entryName = entryName;
this.httpClient = httpClient;
this.records = new Map();
this.type = type;
this.totalSize = BigInt(0);
this.lastAccess = 0;
}
/**
* Add record to batch
* @param ts timestamp of record as a UNIX timestamp in microseconds
* @param data {Buffer | string} data to write
* @param contentType default: application/octet-stream
* @param labels default: {}
*/
add(ts, data, contentType, labels) {
const _contentType = contentType ?? "application/octet-stream";
const _labels = labels ?? {};
const _data = data instanceof Buffer ? data : Buffer.from(data, "utf-8");
this.totalSize += BigInt(_data.length);
this.lastAccess = Date.now();
this.records.set(ts, {
data: _data,
contentType: _contentType,
labels: _labels,
});
}
/**
* Add only labels to batch
* Use for updating labels
* @param ts timestamp of record as a UNIX timestamp in microseconds
* @param labels
*/
addOnlyLabels(ts, labels) {
this.records.set(ts, {
data: Buffer.from(""),
contentType: "",
labels: labels,
});
}
/**
* Add only timestamp to batch
* Use for removing records
* @param ts timestamp of record as a UNIX timestamp in microseconds
*/
addOnlyTimestamp(ts) {
this.records.set(ts, {
data: Buffer.from(""),
contentType: "",
labels: {},
});
}
/**
* Write batch to entry
*/
async write() {
const headers = {};
const chunks = [];
let contentLength = 0;
for (const [ts, { data, contentType, labels }] of this.items()) {
contentLength += data.length;
chunks.push(data);
const headerName = `x-reduct-time-${ts}`;
let headerValue = "0,";
if (this.type == BatchType.WRITE) {
headerValue = `${data.length},${contentType}`;
}
for (const [key, value] of Object.entries(labels)) {
if (value.toString().includes(",")) {
headerValue += `,${key}="${value}"`;
}
else {
headerValue += `,${key}=${value}`;
}
}
headers[headerName] = headerValue;
}
let response;
switch (this.type) {
case BatchType.WRITE: {
const stream = new ReadableStream({
start(ctrl) {
for (const chunk of chunks) {
ctrl.enqueue(chunk);
}
ctrl.close();
},
});
headers["Content-Type"] = "application/octet-stream";
headers["Content-Length"] = contentLength.toString();
response = await this.httpClient.post(`/b/${this.bucketName}/${this.entryName}/batch`, stream, headers);
break;
}
case BatchType.UPDATE:
response = await this.httpClient.patch(`/b/${this.bucketName}/${this.entryName}/batch`, "", headers);
break;
case BatchType.REMOVE:
response = await this.httpClient.delete(`/b/${this.bucketName}/${this.entryName}/batch`, headers);
break;
}
const errors = new Map();
for (const [key, value] of response.headers.entries()) {
if (key.startsWith("x-reduct-error-")) {
const ts = BigInt(key.slice(15));
const [code, message] = value.split(",", 2);
errors.set(ts, new APIError(message, Number.parseInt(code)));
}
}
return errors;
}
/**
* Get records in batch sorted by timestamp
*/
items() {
return new Map([...this.records.entries()].sort()).entries();
}
/**
* Get total size of batch
*/
size() {
return this.totalSize;
}
/**
* Get last access time of batch
*/
lastAccessTime() {
return this.lastAccess;
}
/**
* Get number of records in batch
*/
recordCount() {
return this.records.size;
}
/**
* Clear batch
*/
clear() {
this.records.clear();
this.totalSize = BigInt(0);
this.lastAccess = 0;
}
}