UNPKG

pebblebed

Version:

Simplified interactions with Google Datastore for NodeJS

272 lines (230 loc) 9.32 kB
import DatastoreOperation from "./DatastoreOperation"; import PebblebedModel from "../PebblebedModel"; import Core from "../Core"; import { isNumber } from "../utility/BasicUtils"; import buildDataFromSchema from "../utility/buildDataFromSchema"; import extractSavedIds from "../utility/extractSavedIds"; import replaceIncompleteWithAllocatedIds from "../utility/replaceIncompleteWithAllocatedIds"; import { CreateMessage, throwError, warn } from "../Messaging"; import { EDebugPointId, IPebblebedSaveEntity } from ".."; import serializeJsonProperties from "../utility/serializeJsonProperties"; import { debugPoint } from "../debugging/DebugUtils"; import { convertSaveEntitiesToRegular } from "../utility/convertSaveEntitesToRegular"; import { convertDatastoreDataToRegularData } from "../utility/convertDatastoreDataToRegular"; import { Key, PathType } from "@google-cloud/datastore"; interface IOSaveRunResponse<T> { generatedIds: (string | null)[], savedEntities?: T[] } /*export interface IDatastoreSaveReturnEntities<T> extends DatastoreOperation<T> { useTransaction( transaction: any, options?: { allocateIdsNow?: boolean, } ): IDatastoreSaveRegular<T>; run(): Promise<{ generatedIds: (string|null)[], savedEntities?: T[] }>; } export interface IDatastoreSaveRegular<T> extends DatastoreOperation<T> { useTransaction( transaction: any, options?: { allocateIdsNow?: boolean, } ): IDatastoreSaveRegular<T>; returnSavedEntities(): IDatastoreSaveReturnEntities<T>; run(): Promise<{ generatedIds: (string|null)[] }>; }*/ export default class DatastoreSave<T, R extends IOSaveRunResponse<T> = { generatedIds: (string | null)[] }> extends DatastoreOperation<T> { private dataObjects: any[]; private ignoreAnc = false; private generate = false; private transAllocateIds = false; private returnSaved = false; constructor(model: PebblebedModel<T>, data: T | T[]) { super(model); this.useCache = this.useCache ? Core.Instance.cacheDefaults.onSave : false; if (Array.isArray(data)) { this.dataObjects = data; } else { this.dataObjects = [data]; } } public useTransaction( transaction: any, options = { allocateIdsNow: false, } ) { super.useTransaction(transaction); this.transAllocateIds = options.allocateIdsNow; return this; } public generateUnsetIds() { this.generate = true; return this; } public ignoreDetectedAncestors() { this.ignoreAnc = true; return this; } public returnSavedEntities(): DatastoreSave<T, { generatedIds: (string | null)[], savedEntities: T[] }> { this.returnSaved = true; return this as DatastoreSave<T, { generatedIds: (string | null)[], savedEntities: T[] }>; } public async run(): Promise<R> { const baseKey = this.getBaseKey(); const cachingEnabled = this.useCache && Core.Instance.cacheStore != null && Core.Instance.cacheStore.cacheOnSave; const cachableEntitySourceData: any[] = []; const entities: IPebblebedSaveEntity<T>[] = this.dataObjects.map((data): IPebblebedSaveEntity<T> => { let setAncestors = baseKey; let id: PathType | null = null; const entityKey: Key = data[Core.Instance.dsModule.KEY]; if (this.hasIdProperty && data[this.idProperty!] != null) { switch (this.idType) { case "string": { if (typeof data[this.idProperty!] === "string") { if (data[this.idProperty!].length === 0) { throwError(CreateMessage.OPERATION_STRING_ID_EMPTY(this.model, "SAVE")); } id = data[this.idProperty!]; } break; } case "int": { if (isNumber(data[this.idProperty!])) { id = Core.Instance.dsModule.int(data[this.idProperty!]); } break; } } if (id == null) { throwError(CreateMessage.OPERATION_DATA_ID_TYPE_ERROR(this.model, "SAVE", data[this.idProperty!])); } } else { if (entityKey && entityKey.path && entityKey.path.length > 0 && entityKey.path.length % 2 === 0) { if (entityKey.hasOwnProperty("id")) { id = Core.Instance.dsModule.int(entityKey.id!); } else { id = entityKey.name!; } } else { if (this.hasIdProperty && (this.idType === "string" || !this.generate)) { throwError(CreateMessage.OPERATION_MISSING_ID_ERROR(this.model, "SAVE")); } } } if (!this.ignoreAnc && entityKey && entityKey.parent) { if (setAncestors.length === 0) { setAncestors = entityKey.parent.path; } else { const prevAncestors = entityKey.parent.path.toString(); const nextAncestors = setAncestors.toString(); if (prevAncestors !== nextAncestors) { warn( CreateMessage.OPERATION_CHANGED_ANCESTORS_WARNING( this.model, "SAVE", prevAncestors, nextAncestors ) ); } } } if (this.runValidation && this.model.entityPebbleSchema != null) { // const validation = Core.Joi.validate(data, this.model.entityPebbleSchema.__getJoiSchema()); const validation = this.model.entityPebbleSchema.__getJoiSchema().validate(data); if (validation.error != null) { throwError(`On save entity of kind -> ${this.model.entityKind} : ${validation.error.message}`); } } const key: Key = id ? this.createFullKey(setAncestors.concat([this.kind, id]), entityKey) : this.createFullKey(setAncestors.concat([this.kind]), entityKey); const generated = (id == null); if (entityKey) { delete data[Core.Instance.dsModule.KEY]; } const {dataObject, excludeFromIndexes} = buildDataFromSchema(data, this.schema, this.kind); if (cachingEnabled) { cachableEntitySourceData.push({ key, data: convertDatastoreDataToRegularData(dataObject, this.schema), generated }) } return { key, excludeFromIndexes, generated, data: dataObject as T, }; }); debugPoint(EDebugPointId.ON_SAVE_BUILT_ENTITIES, entities); if (this.transaction) { if (this.transAllocateIds) { const {newEntities, ids} = await replaceIncompleteWithAllocatedIds<T>(entities, this.transaction); this.transaction.save(newEntities); return { generatedIds: ids, ...this.returnSaved && { savedEntities: convertSaveEntitiesToRegular(newEntities, this.idProperty, this.idType, this.schema), }, } as R; } this.transaction.save(entities); return { get generatedIds() { warn(CreateMessage.ACCESS_TRANSACTION_GENERATED_IDS_ERROR); return [null]; }, ...this.returnSaved && { savedEntities: convertSaveEntitiesToRegular(entities, this.idProperty, this.idType, this.schema), }, } as R; } return Core.Instance.dsModule.save(entities).then((data): R => { const saveResponse = extractSavedIds(data)[0]; if (cachingEnabled && cachableEntitySourceData.length > 0) { const cacheEntities: any[] = []; for (let i = 0; i < cachableEntitySourceData.length; i += 1) { cacheEntities.push({ [Core.Instance.dsModule.KEY]: cachableEntitySourceData[i].key, ...cachableEntitySourceData[i].data, }); // Get the generated IDs from the save response (it returns the generated IDs on save) if (cachableEntitySourceData[i].generated) { cacheEntities[i][Core.Instance.dsModule.KEY].path.push(saveResponse.generatedIds[i]); if (this.idType === "int") { cacheEntities[i][Core.Instance.dsModule.KEY].id = saveResponse.generatedIds[i]; } else { cacheEntities[i][Core.Instance.dsModule.KEY].name = saveResponse.generatedIds[i]; } } } if (cacheEntities.length > 0) { serializeJsonProperties(cacheEntities, this.schema); Core.Instance.cacheStore!.setEntitiesAfterLoadOrSave(cacheEntities, this.cachingTimeSeconds); } } if (this.returnSaved) { return { generatedIds: saveResponse.generatedIds, savedEntities: convertSaveEntitiesToRegular(entities.map((e, i) => { if (e.generated) { e.key.path.push(saveResponse.generatedIds[i]); if (this.idType === "int") { e.key.id = saveResponse.generatedIds[i]; } else { e.key.name = saveResponse.generatedIds[i]; } } return e; }), this.idProperty, this.idType, this.schema), } as R } return saveResponse as R; }); } }