ravendb
Version:
RavenDB client for Node.js
300 lines (299 loc) • 12.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DocumentStore = void 0;
const node_crypto_1 = require("node:crypto");
const index_js_1 = require("../Exceptions/index.js");
const RequestExecutor_js_1 = require("../Http/RequestExecutor.js");
const LogUtil_js_1 = require("../Utility/LogUtil.js");
const DocumentStoreBase_js_1 = require("./DocumentStoreBase.js");
const MaintenanceOperationExecutor_js_1 = require("./Operations/MaintenanceOperationExecutor.js");
const OperationExecutor_js_1 = require("./Operations/OperationExecutor.js");
const DocumentSession_js_1 = require("./Session/DocumentSession.js");
const BulkInsertOperation_js_1 = require("./BulkInsertOperation.js");
const DatabaseChanges_js_1 = require("./Changes/DatabaseChanges.js");
const DatabaseSmuggler_js_1 = require("./Smuggler/DatabaseSmuggler.js");
const MultiDatabaseHiLoIdGenerator_js_1 = require("./Identity/MultiDatabaseHiLoIdGenerator.js");
const TypeUtil_js_1 = require("../Utility/TypeUtil.js");
const PromiseUtil_js_1 = require("../Utility/PromiseUtil.js");
const log = (0, LogUtil_js_1.getLogger)({ module: "DocumentStore" });
class DocumentStore extends DocumentStoreBase_js_1.DocumentStoreBase {
_log = (0, LogUtil_js_1.getLogger)({ module: "DocumentStore-" + Math.floor(Math.random() * 1000) });
_databaseChanges = new Map(); //TODO: check usage - it has compound key!
// TBD: private ConcurrentDictionary<string, Lazy<EvictItemsFromCacheBasedOnChanges>> _aggressiveCacheChanges =
// new ConcurrentDictionary<string, Lazy<EvictItemsFromCacheBasedOnChanges>>();
// TBD: private readonly ConcurrentDictionary<string, EvictItemsFromCacheBasedOnChanges>
// _observeChangesAndEvictItemsFromCacheForDatabases =
// new ConcurrentDictionary<string, EvictItemsFromCacheBasedOnChanges>();
_requestExecutors = new Map();
_multiDbHiLo;
_maintenanceOperationExecutor;
_operationExecutor;
_smuggler;
_identifier;
_aggressiveCachingUsed;
constructor(urls, database, authOptions) {
super();
this._database = database;
this.authOptions = authOptions;
this.urls = Array.isArray(urls)
? urls
: [urls];
}
_getDatabaseChangesCacheKey(options) {
return options.databaseName.toLowerCase() + "/" + (options.nodeTag || "<null>");
}
get identifier() {
if (this._identifier) {
return this._identifier;
}
if (!this._urls) {
return null;
}
const urlsString = this._urls.join(", ");
if (this._database) {
return `${urlsString} DB: ${this._database}`;
}
return urlsString;
}
set identifier(identifier) {
this._identifier = identifier;
}
get hiLoIdGenerator() {
return this._multiDbHiLo;
}
/**
* Disposes the document store
*/
dispose() {
this._log.info("Dispose.");
this.emit("beforeDispose");
/* TBD
foreach (var value in _aggressiveCacheChanges.Values)
{
if (value.IsValueCreated == false)
continue;
value.Value.Dispose();
}*/
for (const change of this._databaseChanges.values()) {
change.dispose();
}
/* TODO
// try to wait until all the async disposables are completed
Task.WaitAll(tasks.ToArray(), TimeSpan.FromSeconds(3));
// if this is still going, we continue with disposal, it is for graceful shutdown only, anyway
*/
const disposeChain = Promise.resolve();
disposeChain
.then(async () => {
if (this._multiDbHiLo) {
try {
return await this._multiDbHiLo.returnUnusedRange();
}
catch (err) {
return await this._log.warn("Error returning unused ID range.", err);
}
}
})
.then(async () => {
this._disposed = true;
this.subscriptions.dispose();
const task = new Promise((resolve, reject) => {
let listenersExecCallbacksCount = 0;
const listenersCount = this.listenerCount("afterDispose");
if (listenersCount === 0) {
resolve();
}
else {
this.emit("afterDispose", () => {
if (listenersCount === ++listenersExecCallbacksCount) {
resolve();
}
});
}
});
try {
return await (0, PromiseUtil_js_1.wrapWithTimeout)(task, 5_000);
}
catch (err) {
return await this._log.warn(`Error handling 'afterDispose'`, err);
}
})
.then(() => {
this._log.info(`Disposing request executors ${this._requestExecutors.size}`);
for (const [db, executor] of this._requestExecutors.entries()) {
try {
executor.dispose();
}
catch (err) {
this._log.warn(err, `Error disposing request executor.`);
}
}
})
.finally(() => this.emit("executorsDisposed"));
}
/**
* Opens document session
*/
openSession(databaseOrSessionOptions) {
if (!databaseOrSessionOptions) {
const sessionOptions = {
disableAtomicDocumentWritesInClusterWideTransaction: this.conventions.disableAtomicDocumentWritesInClusterWideTransaction
};
return this.openSession(sessionOptions);
}
this.assertInitialized();
this._ensureNotDisposed();
if (typeof (databaseOrSessionOptions) === "string") {
return this.openSession({
database: databaseOrSessionOptions,
disableAtomicDocumentWritesInClusterWideTransaction: this.conventions.disableAtomicDocumentWritesInClusterWideTransaction
});
}
databaseOrSessionOptions = databaseOrSessionOptions || {};
const sessionOpts = databaseOrSessionOptions;
const sessionId = (0, node_crypto_1.randomUUID)();
const session = new DocumentSession_js_1.DocumentSession(this, sessionId, sessionOpts);
this.registerEvents(session);
this.emit("sessionCreated", { session });
return session;
}
/**
* Gets request executor for specific database. Default is initial database.
*/
getRequestExecutor(database) {
this.assertInitialized();
database = this.getEffectiveDatabase(database);
const databaseLower = database.toLowerCase();
let executor = this._requestExecutors.get(databaseLower);
if (executor) {
return executor;
}
const createRequestExecutor = () => {
const requestExecutor = RequestExecutor_js_1.RequestExecutor.create(this.urls, database, {
authOptions: this.authOptions,
documentConventions: this.conventions
});
this.registerEvents(requestExecutor);
return requestExecutor;
};
const createRequestExecutorForSingleNode = () => {
const forSingleNode = RequestExecutor_js_1.RequestExecutor.createForSingleNodeWithConfigurationUpdates(this.urls[0], database, {
authOptions: this.authOptions,
documentConventions: this.conventions
});
this.registerEvents(forSingleNode);
return forSingleNode;
};
if (!this.conventions.disableTopologyUpdates) {
executor = createRequestExecutor();
}
else {
executor = createRequestExecutorForSingleNode();
}
this._log.info(`New request executor for database ${database}`);
this._requestExecutors.set(databaseLower, executor);
return executor;
}
requestTimeout(timeoutInMs, database) {
this.assertInitialized();
database = this.getEffectiveDatabase(database);
if (!database) {
(0, index_js_1.throwError)("InvalidOperationException", "Cannot use requestTimeout without a default database defined " +
"unless 'database' parameter is provided. Did you forget to pass 'database' parameter?");
}
const requestExecutor = this.getRequestExecutor(database);
const oldTimeout = requestExecutor.defaultTimeout;
requestExecutor.defaultTimeout = timeoutInMs;
return {
dispose: () => requestExecutor.defaultTimeout = oldTimeout
};
}
/**
* Initializes this instance.
*/
initialize() {
if (this._initialized) {
return this;
}
this._assertValidConfiguration();
RequestExecutor_js_1.RequestExecutor.validateUrls(this.urls, this.authOptions);
try {
if (!this.conventions.documentIdGenerator) { // don't overwrite what the user is doing
const generator = new MultiDatabaseHiLoIdGenerator_js_1.MultiDatabaseHiLoIdGenerator(this);
this._multiDbHiLo = generator;
this.conventions.documentIdGenerator =
(dbName, entity) => generator.generateDocumentId(dbName, entity);
}
this.conventions.freeze();
this._initialized = true;
}
catch (e) {
this.dispose();
throw e;
}
return this;
}
/**
* Validate the configuration for the document store
*/
_assertValidConfiguration() {
if (!this._urls || !this._urls.length) {
(0, index_js_1.throwError)("InvalidArgumentException", "Document store URLs cannot be empty");
}
super._assertValidConfiguration();
}
changes(database, nodeTag) {
this.assertInitialized();
const changesOptions = {
databaseName: database || this.database,
nodeTag
};
const cacheKey = this._getDatabaseChangesCacheKey(changesOptions);
if (this._databaseChanges.has(cacheKey)) {
return this._databaseChanges.get(cacheKey);
}
const newChanges = this._createDatabaseChanges(changesOptions);
this._databaseChanges.set(cacheKey, newChanges);
return newChanges;
}
_createDatabaseChanges(node) {
return new DatabaseChanges_js_1.DatabaseChanges(this.getRequestExecutor(node.databaseName), node.databaseName, () => this._databaseChanges.delete(this._getDatabaseChangesCacheKey(node)), node.nodeTag);
}
// TBD public override IDatabaseChanges Changes(string database = null)
// TBD protected virtual IDatabaseChanges CreateDatabaseChanges(string database)
// TBD public override IDisposable AggressivelyCacheFor(TimeSpan cacheDuration, string database = null)
// TBD private void ListenToChangesAndUpdateTheCache(string database)
/**
* Gets maintenance operations executor.
*/
get maintenance() {
this.assertInitialized();
if (!this._maintenanceOperationExecutor) {
this._maintenanceOperationExecutor = new MaintenanceOperationExecutor_js_1.MaintenanceOperationExecutor(this);
}
return this._maintenanceOperationExecutor;
}
get smuggler() {
if (!this._smuggler) {
this._smuggler = new DatabaseSmuggler_js_1.DatabaseSmuggler(this);
}
return this._smuggler;
}
/**
* Gets operations executor.
*/
get operations() {
if (!this._operationExecutor) {
this._operationExecutor = new OperationExecutor_js_1.OperationExecutor(this);
}
return this._operationExecutor;
}
bulkInsert(databaseOrOptions, optionalOptions) {
this.assertInitialized();
const database = TypeUtil_js_1.TypeUtil.isString(databaseOrOptions) ? this.getEffectiveDatabase(databaseOrOptions) : this.getEffectiveDatabase(null);
const options = TypeUtil_js_1.TypeUtil.isString(databaseOrOptions) ? optionalOptions : databaseOrOptions;
return new BulkInsertOperation_js_1.BulkInsertOperation(database, this, options);
}
}
exports.DocumentStore = DocumentStore;
//# sourceMappingURL=DocumentStore.js.map