core: convert everything else to typescript

This commit is contained in:
Abdullah Atta
2023-09-18 15:38:43 +05:00
parent 37ae106713
commit d51079fc88
12 changed files with 232 additions and 523 deletions

View File

@@ -18,7 +18,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { IndexedCollection } from "./indexed-collection";
import MapStub from "../utils/map";
import {
CollectionType,
Collections,
@@ -51,9 +50,7 @@ export class CachedCollection<
const data = await this.collection.indexer.readMulti(
this.collection.indexer.indices
);
if ("dispose" in this.cache && typeof this.cache.dispose === "function")
this.cache.dispose();
this.cache = new MapStub.Map(data);
this.cache = new Map(data);
}
async add(item: MaybeDeletedItem<T>) {

View File

@@ -18,13 +18,17 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import {
ILogReporter,
LogLevel,
LogMessage,
Logger,
NoopLogger,
combineReporters,
consoleReporter,
format
format,
ILogger
} from "@notesnook/logger";
import { IStorage } from "./interfaces";
const WEEK = 86400000 * 7;
@@ -35,32 +39,21 @@ const WEEK = 86400000 * 7;
// 4. Implement functions for log retrieval & filtering
class DatabaseLogReporter {
/**
*
* @param {import("./database/crypto").default} storage
*/
constructor(storage) {
writer: DatabaseLogWriter;
constructor(storage: IStorage) {
this.writer = new DatabaseLogWriter(storage);
}
/**
*
* @param {import("@notesnook/logger").LogMessage} log
*/
write(log) {
write(log: LogMessage) {
this.writer.push(log);
}
}
class DatabaseLogWriter {
/**
*
* @param {import("./database/crypto").default} storage
*/
constructor(storage) {
this.storage = storage;
this.queue = new Map();
this.hasCleared = false;
private queue: Map<string, LogMessage> = new Map();
private hasCleared = false;
constructor(private readonly storage: IStorage) {
setInterval(() => {
setTimeout(() => {
if (!this.hasCleared) {
@@ -72,7 +65,7 @@ class DatabaseLogWriter {
}, 10000);
}
push(message) {
push(message: LogMessage) {
const key = new Date(message.timestamp).toLocaleDateString();
this.queue.set(`${key}:${message.timestamp}`, message);
}
@@ -100,33 +93,27 @@ class DatabaseLogWriter {
}
class DatabaseLogManager {
/**
*
* @param {import("./database/crypto").default} storage
*/
constructor(storage) {
this.storage = storage;
}
constructor(private readonly storage: IStorage) {}
async get() {
const logKeys = await this.storage.getAllKeys();
const logs = await this.storage.readMulti(logKeys);
const logGroups = {};
const logEntries = await this.storage.readMulti<LogMessage>(logKeys);
const logs: Record<string, LogMessage[]> = {};
for (const [key, log] of logs) {
const keyParts = key.split(":");
for (const [logKey, log] of logEntries) {
const keyParts = logKey.split(":");
if (keyParts.length === 1) continue;
const groupKey = keyParts[0];
if (!logGroups[groupKey]) logGroups[groupKey] = [];
logGroups[groupKey].push(log);
const key = keyParts[0];
if (!logs[key]) logs[key] = [];
logs[key].push(log);
}
return Object.keys(logGroups)
return Object.keys(logs)
.sort((a, b) => b.localeCompare(a, undefined, { numeric: true }))
.map((key) => ({
key,
logs: logGroups[key]?.sort((a, b) => a.timestamp - b.timestamp)
logs: logs[key]?.sort((a, b) => a.timestamp - b.timestamp)
}));
}
@@ -135,7 +122,7 @@ class DatabaseLogManager {
await this.storage.removeMulti(logKeys);
}
async delete(key) {
async delete(key: string) {
const logKeys = await this.storage.getAllKeys();
const keysToRemove = [];
for (const logKey of logKeys) {
@@ -148,9 +135,9 @@ class DatabaseLogManager {
}
}
function initialize(storage, disableConsoleLogs) {
function initialize(storage: IStorage, disableConsoleLogs?: boolean) {
if (storage) {
let reporters = [new DatabaseLogReporter(storage)];
const reporters: ILogReporter[] = [new DatabaseLogReporter(storage)];
if (process.env.NODE_ENV !== "production" && !disableConsoleLogs)
reporters.push(consoleReporter);
logger = new Logger({
@@ -161,14 +148,7 @@ function initialize(storage, disableConsoleLogs) {
}
}
/**
* @type {import("@notesnook/logger").ILogger}
*/
var logger = new NoopLogger();
/**
* @type {DatabaseLogManager | undefined}
*/
var logManager;
let logger: ILogger = new NoopLogger();
let logManager: DatabaseLogManager | undefined = undefined;
export { LogLevel, format, initialize, logManager, logger };

View File

@@ -17,15 +17,20 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
var map = Map;
class MapStub {
override(replacement) {
map = replacement;
export class BufferPool {
private freeBuffers: Buffer[] = [];
constructor(private readonly size: number) {}
alloc() {
return this.freeBuffers.pop() || this.allocNew();
}
get Map() {
return map;
private allocNew() {
return Buffer.alloc(this.size);
}
free(buf: Buffer) {
this.freeBuffers.push(buf);
return true;
}
}
export default new MapStub();

View File

@@ -45,14 +45,15 @@ export const hosts = {
export default hosts;
export const getServerNameFromHost = (host) => {
const names = {
[extractHostname(hosts.API_HOST)]: "Notesnook Sync Server",
[extractHostname(hosts.AUTH_HOST)]: "Authentication Server",
[extractHostname(hosts.SSE_HOST)]: "Eventing Server",
[extractHostname(hosts.SUBSCRIPTIONS_HOST)]:
"Subscriptions Management Server",
[extractHostname(hosts.ISSUES_HOST)]: "Bug Reporting Server"
};
return names[host];
const HOSTNAMES = {
[extractHostname(hosts.API_HOST)]: "Notesnook Sync Server",
[extractHostname(hosts.AUTH_HOST)]: "Authentication Server",
[extractHostname(hosts.SSE_HOST)]: "Eventing Server",
[extractHostname(hosts.SUBSCRIPTIONS_HOST)]:
"Subscriptions Management Server",
[extractHostname(hosts.ISSUES_HOST)]: "Bug Reporting Server"
};
export const getServerNameFromHost = (host: string) => {
return HOSTNAMES[host];
};

View File

@@ -16,22 +16,13 @@ GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import db from "mime-db";
// type MimeTypeInfo = {
// source: string;
// extensions?: string[];
// charset?: string;
// compressible?: boolean;
// };
/**
*
* @param {string} filename
* @param {string | undefined} mime
* @returns {string}
*/
export function getFileNameWithExtension(filename, mime) {
export function getFileNameWithExtension(
filename: string,
mime: string | undefined
): string {
if (!mime || mime === "application/octet-stream") return filename;
const mimeData = db[mime];
if (!mimeData || !mimeData.extensions || mimeData.extensions.length === 0)
@@ -59,24 +50,23 @@ export const DocumentMimeTypes = [
"application/vnd.oasis.opendocument.presentation"
];
export const WebClipMimeType = "application/vnd.notesnook.web-clip";
export function isDocument(mime) {
export function isDocument(mime: string) {
return DocumentMimeTypes.some((a) => a.startsWith(mime));
}
export function isWebClip(mime) {
export const WebClipMimeType = "application/vnd.notesnook.web-clip";
export function isWebClip(mime: string) {
return mime === WebClipMimeType;
}
export function isImage(mime) {
export function isImage(mime: string) {
return mime.startsWith("image/");
}
export function isVideo(mime) {
export function isVideo(mime: string) {
return mime.startsWith("video/");
}
export function isAudio(mime) {
export function isAudio(mime: string) {
return mime.startsWith("audio/");
}

View File

@@ -17,20 +17,13 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
export function extractHostname(url) {
var hostname;
export function extractHostname(url: string) {
//find & remove protocol (http, ftp, etc.) and get hostname
if (url.indexOf("//") > -1) {
hostname = url.split("/")[2];
} else {
hostname = url.split("/")[0];
}
const hostname: string =
url.indexOf("//") > -1 ? url.split("/")[2] : url.split("/")[0];
//find & remove port number
// hostname = hostname.split(":")[0];
//find & remove "?"
hostname = hostname.split("?")[0];
return hostname;
return hostname.split("?")[0];
}

View File

@@ -22,28 +22,43 @@ import { logger } from "../logger";
import { getServerNameFromHost } from "./constants";
import { extractHostname } from "./hostname";
function get(url, token) {
return request(url, token, "GET");
type ContentType = "application/json" | "application/x-www-form-urlencoded";
type RequestBody = Record<string, string | number | boolean | undefined> | null;
type JsonRequestBody = Record<string, unknown> | null;
function get(url: string, token?: string) {
return request(url, "GET", token);
}
function deleteRequest(url, token) {
return request(url, token, "DELETE");
function deleteRequest(url: string, token?: string) {
return request(url, "DELETE", token);
}
function patch(url, data, token) {
return bodyRequest(url, data, token, "PATCH");
function patch(url: string, data: RequestBody, token?: string) {
return bodyRequest(url, transformFormData(data), token, "PATCH");
}
patch.json = function (url, data, token) {
return bodyRequest(url, data, token, "PATCH", "application/json");
patch.json = function (url: string, data: JsonRequestBody, token?: string) {
return bodyRequest(
url,
transformJson(data),
token,
"PATCH",
"application/json"
);
};
function post(url, data, token) {
return bodyRequest(url, data, token, "POST");
function post(url: string, data: RequestBody, token?: string) {
return bodyRequest(url, transformFormData(data), token, "POST");
}
post.json = function (url, data, token) {
return bodyRequest(url, data, token, "POST", "application/json");
post.json = function (url: string, data: JsonRequestBody, token?: string) {
return bodyRequest(
url,
transformJson(data),
token,
"POST",
"application/json"
);
};
export default {
@@ -53,84 +68,40 @@ export default {
patch
};
function transformer(data, type) {
if (!data) return;
if (type === "application/json") return JSON.stringify(data);
else {
return Object.entries(data)
.map(([key, value]) =>
value ? `${encodeURIComponent(key)}=${encodeURIComponent(value)}` : ""
)
.join("&");
}
}
/**
*
* @param {Response} response
* @returns
*/
async function handleResponse(response) {
try {
const contentType = response.headers.get("content-type");
if (contentType && contentType.includes("application/json")) {
const json = await response.json();
if (response.ok) {
return json;
}
throw new RequestError(errorTransformer(json));
} else {
if (response.status === 429)
throw new Error("You are being rate limited.");
if (response.ok) return await response.text();
else if (response.status === 401) {
EV.publish(EVENTS.userUnauthorized, response.url);
throw new Error("Unauthorized.");
} else
throw new Error(
`Request failed with status code: ${response.status} ${response.statusText}.`
);
}
} catch (e) {
logger.error(e, "Error while sending request:", { url: response.url });
throw e;
}
}
async function request(url, token, method) {
async function request(url: string, method: "GET" | "DELETE", token?: string) {
return handleResponse(
await fetchWrapped(url, {
method,
headers: getAuthorizationHeader(token)
headers: getHeaders(token)
})
);
}
async function bodyRequest(
url,
data,
token,
method,
contentType = "application/x-www-form-urlencoded"
url: string,
data: string | undefined,
token: string | undefined,
method: "POST" | "PATCH" | "PUT",
contentType: ContentType = "application/x-www-form-urlencoded"
) {
return handleResponse(
await fetchWrapped(url, {
method,
body: transformer(data, contentType),
body: data,
headers: {
...getAuthorizationHeader(token),
...getHeaders(token),
"Content-Type": contentType
}
})
);
}
function getAuthorizationHeader(token) {
return token ? { Authorization: "Bearer " + token } : {};
}
export function errorTransformer(errorJson) {
export function errorTransformer(errorJson: {
error?: string;
errors?: string[];
error_description?: string;
data?: string;
}) {
let errorMessage = "Unknown error.";
let errorCode = "unknown";
@@ -156,8 +127,8 @@ export function errorTransformer(errorJson) {
}
}
default:
errorMessage = error_description || error || "An unknown error occurred.";
errorCode = error;
errorMessage = error_description || error || errorMessage;
errorCode = error || errorCode;
break;
}
@@ -168,12 +139,7 @@ export function errorTransformer(errorJson) {
};
}
/**
*
* @param {RequestInfo} input
* @param {RequestInit} init
*/
async function fetchWrapped(input, init) {
async function fetchWrapped(input: string, init: RequestInit) {
try {
const response = await fetch(input, init);
return response;
@@ -182,17 +148,73 @@ async function fetchWrapped(input, init) {
const serverName = getServerNameFromHost(host);
if (serverName)
throw new Error(
`${serverName} is not responding. Please check your internet connection. If the problem persists, feel free email us at support@streetwriters.co. (Reference error: ${e.message})`
`${serverName} is not responding. Please check your internet connection. If the problem persists, feel free email us at support@streetwriters.co. (Reference error: ${
(e as Error).message
})`
);
throw e;
}
}
async function handleResponse(response: Response) {
try {
const contentType = response.headers.get("content-type");
if (contentType && contentType.includes("application/json")) {
const json = await response.json();
if (response.ok) {
return json;
}
throw new RequestError(errorTransformer(json));
} else {
if (response.status === 429)
throw new Error("You are being rate limited.");
if (response.ok) return await response.text();
else if (response.status === 401) {
EV.publish(EVENTS.userUnauthorized, response.url);
throw new Error("Unauthorized.");
} else
throw new Error(
`Request failed with status code: ${response.status} ${response.statusText}.`
);
}
} catch (e) {
logger.error(e as Error, "Error while sending request:", {
url: response.url
});
throw e;
}
}
export class RequestError extends Error {
constructor(error) {
code: string;
data: unknown;
constructor(error: { code: string; data: unknown; description: string }) {
super(error.description);
this.code = error.code;
this.data = error.data;
}
}
function getHeaders(token?: string | null) {
return token ? { Authorization: "Bearer " + token } : undefined;
}
function transformJson(data: JsonRequestBody) {
return JSON.stringify(data);
}
function transformFormData(data: RequestBody) {
if (data) {
return Object.entries(data)
.map(([key, value]) =>
value
? `${encodeURIComponent(key)}=${
value ? encodeURIComponent(value) : ""
}`
: ""
)
.join("&");
}
}

View File

@@ -18,12 +18,10 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import SparkMD5 from "spark-md5";
import ObjectID from "./object-id";
import { createObjectId } from "./object-id";
export function getId(time?: number) {
if (time)
return new ObjectID(new ObjectID().generate(time / 1000)).toHexString();
return new ObjectID().toHexString();
return createObjectId(time);
}
export function makeId(text: string) {

View File

@@ -1,336 +0,0 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Copied from https://github.com/williamkapke/bson-objectid
import { randomInt } from "./random";
var MACHINE_ID = randomInt();
var index = randomInt();
var pid =
(typeof process === "undefined" || typeof process.pid !== "number"
? randomInt()
: process.pid) % 0xffff;
/**
* Determine if an object is Buffer
*
* Author: Feross Aboukhadijeh <feross@feross.org> <http://feross.org>
* License: MIT
*
*/
var isBuffer = function (obj) {
return !!(
obj != null &&
obj.constructor &&
typeof obj.constructor.isBuffer === "function" &&
obj.constructor.isBuffer(obj)
);
};
// Precomputed hex table enables speedy hex string conversion
var hexTable = [];
for (var i = 0; i < 256; i++) {
hexTable[i] = (i <= 15 ? "0" : "") + i.toString(16);
}
// Regular expression that checks for hex value
var checkForHexRegExp = new RegExp("^[0-9a-fA-F]{24}$");
// Lookup tables
var decodeLookup = [];
i = 0;
while (i < 10) decodeLookup[0x30 + i] = i++;
while (i < 16) decodeLookup[0x41 - 10 + i] = decodeLookup[0x61 - 10 + i] = i++;
/**
* Create a new immutable ObjectID instance
*
* @class Represents the BSON ObjectID type
* @param {String|Number} id Can be a 24 byte hex string, 12 byte binary string or a Number.
* @return {Object} instance of ObjectID.
*/
class ObjectID {
constructor(id) {
if (!(this instanceof ObjectID)) return new ObjectID(id);
if (id && (id instanceof ObjectID || id._bsontype === "ObjectID"))
return id;
this._bsontype = "ObjectID";
// The most common usecase (blank id, new objectId instance)
if (id == null || typeof id === "number") {
// Generate a new id
this.id = this.generate(id);
// Return the object
return;
}
// Check if the passed in id is valid
var valid = ObjectID.isValid(id);
// Throw an error if it's not a valid setup
if (!valid && id != null) {
throw new Error(
"Argument passed in must be a single String of 12 bytes or a string of 24 hex characters"
);
} else if (valid && typeof id === "string" && id.length === 24) {
return ObjectID.createFromHexString(id);
} else if (id != null && id.length === 12) {
// assume 12 byte string
this.id = id;
} else if (id != null && typeof id.toHexString === "function") {
// Duck-typing to support ObjectId from different npm packages
return id;
} else {
throw new Error(
"Argument passed in must be a single String of 12 bytes or a string of 24 hex characters"
);
}
}
/**
* Creates an ObjectID from a second based number, with the rest of the ObjectID zeroed out. Used for comparisons or sorting the ObjectID.
*
* @param {Number} time an integer number representing a number of seconds.
* @return {ObjectID} return the created ObjectID
* @api public
*/
static createFromTime(time) {
time = parseInt(time, 10) % 0xffffffff;
return new ObjectID(hex(8, time) + "0000000000000000");
}
/**
* Creates an ObjectID from a hex string representation of an ObjectID.
*
* @param {String} hexString create a ObjectID from a passed in 24 byte hexstring.
* @return {ObjectID} return the created ObjectID
* @api public
*/
static createFromHexString(hexString) {
// Throw an error if it's not a valid setup
if (
typeof hexString === "undefined" ||
(hexString != null && hexString.length !== 24)
) {
throw new Error(
"Argument passed in must be a single String of 12 bytes or a string of 24 hex characters"
);
}
// Calculate lengths
var data = "";
var i = 0;
while (i < 24) {
data += String.fromCharCode(
(decodeLookup[hexString.charCodeAt(i++)] << 4) |
decodeLookup[hexString.charCodeAt(i++)]
);
}
return new ObjectID(data);
}
/**
* Checks if a value is a valid bson ObjectId
*
* @param {String} objectid Can be a 24 byte hex string or an instance of ObjectID.
* @return {Boolean} return true if the value is a valid bson ObjectID, return false otherwise.
* @api public
*
* THE NATIVE DOCUMENTATION ISN'T CLEAR ON THIS GUY!
* http://mongodb.github.io/node-mongodb-native/api-bson-generated/objectid.html#objectid-isvalid
*/
static isValid(id) {
if (id == null) return false;
if (typeof id === "number") {
return true;
}
if (typeof id === "string") {
return (
id.length === 12 || (id.length === 24 && checkForHexRegExp.test(id))
);
}
if (id instanceof ObjectID) {
return true;
}
if (isBuffer(id)) {
return true;
}
// Duck-Typing detection of ObjectId like objects
if (
typeof id.toHexString === "function" &&
(id.id instanceof Buffer || typeof id.id === "string")
) {
return (
id.id.length === 12 ||
(id.id.length === 24 && checkForHexRegExp.test(id.id))
);
}
return false;
}
/**
* Return the ObjectID id as a 24 byte hex string representation
*
* @return {String} return the 24 byte hex string representation.
* @api public
*/
toHexString() {
if (!this.id || !this.id.length) {
throw new Error(
"invalid ObjectId, ObjectId.id must be either a string or a Buffer, but is [" +
JSON.stringify(this.id) +
"]"
);
}
if (this.id.length === 24) {
return this.id;
}
if (isBuffer(this.id)) {
return this.id.toString("hex");
}
var hexString = "";
for (var i = 0; i < this.id.length; i++) {
hexString += hexTable[this.id.charCodeAt(i)];
}
return hexString;
}
/**
* Compares the equality of this ObjectID with `otherID`.
*
* @param {Object} otherId ObjectID instance to compare against.
* @return {Boolean} the result of comparing two ObjectID's
* @api public
*/
equals(otherId) {
if (otherId instanceof ObjectID) {
return this.toString() === otherId.toString();
} else if (
typeof otherId === "string" &&
ObjectID.isValid(otherId) &&
otherId.length === 12 &&
isBuffer(this.id)
) {
return otherId === this.id.toString("binary");
} else if (
typeof otherId === "string" &&
ObjectID.isValid(otherId) &&
otherId.length === 24
) {
return otherId.toLowerCase() === this.toHexString();
} else if (
typeof otherId === "string" &&
ObjectID.isValid(otherId) &&
otherId.length === 12
) {
return otherId === this.id;
} else if (
otherId != null &&
(otherId instanceof ObjectID || otherId.toHexString)
) {
return otherId.toHexString() === this.toHexString();
} else {
return false;
}
}
/**
* Returns the generation date (accurate up to the second) that this ID was generated.
*
* @return {Date} the generation date
* @api public
*/
getTimestamp() {
var timestamp = new Date();
var time;
if (isBuffer(this.id)) {
time =
this.id[3] |
(this.id[2] << 8) |
(this.id[1] << 16) |
(this.id[0] << 24);
} else {
time =
this.id.charCodeAt(3) |
(this.id.charCodeAt(2) << 8) |
(this.id.charCodeAt(1) << 16) |
(this.id.charCodeAt(0) << 24);
}
timestamp.setTime(Math.floor(time) * 1000);
return timestamp;
}
/**
* Generate a 12 byte id buffer used in ObjectID's
*
* @method
* @param {number} [time] optional parameter allowing to pass in a second based timestamp.
* @return {string} return the 12 byte id buffer string.
*/
generate(time) {
if ("number" !== typeof time) {
time = ~~(Date.now() / 1000);
}
//keep it in the ring!
time = parseInt(time, 10) % 0xffffffff;
var inc = next();
return String.fromCharCode(
(time >> 24) & 0xff,
(time >> 16) & 0xff,
(time >> 8) & 0xff,
time & 0xff,
(MACHINE_ID >> 16) & 0xff,
(MACHINE_ID >> 8) & 0xff,
MACHINE_ID & 0xff,
(pid >> 8) & 0xff,
pid & 0xff,
(inc >> 16) & 0xff,
(inc >> 8) & 0xff,
inc & 0xff
);
}
toJSON() {
return this.toHexString();
}
toString() {
return this.toHexString();
}
}
export default ObjectID;
function next() {
return (index = (index + 1) % 0xffffff);
}
function hex(length, n) {
n = n.toString(16);
return n.length === length ? n : "00000000".substring(n.length, length) + n;
}

View File

@@ -0,0 +1,51 @@
/*
This file is part of the Notesnook project (https://notesnook.com/)
Copyright (C) 2023 Streetwriters (Private) Limited
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import { BufferPool } from "./buffer-pool";
import { randomBytes, randomInt } from "./random";
const PROCESS_UNIQUE = randomBytes(5);
let index = ~~(randomInt() * 0xffffff);
const objectIdPool = new BufferPool(12);
export function createObjectId(date = Date.now()): string {
const buffer = objectIdPool.alloc();
index = (index + 1) % 0xffffff;
const time = ~~(date / 1000);
// 4-byte timestamp
new DataView(buffer.buffer, 0, 4).setUint32(0, time);
// 5-byte process unique
buffer[4] = PROCESS_UNIQUE[0];
buffer[5] = PROCESS_UNIQUE[1];
buffer[6] = PROCESS_UNIQUE[2];
buffer[7] = PROCESS_UNIQUE[3];
buffer[8] = PROCESS_UNIQUE[4];
// 3-byte counter
buffer[11] = index & 0xff;
buffer[10] = (index >> 8) & 0xff;
buffer[9] = (index >> 16) & 0xff;
const objectId = buffer.toString("hex");
objectIdPool.free(buffer);
return objectId;
}