import * as Datastore from "@google-cloud/datastore" ; import * as DatastoreEntity from "@google-cloud/datastore/build/src/entity" ; import { BaseEntity } from "./BaseEntity" ; import { MAX_ENTITIES } from "./constants" ; import { TsDatastoreOrmError } from "./errors/TsDatastoreOrmError" ; import { IncrementHelper } from "./helpers/IncrementHelper" ; import { IndexResaveHelper } from "./helpers/IndexResaveHelper" ; import { Query } from "./queries/Query" ; import { SelectKeyQuery } from "./queries/SelectKeyQuery" ; import { Session } from "./transactions/Session" ; import { tsDatastoreOrm } from "./tsDatastoreOrm" ; import { ICreateValues , IEntityKeyType , IIncrementHelperOptions , IRepositoryParams , IStrongTypeQueryOptions , IWeakTypeQueryOptions , } from "./types" ; import { updateStack } from "./utils" ; export class Repository < T extends typeof BaseEntity > { public readonly datastore : Datastore . Datastore ; public readonly classObject : T ; public readonly namespace : string | undefined ; public readonly kind : string ; constructor ( options ) { this . datastore = options . datastore ; this . classObject = options . classObject ; this . namespace = options . namespace ; this . kind = options . kind ; } public create ( values = { } ) { const entity = new this . classObject ( ) as InstanceType < T > ; ( entity as any ) . _kind = this . kind ; ( entity as any ) . _namespace = this . namespace ; Object . assign ( entity , values ) ; return entity ; } public async findOne ( id ) { const key = tsDatastoreOrm . normalizeAndValidateKey ( id , this . namespace , this . kind ) ; const friendlyErrorStack = tsDatastoreOrm . getFriendlyErrorStack ( ) ; try { const [ response ] = await this . datastore . get ( key ) ; if ( response ) { return await tsDatastoreOrm . loadEntity ( this . classObject , response ) ; } } catch ( err ) { throw Object . assign ( err , friendlyErrorStack && { stack : updateStack ( friendlyErrorStack , err ) } ) ; } } public async findOneWithSession ( id , session ) { const key = tsDatastoreOrm . normalizeAndValidateKey ( id , this . namespace , this . kind ) ; return await session . findOne ( this . classObject , key ) ; } public async findMany ( ids ) { const keys = tsDatastoreOrm . normalizeAndValidateKeys ( ids , this . namespace , this . kind ) ; const friendlyErrorStack = tsDatastoreOrm . getFriendlyErrorStack ( ) ; try { const entities = [ ] ; const [ response ] = await this . datastore . get ( keys ) ; for ( const data of response ) { const entity = await tsDatastoreOrm . loadEntity ( this . classObject , data ) ; entities . push ( entity ) ; } return entities ; } catch ( err ) { throw Object . assign ( err , friendlyErrorStack && { stack : updateStack ( friendlyErrorStack , err ) } ) ; } } public async findManyWithSessions ( ids , session ) { const keys = tsDatastoreOrm . normalizeAndValidateKeys ( ids , this . namespace , this . kind ) ; return await session . findMany ( this . classObject , keys ) ; } public query ( options ? ) ; public query ( options ? ) ; public query ( options ? ) { const query = this . datastore . createQuery ( ) ; query . namespace = this . namespace ; query . kinds = [ this . kind ] ; return new Query ( { datastore : this . datastore , classObject : this . classObject , namespace : this . namespace , kind : this . kind , query , } ) ; } public queryWithSession ( session , options ? ) ; public queryWithSession ( session , options ? ) ; public queryWithSession ( session , options ? ) { const query = session . transaction . createQuery ( ) ; query . namespace = this . namespace ; query . kinds = [ this . kind ] ; return new Query ( { datastore : this . datastore , classObject : this . classObject , namespace : this . namespace , kind : this . kind , query , } ) ; } public selectKeyQuery ( options ? ) ; public selectKeyQuery ( options ? ) ; public selectKeyQuery ( options ? ) { const query = this . datastore . createQuery ( ) ; query . namespace = this . namespace ; query . kinds = [ this . kind ] ; return new SelectKeyQuery ( { datastore : this . datastore , namespace : this . namespace , kind : this . kind , query , } ) ; } public selectKeyQueryWithSession ( session , options ? ) ; public selectKeyQueryWithSession ( session , options ? ) ; public selectKeyQueryWithSession ( session , options ? ) { const query = session . transaction . createQuery ( ) ; query . namespace = this . namespace ; query . kinds = [ this . kind ] ; return new SelectKeyQuery ( { datastore : this . datastore , namespace : this . namespace , kind : this . kind , query , } ) ; } public async allocateIds ( total ) { const friendlyErrorStack = tsDatastoreOrm . getFriendlyErrorStack ( ) ; try { const key = this . datastore . key ( { namespace : this . namespace , path : [ this . kind ] } ) ; const [ ids ] = await this . datastore . allocateIds ( key , total ) ; return ids . map ( x => Number ( x . id ) ) ; } catch ( err ) { throw Object . assign ( err , friendlyErrorStack && { stack : updateStack ( friendlyErrorStack , err ) } ) ; } } public async allocateIdsWithSession ( total , session ) { const key = this . datastore . key ( { namespace : this . namespace , path : [ this . kind ] } ) ; return await session . allocateIds ( key , total ) ; } public async insert < P extends InstanceType < T > | Array < InstanceType < T > > > ( entities ) { return await this . _internalInsert ( entities , false ) ; } public insertWithSession < P extends InstanceType < T > | Array < InstanceType < T > > > ( entities , session ) { tsDatastoreOrm . validateEntity ( entities , this . namespace , this . kind , false ) ; session . insert ( entities ) ; } public async upsert < P extends InstanceType < T > | Array < InstanceType < T > > > ( entities ) { return await this . _internalInsert ( entities , true ) ; } public upsertWithSession < P extends InstanceType < T > | Array < InstanceType < T > > > ( entities , session ) { tsDatastoreOrm . validateEntity ( entities , this . namespace , this . kind , false ) ; session . upsert ( entities ) ; } public async update < P extends InstanceType < T > | Array < InstanceType < T > > > ( entities ) { tsDatastoreOrm . validateEntity ( entities , this . namespace , this . kind ) ; tsDatastoreOrm . runHookOfBeforeUpdate ( entities ) ; const updateDataList = [ ] ; for ( const entity of Array . isArray ( entities ) ? entities : [ entities ] ) { const { updateData } = tsDatastoreOrm . getUpdateData ( entity ) ; updateDataList . push ( updateData ) ; } const friendlyErrorStack = tsDatastoreOrm . getFriendlyErrorStack ( ) ; try { const [ updateResult ] = await this . datastore . update ( updateDataList ) ; } catch ( err ) { throw Object . assign ( err , friendlyErrorStack && { stack : updateStack ( friendlyErrorStack , err ) } ) ; } return entities ; } public updateWithSession < P extends InstanceType < T > | Array < InstanceType < T > > > ( entities , session ) { tsDatastoreOrm . validateEntity ( entities , this . namespace , this . kind ) ; session . update ( entities ) ; } public async delete < P extends IEntityKeyType < T > | Array < IEntityKeyType < T > > > ( entities ) { const keys = [ ] ; const newEntities = Array . isArray ( entities ) ? entities : [ entities ] ; for ( const entity of newEntities ) { const key = tsDatastoreOrm . normalizeAndValidateKey ( entity , this . namespace , this . kind ) ; keys . push ( key ) ; } for ( const entity of newEntities ) { if ( entity instanceof BaseEntity ) { tsDatastoreOrm . runHookOfBeforeDelete ( entity ) ; } } const friendlyErrorStack = tsDatastoreOrm . getFriendlyErrorStack ( ) ; try { const [ deleteResult ] = await this . datastore . delete ( keys ) ; return entities ; } catch ( err ) { throw Object . assign ( err , friendlyErrorStack && { stack : updateStack ( friendlyErrorStack , err ) } ) ; } } public deleteWithSession < P extends IEntityKeyType < T > | Array < IEntityKeyType < T > > > ( entities , session ) { const newEntities = Array . isArray ( entities ) ? entities : [ entities ] ; for ( const entity of newEntities ) { tsDatastoreOrm . normalizeAndValidateKey ( entity , this . namespace , this . kind ) ; } session . delete ( entities ) ; } public async truncate ( ) { const iterator = this . selectKeyQuery ( ) . limit ( MAX_ENTITIES ) . getAsyncIterator ( ) ; let total = 0 ; for await ( const keys of iterator ) { await this . delete ( keys ) ; total += keys . length ; } return total ; } public async getUrl ( ) { const namespace = this . namespace ? this . namespace : "__$DEFAULT$__/query/kind" ; const projectId = await this . datastore . auth . getProjectId ( ) ; return `https://console.cloud.google.com/datastore/entities;kind=${ this . kind };ns=${ namespace }/query/kind?project=${ projectId }` ; } public getIndexResaveHelper ( ) { return new IndexResaveHelper ( { classObject : this . classObject , namespace : this . namespace , kind : this . kind , datastore : this . datastore , } ) ; } public getIncrementHelper ( options = { } ) { return new IncrementHelper ( { classObject : this . classObject , namespace : this . namespace , kind : this . kind , datastore : this . datastore , maxRetry : options . maxRetry || 0 , retryDelay : options . retryDelay || 0 , } ) ; } private async _internalInsert < P extends InstanceType < T > | Array < InstanceType < T > > > ( entities , isUpsert ) { tsDatastoreOrm . validateEntity ( entities , this . namespace , this . kind , false ) ; const insertDataList = [ ] ; const generateEntities = new Map ( ) ; if ( isUpsert ) { tsDatastoreOrm . runHookOfBeforeUpsert ( entities ) ; } else { tsDatastoreOrm . runHookOfBeforeInsert ( entities ) ; } for ( const entity of Array . isArray ( entities ) ? entities : [ entities ] ) { const { isGenerateId , insertData } = tsDatastoreOrm . getInsertData ( entity ) ; if ( isGenerateId ) { if ( generateEntities . has ( entity ) ) { throw new TsDatastoreOrmError ( `You cannot insert the same entity.` ) ; } generateEntities . set ( entity , insertData . key ) ; } insertDataList . push ( insertData ) ; } const friendlyErrorStack = tsDatastoreOrm . getFriendlyErrorStack ( ) ; try { if ( isUpsert ) { const [ response ] = await this . datastore . upsert ( insertDataList ) ; } else { const [ response ] = await this . datastore . insert ( insertDataList ) ; } for ( const [ entity , key ] of generateEntities . entries ( ) ) { entity . _id = Number ( key . id ) ; } return entities ; } catch ( err ) { throw Object . assign ( err , friendlyErrorStack && { stack : updateStack ( friendlyErrorStack , err ) } ) ; } } }