UNPKG

ravendb

Version:
296 lines (295 loc) 11.9 kB
import { randomUUID } from "node:crypto"; import { throwError } from "../Exceptions/index.js"; import { RequestExecutor } from "../Http/RequestExecutor.js"; import { getLogger } from "../Utility/LogUtil.js"; import { DocumentStoreBase } from "./DocumentStoreBase.js"; import { MaintenanceOperationExecutor } from "./Operations/MaintenanceOperationExecutor.js"; import { OperationExecutor } from "./Operations/OperationExecutor.js"; import { DocumentSession } from "./Session/DocumentSession.js"; import { BulkInsertOperation } from "./BulkInsertOperation.js"; import { DatabaseChanges } from "./Changes/DatabaseChanges.js"; import { DatabaseSmuggler } from "./Smuggler/DatabaseSmuggler.js"; import { MultiDatabaseHiLoIdGenerator } from "./Identity/MultiDatabaseHiLoIdGenerator.js"; import { TypeUtil } from "../Utility/TypeUtil.js"; import { wrapWithTimeout } from "../Utility/PromiseUtil.js"; const log = getLogger({ module: "DocumentStore" }); export class DocumentStore extends DocumentStoreBase { _log = 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 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 = randomUUID(); const session = new 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.create(this.urls, database, { authOptions: this.authOptions, documentConventions: this.conventions }); this.registerEvents(requestExecutor); return requestExecutor; }; const createRequestExecutorForSingleNode = () => { const forSingleNode = 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) { 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.validateUrls(this.urls, this.authOptions); try { if (!this.conventions.documentIdGenerator) { // don't overwrite what the user is doing const generator = new 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) { 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(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(this); } return this._maintenanceOperationExecutor; } get smuggler() { if (!this._smuggler) { this._smuggler = new DatabaseSmuggler(this); } return this._smuggler; } /** * Gets operations executor. */ get operations() { if (!this._operationExecutor) { this._operationExecutor = new OperationExecutor(this); } return this._operationExecutor; } bulkInsert(databaseOrOptions, optionalOptions) { this.assertInitialized(); const database = TypeUtil.isString(databaseOrOptions) ? this.getEffectiveDatabase(databaseOrOptions) : this.getEffectiveDatabase(null); const options = TypeUtil.isString(databaseOrOptions) ? optionalOptions : databaseOrOptions; return new BulkInsertOperation(database, this, options); } } //# sourceMappingURL=DocumentStore.js.map