import Boom from '@hapi/boom' ; import type { ISavedObjectTypeRegistry , SavedObject , SavedObjectsBaseOptions , SavedObjectsBulkCreateObject , SavedObjectsBulkGetObject , SavedObjectsBulkResolveObject , SavedObjectsBulkUpdateObject , SavedObjectsCheckConflictsObject , SavedObjectsClientContract , SavedObjectsClosePointInTimeOptions , SavedObjectsCollectMultiNamespaceReferencesObject , SavedObjectsCollectMultiNamespaceReferencesOptions , SavedObjectsCollectMultiNamespaceReferencesResponse , SavedObjectsCreateOptions , SavedObjectsCreatePointInTimeFinderDependencies , SavedObjectsCreatePointInTimeFinderOptions , SavedObjectsFindOptions , SavedObjectsOpenPointInTimeOptions , SavedObjectsRemoveReferencesToOptions , SavedObjectsUpdateObjectsSpacesObject , SavedObjectsUpdateObjectsSpacesOptions , SavedObjectsUpdateOptions , } from 'src/core/server' ; import { SavedObjectsErrorHelpers , SavedObjectsUtils } from '../../../../../src/core/server' ; import { ALL_SPACES_ID } from '../../common/constants' ; import { spaceIdToNamespace } from '../lib/utils/namespace' ; import type { ISpacesClient } from '../spaces_client' ; import type { SpacesServiceStart } from '../spaces_service/spaces_service' ; interface Left < L > { tag : 'Left' ; value : L ; } interface Right < R > { tag : 'Right' ; value : R ; } type Either < L = unknown , R = L > = Left < L > | Right < R > ; const isLeft = < L , R > ( either ) : either is Left < L > => either . tag === 'Left' ; interface SpacesSavedObjectsClientOptions { baseClient : SavedObjectsClientContract ; request : any ; getSpacesService : ( ) => SpacesServiceStart ; typeRegistry : ISavedObjectTypeRegistry ; } const coerceToArray = ( param ) => { if ( Array . isArray ( param ) ) { return param ; } return [ param ] ; } ; const throwErrorIfNamespaceSpecified = ( options ) => { if ( options . namespace ) { throw new Error ( 'Spaces currently determines the namespaces' ) ; } } ; export class SpacesSavedObjectsClient implements SavedObjectsClientContract { private readonly client : SavedObjectsClientContract ; private readonly typeRegistry : ISavedObjectTypeRegistry ; private readonly spaceId : string ; private readonly types : string [ ] ; private readonly spacesClient : ISpacesClient ; public readonly errors : SavedObjectsClientContract [ 'errors' ] ; constructor ( options ) { const { baseClient , request , getSpacesService , typeRegistry } = options ; const spacesService = getSpacesService ( ) ; this . client = baseClient ; this . typeRegistry = typeRegistry ; this . spacesClient = spacesService . createSpacesClient ( request ) ; this . spaceId = spacesService . getSpaceId ( request ) ; this . types = typeRegistry . getAllTypes ( ) . map ( ( t ) => t . name ) ; this . errors = baseClient . errors ; } async checkConflicts ( objects = [ ] , options = { } ) { throwErrorIfNamespaceSpecified ( options ) ; return await this . client . checkConflicts ( objects , { ... options , namespace : spaceIdToNamespace ( this . spaceId ) , } ) ; } async create < T = unknown > ( type , attributes = { } as T , options = { } ) { throwErrorIfNamespaceSpecified ( options ) ; return await this . client . create < T > ( type , attributes , { ... options , namespace : spaceIdToNamespace ( this . spaceId ) , } ) ; } async bulkCreate < T = unknown > ( objects , options = { } ) { throwErrorIfNamespaceSpecified ( options ) ; return await this . client . bulkCreate ( objects , { ... options , namespace : spaceIdToNamespace ( this . spaceId ) , } ) ; } async delete ( type , id , options = { } ) { throwErrorIfNamespaceSpecified ( options ) ; return await this . client . delete ( type , id , { ... options , namespace : spaceIdToNamespace ( this . spaceId ) , } ) ; } async find < T = unknown , A = unknown > ( options ) { let namespaces ; try { namespaces = await this . getSearchableSpaces ( options . namespaces ) ; } catch ( err ) { if ( Boom . isBoom ( err ) && err . output . payload . statusCode === 403 ) { return SavedObjectsUtils . createEmptyFindResponse < T , A > ( options ) ; } throw err ; } if ( namespaces . length === 0 ) { return SavedObjectsUtils . createEmptyFindResponse < T , A > ( options ) ; } return await this . client . find < T , A > ( { ... options , type : ( options . type ? coerceToArray ( options . type ) : this . types ) . filter ( ( type ) => type !== 'space' ) , namespaces , } ) ; } async bulkGet < T = unknown > ( objects = [ ] , options = { } ) { throwErrorIfNamespaceSpecified ( options ) ; let availableSpacesPromise ; const getAvailableSpaces = async ( ) => { if ( ! availableSpacesPromise ) { availableSpacesPromise = this . getSearchableSpaces ( [ ALL_SPACES_ID ] ) . catch ( ( err ) => { if ( Boom . isBoom ( err ) && err . output . payload . statusCode === 403 ) { return [ ] ; } else { throw err ; } } ) ; } return availableSpacesPromise ; } ; const expectedResults = await Promise . all ( objects . map < Promise < Either < SavedObjectsBulkGetObject > > > ( async ( object ) => { const { namespaces , type } = object ; if ( namespaces ?. includes ( ALL_SPACES_ID ) ) { const tag = ! this . typeRegistry . isNamespaceAgnostic ( type ) && ! this . typeRegistry . isShareable ( type ) ? 'Left' : 'Right' ; return { tag , value : { ... object , namespaces : await getAvailableSpaces ( ) } } ; } return { tag : 'Right' , value : object } ; } ) ) ; const objectsToGet = expectedResults . map ( ( { value } ) => value ) ; const { saved_objects : responseObjects } = objectsToGet . length ? await this . client . bulkGet < T > ( objectsToGet , { ... options , namespace : spaceIdToNamespace ( this . spaceId ) , } ) : { saved_objects : [ ] } ; return { saved_objects : expectedResults . map ( ( expectedResult , i ) => { const actualResult = responseObjects [ i ] ; if ( isLeft ( expectedResult ) ) { const { type , id } = expectedResult . value ; return { type , id , error : SavedObjectsErrorHelpers . createBadRequestError ( '"namespaces" can only specify a single space when used with space-isolated types' ) . output . payload , } as unknown as SavedObject < T > ; } return actualResult ; } ) , } ; } async get < T = unknown > ( type , id , options = { } ) { throwErrorIfNamespaceSpecified ( options ) ; return await this . client . get < T > ( type , id , { ... options , namespace : spaceIdToNamespace ( this . spaceId ) , } ) ; } async bulkResolve < T = unknown > ( objects , options = { } ) { throwErrorIfNamespaceSpecified ( options ) ; return await this . client . bulkResolve < T > ( objects , { ... options , namespace : spaceIdToNamespace ( this . spaceId ) , } ) ; } async resolve < T = unknown > ( type , id , options = { } ) { throwErrorIfNamespaceSpecified ( options ) ; return await this . client . resolve < T > ( type , id , { ... options , namespace : spaceIdToNamespace ( this . spaceId ) , } ) ; } async update < T = unknown > ( type , id , attributes , options = { } ) { throwErrorIfNamespaceSpecified ( options ) ; return await this . client . update ( type , id , attributes , { ... options , namespace : spaceIdToNamespace ( this . spaceId ) , } ) ; } async bulkUpdate < T = unknown > ( objects = [ ] , options = { } ) { throwErrorIfNamespaceSpecified ( options ) ; return await this . client . bulkUpdate ( objects , { ... options , namespace : spaceIdToNamespace ( this . spaceId ) , } ) ; } async removeReferencesTo ( type , id , options = { } ) { throwErrorIfNamespaceSpecified ( options ) ; return await this . client . removeReferencesTo ( type , id , { ... options , namespace : spaceIdToNamespace ( this . spaceId ) , } ) ; } async collectMultiNamespaceReferences ( objects , options = { } ) { throwErrorIfNamespaceSpecified ( options ) ; return await this . client . collectMultiNamespaceReferences ( objects , { ... options , namespace : spaceIdToNamespace ( this . spaceId ) , } ) ; } async updateObjectsSpaces ( objects , spacesToAdd , spacesToRemove , options = { } ) { throwErrorIfNamespaceSpecified ( options ) ; return await this . client . updateObjectsSpaces ( objects , spacesToAdd , spacesToRemove , { ... options , namespace : spaceIdToNamespace ( this . spaceId ) , } ) ; } async openPointInTimeForType ( type , options = { } ) { let namespaces ; try { namespaces = await this . getSearchableSpaces ( options . namespaces ) ; } catch ( err ) { if ( Boom . isBoom ( err ) && err . output . payload . statusCode === 403 ) { throw SavedObjectsErrorHelpers . createBadRequestError ( ) ; } throw err ; } if ( namespaces . length === 0 ) { throw SavedObjectsErrorHelpers . createBadRequestError ( ) ; } return await this . client . openPointInTimeForType ( type , { ... options , namespaces , } ) ; } async closePointInTime ( id , options = { } ) { throwErrorIfNamespaceSpecified ( options ) ; return await this . client . closePointInTime ( id , { ... options , namespace : spaceIdToNamespace ( this . spaceId ) , } ) ; } createPointInTimeFinder < T = unknown , A = unknown > ( findOptions , dependencies ? ) { throwErrorIfNamespaceSpecified ( findOptions ) ; return this . client . createPointInTimeFinder < T , A > ( findOptions , { client : this , ... dependencies , } ) ; } private async getSearchableSpaces ( namespaces ? ) { if ( namespaces ) { const availableSpaces = await this . spacesClient . getAll ( { purpose : 'findSavedObjects' } ) ; if ( namespaces . includes ( ALL_SPACES_ID ) ) { return availableSpaces . map ( ( space ) => space . id ) ; } else { return namespaces . filter ( ( namespace ) => availableSpaces . some ( ( space ) => space . id === namespace ) ) ; } } else { return [ this . spaceId ] ; } } }