ravendb
Version:
RavenDB client for Node.js
1,088 lines • 82.5 kB
JavaScript
"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