import { omit } from 'lodash' ; import uuid from 'uuid' ; import { OpenSearchClient , DeleteDocumentResponse , GetResponse , SearchResponse , } from '../../../opensearch/' ; import { getRootPropertiesObjects , IndexMapping } from '../../mappings' ; import { createRepositoryOpenSearchClient , RepositoryOpenSearchClient , } from './repository_opensearch_client' ; import { getSearchDsl } from './search_dsl' ; import { includedFields } from './included_fields' ; import { SavedObjectsErrorHelpers , DecoratedError } from './errors' ; import { decodeRequestVersion , encodeVersion , encodeHitVersion } from '../../version' ; import { IOpenSearchDashboardsMigrator } from '../../migrations' ; import { SavedObjectsSerializer , SavedObjectSanitizedDoc , SavedObjectsRawDoc , SavedObjectsRawDocSource , } from '../../serialization' ; import { SavedObjectsBulkCreateObject , SavedObjectsBulkGetObject , SavedObjectsBulkResponse , SavedObjectsBulkUpdateResponse , SavedObjectsCheckConflictsObject , SavedObjectsCheckConflictsResponse , SavedObjectsCreateOptions , SavedObjectsFindResponse , SavedObjectsFindResult , SavedObjectsUpdateOptions , SavedObjectsUpdateResponse , SavedObjectsBulkUpdateObject , SavedObjectsBulkUpdateOptions , SavedObjectsDeleteOptions , SavedObjectsAddToNamespacesOptions , SavedObjectsAddToNamespacesResponse , SavedObjectsDeleteFromNamespacesOptions , SavedObjectsDeleteFromNamespacesResponse , } from '../saved_objects_client' ; import { SavedObject , SavedObjectsBaseOptions , SavedObjectsFindOptions , SavedObjectsMigrationVersion , MutatingOperationRefreshSetting , } from '../../types' ; import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry' ; import { validateConvertFilterToKueryNode } from './filter_utils' ; import { ALL_NAMESPACES_STRING , FIND_DEFAULT_PAGE , FIND_DEFAULT_PER_PAGE , SavedObjectsUtils , } from './utils' ; type Left = { tag : 'Left' ; error : Record < string , any > ; } ; type Right = { tag : 'Right' ; value : Record < string , any > ; } ; type Either = Left | Right ; const isLeft = ( either ) : either is Left => either . tag === 'Left' ; const isRight = ( either ) : either is Right => either . tag === 'Right' ; export interface SavedObjectsRepositoryOptions { index : string ; mappings : IndexMapping ; client : OpenSearchClient ; typeRegistry : SavedObjectTypeRegistry ; serializer : SavedObjectsSerializer ; migrator : IOpenSearchDashboardsMigrator ; allowedTypes : string [ ] ; } public export interface SavedObjectsIncrementCounterOptions extends SavedObjectsBaseOptions { migrationVersion ? : SavedObjectsMigrationVersion ; /** The OpenSearch Refresh setting for this operation */ refresh ? : MutatingOperationRefreshSetting ; } public export interface SavedObjectsDeleteByNamespaceOptions extends SavedObjectsBaseOptions { /** The OpenSearch supports only boolean flag for this operation */ refresh ? : boolean ; } const DEFAULT_REFRESH_SETTING = 'wait_for' ; /**
 * See  SavedObjectsRepository public export type ISavedObjectsRepository = Pick < SavedObjectsRepository , keyof SavedObjectsRepository > ; public export class SavedObjectsRepository { private _migrator : IOpenSearchDashboardsMigrator ; private _index : string ; private _mappings : IndexMapping ; private _registry : SavedObjectTypeRegistry ; private _allowedTypes : string [ ] ; private readonly client : RepositoryOpenSearchClient ; private _serializer : SavedObjectsSerializer ; internalRemarks internal public static createRepository ( migrator , typeRegistry , indexName , client , includedHiddenTypes = [ ] , injectedConstructor = SavedObjectsRepository ) { const mappings = migrator . getActiveMappings ( ) ; const allTypes = typeRegistry . getAllTypes ( ) . map ( ( t ) => t . name ) ; const serializer = new SavedObjectsSerializer ( typeRegistry ) ; const visibleTypes = allTypes . filter ( ( type ) => ! typeRegistry . isHidden ( type ) ) ; const missingTypeMappings = includedHiddenTypes . filter ( ( type ) => ! allTypes . includes ( type ) ) ; if ( missingTypeMappings . length > 0 ) { throw new Error ( `Missing mappings for saved objects types: '${ missingTypeMappings . join ( ', ' ) }'` ) ; } const allowedTypes = [ ... new Set ( visibleTypes . concat ( includedHiddenTypes ) ) ] ; return new injectedConstructor ( { index : indexName , migrator , mappings , typeRegistry , serializer , allowedTypes , client , } ) ; } private constructor ( options ) { const { index , mappings , client , typeRegistry , serializer , migrator , allowedTypes = [ ] , } = options ; this . _migrator = migrator ; this . _index = index ; this . _mappings = mappings ; this . _registry = typeRegistry ; this . client = createRepositoryOpenSearchClient ( client ) ; if ( allowedTypes . length === 0 ) { throw new Error ( 'Empty or missing types for saved object repository!' ) ; } this . _allowedTypes = allowedTypes ; this . _serializer = serializer ; } param { string } type param { object } attributes param { object } options property property property property property returns { promise } public async create < T = unknown > ( type , attributes , options = { } ) { const { id , migrationVersion , overwrite = false , references = [ ] , refresh = DEFAULT_REFRESH_SETTING , originId , initialNamespaces , version , } = options ; const namespace = normalizeNamespace ( options . namespace ) ; if ( initialNamespaces ) { if ( ! this . _registry . isMultiNamespace ( type ) ) { throw SavedObjectsErrorHelpers . createBadRequestError ( '"options.initialNamespaces" can only be used on multi-namespace types' ) ; } else if ( ! initialNamespaces . length ) { throw SavedObjectsErrorHelpers . createBadRequestError ( '"options.initialNamespaces" must be a non-empty array of strings' ) ; } } if ( ! this . _allowedTypes . includes ( type ) ) { throw SavedObjectsErrorHelpers . createUnsupportedTypeError ( type ) ; } const time = this . _getCurrentTime ( ) ; let savedObjectNamespace ; let savedObjectNamespaces ; if ( this . _registry . isSingleNamespace ( type ) && namespace ) { savedObjectNamespace = namespace ; } else if ( this . _registry . isMultiNamespace ( type ) ) { if ( id && overwrite ) { const existingNamespaces = await this . preflightGetNamespaces ( type , id , namespace ) ; savedObjectNamespaces = initialNamespaces || existingNamespaces ; } else { savedObjectNamespaces = initialNamespaces || getSavedObjectNamespaces ( namespace ) ; } } const migrated = this . _migrator . migrateDocument ( { id , type , ... ( savedObjectNamespace && { namespace : savedObjectNamespace } ) , ... ( savedObjectNamespaces && { namespaces : savedObjectNamespaces } ) , originId , attributes , migrationVersion , updated_at : time , ... ( Array . isArray ( references ) && { references } ) , } ) ; const raw = this . _serializer . savedObjectToRaw ( migrated as SavedObjectSanitizedDoc ) ; const requestParams = { id : raw . _id , index : this . getIndexForType ( type ) , refresh , body : raw . _source , ... ( overwrite && version ? decodeRequestVersion ( version ) : { } ) , } ; const { body } = id && overwrite ? await this . client . index ( requestParams ) : await this . client . create ( requestParams ) ; return this . _rawToSavedObject < T > ( { ... raw , ... body , } ) ; } param { array } objects param { object } options property property returns { promise } async bulkCreate < T = unknown > ( objects , options = { } ) { const { overwrite = false , refresh = DEFAULT_REFRESH_SETTING } = options ; const namespace = normalizeNamespace ( options . namespace ) ; const time = this . _getCurrentTime ( ) ; let bulkGetRequestIndexCounter = 0 ; const expectedResults = objects . map ( ( object ) => { let error ; if ( ! this . _allowedTypes . includes ( object . type ) ) { error = SavedObjectsErrorHelpers . createUnsupportedTypeError ( object . type ) ; } else if ( object . initialNamespaces ) { if ( ! this . _registry . isMultiNamespace ( object . type ) ) { error = SavedObjectsErrorHelpers . createBadRequestError ( '"initialNamespaces" can only be used on multi-namespace types' ) ; } else if ( ! object . initialNamespaces . length ) { error = SavedObjectsErrorHelpers . createBadRequestError ( '"initialNamespaces" must be a non-empty array of strings' ) ; } } if ( error ) { return { tag : 'Left' as 'Left' , error : { id : object . id , type : object . type , error : errorContent ( error ) } , } ; } const method = object . id && overwrite ? 'index' : 'create' ; const requiresNamespacesCheck = object . id && this . _registry . isMultiNamespace ( object . type ) ; if ( object . id == null ) object . id = uuid . v1 ( ) ; return { tag : 'Right' as 'Right' , value : { method , object , ... ( requiresNamespacesCheck && { opensearchRequestIndex : bulkGetRequestIndexCounter ++ } ) , } , } ; } ) ; const bulkGetDocs = expectedResults . filter ( isRight ) . filter ( ( { value } ) => value . opensearchRequestIndex !== undefined ) . map ( ( { value : { object : { type , id } } } ) => ( { _id : this . _serializer . generateRawId ( namespace , type , id ) , _index : this . getIndexForType ( type ) , _source : [ 'type' , 'namespaces' ] , } ) ) ; const bulkGetResponse = bulkGetDocs . length ? await this . client . mget ( { body : { docs : bulkGetDocs , } , } , { ignore : [ 404 ] } ) : undefined ; let bulkRequestIndexCounter = 0 ; const bulkCreateParams = [ ] ; const expectedBulkResults = expectedResults . map ( ( expectedBulkGetResult ) => { if ( isLeft ( expectedBulkGetResult ) ) { return expectedBulkGetResult ; } let savedObjectNamespace ; let savedObjectNamespaces ; let versionProperties ; const { opensearchRequestIndex , object : { initialNamespaces , version , ... object } , method , } = expectedBulkGetResult . value ; if ( opensearchRequestIndex !== undefined ) { const indexFound = bulkGetResponse ?. statusCode !== 404 ; const actualResult = indexFound ? bulkGetResponse ?. body . docs [ opensearchRequestIndex ] : undefined ; const docFound = indexFound && actualResult . found === true ; if ( docFound && ! this . rawDocExistsInNamespace ( actualResult , namespace ) ) { const { id , type } = object ; return { tag : 'Left' as 'Left' , error : { id , type , error : { ... errorContent ( SavedObjectsErrorHelpers . createConflictError ( type , id ) ) , metadata : { isNotOverwritable : true } , } , } , } ; } savedObjectNamespaces = initialNamespaces || getSavedObjectNamespaces ( namespace , docFound && actualResult ) ; versionProperties = getExpectedVersionProperties ( version , actualResult ) ; } else { if ( this . _registry . isSingleNamespace ( object . type ) ) { savedObjectNamespace = namespace ; } else if ( this . _registry . isMultiNamespace ( object . type ) ) { savedObjectNamespaces = initialNamespaces || getSavedObjectNamespaces ( namespace ) ; } versionProperties = getExpectedVersionProperties ( version ) ; } const expectedResult = { opensearchRequestIndex : bulkRequestIndexCounter ++ , requestedId : object . id , rawMigratedDoc : this . _serializer . savedObjectToRaw ( this . _migrator . migrateDocument ( { id : object . id , type : object . type , attributes : object . attributes , migrationVersion : object . migrationVersion , ... ( savedObjectNamespace && { namespace : savedObjectNamespace } ) , ... ( savedObjectNamespaces && { namespaces : savedObjectNamespaces } ) , updated_at : time , references : object . references || [ ] , originId : object . originId , } ) as SavedObjectSanitizedDoc ) , } ; bulkCreateParams . push ( { [ method ] : { _id : expectedResult . rawMigratedDoc . _id , _index : this . getIndexForType ( object . type ) , ... ( overwrite && versionProperties ) , } , } , expectedResult . rawMigratedDoc . _source ) ; return { tag : 'Right' as 'Right' , value : expectedResult } ; } ) ; const bulkResponse = bulkCreateParams . length ? await this . client . bulk ( { refresh , body : bulkCreateParams , } ) : undefined ; return { saved_objects : expectedBulkResults . map ( ( expectedResult ) => { if ( isLeft ( expectedResult ) ) { return expectedResult . error as any ; } const { requestedId , rawMigratedDoc , opensearchRequestIndex } = expectedResult . value ; const { error , ... rawResponse } = Object . values ( bulkResponse ?. body . items [ opensearchRequestIndex ] ) [ 0 ] as any ; if ( error ) { return { id : requestedId , type : rawMigratedDoc . _source . type , error : getBulkOperationError ( error , rawMigratedDoc . _source . type , requestedId ) , } ; } return this . _rawToSavedObject ( { ... rawMigratedDoc , ... { _seq_no : rawResponse . _seq_no , _primary_term : rawResponse . _primary_term } , } ) ; } ) , } ; } /**
     * Check what conflicts will result when creating a given array of saved objects. This includes "unresolvable conflicts", which are
     * multi-namespace objects that exist in a different namespace; such conflicts cannot be resolved/overwritten.
     */ async checkConflicts ( objects = [ ] , options = { } ) { if ( objects . length === 0 ) { return { errors : [ ] } ; } const namespace = normalizeNamespace ( options . namespace ) ; let bulkGetRequestIndexCounter = 0 ; const expectedBulkGetResults = objects . map ( ( object ) => { const { type , id } = object ; if ( ! this . _allowedTypes . includes ( type ) ) { return { tag : 'Left' as 'Left' , error : { id , type , error : errorContent ( SavedObjectsErrorHelpers . createUnsupportedTypeError ( type ) ) , } , } ; } return { tag : 'Right' as 'Right' , value : { type , id , opensearchRequestIndex : bulkGetRequestIndexCounter ++ , } , } ; } ) ; const bulkGetDocs = expectedBulkGetResults . filter ( isRight ) . map ( ( { value : { type , id } } ) => ( { _id : this . _serializer . generateRawId ( namespace , type , id ) , _index : this . getIndexForType ( type ) , _source : [ 'type' , 'namespaces' ] , } ) ) ; const bulkGetResponse = bulkGetDocs . length ? await this . client . mget ( { body : { docs : bulkGetDocs , } , } , { ignore : [ 404 ] } ) : undefined ; const errors = [ ] ; expectedBulkGetResults . forEach ( ( expectedResult ) => { if ( isLeft ( expectedResult ) ) { errors . push ( expectedResult . error as any ) ; return ; } const { type , id , opensearchRequestIndex } = expectedResult . value ; const doc = bulkGetResponse ?. body . docs [ opensearchRequestIndex ] ; if ( doc . found ) { errors . push ( { id , type , error : { ... errorContent ( SavedObjectsErrorHelpers . createConflictError ( type , id ) ) , ... ( ! this . rawDocExistsInNamespace ( doc , namespace ) && { metadata : { isNotOverwritable : true } , } ) , } , } ) ; } } ) ; return { errors } ; } param { string } type param { string } id param { object } options property returns { promise } async delete ( type , id , options = { } ) { if ( ! this . _allowedTypes . includes ( type ) ) { throw SavedObjectsErrorHelpers . createGenericNotFoundError ( type , id ) ; } const { refresh = DEFAULT_REFRESH_SETTING , force } = options ; const namespace = normalizeNamespace ( options . namespace ) ; const rawId = this . _serializer . generateRawId ( namespace , type , id ) ; let preflightResult ; if ( this . _registry . isMultiNamespace ( type ) ) { preflightResult = await this . preflightCheckIncludesNamespace ( type , id , namespace ) ; const existingNamespaces = getSavedObjectNamespaces ( undefined , preflightResult ) ?? [ ] ; if ( ! force && ( existingNamespaces . length > 1 || existingNamespaces . includes ( ALL_NAMESPACES_STRING ) ) ) { throw SavedObjectsErrorHelpers . createBadRequestError ( 'Unable to delete saved object that exists in multiple namespaces, use the `force` option to delete it anyway' ) ; } } const { body , statusCode } = await this . client . delete < DeleteDocumentResponse > ( { id : rawId , index : this . getIndexForType ( type ) , ... getExpectedVersionProperties ( undefined , preflightResult ) , refresh , } , { ignore : [ 404 ] } ) ; const deleted = body . result === 'deleted' ; if ( deleted ) { return { } ; } const deleteDocNotFound = body . result === 'not_found' ; const deleteIndexNotFound = body . error && body . error . type === 'index_not_found_exception' ; if ( deleteDocNotFound || deleteIndexNotFound ) { throw SavedObjectsErrorHelpers . createGenericNotFoundError ( type , id ) ; } throw new Error ( `Unexpected OpenSearch DELETE response: ${ JSON . stringify ( { type , id , response : { body , statusCode } , } ) }` ) ; } param { string } namespace returns { promise } async deleteByNamespace ( namespace , options = { } ) { if ( ! namespace || typeof namespace !== 'string' || namespace === '*' ) { throw new TypeError ( `namespace is required, and must be a string that is not equal to '*'` ) ; } const allTypes = Object . keys ( getRootPropertiesObjects ( this . _mappings ) ) ; const typesToUpdate = allTypes . filter ( ( type ) => ! this . _registry . isNamespaceAgnostic ( type ) ) ; const { body } = await this . client . updateByQuery ( { index : this . getIndicesForTypes ( typesToUpdate ) , refresh : options . refresh , body : { script : { source : `
              if (!ctx._source.containsKey('namespaces')) {
                ctx.op = "delete";
              } else {
                ctx._source['namespaces'].removeAll(Collections.singleton(params['namespace']));
                if (ctx._source['namespaces'].empty) {
                  ctx.op = "delete";
                }
              }
            ` , lang : 'painless' , params : { namespace } , } , conflicts : 'proceed' , ... getSearchDsl ( this . _mappings , this . _registry , { namespaces : namespace ? [ namespace ] : undefined , type : typesToUpdate , } ) , } , } , { ignore : [ 404 ] } ) ; return body ; } param { object } options property property property property property property property property property property property property returns { promise } async find < T = unknown > ( options ) { const { search , defaultSearchOperator = 'OR' , searchFields , rootSearchFields , hasReference , page = FIND_DEFAULT_PAGE , perPage = FIND_DEFAULT_PER_PAGE , sortField , sortOrder , fields , namespaces , type , typeToNamespacesMap , filter , preference , } = options ; if ( ! type && ! typeToNamespacesMap ) { throw SavedObjectsErrorHelpers . createBadRequestError ( 'options.type must be a string or an array of strings' ) ; } else if ( namespaces ?. length === 0 && ! typeToNamespacesMap ) { throw SavedObjectsErrorHelpers . createBadRequestError ( 'options.namespaces cannot be an empty array' ) ; } else if ( type && typeToNamespacesMap ) { throw SavedObjectsErrorHelpers . createBadRequestError ( 'options.type must be an empty string when options.typeToNamespacesMap is used' ) ; } else if ( ( ! namespaces || namespaces ?. length ) && typeToNamespacesMap ) { throw SavedObjectsErrorHelpers . createBadRequestError ( 'options.namespaces must be an empty array when options.typeToNamespacesMap is used' ) ; } const types = type ? Array . isArray ( type ) ? type : [ type ] : Array . from ( typeToNamespacesMap ! . keys ( ) ) ; const allowedTypes = types . filter ( ( t ) => this . _allowedTypes . includes ( t ) ) ; if ( allowedTypes . length === 0 ) { return SavedObjectsUtils . createEmptyFindResponse < T > ( options ) ; } if ( searchFields && ! Array . isArray ( searchFields ) ) { throw SavedObjectsErrorHelpers . createBadRequestError ( 'options.searchFields must be an array' ) ; } if ( fields && ! Array . isArray ( fields ) ) { throw SavedObjectsErrorHelpers . createBadRequestError ( 'options.fields must be an array' ) ; } let kueryNode ; try { if ( filter ) { kueryNode = validateConvertFilterToKueryNode ( allowedTypes , filter , this . _mappings ) ; } } catch ( e ) { if ( e . name === 'DQLSyntaxError' ) { throw SavedObjectsErrorHelpers . createBadRequestError ( 'DQLSyntaxError: ' + e . message ) ; } else { throw e ; } } const opensearchOptions = { index : this . getIndicesForTypes ( allowedTypes ) , size : perPage , from : perPage * ( page - 1 ) , _source : includedFields ( type , fields ) , rest_total_hits_as_int : true , preference , body : { seq_no_primary_term : true , ... getSearchDsl ( this . _mappings , this . _registry , { search , defaultSearchOperator , searchFields , rootSearchFields , type : allowedTypes , sortField , sortOrder , namespaces , typeToNamespacesMap , hasReference , kueryNode , } ) , } , } ; const { body , statusCode } = await this . client . search < SearchResponse < any > > ( opensearchOptions , { ignore : [ 404 ] , } ) ; if ( statusCode === 404 ) { return { page , per_page : perPage , total : 0 , saved_objects : [ ] , } ; } return { page , per_page : perPage , total : body . hits . total , saved_objects : body . hits . hits . map ( ( hit ) : SavedObjectsFindResult => ( { ... this . _rawToSavedObject ( hit ) , score : ( hit as any ) . _score , } ) ) , } as SavedObjectsFindResponse < T > ; } param { array } objects param { object } options property returns { promise } example async bulkGet < T = unknown > ( objects = [ ] , options = { } ) { const namespace = normalizeNamespace ( options . namespace ) ; if ( objects . length === 0 ) { return { saved_objects : [ ] } ; } let bulkGetRequestIndexCounter = 0 ; const expectedBulkGetResults = objects . map ( ( object ) => { const { type , id , fields } = object ; if ( ! this . _allowedTypes . includes ( type ) ) { return { tag : 'Left' as 'Left' , error : { id , type , error : errorContent ( SavedObjectsErrorHelpers . createUnsupportedTypeError ( type ) ) , } , } ; } return { tag : 'Right' as 'Right' , value : { type , id , fields , opensearchRequestIndex : bulkGetRequestIndexCounter ++ , } , } ; } ) ; const bulkGetDocs = expectedBulkGetResults . filter ( isRight ) . map ( ( { value : { type , id , fields } } ) => ( { _id : this . _serializer . generateRawId ( namespace , type , id ) , _index : this . getIndexForType ( type ) , _source : includedFields ( type , fields ) , } ) ) ; const bulkGetResponse = bulkGetDocs . length ? await this . client . mget ( { body : { docs : bulkGetDocs , } , } , { ignore : [ 404 ] } ) : undefined ; return { saved_objects : expectedBulkGetResults . map ( ( expectedResult ) => { if ( isLeft ( expectedResult ) ) { return expectedResult . error as any ; } const { type , id , opensearchRequestIndex } = expectedResult . value ; const doc = bulkGetResponse ?. body . docs [ opensearchRequestIndex ] ; if ( ! doc . found || ! this . rawDocExistsInNamespace ( doc , namespace ) ) { return ( { id , type , error : errorContent ( SavedObjectsErrorHelpers . createGenericNotFoundError ( type , id ) ) , } as any ) as SavedObject < T > ; } const { originId , updated_at : updatedAt } = doc . _source ; let namespaces = [ ] ; if ( ! this . _registry . isNamespaceAgnostic ( type ) ) { namespaces = doc . _source . namespaces ?? [ SavedObjectsUtils . namespaceIdToString ( doc . _source . namespace ) , ] ; } return { id , type , namespaces , ... ( originId && { originId } ) , ... ( updatedAt && { updated_at : updatedAt } ) , version : encodeHitVersion ( doc ) , attributes : doc . _source [ type ] , references : doc . _source . references || [ ] , migrationVersion : doc . _source . migrationVersion , } ; } ) , } ; } param { string } type param { string } id param { object } options property returns { promise } async get < T = unknown > ( type , id , options = { } ) { if ( ! this . _allowedTypes . includes ( type ) ) { throw SavedObjectsErrorHelpers . createGenericNotFoundError ( type , id ) ; } const namespace = normalizeNamespace ( options . namespace ) ; const { body , statusCode } = await this . client . get < GetResponse < SavedObjectsRawDocSource > > ( { id : this . _serializer . generateRawId ( namespace , type , id ) , index : this . getIndexForType ( type ) , } , { ignore : [ 404 ] } ) ; const docNotFound = body . found === false ; const indexNotFound = statusCode === 404 ; if ( docNotFound || indexNotFound || ! this . rawDocExistsInNamespace ( body , namespace ) ) { throw SavedObjectsErrorHelpers . createGenericNotFoundError ( type , id ) ; } const { originId , updated_at : updatedAt } = body . _source ; let namespaces = [ ] ; if ( ! this . _registry . isNamespaceAgnostic ( type ) ) { namespaces = body . _source . namespaces ?? [ SavedObjectsUtils . namespaceIdToString ( body . _source . namespace ) , ] ; } return { id , type , namespaces , ... ( originId && { originId } ) , ... ( updatedAt && { updated_at : updatedAt } ) , version : encodeHitVersion ( body ) , attributes : body . _source [ type ] , references : body . _source . references || [ ] , migrationVersion : body . _source . migrationVersion , } ; } param { string } type param { string } id param { object } options property property property returns { promise } async update < T = unknown > ( type , id , attributes , options = { } ) { if ( ! this . _allowedTypes . includes ( type ) ) { throw SavedObjectsErrorHelpers . createGenericNotFoundError ( type , id ) ; } const { version , references , refresh = DEFAULT_REFRESH_SETTING } = options ; const namespace = normalizeNamespace ( options . namespace ) ; let preflightResult ; if ( this . _registry . isMultiNamespace ( type ) ) { preflightResult = await this . preflightCheckIncludesNamespace ( type , id , namespace ) ; } const time = this . _getCurrentTime ( ) ; const doc = { [ type ] : attributes , updated_at : time , ... ( Array . isArray ( references ) && { references } ) , } ; const { body , statusCode } = await this . client . update ( { id : this . _serializer . generateRawId ( namespace , type , id ) , index : this . getIndexForType ( type ) , ... getExpectedVersionProperties ( version , preflightResult ) , refresh , body : { doc , } , _source_includes : [ 'namespace' , 'namespaces' , 'originId' ] , } , { ignore : [ 404 ] } ) ; if ( statusCode === 404 ) { throw SavedObjectsErrorHelpers . createGenericNotFoundError ( type , id ) ; } const { originId } = body . get . _source ; let namespaces = [ ] ; if ( ! this . _registry . isNamespaceAgnostic ( type ) ) { namespaces = body . get . _source . namespaces ?? [ SavedObjectsUtils . namespaceIdToString ( body . get . _source . namespace ) , ] ; } return { id , type , updated_at : time , version : encodeHitVersion ( body ) , namespaces , ... ( originId && { originId } ) , references , attributes , } ; } /**
     * Adds one or more namespaces to a given multi-namespace saved object. This method and
     * [`deleteFromNamespaces`] SavedObjectsRepository . deleteFromNamespaces are the only ways to change which Spaces a multi-namespace
     * saved object is shared to.
      async addToNamespaces ( type , id , namespaces , options = { } ) { if ( ! this . _allowedTypes . includes ( type ) ) { throw SavedObjectsErrorHelpers . createGenericNotFoundError ( type , id ) ; } if ( ! this . _registry . isMultiNamespace ( type ) ) { throw SavedObjectsErrorHelpers . createBadRequestError ( `${ type } doesn't support multiple namespaces` ) ; } if ( ! namespaces . length ) { throw SavedObjectsErrorHelpers . createBadRequestError ( 'namespaces must be a non-empty array of strings' ) ; } const { version , namespace , refresh = DEFAULT_REFRESH_SETTING } = options ; const rawId = this . _serializer . generateRawId ( undefined , type , id ) ; const preflightResult = await this . preflightCheckIncludesNamespace ( type , id , namespace ) ; const existingNamespaces = getSavedObjectNamespaces ( undefined , preflightResult ) ; const time = this . _getCurrentTime ( ) ; const doc = { updated_at : time , namespaces : existingNamespaces ? unique ( existingNamespaces . concat ( namespaces ) ) : namespaces , } ; const { statusCode } = await this . client . update ( { id : rawId , index : this . getIndexForType ( type ) , ... getExpectedVersionProperties ( version , preflightResult ) , refresh , body : { doc , } , } , { ignore : [ 404 ] } ) ; if ( statusCode === 404 ) { throw SavedObjectsErrorHelpers . createGenericNotFoundError ( type , id ) ; } return { namespaces : doc . namespaces } ; } /**
     * Removes one or more namespaces from a given multi-namespace saved object. If no namespaces remain, the saved object is deleted
     * entirely. This method and [`addToNamespaces`] SavedObjectsRepository . addToNamespaces are the only ways to change which Spaces a
     * multi-namespace saved object is shared to.
      async deleteFromNamespaces ( type , id , namespaces , options = { } ) { if ( ! this . _allowedTypes . includes ( type ) ) { throw SavedObjectsErrorHelpers . createGenericNotFoundError ( type , id ) ; } if ( ! this . _registry . isMultiNamespace ( type ) ) { throw SavedObjectsErrorHelpers . createBadRequestError ( `${ type } doesn't support multiple namespaces` ) ; } if ( ! namespaces . length ) { throw SavedObjectsErrorHelpers . createBadRequestError ( 'namespaces must be a non-empty array of strings' ) ; } const { namespace , refresh = DEFAULT_REFRESH_SETTING } = options ; const rawId = this . _serializer . generateRawId ( undefined , type , id ) ; const preflightResult = await this . preflightCheckIncludesNamespace ( type , id , namespace ) ; const existingNamespaces = getSavedObjectNamespaces ( undefined , preflightResult ) ; const remainingNamespaces = existingNamespaces ?. filter ( ( x ) => ! namespaces . includes ( x ) ) ; if ( remainingNamespaces ?. length ) { const time = this . _getCurrentTime ( ) ; const doc = { updated_at : time , namespaces : remainingNamespaces , } ; const { statusCode } = await this . client . update ( { id : rawId , index : this . getIndexForType ( type ) , ... getExpectedVersionProperties ( undefined , preflightResult ) , refresh , body : { doc , } , } , { ignore : [ 404 ] , } ) ; if ( statusCode === 404 ) { throw SavedObjectsErrorHelpers . createGenericNotFoundError ( type , id ) ; } return { namespaces : doc . namespaces } ; } else { const { body , statusCode } = await this . client . delete < DeleteDocumentResponse > ( { id : this . _serializer . generateRawId ( undefined , type , id ) , refresh , ... getExpectedVersionProperties ( undefined , preflightResult ) , index : this . getIndexForType ( type ) , } , { ignore : [ 404 ] , } ) ; const deleted = body . result === 'deleted' ; if ( deleted ) { return { namespaces : [ ] } ; } const deleteDocNotFound = body . result === 'not_found' ; const deleteIndexNotFound = body . error && body . error . type === 'index_not_found_exception' ; if ( deleteDocNotFound || deleteIndexNotFound ) { throw SavedObjectsErrorHelpers . createGenericNotFoundError ( type , id ) ; } throw new Error ( `Unexpected OpenSearch DELETE response: ${ JSON . stringify ( { type , id , response : { body , statusCode } , } ) }` ) ; } } param { array } objects property property returns { promise } async bulkUpdate < T = unknown > ( objects , options = { } ) { const time = this . _getCurrentTime ( ) ; const namespace = normalizeNamespace ( options . namespace ) ; let bulkGetRequestIndexCounter = 0 ; const expectedBulkGetResults = objects . map ( ( object ) => { const { type , id } = object ; if ( ! this . _allowedTypes . includes ( type ) ) { return { tag : 'Left' as 'Left' , error : { id , type , error : errorContent ( SavedObjectsErrorHelpers . createGenericNotFoundError ( type , id ) ) , } , } ; } const { attributes , references , version , namespace : objectNamespace } = object ; if ( objectNamespace === ALL_NAMESPACES_STRING ) { return { tag : 'Left' as 'Left' , error : { id , type , error : errorContent ( SavedObjectsErrorHelpers . createBadRequestError ( '"namespace" cannot be "*"' ) ) , } , } ; } const documentToSave = { [ type ] : attributes , updated_at : time , ... ( Array . isArray ( references ) && { references } ) , } ; const requiresNamespacesCheck = this . _registry . isMultiNamespace ( object . type ) ; return { tag : 'Right' as 'Right' , value : { type , id , version , documentToSave , objectNamespace , ... ( requiresNamespacesCheck && { opensearchRequestIndex : bulkGetRequestIndexCounter ++ } ) , } , } ; } ) ; const getNamespaceId = ( objectNamespace ? ) => objectNamespace !== undefined ? SavedObjectsUtils . namespaceStringToId ( objectNamespace ) : namespace ; const getNamespaceString = ( objectNamespace ? ) => objectNamespace ?? SavedObjectsUtils . namespaceIdToString ( namespace ) ; const bulkGetDocs = expectedBulkGetResults . filter ( isRight ) . filter ( ( { value } ) => value . opensearchRequestIndex !== undefined ) . map ( ( { value : { type , id , objectNamespace } } ) => ( { _id : this . _serializer . generateRawId ( getNamespaceId ( objectNamespace ) , type , id ) , _index : this . getIndexForType ( type ) , _source : [ 'type' , 'namespaces' ] , } ) ) ; const bulkGetResponse = bulkGetDocs . length ? await this . client . mget ( { body : { docs : bulkGetDocs , } , } , { ignore : [ 404 ] , } ) : undefined ; let bulkUpdateRequestIndexCounter = 0 ; const bulkUpdateParams = [ ] ; const expectedBulkUpdateResults = expectedBulkGetResults . map ( ( expectedBulkGetResult ) => { if ( isLeft ( expectedBulkGetResult ) ) { return expectedBulkGetResult ; } const { opensearchRequestIndex , id , type , version , documentToSave , objectNamespace , } = expectedBulkGetResult . value ; let namespaces ; let versionProperties ; if ( opensearchRequestIndex !== undefined ) { const indexFound = bulkGetResponse ?. statusCode !== 404 ; const actualResult = indexFound ? bulkGetResponse ?. body . docs [ opensearchRequestIndex ] : undefined ; const docFound = indexFound && actualResult . found === true ; if ( ! docFound || ! this . rawDocExistsInNamespace ( actualResult , getNamespaceId ( objectNamespace ) ) ) { return { tag : 'Left' as 'Left' , error : { id , type , error : errorContent ( SavedObjectsErrorHelpers . createGenericNotFoundError ( type , id ) ) , } , } ; } namespaces = actualResult . _source . namespaces ?? [ SavedObjectsUtils . namespaceIdToString ( actualResult . _source . namespace ) , ] ; versionProperties = getExpectedVersionProperties ( version , actualResult ) ; } else { if ( this . _registry . isSingleNamespace ( type ) ) { namespaces = [ getNamespaceString ( objectNamespace ) ] ; } versionProperties = getExpectedVersionProperties ( version ) ; } const expectedResult = { type , id , namespaces , opensearchRequestIndex : bulkUpdateRequestIndexCounter ++ , documentToSave : expectedBulkGetResult . value . documentToSave , } ; bulkUpdateParams . push ( { update : { _id : this . _serializer . generateRawId ( getNamespaceId ( objectNamespace ) , type , id ) , _index : this . getIndexForType ( type ) , ... versionProperties , } , } , { doc : documentToSave } ) ; return { tag : 'Right' as 'Right' , value : expectedResult } ; } ) ; const { refresh = DEFAULT_REFRESH_SETTING } = options ; const bulkUpdateResponse = bulkUpdateParams . length ? await this . client . bulk ( { refresh , body : bulkUpdateParams , _source_includes : [ 'originId' ] , } ) : undefined ; return { saved_objects : expectedBulkUpdateResults . map ( ( expectedResult ) => { if ( isLeft ( expectedResult ) ) { return expectedResult . error as any ; } const { type , id , namespaces , documentToSave , opensearchRequestIndex , } = expectedResult . value ; const response = bulkUpdateResponse ?. body . items [ opensearchRequestIndex ] ; const { error , _seq_no : seqNo , _primary_term : primaryTerm , get } = Object . values ( response ) [ 0 ] as any ; const { [ type ] : attributes , references , updated_at } = documentToSave ; if ( error ) { return { id , type , error : getBulkOperationError ( error , type , id ) , } ; } const { originId } = get . _source ; return { id , type , ... ( namespaces && { namespaces } ) , ... ( originId && { originId } ) , updated_at , version : encodeVersion ( seqNo , primaryTerm ) , attributes , references , } ; } ) , } ; } param { string } type param { string } id param { string } counterFieldName param { object } options property returns { promise } async incrementCounter ( type , id , counterFieldName , options = { } ) { if ( typeof type !== 'string' ) { throw new Error ( '"type" argument must be a string' ) ; } if ( typeof counterFieldName !== 'string' ) { throw new Error ( '"counterFieldName" argument must be a string' ) ; } if ( ! this . _allowedTypes . includes ( type ) ) { throw SavedObjectsErrorHelpers . createUnsupportedTypeError ( type ) ; } const { migrationVersion , refresh = DEFAULT_REFRESH_SETTING } = options ; const namespace = normalizeNamespace ( options . namespace ) ; const time = this . _getCurrentTime ( ) ; let savedObjectNamespace ; let savedObjectNamespaces ; if ( this . _registry . isSingleNamespace ( type ) && namespace ) { savedObjectNamespace = namespace ; } else if ( this . _registry . isMultiNamespace ( type ) ) { savedObjectNamespaces = await this . preflightGetNamespaces ( type , id , namespace ) ; } const migrated = this . _migrator . migrateDocument ( { id , type , ... ( savedObjectNamespace && { namespace : savedObjectNamespace } ) , ... ( savedObjectNamespaces && { namespaces : savedObjectNamespaces } ) , attributes : { [ counterFieldName ] : 1 } , migrationVersion , updated_at : time , } ) ; const raw = this . _serializer . savedObjectToRaw ( migrated as SavedObjectSanitizedDoc ) ; const { body } = await this . client . update ( { id : raw . _id , index : this . getIndexForType ( type ) , refresh , _source : 'true' , body : { script : { source : `
              if (ctx._source[params.type][params.counterFieldName] == null) {
                ctx._source[params.type][params.counterFieldName] = params.count;
              }
              else {
                ctx._source[params.type][params.counterFieldName] += params.count;
              }
              ctx._source.updated_at = params.time;
            ` , lang : 'painless' , params : { count : 1 , time , type , counterFieldName , } , } , upsert : raw . _source , } , } ) ; const { originId } = body . get . _source ; return { id , type , ... ( savedObjectNamespaces && { namespaces : savedObjectNamespaces } ) , ... ( originId && { originId } ) , updated_at : time , references : body . get . _source . references , version : encodeHitVersion ( body ) , attributes : body . get . _source [ type ] , } ; } param type private getIndexForType ( type ) { return this . _registry . getIndex ( type ) || this . _index ; } param types private getIndicesForTypes ( types ) { return unique ( types . map ( ( t ) => this . getIndexForType ( t ) ) ) ; } private _getCurrentTime ( ) { return new Date ( ) . toISOString ( ) ; } private _rawToSavedObject < T = unknown > ( raw ) { const savedObject = this . _serializer . rawToSavedObject ( raw ) ; const { namespace , type } = savedObject ; if ( this . _registry . isSingleNamespace ( type ) ) { savedObject . namespaces = [ SavedObjectsUtils . namespaceIdToString ( namespace ) ] ; } return omit ( savedObject , 'namespace' ) as SavedObject < T > ; } /**
     * Check to ensure that a raw document exists in a namespace. If the document is not a multi-namespace type, then this returns `true` as
     * we rely on the guarantees of the document ID format. If the document is a multi-namespace type, this checks to ensure that the
     * document's `namespaces` value includes the string representation of the given namespace.
     *
     * WARNING: This should only be used for documents that were retrieved from OpenSearch. Otherwise, the guarantees of the document ID
     * format mentioned above do not apply.
     */ private rawDocExistsInNamespace ( raw , namespace ? ) { const rawDocType = raw . _source . type ; if ( ! this . _registry . isMultiNamespace ( rawDocType ) ) { return true ; } const namespaces = raw . _source . namespaces ; const existsInNamespace = namespaces ?. includes ( SavedObjectsUtils . namespaceIdToString ( namespace ) ) || namespaces ?. includes ( '*' ) ; return existsInNamespace ?? false ; } param type param id param namespace returns throws private async preflightGetNamespaces ( type , id , namespace ? ) { if ( ! this . _registry . isMultiNamespace ( type ) ) { throw new Error ( `Cannot make preflight get request for non-multi-namespace type '${ type }'.` ) ; } const { body , statusCode } = await this . client . get < GetResponse < SavedObjectsRawDocSource > > ( { id : this . _serializer . generateRawId ( undefined , type , id ) , index : this . getIndexForType ( type ) , } , { ignore : [ 404 ] , } ) ; const indexFound = statusCode !== 404 ; const docFound = indexFound && body . found === true ; if ( docFound ) { if ( ! this . rawDocExistsInNamespace ( body , namespace ) ) { throw SavedObjectsErrorHelpers . createConflictError ( type , id ) ; } return getSavedObjectNamespaces ( namespace , body ) ; } return getSavedObjectNamespaces ( namespace ) ; } param type param id param namespace returns throws private async preflightCheckIncludesNamespace ( type , id , namespace ? ) { if ( ! this . _registry . isMultiNamespace ( type ) ) { throw new Error ( `Cannot make preflight get request for non-multi-namespace type '${ type }'.` ) ; } const rawId = this . _serializer . generateRawId ( undefined , type , id ) ; const { body , statusCode } = await this . client . get < GetResponse < SavedObjectsRawDocSource > > ( { id : rawId , index : this . getIndexForType ( type ) , } , { ignore : [ 404 ] } ) ; const indexFound = statusCode !== 404 ; const docFound = indexFound && body . found === true ; if ( ! docFound || ! this . rawDocExistsInNamespace ( body , namespace ) ) { throw SavedObjectsErrorHelpers . createGenericNotFoundError ( type , id ) ; } return body as SavedObjectsRawDoc ; } } function getBulkOperationError ( error , type , id ) { switch ( error . type ) { case 'version_conflict_engine_exception' : return errorContent ( SavedObjectsErrorHelpers . createConflictError ( type , id ) ) ; case 'document_missing_exception' : return errorContent ( SavedObjectsErrorHelpers . createGenericNotFoundError ( type , id ) ) ; default : return { message : error . reason || JSON . stringify ( error ) , } ; } } param version param document function getExpectedVersionProperties ( version ? , document ? ) { if ( version ) { return decodeRequestVersion ( version ) ; } else if ( document ) { return { if_seq_no : document . _seq_no , if_primary_term : document . _primary_term , } ; } return { } ; } param namespace param document function getSavedObjectNamespaces ( namespace ? , document ? ) { if ( document ) { return document . _source ?. namespaces ; } return [ SavedObjectsUtils . namespaceIdToString ( namespace ) ] ; } /**
 * Ensure that a namespace is always in its namespace ID representation.
 * This allows `'default'` to be used interchangeably with `undefined`.
 */ const normalizeNamespace = ( namespace ? ) => { if ( namespace === ALL_NAMESPACES_STRING ) { throw SavedObjectsErrorHelpers . createBadRequestError ( '"options.namespace" cannot be "*"' ) ; } else if ( namespace === undefined ) { return namespace ; } else { return SavedObjectsUtils . namespaceStringToId ( namespace ) ; } } ; /**
 * Extracts the contents of a decorated error to return the attributes for bulk operations.
 */ const errorContent = ( error ) => error . output . payload ; const unique = ( array ) => [ ... new Set ( array ) ] ;