import { pick , throttle , cloneDeep } from 'lodash' ; import { resolve as resolveUrl } from 'url' ; import type { PublicMethodsOf } from '@osd/utility-types' ; import { SavedObject , SavedObjectReference , SavedObjectsClientContract as SavedObjectsApi , SavedObjectsFindOptions as SavedObjectFindOptionsServer , SavedObjectsMigrationVersion , } from '../../server' ; import { SimpleSavedObject } from './simple_saved_object' ; import { HttpFetchOptions , HttpSetup } from '../http' ; type SavedObjectsFindOptions = Omit < SavedObjectFindOptionsServer , 'sortOrder' | 'rootSearchFields' | 'typeToNamespacesMap' > ; type PromiseType < T extends Promise < any > > = T extends Promise < infer U > ? U : never ; public export interface SavedObjectsCreateOptions { /**
     * (Not recommended) Specify an id instead of having the saved objects service generate one for you.
     */ id ? : string ; /** If a document with the given `id` already exists, overwrite it's contents (default=false). */ overwrite ? : boolean ; /** {@inheritDoc SavedObjectsMigrationVersion} */ migrationVersion ? : SavedObjectsMigrationVersion ; references ? : SavedObjectReference [ ] ; } param type param attributes public export interface SavedObjectsBulkCreateObject < T = unknown > extends SavedObjectsCreateOptions { type : string ; attributes : T ; } public export interface SavedObjectsBulkCreateOptions { /** If a document with the given `id` already exists, overwrite it's contents (default=false). */ overwrite ? : boolean ; } public export interface SavedObjectsBulkUpdateObject < T = unknown > { type : string ; id : string ; attributes : T ; version ? : string ; references ? : SavedObjectReference [ ] ; } public export interface SavedObjectsBulkUpdateOptions { namespace ? : string ; } public export interface SavedObjectsUpdateOptions { version ? : string ; /** {@inheritDoc SavedObjectsMigrationVersion} */ migrationVersion ? : SavedObjectsMigrationVersion ; references ? : SavedObjectReference [ ] ; } public export interface SavedObjectsBatchResponse < T = unknown > { savedObjects : Array < SimpleSavedObject < T > > ; } public export interface SavedObjectsDeleteOptions { /** Force deletion of an object that exists in multiple namespaces */ force ? : boolean ; } public export interface SavedObjectsFindResponsePublic < T = unknown > extends SavedObjectsBatchResponse < T > { total : number ; perPage : number ; page : number ; } interface BatchQueueEntry { type : string ; id : string ; resolve : < T = unknown > ( value : SimpleSavedObject < T > | SavedObject < T > ) => void ; reject : ( reason ? : any ) => void ; } const join = ( ... uriComponents ) => uriComponents . filter ( ( comp ) : comp is string => Boolean ( comp ) ) . map ( encodeURIComponent ) . join ( '/' ) ; type { integer } const BATCH_INTERVAL = 100 ; const API_BASE_URL = '/api/saved_objects/' ; /**
 * SavedObjectsClientContract as implemented by the  SavedObjectsClient public export type SavedObjectsClientContract = PublicMethodsOf < SavedObjectsClient > ; interface ObjectTypeAndId { id : string ; type : string ; } const getObjectsToFetch = ( queue ) : ObjectTypeAndId [ ] => { const objects = [ ] ; const inserted = new Set < string > ( ) ; queue . forEach ( ( { id , type } ) => { if ( ! inserted . has ( `${ type }|${ id }` ) ) { objects . push ( { id , type } ) ; inserted . add ( `${ type }|${ id }` ) ; } } ) ; return objects ; } ; public export class SavedObjectsClient { private http : HttpSetup ; private batchQueue : BatchQueueEntry [ ] ; /**
     * Throttled processing of get requests into bulk requests at 100ms interval
     */ private processBatchQueue = throttle ( async ( ) => { const queue = [ ... this . batchQueue ] ; this . batchQueue = [ ] ; try { const objectsToFetch = getObjectsToFetch ( queue ) ; const { saved_objects : savedObjects } = await this . performBulkGet ( objectsToFetch ) ; queue . forEach ( ( queueItem ) => { const foundObject = savedObjects . find ( ( savedObject ) => { return savedObject . id === queueItem . id && savedObject . type === queueItem . type ; } ) ; if ( foundObject ) { queueItem . resolve ( this . createSavedObject ( cloneDeep ( foundObject ) ) ) ; } else { queueItem . resolve ( this . createSavedObject ( pick ( queueItem , [ 'id' , 'type' ] ) as SavedObject ) ) ; } } ) ; } catch ( err ) { queue . forEach ( ( queueItem ) => { queueItem . reject ( err ) ; } ) ; } } , BATCH_INTERVAL , { leading : false } ) ; internal constructor ( http ) { this . http = http ; this . batchQueue = [ ] ; } param type param attributes param options returns public create = < T = unknown > ( type , attributes , options = { } ) : Promise < SimpleSavedObject < T > > => { if ( ! type || ! attributes ) { return Promise . reject ( new Error ( 'requires type and attributes' ) ) ; } const path = this . getPath ( [ type , options . id ] ) ; const query = { overwrite : options . overwrite , } ; const createRequest = this . savedObjectsFetch ( path , { method : 'POST' , query , body : JSON . stringify ( { attributes , migrationVersion : options . migrationVersion , references : options . references , } ) , } ) ; return createRequest . then ( ( resp ) => this . createSavedObject ( resp ) ) ; } ; param { array } objects param { object } options property returns public bulkCreate = ( objects = [ ] , options = { overwrite : false } ) => { const path = this . getPath ( [ '_bulk_create' ] ) ; const query = { overwrite : options . overwrite } ; const request = this . savedObjectsFetch ( path , { method : 'POST' , query , body : JSON . stringify ( objects ) , } ) ; return request . then ( ( resp ) => { resp . saved_objects = resp . saved_objects . map ( ( d ) => this . createSavedObject ( d ) ) ; return renameKeys < PromiseType < ReturnType < SavedObjectsApi [ 'bulkCreate' ] > > , SavedObjectsBatchResponse > ( { saved_objects : 'savedObjects' } , resp ) as SavedObjectsBatchResponse ; } ) ; } ; param type param id returns public delete = ( type , id , options ? ) : ReturnType < SavedObjectsApi [ 'delete' ] > => { if ( ! type || ! id ) { return Promise . reject ( new Error ( 'requires type and id' ) ) ; } const query = { force : ! ! options ?. force , } ; return this . savedObjectsFetch ( this . getPath ( [ type , id ] ) , { method : 'DELETE' , query } ) ; } ; param { object } options property property property property property property property returns public find = < T = unknown > ( options ) : Promise < SavedObjectsFindResponsePublic < T > > => { const path = this . getPath ( [ '_find' ] ) ; const renameMap = { defaultSearchOperator : 'default_search_operator' , fields : 'fields' , hasReference : 'has_reference' , page : 'page' , perPage : 'per_page' , search : 'search' , searchFields : 'search_fields' , sortField : 'sort_field' , type : 'type' , filter : 'filter' , namespaces : 'namespaces' , preference : 'preference' , } ; const renamedQuery = renameKeys < SavedObjectsFindOptions , any > ( renameMap , options ) ; const query = pick . apply ( null , [ renamedQuery , ... Object . values < string > ( renameMap ) ] ) ; const request = this . savedObjectsFetch ( path , { method : 'GET' , query , } ) ; return request . then ( ( resp ) => { return renameKeys < PromiseType < ReturnType < SavedObjectsApi [ 'find' ] > > , SavedObjectsFindResponsePublic > ( { saved_objects : 'savedObjects' , total : 'total' , per_page : 'perPage' , page : 'page' , } , { ... resp , saved_objects : resp . saved_objects . map ( ( d ) => this . createSavedObject ( d ) ) , } ) as SavedObjectsFindResponsePublic < T > ; } ) ; } ; param { string } type param { string } id returns public get = < T = unknown > ( type , id ) : Promise < SimpleSavedObject < T > > => { if ( ! type || ! id ) { return Promise . reject ( new Error ( 'requires type and id' ) ) ; } return new Promise ( ( resolve , reject ) => { this . batchQueue . push ( { type , id , resolve , reject } as BatchQueueEntry ) ; this . processBatchQueue ( ) ; } ) ; } ; param { array } objects returns example public bulkGet = ( objects = [ ] ) => { const filteredObjects = objects . map ( ( obj ) => pick ( obj , [ 'id' , 'type' ] ) ) ; return this . performBulkGet ( filteredObjects ) . then ( ( resp ) => { resp . saved_objects = resp . saved_objects . map ( ( d ) => this . createSavedObject ( d ) ) ; return renameKeys < PromiseType < ReturnType < SavedObjectsApi [ 'bulkGet' ] > > , SavedObjectsBatchResponse > ( { saved_objects : 'savedObjects' } , resp ) as SavedObjectsBatchResponse ; } ) ; } ; private async performBulkGet ( objects ) { const path = this . getPath ( [ '_bulk_get' ] ) ; const request = this . savedObjectsFetch ( path , { method : 'POST' , body : JSON . stringify ( objects ) , } ) ; return request ; } param { string } type param { string } id param { object } attributes param { object } options prop prop returns public update < T = unknown > ( type , id , attributes , { version , migrationVersion , references } = { } ) { if ( ! type || ! id || ! attributes ) { return Promise . reject ( new Error ( 'requires type, id and attributes' ) ) ; } const path = this . getPath ( [ type , id ] ) ; const body = { attributes , migrationVersion , references , version , } ; return this . savedObjectsFetch ( path , { method : 'PUT' , body : JSON . stringify ( body ) , } ) . then ( ( resp ) => { return this . createSavedObject ( resp ) ; } ) ; } param { array } objects returns public bulkUpdate < T = unknown > ( objects = [ ] ) { const path = this . getPath ( [ '_bulk_update' ] ) ; return this . savedObjectsFetch ( path , { method : 'PUT' , body : JSON . stringify ( objects ) , } ) . then ( ( resp ) => { resp . saved_objects = resp . saved_objects . map ( ( d ) => this . createSavedObject ( d ) ) ; return renameKeys < PromiseType < ReturnType < SavedObjectsApi [ 'bulkUpdate' ] > > , SavedObjectsBatchResponse > ( { saved_objects : 'savedObjects' } , resp ) as SavedObjectsBatchResponse ; } ) ; } private createSavedObject < T = unknown > ( options ) { return new SimpleSavedObject ( this , options ) ; } private getPath ( path ) { return resolveUrl ( API_BASE_URL , join ( ... path ) ) ; } /**
     * To ensure we don't break backwards compatibility, savedObjectsFetch keeps
     * the old kfetch error format of `{res: {status: number}}` whereas `http.fetch`
     * uses `{response: {status: number}}`.
     */ private savedObjectsFetch ( path , { method , query , body } ) { return this . http . fetch ( path , { method , query , body } ) ; } } param keysMap param obj const renameKeys = < T extends Record < string , any > , U extends Record < string , any > > ( keysMap , obj ) => Object . keys ( obj ) . reduce ( ( acc , key ) => { return { ... acc , ... { [ keysMap [ key ] || key ] : obj [ key ] } , } ; } , { } ) ;