UNPKG

ravendb

Version:
1,088 lines 82.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DeletedEntitiesHolder = exports.DocumentsByEntityHolder = exports.InMemoryDocumentSessionOperations = void 0; const EntityToJson_js_1 = require("./EntityToJson.js"); const IDocumentSession_js_1 = require("./IDocumentSession.js"); const SessionEvents_js_1 = require("./SessionEvents.js"); const index_js_1 = require("../../Exceptions/index.js"); const DocumentsById_js_1 = require("./DocumentsById.js"); const DocumentInfo_js_1 = require("./DocumentInfo.js"); const CommandData_js_1 = require("../Commands/CommandData.js"); const GenerateEntityIdOnTheClient_js_1 = require("../Identity/GenerateEntityIdOnTheClient.js"); const index_js_2 = require("../../Mapping/Json/index.js"); const Constants_js_1 = require("../../Constants.js"); const DateUtil_js_1 = require("../../Utility/DateUtil.js"); const ObjectUtil_js_1 = require("../../Utility/ObjectUtil.js"); const IncludesUtil_js_1 = require("./IncludesUtil.js"); const TypeUtil_js_1 = require("../../Utility/TypeUtil.js"); const IdTypeAndName_js_1 = require("../IdTypeAndName.js"); const DocumentsChanges_js_1 = require("./DocumentsChanges.js"); const node_events_1 = require("node:events"); const JsonOperation_js_1 = require("../../Mapping/JsonOperation.js"); const MetadataAsDictionary_js_1 = require("../../Mapping/MetadataAsDictionary.js"); const CaseInsensitiveKeysMap_js_1 = require("../../Primitives/CaseInsensitiveKeysMap.js"); const CaseInsensitiveStringSet_js_1 = require("../../Primitives/CaseInsensitiveStringSet.js"); const SessionOperationExecutor_js_1 = require("../Operations/SessionOperationExecutor.js"); const StringUtil_js_1 = require("../../Utility/StringUtil.js"); const ForceRevisionCommandData_js_1 = require("../Commands/Batches/ForceRevisionCommandData.js"); const TimeSeriesRangeResult_js_1 = require("../Operations/TimeSeries/TimeSeriesRangeResult.js"); const DatesComparator_js_1 = require("../../Primitives/DatesComparator.js"); const GetTimeSeriesOperation_js_1 = require("../Operations/TimeSeries/GetTimeSeriesOperation.js"); const ShardedBatchOptions_js_1 = require("../Commands/Batches/ShardedBatchOptions.js"); class InMemoryDocumentSessionOperations extends node_events_1.EventEmitter { _requestExecutor; _operationExecutor; _pendingLazyOperations = []; static _instancesCounter = 0; _hash = ++InMemoryDocumentSessionOperations._instancesCounter; _disposed; _id; get id() { return this._id; } _knownMissingIds = CaseInsensitiveStringSet_js_1.CaseInsensitiveStringSet.create(); _externalState; disableAtomicDocumentWritesInClusterWideTransaction; _transactionMode; get externalState() { if (!this._externalState) { this._externalState = new Map(); } return this._externalState; } getCurrentSessionNode() { return this.sessionInfo.getCurrentSessionNode(this._requestExecutor); } documentsById = new DocumentsById_js_1.DocumentsById(); /** * map holding the data required to manage Counters tracking for RavenDB's Unit of Work */ get countersByDocId() { if (!this._countersByDocId) { this._countersByDocId = CaseInsensitiveKeysMap_js_1.CaseInsensitiveKeysMap.create(); } return this._countersByDocId; } _countersByDocId; _timeSeriesByDocId; get timeSeriesByDocId() { if (!this._timeSeriesByDocId) { this._timeSeriesByDocId = CaseInsensitiveKeysMap_js_1.CaseInsensitiveKeysMap.create(); } return this._timeSeriesByDocId; } noTracking; idsForCreatingForcedRevisions = CaseInsensitiveKeysMap_js_1.CaseInsensitiveKeysMap.create(); includedDocumentsById = CaseInsensitiveKeysMap_js_1.CaseInsensitiveKeysMap.create(); /** * Translate between an CV and its associated entity */ includeRevisionsByChangeVector = CaseInsensitiveKeysMap_js_1.CaseInsensitiveKeysMap.create(); /** * Translate between an ID and its associated entity */ includeRevisionsIdByDateTimeBefore = CaseInsensitiveKeysMap_js_1.CaseInsensitiveKeysMap.create(); documentsByEntity = new DocumentsByEntityHolder(); deletedEntities = new DeletedEntitiesHolder(); _documentStore; _databaseName; _saveChangesOptions; get databaseName() { return this._databaseName; } get documentStore() { return this._documentStore; } get requestExecutor() { return this._requestExecutor; } get sessionInfo() { return this._sessionInfo; } get operations() { if (!this._operationExecutor) { this._operationExecutor = new SessionOperationExecutor_js_1.SessionOperationExecutor(this); } return this._operationExecutor; } _numberOfRequests = 0; get numberOfRequests() { return this._numberOfRequests; } getNumberOfEntitiesInUnitOfWork() { return this.documentsByEntity.size; } get storeIdentifier() { return `${this._documentStore.identifier};${this._databaseName}`; } get conventions() { return this._requestExecutor.conventions; } maxNumberOfRequestsPerSession; useOptimisticConcurrency; _deferredCommands = []; // keys are produced with IdTypeAndName.keyFor() method deferredCommandsMap = new Map(); get deferredCommands() { return this._deferredCommands; } get deferredCommandsCount() { return this._deferredCommands.length; } _generateEntityIdOnTheClient; get generateEntityIdOnTheClient() { return this._generateEntityIdOnTheClient; } _entityToJson; get entityToJson() { return this._entityToJson; } _sessionInfo; constructor(documentStore, id, options) { super(); this._id = id; this._databaseName = options.database || documentStore.database; if (StringUtil_js_1.StringUtil.isNullOrWhitespace(this._databaseName)) { InMemoryDocumentSessionOperations._throwNoDatabase(); } this._documentStore = documentStore; this._requestExecutor = options.requestExecutor || documentStore.getRequestExecutor(this._databaseName); this.noTracking = options.noTracking; this.useOptimisticConcurrency = this._requestExecutor.conventions.isUseOptimisticConcurrency(); this.maxNumberOfRequestsPerSession = this._requestExecutor.conventions.maxNumberOfRequestsPerSession; this._generateEntityIdOnTheClient = new GenerateEntityIdOnTheClient_js_1.GenerateEntityIdOnTheClient(this._requestExecutor.conventions, (obj) => this._generateId(obj)); this._entityToJson = new EntityToJson_js_1.EntityToJson(this); this._sessionInfo = new IDocumentSession_js_1.SessionInfo(this, options, documentStore); this._transactionMode = options.transactionMode; this.disableAtomicDocumentWritesInClusterWideTransaction = options.disableAtomicDocumentWritesInClusterWideTransaction; const shardedBatchBehavior = options.shardedBatchBehavior ?? this.requestExecutor.conventions.sharding.batchBehavior; const shardedBatchOptions = (0, ShardedBatchOptions_js_1.forBehavior)(shardedBatchBehavior); if (shardedBatchOptions) { this._saveChangesOptions = { shardedOptions: shardedBatchOptions, replicationOptions: null, indexOptions: null }; } } /** * Gets the metadata for the specified entity. */ getMetadataFor(instance) { if (!instance) { (0, index_js_1.throwError)("InvalidOperationException", "Instance cannot be null or undefined."); } const documentInfo = this._getDocumentInfo(instance); return this._makeMetadataInstance(documentInfo); } /** * Gets all counter names for the specified entity. */ getCountersFor(instance) { if (!instance) { (0, index_js_1.throwError)("InvalidArgumentException", "Instance cannot be null."); } const documentInfo = this._getDocumentInfo(instance); const countersArray = documentInfo.metadata[Constants_js_1.CONSTANTS.Documents.Metadata.COUNTERS]; if (!countersArray) { return null; } return countersArray; } /** * Gets all time series names for the specified entity. * @param instance Entity */ getTimeSeriesFor(instance) { if (!instance) { (0, index_js_1.throwError)("InvalidArgumentException", "Instance cannot be null"); } const documentInfo = this._getDocumentInfo(instance); return documentInfo.metadata[Constants_js_1.CONSTANTS.Documents.Metadata.TIME_SERIES] || []; } _makeMetadataInstance(docInfo) { const metadataInstance = docInfo.metadataInstance; if (metadataInstance) { return metadataInstance; } const metadataAsJson = docInfo.metadata; const metadata = (0, MetadataAsDictionary_js_1.createMetadataDictionary)({ raw: metadataAsJson }); docInfo.entity[Constants_js_1.CONSTANTS.Documents.Metadata.KEY] = docInfo.metadataInstance = metadata; return metadata; } _getDocumentInfo(instance) { const documentInfo = this.documentsByEntity.get(instance); if (documentInfo) { return documentInfo; } let idRef; if (!this._generateEntityIdOnTheClient.tryGetIdFromInstance(instance, (_idRef) => idRef = _idRef)) { (0, index_js_1.throwError)("InvalidOperationException", "Could not find the document id for " + instance); } this._assertNoNonUniqueInstance(instance, idRef); (0, index_js_1.throwError)("InvalidArgumentException", "Document " + idRef + " doesn't exist in the session"); } _assertNoNonUniqueInstance(entity, id) { if (!id || id.at(-1) === "|" || id.at(-1) === this.conventions.identityPartsSeparator) { return; } const info = this.documentsById.getValue(id); if (!info || info.entity === entity) { return; } (0, index_js_1.throwError)("NonUniqueObjectException", "Attempted to associate a different object with id '" + id + "'."); } /** * Gets the Change Vector for the specified entity. * If the entity is transient, it will load the change vector from the store * and associate the current state of the entity with the change vector from the server. */ getChangeVectorFor(instance) { if (!instance) { (0, index_js_1.throwError)("InvalidArgumentException", "Instance cannot be null or undefined."); } const documentInfo = this._getDocumentInfo(instance); const changeVector = documentInfo.metadata[Constants_js_1.CONSTANTS.Documents.Metadata.CHANGE_VECTOR]; if (changeVector) { return changeVector.toString(); } return null; } getLastModifiedFor(instance) { if (!instance) { (0, index_js_1.throwError)("InvalidArgumentException", "Instance cannot be null or undefined."); } const documentInfo = this._getDocumentInfo(instance); const lastModified = documentInfo.metadata["@last-modified"]; return DateUtil_js_1.DateUtil.utc.parse(lastModified); } /** * Returns whether a document with the specified id is loaded in the * current session */ isLoaded(id) { return this.isLoadedOrDeleted(id); } isLoadedOrDeleted(id) { const documentInfo = this.documentsById.getValue(id); return !!(documentInfo && (documentInfo.document || documentInfo.entity)) || this.isDeleted(id) || this.includedDocumentsById.has(id); } /** * Returns whether a document with the specified id is deleted * or known to be missing */ isDeleted(id) { return this._knownMissingIds.has(id); } /** * Gets the document id. */ getDocumentId(instance) { if (!instance) { return null; } const value = this.documentsByEntity.get(instance); return value ? value.id : null; } incrementRequestCount() { if (++this._numberOfRequests > this.maxNumberOfRequestsPerSession) { (0, index_js_1.throwError)("InvalidOperationException", `The maximum number of requests (${this.maxNumberOfRequestsPerSession}) allowed for this session has been reached.` + "Raven limits the number of remote calls that a session is allowed to make as an early warning system. Sessions are expected to be short lived, and " + "Raven provides facilities like load(string[] keys) to load multiple documents at once and batch saves (call SaveChanges() only once)." + "You can increase the limit by setting DocumentConvention.MaxNumberOfRequestsPerSession or MaxNumberOfRequestsPerSession, but it is" + "advisable that you'll look into reducing the number of remote calls first, since that will speed up your application significantly and result in a" + "more responsive application."); } } checkIfAllChangeVectorsAreAlreadyIncluded(changeVectors) { if (!this.includeRevisionsByChangeVector) { return false; } for (const cv of changeVectors) { if (!this.includeRevisionsByChangeVector.has(cv)) { return false; } } return true; } checkIfRevisionByDateTimeBeforeAlreadyIncluded(id, dateTime) { if (!this.includeRevisionsIdByDateTimeBefore) { return false; } const dictionaryDateTimeToDocument = this.includeRevisionsIdByDateTimeBefore.get(id); return dictionaryDateTimeToDocument && dictionaryDateTimeToDocument.has(dateTime.getTime()); } checkIfIdAlreadyIncluded(ids, includes) { for (const id of ids) { if (this._knownMissingIds.has(id)) { continue; } // Check if document was already loaded, the check if we've received it through include let documentInfo = this.documentsById.getValue(id); if (!documentInfo) { documentInfo = this.includedDocumentsById.get(id); if (!documentInfo) { return false; } } if (!documentInfo.entity && !documentInfo.document) { return false; } if (!includes) { continue; } for (const include of includes) { let hasAll = true; IncludesUtil_js_1.IncludesUtil.include(documentInfo.document, include, (includeId) => { hasAll = hasAll && this.isLoaded(includeId); }); if (!hasAll) { return false; } } } return true; } trackEntity(entityType, idOrDocumentInfo, document, metadata, noTracking) { let id; if (TypeUtil_js_1.TypeUtil.isObject(idOrDocumentInfo)) { const info = idOrDocumentInfo; return this.trackEntity(entityType, info.id, info.document, info.metadata, this.noTracking); } else { id = idOrDocumentInfo; } // if noTracking is session-wide then we want to override the passed argument noTracking = this.noTracking || noTracking; if (!id) { return this._deserializeFromTransformer(entityType, null, document, false); } let docInfo = this.documentsById.getValue(id); if (docInfo) { // the local instance may have been changed, we adhere to the current Unit of Work // instance, and return that, ignoring anything new. if (!docInfo.entity) { docInfo.entity = this.entityToJson.convertToEntity(entityType, id, document, !noTracking); this._makeMetadataInstance(docInfo); } if (!noTracking) { this.includedDocumentsById.delete(id); this.documentsByEntity.put(docInfo.entity, docInfo); } this.onAfterConversionToEntityInvoke(id, docInfo.document, docInfo.entity); return docInfo.entity; } docInfo = this.includedDocumentsById.get(id); if (docInfo) { if (!docInfo.entity) { docInfo.entity = this.entityToJson.convertToEntity(entityType, id, document, !noTracking); this._makeMetadataInstance(docInfo); } if (!noTracking) { this.includedDocumentsById.delete(id); this.documentsById.add(docInfo); this.documentsByEntity.put(docInfo.entity, docInfo); } this.onAfterConversionToEntityInvoke(id, docInfo.document, docInfo.entity); return docInfo.entity; } const entity = this.entityToJson.convertToEntity(entityType, id, document, !noTracking); const changeVector = metadata[Constants_js_1.CONSTANTS.Documents.Metadata.CHANGE_VECTOR]; if (!changeVector) { (0, index_js_1.throwError)("InvalidOperationException", "Document " + id + " must have Change Vector."); } if (!noTracking) { const newDocumentInfo = new DocumentInfo_js_1.DocumentInfo(); newDocumentInfo.id = id; newDocumentInfo.document = document; newDocumentInfo.metadata = metadata; newDocumentInfo.entity = entity; newDocumentInfo.changeVector = changeVector; this.documentsById.add(newDocumentInfo); this.documentsByEntity.put(entity, newDocumentInfo); this._makeMetadataInstance(newDocumentInfo); } this.onAfterConversionToEntityInvoke(id, document, entity); return entity; } registerExternalLoadedIntoTheSession(info) { if (this.noTracking) { return; } const existing = this.documentsById.getValue(info.id); if (existing) { if (existing.entity === info.entity) { return; } (0, index_js_1.throwError)("InvalidOperationException", "The document " + info.id + " is already in the session with a different entity instance."); } const existingEntity = this.documentsByEntity.get(info.entity); if (existingEntity) { if (StringUtil_js_1.StringUtil.equalsIgnoreCase(existingEntity.id, info.id)) { return; } (0, index_js_1.throwError)("InvalidOperationException", "Attempted to load an entity with id " + info.id + ", but the entity instance already exists in the session with id: " + existing.id); } this.documentsByEntity.put(info.entity, info); this.documentsById.add(info); this.includedDocumentsById.delete(info.id); } _deserializeFromTransformer(clazz, id, document, trackEntity) { const entity = this.entityToJson.convertToEntity(clazz, id, document, trackEntity); this.onAfterConversionToEntityInvoke(id, document, entity); return entity; } registerIncludes(includes) { if (this.noTracking) { return; } if (!includes) { return; } for (const fieldName of Object.keys(includes)) { const fieldValue = includes[fieldName]; if (TypeUtil_js_1.TypeUtil.isNullOrUndefined(fieldValue)) { continue; } const newDocumentInfo = DocumentInfo_js_1.DocumentInfo.getNewDocumentInfo(fieldValue); if ((0, index_js_2.tryGetConflict)(newDocumentInfo.metadata)) { continue; } this.includedDocumentsById.set(newDocumentInfo.id, newDocumentInfo); } } registerRevisionIncludes(revisionIncludes) { if (this.noTracking) { return; } if (!revisionIncludes) { return; } if (!this.includeRevisionsByChangeVector) { this.includeRevisionsByChangeVector = CaseInsensitiveKeysMap_js_1.CaseInsensitiveKeysMap.create(); } if (!this.includeRevisionsIdByDateTimeBefore) { this.includeRevisionsIdByDateTimeBefore = CaseInsensitiveKeysMap_js_1.CaseInsensitiveKeysMap.create(); } for (const obj of revisionIncludes) { if (!obj) { continue; } const json = obj; const id = json.Id; const changeVector = json.ChangeVector; const beforeAsText = json.Before; const dateTime = beforeAsText ? DateUtil_js_1.DateUtil.utc.parse(beforeAsText) : null; const revision = json.Revision; this.includeRevisionsByChangeVector.set(changeVector, DocumentInfo_js_1.DocumentInfo.getNewDocumentInfo(revision)); if (dateTime && !StringUtil_js_1.StringUtil.isNullOrWhitespace(id)) { const map = new Map(); this.includeRevisionsIdByDateTimeBefore.set(id, map); const documentInfo = new DocumentInfo_js_1.DocumentInfo(); documentInfo.document = revision; map.set(dateTime.getTime(), documentInfo); } } } registerMissingIncludes(results, includes, includePaths) { if (this.noTracking) { return; } if (!includePaths || !includePaths.length) { return; } for (const result of results) { for (const include of includePaths) { if (include === Constants_js_1.CONSTANTS.Documents.Indexing.Fields.DOCUMENT_ID_FIELD_NAME) { continue; } IncludesUtil_js_1.IncludesUtil.include(result, include, id => { if (!id) { return; } if (this.isLoaded(id)) { return; } const document = includes[id]; if (document) { const metadata = document.get(Constants_js_1.CONSTANTS.Documents.Metadata.KEY); if ((0, index_js_2.tryGetConflict)(metadata)) { return; } } this.registerMissing(id); }); } } } registerMissing(idOrIds) { if (this.noTracking) { return; } if (TypeUtil_js_1.TypeUtil.isArray(idOrIds)) { for (const id of idOrIds) { this._knownMissingIds.add(id); } } else { this._knownMissingIds.add(idOrIds); } } unregisterMissing(id) { this._knownMissingIds.delete(id); } registerCounters(resultCounters, idsOrCountersToInclude, countersToInclude, gotAll) { if (Array.isArray(idsOrCountersToInclude)) { this._registerCountersWithIdsList(resultCounters, idsOrCountersToInclude, countersToInclude, gotAll); } else { this._registerCountersWithCountersToIncludeObj(resultCounters, idsOrCountersToInclude); } } _registerCountersWithIdsList(resultCounters, ids, countersToInclude, gotAll) { if (this.noTracking) { return; } if (!resultCounters || Object.keys(resultCounters).length === 0) { if (gotAll) { for (const id of ids) { this._setGotAllCountersForDocument(id); } return; } } else { this._registerCountersInternal(resultCounters, null, false, gotAll); } this._registerMissingCounters(ids, countersToInclude); } _registerCountersWithCountersToIncludeObj(resultCounters, countersToInclude) { if (this.noTracking) { return; } if (!resultCounters || Object.keys(resultCounters).length === 0) { this._setGotAllInCacheIfNeeded(countersToInclude); } else { this._registerCountersInternal(resultCounters, countersToInclude, true, false); } this._registerMissingCounters(countersToInclude); } _registerCountersInternal(resultCounters, countersToInclude, fromQueryResult, gotAll) { for (const [field, value] of Object.entries(resultCounters)) { if (!value) { continue; } let counters = []; if (fromQueryResult) { counters = countersToInclude[field]; gotAll = counters && counters.length === 0; } if (value.length === 0 && !gotAll) { const cache = this.countersByDocId.get(field); if (!cache) { continue; } for (const counter of counters) { cache.data.delete(counter); } this._countersByDocId.set(field, cache); continue; } this._registerCountersForDocument(field, gotAll, value, countersToInclude); } } _registerCountersForDocument(id, gotAll, counters, countersToInclude) { let cache = this.countersByDocId.get(id); if (!cache) { cache = { gotAll, data: CaseInsensitiveKeysMap_js_1.CaseInsensitiveKeysMap.create() }; } const deletedCounters = cache.data.size === 0 ? new Set() : (countersToInclude[id].length === 0 ? new Set(cache.data.keys()) : new Set(countersToInclude[id])); for (const counterJson of counters) { if (!counterJson) { continue; } const counterName = counterJson["counterName"]; const totalValue = counterJson["totalValue"]; if (counterName && totalValue) { cache.data.set(counterName, totalValue); deletedCounters.delete(counterName); } } if (deletedCounters.size > 0) { for (const name of deletedCounters) { cache.data.delete(name); } } cache.gotAll = gotAll; this._countersByDocId.set(id, cache); } _setGotAllInCacheIfNeeded(countersToInclude) { for (const [key, value] of Object.entries(countersToInclude)) { if (value.length > 0) { continue; } this._setGotAllCountersForDocument(key); } } _setGotAllCountersForDocument(id) { let cache = this.countersByDocId.get(id); if (!cache) { cache = { gotAll: false, data: CaseInsensitiveKeysMap_js_1.CaseInsensitiveKeysMap.create() }; } cache.gotAll = true; this._countersByDocId.set(id, cache); } _registerMissingCounters(idsOrCountersToInclude, countersToInclude) { if (Array.isArray(idsOrCountersToInclude)) { this._registerMissingCountersWithIdsList(idsOrCountersToInclude, countersToInclude); } else { this._registerMissingCountersWithCountersToIncludeObj(idsOrCountersToInclude); } } registerTimeSeries(resultTimeSeries) { if (this.noTracking || !resultTimeSeries) { return; } for (const [id, perDocTs] of Object.entries(resultTimeSeries)) { if (!perDocTs) { continue; } let cache = this.timeSeriesByDocId.get(id); if (!cache) { cache = CaseInsensitiveKeysMap_js_1.CaseInsensitiveKeysMap.create(); this.timeSeriesByDocId.set(id, cache); } if (!TypeUtil_js_1.TypeUtil.isObject(perDocTs)) { (0, index_js_1.throwError)("InvalidOperationException", "Unable to read time series range results on document: '" + id + "'."); } for (const [name, perNameTs] of Object.entries(perDocTs)) { if (!perNameTs) { continue; } if (!TypeUtil_js_1.TypeUtil.isArray(perNameTs)) { (0, index_js_1.throwError)("InvalidOperationException", "Unable to read time series range results on document: '" + id + "', time series: '" + name + "'."); } for (const range of perNameTs) { const newRange = InMemoryDocumentSessionOperations._parseTimeSeriesRangeResult(range, id, name); InMemoryDocumentSessionOperations._addToCache(cache, newRange, name); } } } } static _addToCache(cache, newRange, name) { const localRanges = cache.get(name); if (!localRanges || !localRanges.length) { // no local ranges in cache for this series cache.set(name, [newRange]); return; } if (DatesComparator_js_1.DatesComparator.compare((0, DatesComparator_js_1.leftDate)(localRanges[0].from), (0, DatesComparator_js_1.rightDate)(newRange.to)) > 0 || DatesComparator_js_1.DatesComparator.compare((0, DatesComparator_js_1.rightDate)(localRanges.at(-1).to), (0, DatesComparator_js_1.leftDate)(newRange.from)) < 0) { // the entire range [from, to] is out of cache bounds const index = DatesComparator_js_1.DatesComparator.compare((0, DatesComparator_js_1.leftDate)(localRanges[0].from), (0, DatesComparator_js_1.rightDate)(newRange.to)) > 0 ? 0 : localRanges.length; localRanges.splice(index, 0, newRange); return; } let toRangeIndex; let fromRangeIndex = -1; let rangeAlreadyInCache = false; for (toRangeIndex = 0; toRangeIndex < localRanges.length; toRangeIndex++) { if (DatesComparator_js_1.DatesComparator.compare((0, DatesComparator_js_1.leftDate)(localRanges[toRangeIndex].from), (0, DatesComparator_js_1.leftDate)(newRange.from)) <= 0) { if (DatesComparator_js_1.DatesComparator.compare((0, DatesComparator_js_1.rightDate)(localRanges[toRangeIndex].to), (0, DatesComparator_js_1.rightDate)(newRange.to)) >= 0) { rangeAlreadyInCache = true; break; } fromRangeIndex = toRangeIndex; continue; } if (DatesComparator_js_1.DatesComparator.compare((0, DatesComparator_js_1.rightDate)(localRanges[toRangeIndex].to), (0, DatesComparator_js_1.rightDate)(newRange.to)) >= 0) { break; } } if (rangeAlreadyInCache) { InMemoryDocumentSessionOperations._updateExistingRange(localRanges[toRangeIndex], newRange); return; } const mergedValues = InMemoryDocumentSessionOperations._mergeRanges(fromRangeIndex, toRangeIndex, localRanges, newRange); InMemoryDocumentSessionOperations.addToCache(name, newRange.from, newRange.to, fromRangeIndex, toRangeIndex, localRanges, cache, mergedValues); } static addToCache(timeseries, from, to, fromRangeIndex, toRangeIndex, ranges, cache, values) { if (fromRangeIndex === -1) { // didn't find a 'fromRange' => all ranges in cache start after 'from' if (toRangeIndex === ranges.length) { // the requested range [from, to] contains all the ranges that are in cache // e.g. if cache is : [[2,3], [4,5], [7, 10]] // and the requested range is : [1, 15] // after this action cache will be : [[1, 15]] const timeSeriesRangeResult = new TimeSeriesRangeResult_js_1.TimeSeriesRangeResult(); timeSeriesRangeResult.from = from; timeSeriesRangeResult.to = to; timeSeriesRangeResult.entries = values; const result = []; result.push(timeSeriesRangeResult); cache.set(timeseries, result); return; } if (DatesComparator_js_1.DatesComparator.compare((0, DatesComparator_js_1.leftDate)(ranges[toRangeIndex].from), (0, DatesComparator_js_1.rightDate)(to)) > 0) { // requested range ends before 'toRange' starts // remove all ranges that come before 'toRange' from cache // add the new range at the beginning of the list // e.g. if cache is : [[2,3], [4,5], [7,10]] // and the requested range is : [1,6] // after this action cache will be : [[1,6], [7,10]] ranges.splice(0, toRangeIndex); const timeSeriesRangeResult = new TimeSeriesRangeResult_js_1.TimeSeriesRangeResult(); timeSeriesRangeResult.from = from; timeSeriesRangeResult.to = to; timeSeriesRangeResult.entries = values; ranges.splice(0, 0, timeSeriesRangeResult); return; } // the requested range ends inside 'toRange' // merge the result from server into 'toRange' // remove all ranges that come before 'toRange' from cache // e.g. if cache is : [[2,3], [4,5], [7,10]] // and the requested range is : [1,8] // after this action cache will be : [[1,10]] ranges[toRangeIndex].from = from; ranges[toRangeIndex].entries = values; ranges.splice(0, toRangeIndex); return; } // found a 'fromRange' if (toRangeIndex === ranges.length) { // didn't find a 'toRange' => all the ranges in cache end before 'to' if (DatesComparator_js_1.DatesComparator.compare((0, DatesComparator_js_1.rightDate)(ranges[fromRangeIndex].to), (0, DatesComparator_js_1.leftDate)(from)) < 0) { // requested range starts after 'fromRange' ends, // so it needs to be placed right after it // remove all the ranges that come after 'fromRange' from cache // add the merged values as a new range at the end of the list // e.g. if cache is : [[2,3], [5,6], [7,10]] // and the requested range is : [4,12] // then 'fromRange' is : [2,3] // after this action cache will be : [[2,3], [4,12]] ranges.splice(fromRangeIndex + 1, ranges.length - fromRangeIndex - 1); const timeSeriesRangeResult = new TimeSeriesRangeResult_js_1.TimeSeriesRangeResult(); timeSeriesRangeResult.from = from; timeSeriesRangeResult.to = to; timeSeriesRangeResult.entries = values; ranges.push(timeSeriesRangeResult); return; } // the requested range starts inside 'fromRange' // merge result into 'fromRange' // remove all the ranges from cache that come after 'fromRange' // e.g. if cache is : [[2,3], [4,6], [7,10]] // and the requested range is : [5,12] // then 'fromRange' is [4,6] // after this action cache will be : [[2,3], [4,12]] ranges[fromRangeIndex].to = to; ranges[fromRangeIndex].entries = values; ranges.splice(fromRangeIndex + 1, ranges.length - fromRangeIndex - 1); return; } // found both 'fromRange' and 'toRange' // the requested range is inside cache bounds if (DatesComparator_js_1.DatesComparator.compare((0, DatesComparator_js_1.rightDate)(ranges[fromRangeIndex].to), (0, DatesComparator_js_1.leftDate)(from)) < 0) { // requested range starts after 'fromRange' ends if (DatesComparator_js_1.DatesComparator.compare((0, DatesComparator_js_1.leftDate)(ranges[toRangeIndex].from), (0, DatesComparator_js_1.rightDate)(to)) > 0) { // requested range ends before 'toRange' starts // remove all ranges in between 'fromRange' and 'toRange' // place new range in between 'fromRange' and 'toRange' // e.g. if cache is : [[2,3], [5,6], [7,8], [10,12]] // and the requested range is : [4,9] // then 'fromRange' is [2,3] and 'toRange' is [10,12] // after this action cache will be : [[2,3], [4,9], [10,12]] ranges.splice(fromRangeIndex + 1, toRangeIndex - fromRangeIndex - 1); const timeSeriesRangeResult = new TimeSeriesRangeResult_js_1.TimeSeriesRangeResult(); timeSeriesRangeResult.from = from; timeSeriesRangeResult.to = to; timeSeriesRangeResult.entries = values; ranges.splice(fromRangeIndex + 1, 0, timeSeriesRangeResult); return; } // requested range ends inside 'toRange' // merge the new range into 'toRange' // remove all ranges in between 'fromRange' and 'toRange' // e.g. if cache is : [[2,3], [5,6], [7,10]] // and the requested range is : [4,9] // then 'fromRange' is [2,3] and 'toRange' is [7,10] // after this action cache will be : [[2,3], [4,10]] ranges.splice(fromRangeIndex + 1, toRangeIndex - fromRangeIndex - 1); ranges[toRangeIndex].from = from; ranges[toRangeIndex].entries = values; return; } // the requested range starts inside 'fromRange' if (DatesComparator_js_1.DatesComparator.compare((0, DatesComparator_js_1.leftDate)(ranges[toRangeIndex].from), (0, DatesComparator_js_1.rightDate)(to)) > 0) { // requested range ends before 'toRange' starts // remove all ranges in between 'fromRange' and 'toRange' // merge new range into 'fromRange' // e.g. if cache is : [[2,4], [5,6], [8,10]] // and the requested range is : [3,7] // then 'fromRange' is [2,4] and 'toRange' is [8,10] // after this action cache will be : [[2,7], [8,10]] ranges[fromRangeIndex].to = to; ranges[fromRangeIndex].entries = values; ranges.splice(fromRangeIndex + 1, toRangeIndex - fromRangeIndex - 1); return; } // the requested range starts inside 'fromRange' // and ends inside 'toRange' // merge all ranges in between 'fromRange' and 'toRange' // into a single range [fromRange.From, toRange.To] // e.g. if cache is : [[2,4], [5,6], [8,10]] // and the requested range is : [3,9] // then 'fromRange' is [2,4] and 'toRange' is [8,10] // after this action cache will be : [[2,10]] ranges[fromRangeIndex].to = ranges[toRangeIndex].to; ranges[fromRangeIndex].entries = values; ranges.splice(fromRangeIndex + 1, toRangeIndex - fromRangeIndex); } static _parseTimeSeriesRangeResult(json, id, databaseName) { return (0, GetTimeSeriesOperation_js_1.reviveTimeSeriesRangeResult)(json); } static _mergeRanges(fromRangeIndex, toRangeIndex, localRanges, newRange) { const mergedValues = []; if (fromRangeIndex !== -1 && localRanges[fromRangeIndex].to.getTime() >= newRange.from.getTime()) { for (const val of localRanges[fromRangeIndex].entries) { if (val.timestamp.getTime() >= newRange.from.getTime()) { break; } mergedValues.push(val); } } mergedValues.push(...newRange.entries); if (toRangeIndex < localRanges.length && DatesComparator_js_1.DatesComparator.compare((0, DatesComparator_js_1.leftDate)(localRanges[toRangeIndex].from), (0, DatesComparator_js_1.rightDate)(newRange.to)) <= 0) { for (const val of localRanges[toRangeIndex].entries) { if (val.timestamp.getTime() <= newRange.to.getTime()) { continue; } mergedValues.push(val); } } return mergedValues; } static _updateExistingRange(localRange, newRange) { const newValues = []; let index; for (index = 0; index < localRange.entries.length; index++) { if (localRange.entries[index].timestamp.getTime() >= newRange.from.getTime()) { break; } newValues.push(localRange.entries[index]); } newValues.push(...newRange.entries); for (const item of localRange.entries) { if (item.timestamp.getTime() <= newRange.to.getTime()) { continue; } newValues.push(item); } localRange.entries = newValues; } _registerMissingCountersWithCountersToIncludeObj(countersToInclude) { if (!countersToInclude) { return; } for (const [key, value] of Object.entries(countersToInclude)) { let cache = this.countersByDocId.get(key); if (!cache) { cache = { gotAll: false, data: CaseInsensitiveKeysMap_js_1.CaseInsensitiveKeysMap.create() }; this.countersByDocId.set(key, cache); } for (const counter of value) { if (cache.data.has(counter)) { continue; } cache.data.set(counter, null); } } } _registerMissingCountersWithIdsList(ids, countersToInclude) { if (!countersToInclude) { return; } for (const counter of countersToInclude) { for (const id of ids) { let cache = this.countersByDocId.get(id); if (!cache) { cache = { gotAll: false, data: CaseInsensitiveKeysMap_js_1.CaseInsensitiveKeysMap.create() }; this.countersByDocId.set(id, cache); } if (cache.data.has(counter)) { continue; } cache.data.set(counter, null); } } } store(entity, id, docTypeOrOptions) { let documentType = null; let options = {}; // figure out third arg if (TypeUtil_js_1.TypeUtil.isDocumentType(docTypeOrOptions)) { documentType = docTypeOrOptions; } else if (TypeUtil_js_1.TypeUtil.isObject(docTypeOrOptions)) { options = docTypeOrOptions; } const changeVector = options.changeVector; documentType = documentType || options.documentType; this.conventions.tryRegisterJsType(documentType); if (entity.constructor !== Object) { this.conventions.tryRegisterJsType(entity.constructor); } let forceConcurrencyCheck; if (!TypeUtil_js_1.TypeUtil.isUndefined(changeVector)) { forceConcurrencyCheck = changeVector === null ? "Disabled" : "Forced"; } else if (!TypeUtil_js_1.TypeUtil.isNullOrUndefined(id)) { forceConcurrencyCheck = "Auto"; } else { const hasId = this._generateEntityIdOnTheClient.tryGetIdFromInstance(entity); forceConcurrencyCheck = !hasId ? "Forced" : "Auto"; } return this._storeInternal(entity, changeVector, id, forceConcurrencyCheck, documentType); } _generateDocumentKeysOnStore = true; async _storeInternal(entity, changeVector, id, forceConcurrencyCheck, documentType) { if (this.noTracking) { (0, index_js_1.throwError)("InvalidOperationException", "Cannot store entity. Entity tracking is disabled in this session."); } if (!entity) { (0, index_js_1.throwError)("InvalidArgumentException", "Entity cannot be null or undefined."); } const value = this.documentsByEntity.get(entity); if (value) { if (id && !StringUtil_js_1.StringUtil.equalsIgnoreCase(value.id, id)) { (0, index_js_1.throwError)("InvalidOperationException", "Cannot store the same entity (id: " + value.id + ") with different id (" + id + ")"); } value.changeVector = changeVector || value.changeVector; value.concurrencyCheckMode = forceConcurrencyCheck; return; } if (!id) { if (this._generateDocumentKeysOnStore) { id = await this._generateEntityIdOnTheClient.generateDocumentKeyForStorage(entity); } else { this._rememberEntityForDocumentIdGeneration(entity); } } else { this._generateEntityIdOnTheClient.trySetIdentity(entity, id); } const cmdKey = IdTypeAndName_js_1.IdTypeAndName.keyFor(id, "ClientAnyCommand", null); if (this.deferredCommandsMap.has(cmdKey)) { (0, index_js_1.throwError)("InvalidOperationException", "Can't store document, there is a deferred command registered " + "for this document in the session. Document id: " + id); } if (this.deletedEntities.contains(entity)) { (0, index_js_1.throwError)("InvalidOperationException", "Can't store object, it was already deleted in this session. Document id: " + id); } // we make the check here even if we just generated the ID // users can override the ID generation behavior, and we need // to detect if they generate duplicates. this._assertNoNonUniqueInstance(entity, id); const conventions = this._requestExecutor.conventions; const typeDesc = conventions.getJsTypeByDocumentType(documentType); const collectionName = documentType ? conventions.getCollectionNameForType(typeDesc) : conventions.getCollectionNameForEntity(entity); const metadata = {}; if (collectionName) { metadata[Constants_js_1.CONSTANTS.Documents.Metadata.COLLECTION] = collectionName; } const entityType = documentType ? conventions.getJsTypeByDocumentType(documentType) : conventions.getTypeDescriptorByEntity(entity); const jsType = conventions.getJsTypeName(entityType); if (jsType) { metadata[Constants_js_1.CONSTANTS.Documents.Metadata.RAVEN_JS_TYPE] = jsType; } if (id) { this._knownMissingIds.delete(id); } this._storeEntityInUnitOfWork(id, entity, changeVector, metadata, forceConcurrencyCheck, documentType); } _storeEntityInUnitOfWork(id, entity, changeVector, metadata, forceConcurrencyCheck, documentType) { if (id) { this._knownMissingIds.delete(id); } if (this.transactionMode === "ClusterWide") { if (!changeVector) { let changeVectorInner; if (this.clusterSession.tryGetMissingAtomicGuardFor(id, r => changeVectorInner = r)) { changeVector = changeVectorInner; } } } const documentInfo = new DocumentInfo_js_1.DocumentInfo(); documentInfo.id = id; documentInfo.metadata = metadata; documentInfo.changeVector = changeVector; documentInfo.concurrencyCheckMode = forceConcurrencyCheck; documentInfo.entity = entity; documentInfo.newDocument = true; documentInfo.document = null; this.documentsByEntity.put(entity, documentInfo); if (id) { this.documentsById.add(documentInfo); } } _rememberEntityForDocumentIdGeneration(entity) { (0, index_js_1.throwError)("NotImplementedException", "You cannot set GenerateDocumentIdsOnStore to false" + " without implementing RememberEntityForDocumentIdGeneration"); } prepareForSaveChanges() { const result = this._newSaveChangesData(); const deferredCommandsCount = this._deferredCommands.length; this._prepareForEntitiesDeletion(result, null); this._prepareForEntitiesPuts(result); this._prepareForCreatingRevisionsFromIds(result); this._prepareCompareExchangeEntities(result); if (this._deferredCommands.length > deferredCommandsCount) { // this allow OnBeforeStore to call Defer during the call to include // additional values during the same SaveChanges call for (let i = deferredCommandsCount; i < this._deferredCommands.length; i++) { result.deferredCommands.push(this._deferredCommands[i]); } for (const item of this.deferredCommandsMap.entries()) { result.deferredCommandsMap.set(item[0], item[1]); } } for (const deferredCommand of result.deferredCommands) { if (deferredCommand.onBeforeSaveChanges) { deferredCommand.onBeforeSaveChanges(this); } } return result; } validateClusterTransaction(result) { if (this._transactionMode !== "ClusterWide") { return; } if (this.useOptimisticConcurrency) { (0, index_js_1.throwError)("InvalidOperationException", "useOptimisticConcurrency is not supported with TransactionMode set to " + "ClusterWide"); } for (const commandData of result.sessionCommands) { switch (commandData.type) { case "PUT": case "DELETE": { if (commandData