diff --git a/docs/development/core/server/kibana-plugin-server.coresetup.md b/docs/development/core/server/kibana-plugin-server.coresetup.md
index c51459bc41a43..1ad1641beb83b 100644
--- a/docs/development/core/server/kibana-plugin-server.coresetup.md
+++ b/docs/development/core/server/kibana-plugin-server.coresetup.md
@@ -19,5 +19,6 @@ export interface CoreSetup
| [context](./kibana-plugin-server.coresetup.context.md) | ContextSetup
| [ContextSetup](./kibana-plugin-server.contextsetup.md) |
| [elasticsearch](./kibana-plugin-server.coresetup.elasticsearch.md) | ElasticsearchServiceSetup
| [ElasticsearchServiceSetup](./kibana-plugin-server.elasticsearchservicesetup.md) |
| [http](./kibana-plugin-server.coresetup.http.md) | HttpServiceSetup
| [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md) |
+| [savedObjects](./kibana-plugin-server.coresetup.savedobjects.md) | SavedObjectsServiceSetup
| [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) |
| [uiSettings](./kibana-plugin-server.coresetup.uisettings.md) | UiSettingsServiceSetup
| [UiSettingsServiceSetup](./kibana-plugin-server.uisettingsservicesetup.md) |
diff --git a/docs/development/core/server/kibana-plugin-server.coresetup.savedobjects.md b/docs/development/core/server/kibana-plugin-server.coresetup.savedobjects.md
new file mode 100644
index 0000000000000..96acc1ffce194
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.coresetup.savedobjects.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CoreSetup](./kibana-plugin-server.coresetup.md) > [savedObjects](./kibana-plugin-server.coresetup.savedobjects.md)
+
+## CoreSetup.savedObjects property
+
+[SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md)
+
+Signature:
+
+```typescript
+savedObjects: SavedObjectsServiceSetup;
+```
diff --git a/docs/development/core/server/kibana-plugin-server.corestart.md b/docs/development/core/server/kibana-plugin-server.corestart.md
index da80ae8be93af..a675c45a29820 100644
--- a/docs/development/core/server/kibana-plugin-server.corestart.md
+++ b/docs/development/core/server/kibana-plugin-server.corestart.md
@@ -11,3 +11,10 @@ Context passed to the plugins `start` method.
```typescript
export interface CoreStart
```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [savedObjects](./kibana-plugin-server.corestart.savedobjects.md) | SavedObjectsServiceStart
| [SavedObjectsServiceStart](./kibana-plugin-server.savedobjectsservicestart.md) |
+
diff --git a/docs/development/core/server/kibana-plugin-server.corestart.savedobjects.md b/docs/development/core/server/kibana-plugin-server.corestart.savedobjects.md
new file mode 100644
index 0000000000000..531b04e9eed07
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.corestart.savedobjects.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [CoreStart](./kibana-plugin-server.corestart.md) > [savedObjects](./kibana-plugin-server.corestart.savedobjects.md)
+
+## CoreStart.savedObjects property
+
+[SavedObjectsServiceStart](./kibana-plugin-server.savedobjectsservicestart.md)
+
+Signature:
+
+```typescript
+savedObjects: SavedObjectsServiceStart;
+```
diff --git a/docs/development/core/server/kibana-plugin-server.isavedobjectsrepository.md b/docs/development/core/server/kibana-plugin-server.isavedobjectsrepository.md
new file mode 100644
index 0000000000000..7863d1b0ca49d
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.isavedobjectsrepository.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [ISavedObjectsRepository](./kibana-plugin-server.isavedobjectsrepository.md)
+
+## ISavedObjectsRepository type
+
+See [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md)
+
+Signature:
+
+```typescript
+export declare type ISavedObjectsRepository = Pick;
+```
diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md
index 2bcf141f2db73..17c5136fdc318 100644
--- a/docs/development/core/server/kibana-plugin-server.md
+++ b/docs/development/core/server/kibana-plugin-server.md
@@ -22,6 +22,7 @@ The plugin integrates with the core system via lifecycle events: `setup`
| [KibanaRequest](./kibana-plugin-server.kibanarequest.md) | Kibana specific abstraction for an incoming request. |
| [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) | |
| [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) | |
+| [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) | |
| [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md) | Serves the same purpose as "normal" ClusterClient
but exposes additional callAsCurrentUser
method that doesn't use credentials of the Kibana internal user (as callAsInternalUser
does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API.See [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md). |
## Enumerations
@@ -98,6 +99,7 @@ The plugin integrates with the core system via lifecycle events: `setup`
| [SavedObjectsClientProviderOptions](./kibana-plugin-server.savedobjectsclientprovideroptions.md) | Options to control the creation of the Saved Objects Client. |
| [SavedObjectsClientWrapperOptions](./kibana-plugin-server.savedobjectsclientwrapperoptions.md) | Options passed to each SavedObjectsClientWrapperFactory to aid in creating the wrapper instance. |
| [SavedObjectsCreateOptions](./kibana-plugin-server.savedobjectscreateoptions.md) | |
+| [SavedObjectsDeleteByNamespaceOptions](./kibana-plugin-server.savedobjectsdeletebynamespaceoptions.md) | |
| [SavedObjectsDeleteOptions](./kibana-plugin-server.savedobjectsdeleteoptions.md) | |
| [SavedObjectsExportOptions](./kibana-plugin-server.savedobjectsexportoptions.md) | Options controlling the export operation. |
| [SavedObjectsExportResultDetails](./kibana-plugin-server.savedobjectsexportresultdetails.md) | Structure of the export result details entry |
@@ -111,10 +113,13 @@ The plugin integrates with the core system via lifecycle events: `setup`
| [SavedObjectsImportRetry](./kibana-plugin-server.savedobjectsimportretry.md) | Describes a retry operation for importing a saved object. |
| [SavedObjectsImportUnknownError](./kibana-plugin-server.savedobjectsimportunknownerror.md) | Represents a failure to import due to an unknown reason. |
| [SavedObjectsImportUnsupportedTypeError](./kibana-plugin-server.savedobjectsimportunsupportedtypeerror.md) | Represents a failure to import due to having an unsupported saved object type. |
+| [SavedObjectsIncrementCounterOptions](./kibana-plugin-server.savedobjectsincrementcounteroptions.md) | |
| [SavedObjectsMigrationLogger](./kibana-plugin-server.savedobjectsmigrationlogger.md) | |
| [SavedObjectsMigrationVersion](./kibana-plugin-server.savedobjectsmigrationversion.md) | Information about the migrations that have been applied to this SavedObject. When Kibana starts up, KibanaMigrator detects outdated documents and migrates them based on this value. For each migration that has been applied, the plugin's name is used as a key and the latest migration version as the value. |
| [SavedObjectsRawDoc](./kibana-plugin-server.savedobjectsrawdoc.md) | A raw document as represented directly in the saved object index. |
| [SavedObjectsResolveImportErrorsOptions](./kibana-plugin-server.savedobjectsresolveimporterrorsoptions.md) | Options to control the "resolve import" operation. |
+| [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceSetup API exposes methods for creating and registering Saved Object client wrappers. |
+| [SavedObjectsServiceStart](./kibana-plugin-server.savedobjectsservicestart.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceStart API provides a scoped Saved Objects client for interacting with Saved Objects. |
| [SavedObjectsUpdateOptions](./kibana-plugin-server.savedobjectsupdateoptions.md) | |
| [SavedObjectsUpdateResponse](./kibana-plugin-server.savedobjectsupdateresponse.md) | |
| [SessionCookieValidationResult](./kibana-plugin-server.sessioncookievalidationresult.md) | Return type from a function to validate cookie contents. |
@@ -152,6 +157,7 @@ The plugin integrates with the core system via lifecycle events: `setup`
| [IClusterClient](./kibana-plugin-server.iclusterclient.md) | Represents an Elasticsearch cluster API client and allows to call API on behalf of the internal Kibana user and the actual user that is derived from the request headers (via asScoped(...)
).See [ClusterClient](./kibana-plugin-server.clusterclient.md). |
| [IContextProvider](./kibana-plugin-server.icontextprovider.md) | A function that returns a context value for a specific key of given context type. |
| [IsAuthenticated](./kibana-plugin-server.isauthenticated.md) | Return authentication status for a request. |
+| [ISavedObjectsRepository](./kibana-plugin-server.isavedobjectsrepository.md) | See [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) |
| [IScopedClusterClient](./kibana-plugin-server.iscopedclusterclient.md) | Serves the same purpose as "normal" ClusterClient
but exposes additional callAsCurrentUser
method that doesn't use credentials of the Kibana internal user (as callAsInternalUser
does) to request Elasticsearch API, but rather passes HTTP headers extracted from the current user request to the API.See [ScopedClusterClient](./kibana-plugin-server.scopedclusterclient.md). |
| [KibanaRequestRouteOptions](./kibana-plugin-server.kibanarequestrouteoptions.md) | Route options: If 'GET' or 'OPTIONS' method, body options won't be returned. |
| [KibanaResponseFactory](./kibana-plugin-server.kibanaresponsefactory.md) | Creates an object containing request response payload, HTTP headers, error details, and other data transmitted to the client. |
@@ -180,6 +186,7 @@ The plugin integrates with the core system via lifecycle events: `setup`
| [SavedObjectAttribute](./kibana-plugin-server.savedobjectattribute.md) | Type definition for a Saved Object attribute value |
| [SavedObjectAttributeSingle](./kibana-plugin-server.savedobjectattributesingle.md) | Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-server.savedobjectattribute.md) |
| [SavedObjectsClientContract](./kibana-plugin-server.savedobjectsclientcontract.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing plugin state.\#\# SavedObjectsClient errorsSince the SavedObjectsClient has its hands in everything we are a little paranoid about the way we present errors back to to application code. Ideally, all errors will be either:1. Caused by bad implementation (ie. undefined is not a function) and as such unpredictable 2. An error that has been classified and decorated appropriately by the decorators in [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md)Type 1 errors are inevitable, but since all expected/handle-able errors should be Type 2 the isXYZError()
helpers exposed at SavedObjectsErrorHelpers
should be used to understand and manage error responses from the SavedObjectsClient
.Type 2 errors are decorated versions of the source error, so if the elasticsearch client threw an error it will be decorated based on its type. That means that rather than looking for error.body.error.type
or doing substring checks on error.body.error.reason
, just use the helpers to understand the meaning of the error:\`\`\`js if (SavedObjectsErrorHelpers.isNotFoundError(error)) { // handle 404 }if (SavedObjectsErrorHelpers.isNotAuthorizedError(error)) { // 401 handling should be automatic, but in case you wanted to know }// always rethrow the error unless you handle it throw error; \`\`\`\#\#\# 404s from missing indexFrom the perspective of application code and APIs the SavedObjectsClient is a black box that persists objects. One of the internal details that users have no control over is that we use an elasticsearch index for persistance and that index might be missing.At the time of writing we are in the process of transitioning away from the operating assumption that the SavedObjects index is always available. Part of this transition is handling errors resulting from an index missing. These used to trigger a 500 error in most cases, and in others cause 404s with different error messages.From my (Spencer) perspective, a 404 from the SavedObjectsApi is a 404; The object the request/call was targeting could not be found. This is why \#14141 takes special care to ensure that 404 errors are generic and don't distinguish between index missing or document missing.\#\#\# 503s from missing indexUnlike all other methods, create requests are supposed to succeed even when the Kibana index does not exist because it will be automatically created by elasticsearch. When that is not the case it is because Elasticsearch's action.auto_create_index
setting prevents it from being created automatically so we throw a special 503 with the intention of informing the user that their Elasticsearch settings need to be updated.See [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) See [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) |
+| [SavedObjectsClientFactory](./kibana-plugin-server.savedobjectsclientfactory.md) | Describes the factory used to create instances of the Saved Objects Client. |
| [SavedObjectsClientWrapperFactory](./kibana-plugin-server.savedobjectsclientwrapperfactory.md) | Describes the factory used to create instances of Saved Objects Client Wrappers. |
| [UiSettingsType](./kibana-plugin-server.uisettingstype.md) | UI element type to represent the settings. |
diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclient._constructor_.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclient._constructor_.md
deleted file mode 100644
index 0bcca3ec57b54..0000000000000
--- a/docs/development/core/server/kibana-plugin-server.savedobjectsclient._constructor_.md
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) > [(constructor)](./kibana-plugin-server.savedobjectsclient._constructor_.md)
-
-## SavedObjectsClient.(constructor)
-
-Constructs a new instance of the `SavedObjectsClient` class
-
-Signature:
-
-```typescript
-constructor(repository: SavedObjectsRepository);
-```
-
-## Parameters
-
-| Parameter | Type | Description |
-| --- | --- | --- |
-| repository | SavedObjectsRepository
| |
-
diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.md
index cc00934a1e1fd..17d29bb912c83 100644
--- a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.md
+++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.md
@@ -4,19 +4,12 @@
## SavedObjectsClient class
-
Signature:
```typescript
export declare class SavedObjectsClient
```
-## Constructors
-
-| Constructor | Modifiers | Description |
-| --- | --- | --- |
-| [(constructor)(repository)](./kibana-plugin-server.savedobjectsclient._constructor_.md) | | Constructs a new instance of the SavedObjectsClient
class |
-
## Properties
| Property | Modifiers | Type | Description |
@@ -37,3 +30,7 @@ export declare class SavedObjectsClient
| [get(type, id, options)](./kibana-plugin-server.savedobjectsclient.get.md) | | Retrieves a single object |
| [update(type, id, attributes, options)](./kibana-plugin-server.savedobjectsclient.update.md) | | Updates an SavedObject |
+## Remarks
+
+The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `SavedObjectsClient` class.
+
diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclientfactory.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclientfactory.md
new file mode 100644
index 0000000000000..9e30759720680
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclientfactory.md
@@ -0,0 +1,15 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsClientFactory](./kibana-plugin-server.savedobjectsclientfactory.md)
+
+## SavedObjectsClientFactory type
+
+Describes the factory used to create instances of the Saved Objects Client.
+
+Signature:
+
+```typescript
+export declare type SavedObjectsClientFactory = ({ request, }: {
+ request: Request;
+}) => SavedObjectsClientContract;
+```
diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsdeletebynamespaceoptions.md b/docs/development/core/server/kibana-plugin-server.savedobjectsdeletebynamespaceoptions.md
new file mode 100644
index 0000000000000..df4ce1b4b8428
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.savedobjectsdeletebynamespaceoptions.md
@@ -0,0 +1,19 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsDeleteByNamespaceOptions](./kibana-plugin-server.savedobjectsdeletebynamespaceoptions.md)
+
+## SavedObjectsDeleteByNamespaceOptions interface
+
+
+Signature:
+
+```typescript
+export interface SavedObjectsDeleteByNamespaceOptions extends SavedObjectsBaseOptions
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [refresh](./kibana-plugin-server.savedobjectsdeletebynamespaceoptions.refresh.md) | MutatingOperationRefreshSetting
| The Elasticsearch Refresh setting for this operation |
+
diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsdeletebynamespaceoptions.refresh.md b/docs/development/core/server/kibana-plugin-server.savedobjectsdeletebynamespaceoptions.refresh.md
new file mode 100644
index 0000000000000..2332520ac388f
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.savedobjectsdeletebynamespaceoptions.refresh.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsDeleteByNamespaceOptions](./kibana-plugin-server.savedobjectsdeletebynamespaceoptions.md) > [refresh](./kibana-plugin-server.savedobjectsdeletebynamespaceoptions.refresh.md)
+
+## SavedObjectsDeleteByNamespaceOptions.refresh property
+
+The Elasticsearch Refresh setting for this operation
+
+Signature:
+
+```typescript
+refresh?: MutatingOperationRefreshSetting;
+```
diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.md b/docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.md
new file mode 100644
index 0000000000000..38ee40157888f
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.md
@@ -0,0 +1,20 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsIncrementCounterOptions](./kibana-plugin-server.savedobjectsincrementcounteroptions.md)
+
+## SavedObjectsIncrementCounterOptions interface
+
+
+Signature:
+
+```typescript
+export interface SavedObjectsIncrementCounterOptions extends SavedObjectsBaseOptions
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [migrationVersion](./kibana-plugin-server.savedobjectsincrementcounteroptions.migrationversion.md) | SavedObjectsMigrationVersion
| |
+| [refresh](./kibana-plugin-server.savedobjectsincrementcounteroptions.refresh.md) | MutatingOperationRefreshSetting
| The Elasticsearch Refresh setting for this operation |
+
diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.migrationversion.md b/docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.migrationversion.md
new file mode 100644
index 0000000000000..3b80dea4fecde
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.migrationversion.md
@@ -0,0 +1,11 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsIncrementCounterOptions](./kibana-plugin-server.savedobjectsincrementcounteroptions.md) > [migrationVersion](./kibana-plugin-server.savedobjectsincrementcounteroptions.migrationversion.md)
+
+## SavedObjectsIncrementCounterOptions.migrationVersion property
+
+Signature:
+
+```typescript
+migrationVersion?: SavedObjectsMigrationVersion;
+```
diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.refresh.md b/docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.refresh.md
new file mode 100644
index 0000000000000..acd8d6f0916f9
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.savedobjectsincrementcounteroptions.refresh.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsIncrementCounterOptions](./kibana-plugin-server.savedobjectsincrementcounteroptions.md) > [refresh](./kibana-plugin-server.savedobjectsincrementcounteroptions.refresh.md)
+
+## SavedObjectsIncrementCounterOptions.refresh property
+
+The Elasticsearch Refresh setting for this operation
+
+Signature:
+
+```typescript
+refresh?: MutatingOperationRefreshSetting;
+```
diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkcreate.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkcreate.md
new file mode 100644
index 0000000000000..003bc6ac72466
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkcreate.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [bulkCreate](./kibana-plugin-server.savedobjectsrepository.bulkcreate.md)
+
+## SavedObjectsRepository.bulkCreate() method
+
+Creates multiple documents at once
+
+Signature:
+
+```typescript
+bulkCreate(objects: Array>, options?: SavedObjectsCreateOptions): Promise>;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| objects | Array<SavedObjectsBulkCreateObject<T>>
| |
+| options | SavedObjectsCreateOptions
| |
+
+Returns:
+
+`Promise>`
+
+{promise} - {saved\_objects: \[\[{ id, type, version, references, attributes, error: { message } }\]}
+
diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkget.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkget.md
new file mode 100644
index 0000000000000..605984d5dea30
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkget.md
@@ -0,0 +1,31 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [bulkGet](./kibana-plugin-server.savedobjectsrepository.bulkget.md)
+
+## SavedObjectsRepository.bulkGet() method
+
+Returns an array of objects by id
+
+Signature:
+
+```typescript
+bulkGet(objects?: SavedObjectsBulkGetObject[], options?: SavedObjectsBaseOptions): Promise>;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| objects | SavedObjectsBulkGetObject[]
| |
+| options | SavedObjectsBaseOptions
| |
+
+Returns:
+
+`Promise>`
+
+{promise} - { saved\_objects: \[{ id, type, version, attributes }\] }
+
+## Example
+
+bulkGet(\[ { id: 'one', type: 'config' }, { id: 'foo', type: 'index-pattern' } \])
+
diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkupdate.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkupdate.md
new file mode 100644
index 0000000000000..52a73c83b4c3a
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.bulkupdate.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [bulkUpdate](./kibana-plugin-server.savedobjectsrepository.bulkupdate.md)
+
+## SavedObjectsRepository.bulkUpdate() method
+
+Updates multiple objects in bulk
+
+Signature:
+
+```typescript
+bulkUpdate(objects: Array>, options?: SavedObjectsBulkUpdateOptions): Promise>;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| objects | Array<SavedObjectsBulkUpdateObject<T>>
| |
+| options | SavedObjectsBulkUpdateOptions
| |
+
+Returns:
+
+`Promise>`
+
+{promise} - {saved\_objects: \[\[{ id, type, version, references, attributes, error: { message } }\]}
+
diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.create.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.create.md
new file mode 100644
index 0000000000000..3a731629156e2
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.create.md
@@ -0,0 +1,28 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [create](./kibana-plugin-server.savedobjectsrepository.create.md)
+
+## SavedObjectsRepository.create() method
+
+Persists an object
+
+Signature:
+
+```typescript
+create(type: string, attributes: T, options?: SavedObjectsCreateOptions): Promise>;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| type | string
| |
+| attributes | T
| |
+| options | SavedObjectsCreateOptions
| |
+
+Returns:
+
+`Promise>`
+
+{promise} - { id, type, version, attributes }
+
diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.delete.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.delete.md
new file mode 100644
index 0000000000000..52c36d2da162d
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.delete.md
@@ -0,0 +1,28 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [delete](./kibana-plugin-server.savedobjectsrepository.delete.md)
+
+## SavedObjectsRepository.delete() method
+
+Deletes an object
+
+Signature:
+
+```typescript
+delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| type | string
| |
+| id | string
| |
+| options | SavedObjectsDeleteOptions
| |
+
+Returns:
+
+`Promise<{}>`
+
+{promise}
+
diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.deletebynamespace.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.deletebynamespace.md
new file mode 100644
index 0000000000000..ab6eb30e664f1
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.deletebynamespace.md
@@ -0,0 +1,27 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [deleteByNamespace](./kibana-plugin-server.savedobjectsrepository.deletebynamespace.md)
+
+## SavedObjectsRepository.deleteByNamespace() method
+
+Deletes all objects from the provided namespace.
+
+Signature:
+
+```typescript
+deleteByNamespace(namespace: string, options?: SavedObjectsDeleteByNamespaceOptions): Promise;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| namespace | string
| |
+| options | SavedObjectsDeleteByNamespaceOptions
| |
+
+Returns:
+
+`Promise`
+
+{promise} - { took, timed\_out, total, deleted, batches, version\_conflicts, noops, retries, failures }
+
diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.find.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.find.md
new file mode 100644
index 0000000000000..3c2855ed9a50c
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.find.md
@@ -0,0 +1,24 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [find](./kibana-plugin-server.savedobjectsrepository.find.md)
+
+## SavedObjectsRepository.find() method
+
+Signature:
+
+```typescript
+find({ search, defaultSearchOperator, searchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespace, type, filter, }: SavedObjectsFindOptions): Promise>;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| { search, defaultSearchOperator, searchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespace, type, filter, } | SavedObjectsFindOptions
| |
+
+Returns:
+
+`Promise>`
+
+{promise} - { saved\_objects: \[{ id, type, version, attributes }\], total, per\_page, page }
+
diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.get.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.get.md
new file mode 100644
index 0000000000000..dd1d81f225937
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.get.md
@@ -0,0 +1,28 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [get](./kibana-plugin-server.savedobjectsrepository.get.md)
+
+## SavedObjectsRepository.get() method
+
+Gets a single object
+
+Signature:
+
+```typescript
+get(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| type | string
| |
+| id | string
| |
+| options | SavedObjectsBaseOptions
| |
+
+Returns:
+
+`Promise>`
+
+{promise} - { id, type, version, attributes }
+
diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.incrementcounter.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.incrementcounter.md
new file mode 100644
index 0000000000000..f20e9a73d99a1
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.incrementcounter.md
@@ -0,0 +1,43 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [incrementCounter](./kibana-plugin-server.savedobjectsrepository.incrementcounter.md)
+
+## SavedObjectsRepository.incrementCounter() method
+
+Increases a counter field by one. Creates the document if one doesn't exist for the given id.
+
+Signature:
+
+```typescript
+incrementCounter(type: string, id: string, counterFieldName: string, options?: SavedObjectsIncrementCounterOptions): Promise<{
+ id: string;
+ type: string;
+ updated_at: string;
+ references: any;
+ version: string;
+ attributes: any;
+ }>;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| type | string
| |
+| id | string
| |
+| counterFieldName | string
| |
+| options | SavedObjectsIncrementCounterOptions
| |
+
+Returns:
+
+`Promise<{
+ id: string;
+ type: string;
+ updated_at: string;
+ references: any;
+ version: string;
+ attributes: any;
+ }>`
+
+{promise}
+
diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.md
new file mode 100644
index 0000000000000..019363776590c
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.md
@@ -0,0 +1,31 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md)
+
+## SavedObjectsRepository class
+
+Signature:
+
+```typescript
+export declare class SavedObjectsRepository
+```
+
+## Methods
+
+| Method | Modifiers | Description |
+| --- | --- | --- |
+| [bulkCreate(objects, options)](./kibana-plugin-server.savedobjectsrepository.bulkcreate.md) | | Creates multiple documents at once |
+| [bulkGet(objects, options)](./kibana-plugin-server.savedobjectsrepository.bulkget.md) | | Returns an array of objects by id |
+| [bulkUpdate(objects, options)](./kibana-plugin-server.savedobjectsrepository.bulkupdate.md) | | Updates multiple objects in bulk |
+| [create(type, attributes, options)](./kibana-plugin-server.savedobjectsrepository.create.md) | | Persists an object |
+| [delete(type, id, options)](./kibana-plugin-server.savedobjectsrepository.delete.md) | | Deletes an object |
+| [deleteByNamespace(namespace, options)](./kibana-plugin-server.savedobjectsrepository.deletebynamespace.md) | | Deletes all objects from the provided namespace. |
+| [find({ search, defaultSearchOperator, searchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespace, type, filter, })](./kibana-plugin-server.savedobjectsrepository.find.md) | | |
+| [get(type, id, options)](./kibana-plugin-server.savedobjectsrepository.get.md) | | Gets a single object |
+| [incrementCounter(type, id, counterFieldName, options)](./kibana-plugin-server.savedobjectsrepository.incrementcounter.md) | | Increases a counter field by one. Creates the document if one doesn't exist for the given id. |
+| [update(type, id, attributes, options)](./kibana-plugin-server.savedobjectsrepository.update.md) | | Updates an object |
+
+## Remarks
+
+The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `SavedObjectsRepository` class.
+
diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.update.md b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.update.md
new file mode 100644
index 0000000000000..15890ab9211aa
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.savedobjectsrepository.update.md
@@ -0,0 +1,29 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsRepository](./kibana-plugin-server.savedobjectsrepository.md) > [update](./kibana-plugin-server.savedobjectsrepository.update.md)
+
+## SavedObjectsRepository.update() method
+
+Updates an object
+
+Signature:
+
+```typescript
+update(type: string, id: string, attributes: Partial, options?: SavedObjectsUpdateOptions): Promise>;
+```
+
+## Parameters
+
+| Parameter | Type | Description |
+| --- | --- | --- |
+| type | string
| |
+| id | string
| |
+| attributes | Partial<T>
| |
+| options | SavedObjectsUpdateOptions
| |
+
+Returns:
+
+`Promise>`
+
+{promise}
+
diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md
new file mode 100644
index 0000000000000..e787d737ada17
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) > [addClientWrapper](./kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md)
+
+## SavedObjectsServiceSetup.addClientWrapper property
+
+Add a client wrapper with the given priority.
+
+Signature:
+
+```typescript
+addClientWrapper: (priority: number, id: string, factory: SavedObjectsClientWrapperFactory) => void;
+```
diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createinternalrepository.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createinternalrepository.md
new file mode 100644
index 0000000000000..492aa1a2453a1
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createinternalrepository.md
@@ -0,0 +1,18 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) > [createInternalRepository](./kibana-plugin-server.savedobjectsservicesetup.createinternalrepository.md)
+
+## SavedObjectsServiceSetup.createInternalRepository property
+
+Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the internal Kibana user for authenticating with Elasticsearch.
+
+Signature:
+
+```typescript
+createInternalRepository: (extraTypes?: string[]) => ISavedObjectsRepository;
+```
+
+## Remarks
+
+The repository should only be used for creating and registering a client factory or client wrapper. Using the repository directly for interacting with Saved Objects is an anti-pattern. Use the Saved Objects client from the [SavedObjectsServiceStart\#getScopedClient](./kibana-plugin-server.savedobjectsservicestart.md) method or the [route handler context](./kibana-plugin-server.requesthandlercontext.md) instead.
+
diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createscopedrepository.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createscopedrepository.md
new file mode 100644
index 0000000000000..fc5aa40c21a20
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.createscopedrepository.md
@@ -0,0 +1,18 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) > [createScopedRepository](./kibana-plugin-server.savedobjectsservicesetup.createscopedrepository.md)
+
+## SavedObjectsServiceSetup.createScopedRepository property
+
+Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the credentials from the passed in request to authenticate with Elasticsearch.
+
+Signature:
+
+```typescript
+createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository;
+```
+
+## Remarks
+
+The repository should only be used for creating and registering a client factory or client wrapper. Using the repository directly for interacting with Saved Objects is an anti-pattern. Use the Saved Objects client from the [SavedObjectsServiceStart\#getScopedClient](./kibana-plugin-server.savedobjectsservicestart.md) method or the [route handler context](./kibana-plugin-server.requesthandlercontext.md) instead.
+
diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.md
new file mode 100644
index 0000000000000..dd97b45f590e2
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.md
@@ -0,0 +1,35 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md)
+
+## SavedObjectsServiceSetup interface
+
+Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceSetup API exposes methods for creating and registering Saved Object client wrappers.
+
+Signature:
+
+```typescript
+export interface SavedObjectsServiceSetup
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [addClientWrapper](./kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md) | (priority: number, id: string, factory: SavedObjectsClientWrapperFactory<KibanaRequest>) => void
| Add a client wrapper with the given priority. |
+| [createInternalRepository](./kibana-plugin-server.savedobjectsservicesetup.createinternalrepository.md) | (extraTypes?: string[]) => ISavedObjectsRepository
| Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the internal Kibana user for authenticating with Elasticsearch. |
+| [createScopedRepository](./kibana-plugin-server.savedobjectsservicesetup.createscopedrepository.md) | (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository
| Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the credentials from the passed in request to authenticate with Elasticsearch. |
+| [setClientFactory](./kibana-plugin-server.savedobjectsservicesetup.setclientfactory.md) | (customClientFactory: SavedObjectsClientFactory<KibanaRequest>) => void
| Set a default factory for creating Saved Objects clients. Only one client factory can be set, subsequent calls to this method will fail. |
+
+## Remarks
+
+Note: The Saved Object setup API's should only be used for creating and registering client wrappers. Constructing a Saved Objects client or repository for use within your own plugin won't have any of the registered wrappers applied and is considered an anti-pattern. Use the Saved Objects client from the [SavedObjectsServiceStart\#getScopedClient](./kibana-plugin-server.savedobjectsservicestart.md) method or the [route handler context](./kibana-plugin-server.requesthandlercontext.md) instead.
+
+When plugins access the Saved Objects client, a new client is created using the factory provided to `setClientFactory` and wrapped by all wrappers registered through `addClientWrapper`. To create a factory or wrapper, plugins will have to construct a Saved Objects client. First create a repository by calling `scopedRepository` or `internalRepository` and then use this repository as the argument to the [SavedObjectsClient](./kibana-plugin-server.savedobjectsclient.md) constructor.
+
+## Example
+
+import {SavedObjectsClient, CoreSetup} from 'src/core/server';
+
+export class Plugin() { setup: (core: CoreSetup) => { core.savedObjects.setClientFactory(({request: KibanaRequest}) => { return new SavedObjectsClient(core.savedObjects.scopedRepository(request)); }) } }
+
diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.setclientfactory.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.setclientfactory.md
new file mode 100644
index 0000000000000..544e0b9d5fa73
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.setclientfactory.md
@@ -0,0 +1,13 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceSetup](./kibana-plugin-server.savedobjectsservicesetup.md) > [setClientFactory](./kibana-plugin-server.savedobjectsservicesetup.setclientfactory.md)
+
+## SavedObjectsServiceSetup.setClientFactory property
+
+Set a default factory for creating Saved Objects clients. Only one client factory can be set, subsequent calls to this method will fail.
+
+Signature:
+
+```typescript
+setClientFactory: (customClientFactory: SavedObjectsClientFactory) => void;
+```
diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.getscopedclient.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.getscopedclient.md
new file mode 100644
index 0000000000000..e87979a124bdc
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.getscopedclient.md
@@ -0,0 +1,15 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceStart](./kibana-plugin-server.savedobjectsservicestart.md) > [getScopedClient](./kibana-plugin-server.savedobjectsservicestart.getscopedclient.md)
+
+## SavedObjectsServiceStart.getScopedClient property
+
+Creates a [Saved Objects client](./kibana-plugin-server.savedobjectsclientcontract.md) that uses the credentials from the passed in request to authenticate with Elasticsearch. If other plugins have registered Saved Objects client wrappers, these will be applied to extend the functionality of the client.
+
+A client that is already scoped to the incoming request is also exposed from the route handler context see [RequestHandlerContext](./kibana-plugin-server.requesthandlercontext.md).
+
+Signature:
+
+```typescript
+getScopedClient: (req: KibanaRequest, options?: SavedObjectsClientProviderOptions) => SavedObjectsClientContract;
+```
diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.md
new file mode 100644
index 0000000000000..5a869b3b6c1cb
--- /dev/null
+++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicestart.md
@@ -0,0 +1,20 @@
+
+
+[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [SavedObjectsServiceStart](./kibana-plugin-server.savedobjectsservicestart.md)
+
+## SavedObjectsServiceStart interface
+
+Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing and querying state. The SavedObjectsServiceStart API provides a scoped Saved Objects client for interacting with Saved Objects.
+
+Signature:
+
+```typescript
+export interface SavedObjectsServiceStart
+```
+
+## Properties
+
+| Property | Type | Description |
+| --- | --- | --- |
+| [getScopedClient](./kibana-plugin-server.savedobjectsservicestart.getscopedclient.md) | (req: KibanaRequest, options?: SavedObjectsClientProviderOptions) => SavedObjectsClientContract
| Creates a [Saved Objects client](./kibana-plugin-server.savedobjectsclientcontract.md) that uses the credentials from the passed in request to authenticate with Elasticsearch. If other plugins have registered Saved Objects client wrappers, these will be applied to extend the functionality of the client.A client that is already scoped to the incoming request is also exposed from the route handler context see [RequestHandlerContext](./kibana-plugin-server.requesthandlercontext.md). |
+
diff --git a/src/core/server/index.ts b/src/core/server/index.ts
index b537b4ac0a539..a54ada233bbc9 100644
--- a/src/core/server/index.ts
+++ b/src/core/server/index.ts
@@ -45,6 +45,7 @@ import { PluginsServiceSetup, PluginsServiceStart, PluginOpaqueId } from './plug
import { ContextSetup } from './context';
import { IUiSettingsClient, UiSettingsServiceSetup } from './ui_settings';
import { SavedObjectsClientContract } from './saved_objects/types';
+import { SavedObjectsServiceSetup, SavedObjectsServiceStart } from './saved_objects';
export { bootstrap } from './bootstrap';
export { ConfigPath, ConfigService, EnvironmentMode, PackageInfo } from './config';
@@ -149,6 +150,7 @@ export {
SavedObjectsClientProviderOptions,
SavedObjectsClientWrapperFactory,
SavedObjectsClientWrapperOptions,
+ SavedObjectsClientFactory,
SavedObjectsCreateOptions,
SavedObjectsErrorHelpers,
SavedObjectsExportOptions,
@@ -170,7 +172,13 @@ export {
SavedObjectsLegacyService,
SavedObjectsUpdateOptions,
SavedObjectsUpdateResponse,
+ SavedObjectsServiceStart,
+ SavedObjectsServiceSetup,
SavedObjectsDeleteOptions,
+ ISavedObjectsRepository,
+ SavedObjectsRepository,
+ SavedObjectsDeleteByNamespaceOptions,
+ SavedObjectsIncrementCounterOptions,
} from './saved_objects';
export {
@@ -238,6 +246,8 @@ export interface CoreSetup {
elasticsearch: ElasticsearchServiceSetup;
/** {@link HttpServiceSetup} */
http: HttpServiceSetup;
+ /** {@link SavedObjectsServiceSetup} */
+ savedObjects: SavedObjectsServiceSetup;
/** {@link UiSettingsServiceSetup} */
uiSettings: UiSettingsServiceSetup;
}
@@ -247,6 +257,9 @@ export interface CoreSetup {
*
* @public
*/
-export interface CoreStart {} // eslint-disable-line @typescript-eslint/no-empty-interface
+export interface CoreStart {
+ /** {@link SavedObjectsServiceStart} */
+ savedObjects: SavedObjectsServiceStart;
+}
export { ContextSetup, PluginsServiceSetup, PluginsServiceStart, PluginOpaqueId };
diff --git a/src/core/server/internal_types.ts b/src/core/server/internal_types.ts
index 1330c5aee64fd..d1a65c6f3437e 100644
--- a/src/core/server/internal_types.ts
+++ b/src/core/server/internal_types.ts
@@ -21,7 +21,10 @@ import { InternalElasticsearchServiceSetup } from './elasticsearch';
import { InternalHttpServiceSetup } from './http';
import { InternalUiSettingsServiceSetup } from './ui_settings';
import { ContextSetup } from './context';
-import { SavedObjectsServiceStart } from './saved_objects';
+import {
+ InternalSavedObjectsServiceStart,
+ InternalSavedObjectsServiceSetup,
+} from './saved_objects';
/** @internal */
export interface InternalCoreSetup {
@@ -29,11 +32,12 @@ export interface InternalCoreSetup {
http: InternalHttpServiceSetup;
elasticsearch: InternalElasticsearchServiceSetup;
uiSettings: InternalUiSettingsServiceSetup;
+ savedObjects: InternalSavedObjectsServiceSetup;
}
/**
* @internal
*/
export interface InternalCoreStart {
- savedObjects: SavedObjectsServiceStart;
+ savedObjects: InternalSavedObjectsServiceStart;
}
diff --git a/src/core/server/legacy/legacy_service.test.ts b/src/core/server/legacy/legacy_service.test.ts
index 030caa8324521..286e1a0612c94 100644
--- a/src/core/server/legacy/legacy_service.test.ts
+++ b/src/core/server/legacy/legacy_service.test.ts
@@ -43,10 +43,9 @@ import { BasePathProxyServer } from '../http';
import { loggingServiceMock } from '../logging/logging_service.mock';
import { DiscoveredPlugin } from '../plugins';
-import { KibanaMigrator } from '../saved_objects/migrations';
-import { ISavedObjectsClientProvider } from '../saved_objects';
import { httpServiceMock } from '../http/http_service.mock';
import { uiSettingsServiceMock } from '../ui_settings/ui_settings_service.mock';
+import { savedObjectsServiceMock } from '../saved_objects/saved_objects_service.mock';
const MockKbnServer: jest.Mock = KbnServer as any;
@@ -79,7 +78,7 @@ beforeEach(() => {
getAuthHeaders: () => undefined,
} as any,
},
-
+ savedObjects: savedObjectsServiceMock.createSetupContract(),
plugins: {
contracts: new Map([['plugin-id', 'plugin-value']]),
uiPlugins: {
@@ -94,10 +93,7 @@ beforeEach(() => {
startDeps = {
core: {
- savedObjects: {
- migrator: {} as KibanaMigrator,
- clientProvider: {} as ISavedObjectsClientProvider,
- },
+ savedObjects: savedObjectsServiceMock.createStartContract(),
plugins: { contracts: new Map() },
},
plugins: {},
@@ -128,6 +124,7 @@ describe('once LegacyService is set up with connection info', () => {
configService: configService as any,
});
+ await legacyService.discoverPlugins();
await legacyService.setup(setupDeps);
await legacyService.start(startDeps);
@@ -153,6 +150,7 @@ describe('once LegacyService is set up with connection info', () => {
logger,
configService: configService as any,
});
+ await legacyService.discoverPlugins();
await legacyService.setup(setupDeps);
await legacyService.start(startDeps);
@@ -180,6 +178,7 @@ describe('once LegacyService is set up with connection info', () => {
configService: configService as any,
});
+ await legacyService.discoverPlugins();
await legacyService.setup(setupDeps);
await expect(legacyService.start(startDeps)).rejects.toThrowErrorMatchingInlineSnapshot(
`"something failed"`
@@ -199,9 +198,12 @@ describe('once LegacyService is set up with connection info', () => {
configService: configService as any,
});
- await expect(legacyService.setup(setupDeps)).rejects.toThrowErrorMatchingInlineSnapshot(
+ await expect(legacyService.discoverPlugins()).rejects.toThrowErrorMatchingInlineSnapshot(
`"something failed"`
);
+ await expect(legacyService.setup(setupDeps)).rejects.toThrowErrorMatchingInlineSnapshot(
+ `"Legacy service has not discovered legacy plugins yet. Ensure LegacyService.discoverPlugins() is called before LegacyService.setup()"`
+ );
await expect(legacyService.start(startDeps)).rejects.toThrowErrorMatchingInlineSnapshot(
`"Legacy service is not setup yet."`
);
@@ -217,6 +219,7 @@ describe('once LegacyService is set up with connection info', () => {
logger,
configService: configService as any,
});
+ await legacyService.discoverPlugins();
await legacyService.setup(setupDeps);
await legacyService.start(startDeps);
@@ -237,6 +240,7 @@ describe('once LegacyService is set up with connection info', () => {
logger,
configService: configService as any,
});
+ await legacyService.discoverPlugins();
await legacyService.setup(setupDeps);
await legacyService.start(startDeps);
@@ -261,6 +265,7 @@ describe('once LegacyService is set up with connection info', () => {
logger,
configService: configService as any,
});
+ await legacyService.discoverPlugins();
await legacyService.setup(setupDeps);
await legacyService.start(startDeps);
@@ -280,7 +285,7 @@ describe('once LegacyService is set up without connection info', () => {
let legacyService: LegacyService;
beforeEach(async () => {
legacyService = new LegacyService({ coreId, env, logger, configService: configService as any });
-
+ await legacyService.discoverPlugins();
await legacyService.setup(setupDeps);
await legacyService.start(startDeps);
});
@@ -329,6 +334,7 @@ describe('once LegacyService is set up in `devClusterMaster` mode', () => {
configService: configService as any,
});
+ await devClusterLegacyService.discoverPlugins();
await devClusterLegacyService.setup(setupDeps);
await devClusterLegacyService.start(startDeps);
@@ -350,6 +356,7 @@ describe('once LegacyService is set up in `devClusterMaster` mode', () => {
configService: configService as any,
});
+ await devClusterLegacyService.discoverPlugins();
await devClusterLegacyService.setup(setupDeps);
await devClusterLegacyService.start(startDeps);
diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts
index 99963ad9ce3e8..fd081b23a0ef2 100644
--- a/src/core/server/legacy/legacy_service.ts
+++ b/src/core/server/legacy/legacy_service.ts
@@ -74,14 +74,17 @@ export interface LegacyServiceStartDeps {
}
/** @internal */
-export interface LegacyServiceSetup {
+export interface LegacyServiceDiscoverPlugins {
pluginSpecs: LegacyPluginSpec[];
uiExports: SavedObjectsLegacyUiExports;
pluginExtendedConfig: Config;
}
/** @internal */
-export class LegacyService implements CoreService {
+export type ILegacyService = Pick;
+
+/** @internal */
+export class LegacyService implements CoreService {
/** Symbol to represent the legacy platform as a fake "plugin". Used by the ContextService */
public readonly legacyId = Symbol();
private readonly log: Logger;
@@ -111,9 +114,7 @@ export class LegacyService implements CoreService {
.pipe(map(rawConfig => new HttpConfig(rawConfig, coreContext.env)));
}
- public async setup(setupDeps: LegacyServiceSetupDeps) {
- this.setupDeps = setupDeps;
-
+ public async discoverPlugins(): Promise {
this.update$ = this.coreContext.configService.getConfig$().pipe(
tap(config => {
if (this.kbnServer !== undefined) {
@@ -164,6 +165,16 @@ export class LegacyService implements CoreService {
};
}
+ public async setup(setupDeps: LegacyServiceSetupDeps) {
+ this.log.debug('setting up legacy service');
+ if (!this.legacyRawConfig || !this.legacyPlugins || !this.settings) {
+ throw new Error(
+ 'Legacy service has not discovered legacy plugins yet. Ensure LegacyService.discoverPlugins() is called before LegacyService.setup()'
+ );
+ }
+ this.setupDeps = setupDeps;
+ }
+
public async start(startDeps: LegacyServiceStartDeps) {
const { setupDeps } = this;
if (!setupDeps || !this.legacyRawConfig || !this.legacyPlugins || !this.settings) {
@@ -249,11 +260,19 @@ export class LegacyService implements CoreService {
basePath: setupDeps.core.http.basePath,
isTlsEnabled: setupDeps.core.http.isTlsEnabled,
},
+ savedObjects: {
+ setClientFactory: setupDeps.core.savedObjects.setClientFactory,
+ addClientWrapper: setupDeps.core.savedObjects.addClientWrapper,
+ createInternalRepository: setupDeps.core.savedObjects.createInternalRepository,
+ createScopedRepository: setupDeps.core.savedObjects.createScopedRepository,
+ },
uiSettings: {
register: setupDeps.core.uiSettings.register,
},
};
- const coreStart: CoreStart = {};
+ const coreStart: CoreStart = {
+ savedObjects: { getScopedClient: startDeps.core.savedObjects.getScopedClient },
+ };
// eslint-disable-next-line @typescript-eslint/no-var-requires
const KbnServer = require('../../../legacy/server/kbn_server');
diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts
index b51d5302e3274..a811efdf4b1b9 100644
--- a/src/core/server/mocks.ts
+++ b/src/core/server/mocks.ts
@@ -22,7 +22,9 @@ import { loggingServiceMock } from './logging/logging_service.mock';
import { elasticsearchServiceMock } from './elasticsearch/elasticsearch_service.mock';
import { httpServiceMock } from './http/http_service.mock';
import { contextServiceMock } from './context/context_service.mock';
+import { savedObjectsServiceMock } from './saved_objects/saved_objects_service.mock';
import { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock';
+import { InternalCoreSetup, InternalCoreStart } from './internal_types';
export { httpServerMock } from './http/http_server.mocks';
export { sessionStorageMock } from './http/cookie_session_storage.mocks';
@@ -87,6 +89,7 @@ function createCoreSetupMock() {
context: contextServiceMock.createSetupContract(),
elasticsearch: elasticsearchServiceMock.createSetupContract(),
http: httpMock,
+ savedObjects: savedObjectsServiceMock.createSetupContract(),
uiSettings: uiSettingsMock,
};
@@ -94,24 +97,35 @@ function createCoreSetupMock() {
}
function createCoreStartMock() {
- const mock: MockedKeys = {};
+ const mock: MockedKeys = {
+ savedObjects: savedObjectsServiceMock.createStartContract(),
+ };
return mock;
}
function createInternalCoreSetupMock() {
- const setupDeps = {
+ const setupDeps: InternalCoreSetup = {
context: contextServiceMock.createSetupContract(),
elasticsearch: elasticsearchServiceMock.createSetupContract(),
http: httpServiceMock.createSetupContract(),
uiSettings: uiSettingsServiceMock.createSetupContract(),
+ savedObjects: savedObjectsServiceMock.createSetupContract(),
};
return setupDeps;
}
+function createInternalCoreStartMock() {
+ const startDeps: InternalCoreStart = {
+ savedObjects: savedObjectsServiceMock.createStartContract(),
+ };
+ return startDeps;
+}
+
export const coreMock = {
createSetup: createCoreSetupMock,
createStart: createCoreStartMock,
createInternalSetup: createInternalCoreSetupMock,
+ createInternalStart: createInternalCoreStartMock,
createPluginInitializerContext: pluginInitializerContextMock,
};
diff --git a/src/core/server/plugins/plugin.test.ts b/src/core/server/plugins/plugin.test.ts
index 6aab03a01675d..10259b718577c 100644
--- a/src/core/server/plugins/plugin.test.ts
+++ b/src/core/server/plugins/plugin.test.ts
@@ -66,7 +66,9 @@ configService.atPath.mockReturnValue(new BehaviorSubject({ initialize: true }));
let coreId: symbol;
let env: Env;
let coreContext: CoreContext;
+
const setupDeps = coreMock.createInternalSetup();
+
beforeEach(() => {
coreId = Symbol('core');
env = Env.createDefault(getEnvOptions());
diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts
index 9885a572ad8c0..6edce1b2533cb 100644
--- a/src/core/server/plugins/plugin_context.ts
+++ b/src/core/server/plugins/plugin_context.ts
@@ -123,6 +123,12 @@ export function createPluginSetupContext(
basePath: deps.http.basePath,
isTlsEnabled: deps.http.isTlsEnabled,
},
+ savedObjects: {
+ setClientFactory: deps.savedObjects.setClientFactory,
+ addClientWrapper: deps.savedObjects.addClientWrapper,
+ createInternalRepository: deps.savedObjects.createInternalRepository,
+ createScopedRepository: deps.savedObjects.createScopedRepository,
+ },
uiSettings: {
register: deps.uiSettings.register,
},
@@ -146,5 +152,7 @@ export function createPluginStartContext(
deps: PluginsServiceStartDeps,
plugin: PluginWrapper
): CoreStart {
- return {};
+ return {
+ savedObjects: { getScopedClient: deps.savedObjects.getScopedClient },
+ };
}
diff --git a/src/core/server/plugins/plugins_service.test.ts b/src/core/server/plugins/plugins_service.test.ts
index 7e55faa43360e..df5473bc97d99 100644
--- a/src/core/server/plugins/plugins_service.test.ts
+++ b/src/core/server/plugins/plugins_service.test.ts
@@ -43,6 +43,7 @@ let configService: ConfigService;
let coreId: symbol;
let env: Env;
let mockPluginSystem: jest.Mocked;
+
const setupDeps = coreMock.createInternalSetup();
const logger = loggingServiceMock.create();
diff --git a/src/core/server/plugins/plugins_service.ts b/src/core/server/plugins/plugins_service.ts
index 4c73c2a304dc4..3f9999aad4ab9 100644
--- a/src/core/server/plugins/plugins_service.ts
+++ b/src/core/server/plugins/plugins_service.ts
@@ -28,7 +28,7 @@ import { PluginWrapper } from './plugin';
import { DiscoveredPlugin, PluginConfigDescriptor, PluginName, InternalPluginInfo } from './types';
import { PluginsConfig, PluginsConfigType } from './plugins_config';
import { PluginsSystem } from './plugins_system';
-import { InternalCoreSetup } from '../internal_types';
+import { InternalCoreSetup, InternalCoreStart } from '../internal_types';
import { IConfigService } from '../config';
import { pick } from '../../utils';
@@ -63,7 +63,7 @@ export interface PluginsServiceStart {
export type PluginsServiceSetupDeps = InternalCoreSetup;
/** @internal */
-export interface PluginsServiceStartDeps {} // eslint-disable-line @typescript-eslint/no-empty-interface
+export type PluginsServiceStartDeps = InternalCoreStart;
/** @internal */
export class PluginsService implements CoreService {
diff --git a/src/core/server/plugins/plugins_system.test.ts b/src/core/server/plugins/plugins_system.test.ts
index 6f1788f717f61..18c04af3bb641 100644
--- a/src/core/server/plugins/plugins_system.test.ts
+++ b/src/core/server/plugins/plugins_system.test.ts
@@ -33,7 +33,6 @@ import { loggingServiceMock } from '../logging/logging_service.mock';
import { PluginWrapper } from './plugin';
import { PluginName } from './types';
import { PluginsSystem } from './plugins_system';
-
import { coreMock } from '../mocks';
const logger = loggingServiceMock.create();
@@ -68,7 +67,9 @@ const configService = configServiceMock.create();
configService.atPath.mockReturnValue(new BehaviorSubject({ initialize: true }));
let env: Env;
let coreContext: CoreContext;
+
const setupDeps = coreMock.createInternalSetup();
+const startDeps = coreMock.createInternalStart();
beforeEach(() => {
env = Env.createDefault(getEnvOptions());
@@ -249,7 +250,6 @@ test('correctly orders plugins and returns exposed values for "setup" and "start
expect(plugin.setup).toHaveBeenCalledWith(setupContextMap.get(plugin.name), deps.setup);
}
- const startDeps = {};
expect([...(await pluginsSystem.startPlugins(startDeps))]).toMatchInlineSnapshot(`
Array [
Array [
@@ -382,7 +382,7 @@ test('`uiPlugins` returns only ui plugin dependencies', async () => {
test('can start without plugins', async () => {
await pluginsSystem.setupPlugins(setupDeps);
- const pluginsStart = await pluginsSystem.startPlugins({});
+ const pluginsStart = await pluginsSystem.startPlugins(startDeps);
expect(pluginsStart).toBeInstanceOf(Map);
expect(pluginsStart.size).toBe(0);
@@ -400,7 +400,7 @@ test('`startPlugins` only starts plugins that were setup', async () => {
pluginsSystem.addPlugin(plugin);
});
await pluginsSystem.setupPlugins(setupDeps);
- const result = await pluginsSystem.startPlugins({});
+ const result = await pluginsSystem.startPlugins(startDeps);
expect([...result]).toMatchInlineSnapshot(`
Array [
Array [
diff --git a/src/core/server/saved_objects/index.ts b/src/core/server/saved_objects/index.ts
index 76c62e0841bff..1100c18bcc72f 100644
--- a/src/core/server/saved_objects/index.ts
+++ b/src/core/server/saved_objects/index.ts
@@ -35,6 +35,18 @@ export { SavedObjectsSerializer, RawDoc as SavedObjectsRawDoc } from './serializ
export { SavedObjectsMigrationLogger } from './migrations/core/migration_logger';
-export { SavedObjectsService, SavedObjectsServiceStart } from './saved_objects_service';
+export {
+ SavedObjectsService,
+ InternalSavedObjectsServiceStart,
+ SavedObjectsServiceStart,
+ SavedObjectsServiceSetup,
+ InternalSavedObjectsServiceSetup,
+} from './saved_objects_service';
+
+export {
+ ISavedObjectsRepository,
+ SavedObjectsIncrementCounterOptions,
+ SavedObjectsDeleteByNamespaceOptions,
+} from './service/lib/repository';
export { config } from './saved_objects_config';
diff --git a/src/core/server/saved_objects/migrations/core/build_index_map.ts b/src/core/server/saved_objects/migrations/core/build_index_map.ts
index d7a26b7728f44..3b7ed20d94646 100644
--- a/src/core/server/saved_objects/migrations/core/build_index_map.ts
+++ b/src/core/server/saved_objects/migrations/core/build_index_map.ts
@@ -39,6 +39,7 @@ export interface IndexMap {
* This file contains logic to convert savedObjectSchemas into a dictonary of indexes and documents
*/
export function createIndexMap({
+ /** @deprecated Remove once savedObjectsSchemas are exposed from Core */
config,
kibanaIndexName,
schema,
diff --git a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts
index 51551ae4887b5..b89abc596ad18 100644
--- a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts
+++ b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts
@@ -20,6 +20,7 @@
import _ from 'lodash';
import { KibanaMigratorOptions, KibanaMigrator } from './kibana_migrator';
import { loggingServiceMock } from '../../../logging/logging_service.mock';
+import { SavedObjectsSchema } from '../../schema';
describe('KibanaMigrator', () => {
describe('getActiveMappings', () => {
@@ -112,12 +113,12 @@ function mockOptions({ configValues }: { configValues?: any } = {}): KibanaMigra
},
},
],
- savedObjectSchemas: {
+ savedObjectSchemas: new SavedObjectsSchema({
testtype2: {
isNamespaceAgnostic: false,
indexPattern: 'other-index',
},
- },
+ }),
kibanaConfig: {
enabled: true,
index: '.my-index',
diff --git a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts
index 5bde5deec9382..1b01680c427ee 100644
--- a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts
+++ b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts
@@ -25,7 +25,7 @@
import { Logger } from 'src/core/server/logging';
import { KibanaConfigType } from 'src/core/server/kibana_config';
import { MappingProperties, SavedObjectsMapping, IndexMapping } from '../../mappings';
-import { SavedObjectsSchema, SavedObjectsSchemaDefinition } from '../../schema';
+import { SavedObjectsSchema } from '../../schema';
import { RawSavedObjectDoc, SavedObjectsSerializer } from '../../serialization';
import { docValidator, PropertyValidators } from '../../validation';
import { buildActiveMappings, CallCluster, IndexMigrator } from '../core';
@@ -47,7 +47,7 @@ export interface KibanaMigratorOptions {
logger: Logger;
savedObjectMappings: SavedObjectsMapping[];
savedObjectMigrations: MigrationDefinition;
- savedObjectSchemas: SavedObjectsSchemaDefinition;
+ savedObjectSchemas: SavedObjectsSchema;
savedObjectValidations: PropertyValidators;
}
@@ -87,7 +87,7 @@ export class KibanaMigrator {
this.callCluster = callCluster;
this.kibanaConfig = kibanaConfig;
this.savedObjectsConfig = savedObjectsConfig;
- this.schema = new SavedObjectsSchema(savedObjectSchemas);
+ this.schema = savedObjectSchemas;
this.serializer = new SavedObjectsSerializer(this.schema);
this.mappingProperties = mergeProperties(savedObjectMappings || []);
this.log = logger;
diff --git a/src/core/server/saved_objects/saved_objects_service.mock.ts b/src/core/server/saved_objects/saved_objects_service.mock.ts
index 0a021ee97e26a..b2596146a02d4 100644
--- a/src/core/server/saved_objects/saved_objects_service.mock.ts
+++ b/src/core/server/saved_objects/saved_objects_service.mock.ts
@@ -17,21 +17,44 @@
* under the License.
*/
-import { SavedObjectsService, SavedObjectsServiceStart } from './saved_objects_service';
+import {
+ SavedObjectsService,
+ InternalSavedObjectsServiceSetup,
+ InternalSavedObjectsServiceStart,
+} from './saved_objects_service';
import { mockKibanaMigrator } from './migrations/kibana/kibana_migrator.mock';
import { savedObjectsClientProviderMock } from './service/lib/scoped_client_provider.mock';
+import { savedObjectsRepositoryMock } from './service/lib/repository.mock';
+import { savedObjectsClientMock } from './service/saved_objects_client.mock';
type SavedObjectsServiceContract = PublicMethodsOf;
const createStartContractMock = () => {
- const startContract: jest.Mocked = {
+ const startContract: jest.Mocked = {
clientProvider: savedObjectsClientProviderMock.create(),
+ getScopedClient: jest.fn(),
migrator: mockKibanaMigrator.create(),
};
return startContract;
};
+const createSetupContractMock = () => {
+ const setupContract: jest.Mocked = {
+ getScopedClient: jest.fn(),
+ setClientFactory: jest.fn(),
+ addClientWrapper: jest.fn(),
+ createInternalRepository: jest.fn(),
+ createScopedRepository: jest.fn(),
+ };
+
+ setupContract.getScopedClient.mockReturnValue(savedObjectsClientMock.create());
+ setupContract.createInternalRepository.mockReturnValue(savedObjectsRepositoryMock.create());
+ setupContract.createScopedRepository.mockReturnValue(savedObjectsRepositoryMock.create());
+
+ return setupContract;
+};
+
const createsavedObjectsServiceMock = () => {
const mocked: jest.Mocked = {
setup: jest.fn(),
@@ -39,7 +62,7 @@ const createsavedObjectsServiceMock = () => {
stop: jest.fn(),
};
- mocked.setup.mockResolvedValue({ clientProvider: savedObjectsClientProviderMock.create() });
+ mocked.setup.mockResolvedValue(createSetupContractMock());
mocked.start.mockResolvedValue(createStartContractMock());
mocked.stop.mockResolvedValue();
return mocked;
@@ -47,5 +70,6 @@ const createsavedObjectsServiceMock = () => {
export const savedObjectsServiceMock = {
create: createsavedObjectsServiceMock,
+ createSetupContract: createSetupContractMock,
createStartContract: createStartContractMock,
};
diff --git a/src/core/server/saved_objects/saved_objects_service.test.ts b/src/core/server/saved_objects/saved_objects_service.test.ts
index c31ad90011865..f58939c58e85e 100644
--- a/src/core/server/saved_objects/saved_objects_service.test.ts
+++ b/src/core/server/saved_objects/saved_objects_service.test.ts
@@ -27,7 +27,6 @@ import { of } from 'rxjs';
import * as legacyElasticsearch from 'elasticsearch';
import { Env } from '../config';
import { configServiceMock } from '../mocks';
-import { SavedObjectsClientProvider } from '.';
afterEach(() => {
jest.clearAllMocks();
@@ -51,7 +50,7 @@ describe('SavedObjectsService', () => {
const soService = new SavedObjectsService(coreContext);
const coreSetup = ({
elasticsearch: { adminClient$: of(clusterClient) },
- legacy: { uiExports: { savedObjectMappings: [] }, pluginExtendedConfig: {} },
+ legacyPlugins: { uiExports: { savedObjectMappings: [] }, pluginExtendedConfig: {} },
} as unknown) as SavedObjectsSetupDeps;
await soService.setup(coreSetup, 1);
@@ -60,18 +59,6 @@ describe('SavedObjectsService', () => {
'success'
);
});
-
- it('resolves with clientProvider', async () => {
- const coreContext = mockCoreContext.create();
- const soService = new SavedObjectsService(coreContext);
- const coreSetup = ({
- elasticsearch: { adminClient$: of({ callAsInternalUser: jest.fn() }) },
- legacy: { uiExports: {}, pluginExtendedConfig: {} },
- } as unknown) as SavedObjectsSetupDeps;
-
- const savedObjectsSetup = await soService.setup(coreSetup);
- expect(savedObjectsSetup.clientProvider).toBeInstanceOf(SavedObjectsClientProvider);
- });
});
describe('#start()', () => {
@@ -82,7 +69,7 @@ describe('SavedObjectsService', () => {
const soService = new SavedObjectsService(coreContext);
const coreSetup = ({
elasticsearch: { adminClient$: of({ callAsInternalUser: jest.fn() }) },
- legacy: { uiExports: {}, pluginExtendedConfig: {} },
+ legacyPlugins: { uiExports: {}, pluginExtendedConfig: {} },
} as unknown) as SavedObjectsSetupDeps;
await soService.setup(coreSetup);
@@ -96,7 +83,7 @@ describe('SavedObjectsService', () => {
const soService = new SavedObjectsService(coreContext);
const coreSetup = ({
elasticsearch: { adminClient$: of({ callAsInternalUser: jest.fn() }) },
- legacy: { uiExports: {}, pluginExtendedConfig: {} },
+ legacyPlugins: { uiExports: {}, pluginExtendedConfig: {} },
} as unknown) as SavedObjectsSetupDeps;
await soService.setup(coreSetup);
@@ -110,7 +97,7 @@ describe('SavedObjectsService', () => {
const soService = new SavedObjectsService(coreContext);
const coreSetup = ({
elasticsearch: { adminClient$: of({ callAsInternalUser: jest.fn() }) },
- legacy: { uiExports: {}, pluginExtendedConfig: {} },
+ legacyPlugins: { uiExports: {}, pluginExtendedConfig: {} },
} as unknown) as SavedObjectsSetupDeps;
await soService.setup(coreSetup);
diff --git a/src/core/server/saved_objects/saved_objects_service.ts b/src/core/server/saved_objects/saved_objects_service.ts
index 43c3afa3ed639..589cd9cce400f 100644
--- a/src/core/server/saved_objects/saved_objects_service.ts
+++ b/src/core/server/saved_objects/saved_objects_service.ts
@@ -22,40 +22,159 @@ import { first } from 'rxjs/operators';
import {
SavedObjectsClient,
SavedObjectsSchema,
- SavedObjectsRepository,
- SavedObjectsSerializer,
SavedObjectsClientProvider,
ISavedObjectsClientProvider,
+ SavedObjectsClientProviderOptions,
} from './';
-import { getRootPropertiesObjects } from './mappings';
import { KibanaMigrator, IKibanaMigrator } from './migrations';
import { CoreContext } from '../core_context';
-import { LegacyServiceSetup } from '../legacy/legacy_service';
-import { ElasticsearchServiceSetup } from '../elasticsearch';
+import { LegacyServiceDiscoverPlugins } from '../legacy/legacy_service';
+import { ElasticsearchServiceSetup, APICaller } from '../elasticsearch';
import { KibanaConfigType } from '../kibana_config';
-import { retryCallCluster, migrationsRetryCallCluster } from '../elasticsearch/retry_call_cluster';
+import { migrationsRetryCallCluster } from '../elasticsearch/retry_call_cluster';
import { SavedObjectsConfigType } from './saved_objects_config';
import { KibanaRequest } from '../http';
+import { SavedObjectsClientContract } from './types';
+import { ISavedObjectsRepository } from './service/lib/repository';
+import {
+ SavedObjectsClientFactory,
+ SavedObjectsClientWrapperFactory,
+} from './service/lib/scoped_client_provider';
+import { createRepository } from './service/lib/create_repository';
import { Logger } from '..';
/**
+ * Saved Objects is Kibana's data persisentence mechanism allowing plugins to
+ * use Elasticsearch for storing and querying state. The
+ * SavedObjectsServiceSetup API exposes methods for creating and registering
+ * Saved Object client wrappers.
+ *
+ * @remarks
+ * Note: The Saved Object setup API's should only be used for creating and
+ * registering client wrappers. Constructing a Saved Objects client or
+ * repository for use within your own plugin won't have any of the registered
+ * wrappers applied and is considered an anti-pattern. Use the Saved Objects
+ * client from the
+ * {@link SavedObjectsServiceStart | SavedObjectsServiceStart#getScopedClient }
+ * method or the {@link RequestHandlerContext | route handler context} instead.
+ *
+ * When plugins access the Saved Objects client, a new client is created using
+ * the factory provided to `setClientFactory` and wrapped by all wrappers
+ * registered through `addClientWrapper`. To create a factory or wrapper,
+ * plugins will have to construct a Saved Objects client. First create a
+ * repository by calling `scopedRepository` or `internalRepository` and then
+ * use this repository as the argument to the {@link SavedObjectsClient}
+ * constructor.
+ *
+ * @example
+ * import {SavedObjectsClient, CoreSetup} from 'src/core/server';
+ *
+ * export class Plugin() {
+ * setup: (core: CoreSetup) => {
+ * core.savedObjects.setClientFactory(({request: KibanaRequest}) => {
+ * return new SavedObjectsClient(core.savedObjects.scopedRepository(request));
+ * })
+ * }
+ * }
+ *
* @public
*/
export interface SavedObjectsServiceSetup {
- clientProvider: ISavedObjectsClientProvider;
+ /**
+ * Set a default factory for creating Saved Objects clients. Only one client
+ * factory can be set, subsequent calls to this method will fail.
+ */
+ setClientFactory: (customClientFactory: SavedObjectsClientFactory) => void;
+
+ /**
+ * Add a client wrapper with the given priority.
+ */
+ addClientWrapper: (
+ priority: number,
+ id: string,
+ factory: SavedObjectsClientWrapperFactory
+ ) => void;
+
+ /**
+ * Creates a {@link ISavedObjectsRepository | Saved Objects repository} that
+ * uses the credentials from the passed in request to authenticate with
+ * Elasticsearch.
+ *
+ * @remarks
+ * The repository should only be used for creating and registering a client
+ * factory or client wrapper. Using the repository directly for interacting
+ * with Saved Objects is an anti-pattern. Use the Saved Objects client from
+ * the
+ * {@link SavedObjectsServiceStart | SavedObjectsServiceStart#getScopedClient }
+ * method or the {@link RequestHandlerContext | route handler context}
+ * instead.
+ */
+ createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository;
+
+ /**
+ * Creates a {@link ISavedObjectsRepository | Saved Objects repository} that
+ * uses the internal Kibana user for authenticating with Elasticsearch.
+ *
+ * @remarks
+ * The repository should only be used for creating and registering a client
+ * factory or client wrapper. Using the repository directly for interacting
+ * with Saved Objects is an anti-pattern. Use the Saved Objects client from
+ * the
+ * {@link SavedObjectsServiceStart | SavedObjectsServiceStart#getScopedClient }
+ * method or the {@link RequestHandlerContext | route handler context}
+ * instead.
+ */
+ createInternalRepository: (extraTypes?: string[]) => ISavedObjectsRepository;
+}
+
+/**
+ * @internal
+ */
+export interface InternalSavedObjectsServiceSetup extends SavedObjectsServiceSetup {
+ getScopedClient: (
+ req: KibanaRequest,
+ options?: SavedObjectsClientProviderOptions
+ ) => SavedObjectsClientContract;
}
/**
+ * Saved Objects is Kibana's data persisentence mechanism allowing plugins to
+ * use Elasticsearch for storing and querying state. The
+ * SavedObjectsServiceStart API provides a scoped Saved Objects client for
+ * interacting with Saved Objects.
+ *
* @public
*/
export interface SavedObjectsServiceStart {
+ /**
+ * Creates a {@link SavedObjectsClientContract | Saved Objects client} that
+ * uses the credentials from the passed in request to authenticate with
+ * Elasticsearch. If other plugins have registered Saved Objects client
+ * wrappers, these will be applied to extend the functionality of the client.
+ *
+ * A client that is already scoped to the incoming request is also exposed
+ * from the route handler context see {@link RequestHandlerContext}.
+ */
+ getScopedClient: (
+ req: KibanaRequest,
+ options?: SavedObjectsClientProviderOptions
+ ) => SavedObjectsClientContract;
+}
+
+export interface InternalSavedObjectsServiceStart extends SavedObjectsServiceStart {
+ /**
+ * @deprecated Exposed only for injecting into Legacy
+ */
migrator: IKibanaMigrator;
+ /**
+ * @deprecated Exposed only for injecting into Legacy
+ */
clientProvider: ISavedObjectsClientProvider;
}
/** @internal */
export interface SavedObjectsSetupDeps {
- legacy: LegacyServiceSetup;
+ legacyPlugins: LegacyServiceDiscoverPlugins;
elasticsearch: ElasticsearchServiceSetup;
}
@@ -64,7 +183,7 @@ export interface SavedObjectsSetupDeps {
export interface SavedObjectsStartDeps {}
export class SavedObjectsService
- implements CoreService {
+ implements CoreService {
private migrator: KibanaMigrator | undefined;
private logger: Logger;
private clientProvider: ISavedObjectsClientProvider | undefined;
@@ -74,19 +193,21 @@ export class SavedObjectsService
}
public async setup(
- coreSetup: SavedObjectsSetupDeps,
+ setupDeps: SavedObjectsSetupDeps,
migrationsRetryDelay?: number
- ): Promise {
+ ): Promise {
this.logger.debug('Setting up SavedObjects service');
const {
- savedObjectSchemas,
+ savedObjectSchemas: savedObjectsSchemasDefinition,
savedObjectMappings,
savedObjectMigrations,
savedObjectValidations,
- } = await coreSetup.legacy.uiExports;
+ } = setupDeps.legacyPlugins.uiExports;
+
+ const savedObjectSchemas = new SavedObjectsSchema(savedObjectsSchemasDefinition);
- const adminClient = await coreSetup.elasticsearch.adminClient$.pipe(first()).toPromise();
+ const adminClient = await setupDeps.elasticsearch.adminClient$.pipe(first()).toPromise();
const kibanaConfig = await this.coreContext.configService
.atPath('kibana')
@@ -105,7 +226,7 @@ export class SavedObjectsService
savedObjectValidations,
logger: this.coreContext.logger.get('migrations'),
kibanaVersion: this.coreContext.env.packageInfo.version,
- config: coreSetup.legacy.pluginExtendedConfig,
+ config: setupDeps.legacyPlugins.pluginExtendedConfig,
savedObjectsConfig,
kibanaConfig,
callCluster: migrationsRetryCallCluster(
@@ -115,35 +236,36 @@ export class SavedObjectsService
),
}));
- const mappings = this.migrator.getActiveMappings();
- const allTypes = Object.keys(getRootPropertiesObjects(mappings));
- const schema = new SavedObjectsSchema(savedObjectSchemas);
- const serializer = new SavedObjectsSerializer(schema);
- const visibleTypes = allTypes.filter(type => !schema.isHiddenType(type));
+ const createSORepository = (callCluster: APICaller, extraTypes: string[] = []) => {
+ return createRepository(
+ migrator,
+ savedObjectSchemas,
+ setupDeps.legacyPlugins.pluginExtendedConfig,
+ kibanaConfig.index,
+ callCluster,
+ extraTypes
+ );
+ };
this.clientProvider = new SavedObjectsClientProvider({
defaultClientFactory({ request }) {
- const repository = new SavedObjectsRepository({
- index: kibanaConfig.index,
- config: coreSetup.legacy.pluginExtendedConfig,
- migrator,
- mappings,
- schema,
- serializer,
- allowedTypes: visibleTypes,
- callCluster: retryCallCluster(adminClient.asScoped(request).callAsCurrentUser),
- });
-
+ const repository = createSORepository(adminClient.asScoped(request).callAsCurrentUser);
return new SavedObjectsClient(repository);
},
});
return {
- clientProvider: this.clientProvider,
+ getScopedClient: this.clientProvider.getClient.bind(this.clientProvider),
+ setClientFactory: this.clientProvider.setClientFactory.bind(this.clientProvider),
+ addClientWrapper: this.clientProvider.addClientWrapperFactory.bind(this.clientProvider),
+ createInternalRepository: (extraTypes?: string[]) =>
+ createSORepository(adminClient.callAsInternalUser, extraTypes),
+ createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) =>
+ createSORepository(adminClient.asScoped(req).callAsCurrentUser, extraTypes),
};
}
- public async start(core: SavedObjectsStartDeps): Promise {
+ public async start(core: SavedObjectsStartDeps): Promise {
if (!this.clientProvider) {
throw new Error('#setup() needs to be run first');
}
@@ -171,6 +293,7 @@ export class SavedObjectsService
return {
migrator: this.migrator!,
clientProvider: this.clientProvider,
+ getScopedClient: this.clientProvider.getClient.bind(this.clientProvider),
};
}
diff --git a/src/core/server/saved_objects/schema/schema.ts b/src/core/server/saved_objects/schema/schema.ts
index 06d29bf7dcf32..6be5ca9bfce60 100644
--- a/src/core/server/saved_objects/schema/schema.ts
+++ b/src/core/server/saved_objects/schema/schema.ts
@@ -46,6 +46,7 @@ export class SavedObjectsSchema {
return false;
}
+ // TODO: Remove dependency on config when we move SavedObjectsSchema to NP
public getIndexForType(config: Config, type: string): string | undefined {
if (this.definition != null && this.definition.hasOwnProperty(type)) {
const { indexPattern } = this.definition[type];
diff --git a/src/core/server/saved_objects/service/index.ts b/src/core/server/saved_objects/service/index.ts
index cf0769fced460..f50ee1759dad7 100644
--- a/src/core/server/saved_objects/service/index.ts
+++ b/src/core/server/saved_objects/service/index.ts
@@ -58,6 +58,7 @@ export {
SavedObjectsClientWrapperFactory,
SavedObjectsClientWrapperOptions,
SavedObjectsErrorHelpers,
+ SavedObjectsClientFactory,
} from './lib';
export * from './saved_objects_client';
diff --git a/src/core/server/saved_objects/service/lib/create_repository.test.ts b/src/core/server/saved_objects/service/lib/create_repository.test.ts
new file mode 100644
index 0000000000000..d40a5d04dcd8a
--- /dev/null
+++ b/src/core/server/saved_objects/service/lib/create_repository.test.ts
@@ -0,0 +1,119 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import { SavedObjectsRepository } from './repository';
+import { mockKibanaMigrator } from '../../migrations/kibana/kibana_migrator.mock';
+import { SavedObjectsSchema } from '../../schema';
+import { KibanaMigrator } from '../../migrations';
+import { Config } from 'src/core/server/config';
+import { createRepository } from './create_repository';
+jest.mock('./repository');
+
+describe('#createRepository', () => {
+ const callAdminCluster = jest.fn();
+ const schema = new SavedObjectsSchema({
+ nsAgnosticType: { isNamespaceAgnostic: true },
+ nsType: { indexPattern: 'beats', isNamespaceAgnostic: false },
+ hiddenType: { isNamespaceAgnostic: true, hidden: true },
+ });
+ const mappings = [
+ {
+ pluginId: 'testplugin',
+ properties: {
+ nsAgnosticType: {
+ properties: {
+ name: { type: 'keyword' },
+ },
+ },
+ nsType: {
+ properties: {
+ name: { type: 'keyword' },
+ },
+ },
+ hiddenType: {
+ properties: {
+ name: { type: 'keyword' },
+ },
+ },
+ },
+ },
+ ];
+ const migrator = mockKibanaMigrator.create({ savedObjectMappings: mappings });
+ const RepositoryConstructor = (SavedObjectsRepository as unknown) as jest.Mock<
+ SavedObjectsRepository
+ >;
+
+ beforeEach(() => {
+ RepositoryConstructor.mockClear();
+ });
+
+ it('should not allow a repository with an undefined type', () => {
+ try {
+ createRepository(
+ (migrator as unknown) as KibanaMigrator,
+ schema,
+ {} as Config,
+ '.kibana-test',
+ callAdminCluster,
+ ['unMappedType1', 'unmappedType2']
+ );
+ } catch (e) {
+ expect(e).toMatchInlineSnapshot(
+ `[Error: Missing mappings for saved objects types: 'unMappedType1, unmappedType2']`
+ );
+ }
+ });
+
+ it('should create a repository without hidden types', () => {
+ const repository = createRepository(
+ (migrator as unknown) as KibanaMigrator,
+ schema,
+ {} as Config,
+ '.kibana-test',
+ callAdminCluster
+ );
+ expect(repository).toBeDefined();
+ expect(RepositoryConstructor.mock.calls[0][0].allowedTypes).toMatchInlineSnapshot(`
+ Array [
+ "config",
+ "nsAgnosticType",
+ "nsType",
+ ]
+ `);
+ });
+
+ it('should create a repository with a unique list of hidden types', () => {
+ const repository = createRepository(
+ (migrator as unknown) as KibanaMigrator,
+ schema,
+ {} as Config,
+ '.kibana-test',
+ callAdminCluster,
+ ['hiddenType', 'hiddenType', 'hiddenType']
+ );
+ expect(repository).toBeDefined();
+ expect(RepositoryConstructor.mock.calls[0][0].allowedTypes).toMatchInlineSnapshot(`
+ Array [
+ "config",
+ "nsAgnosticType",
+ "nsType",
+ "hiddenType",
+ ]
+ `);
+ });
+});
diff --git a/src/core/server/saved_objects/service/lib/create_repository.ts b/src/core/server/saved_objects/service/lib/create_repository.ts
new file mode 100644
index 0000000000000..a0e920a95c2c6
--- /dev/null
+++ b/src/core/server/saved_objects/service/lib/create_repository.ts
@@ -0,0 +1,61 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { APICaller } from 'src/core/server/elasticsearch';
+import { Config } from 'src/core/server/config';
+import { retryCallCluster } from '../../../elasticsearch/retry_call_cluster';
+import { KibanaMigrator } from '../../migrations';
+import { SavedObjectsSchema } from '../../schema';
+import { getRootPropertiesObjects } from '../../mappings';
+import { SavedObjectsSerializer } from '../../serialization';
+import { SavedObjectsRepository } from '.';
+
+export const createRepository = (
+ migrator: KibanaMigrator,
+ schema: SavedObjectsSchema,
+ config: Config,
+ indexName: string,
+ callCluster: APICaller,
+ extraTypes: string[] = []
+) => {
+ const mappings = migrator.getActiveMappings();
+ const allTypes = Object.keys(getRootPropertiesObjects(mappings));
+ const serializer = new SavedObjectsSerializer(schema);
+ const visibleTypes = allTypes.filter(type => !schema.isHiddenType(type));
+
+ const missingTypeMappings = extraTypes.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(extraTypes))];
+
+ return new SavedObjectsRepository({
+ index: indexName,
+ config,
+ migrator,
+ mappings,
+ schema,
+ serializer,
+ allowedTypes,
+ callCluster: retryCallCluster(callCluster),
+ });
+};
diff --git a/src/core/server/saved_objects/service/lib/index.ts b/src/core/server/saved_objects/service/lib/index.ts
index 4bc159e17ec0f..afac50a680ed5 100644
--- a/src/core/server/saved_objects/service/lib/index.ts
+++ b/src/core/server/saved_objects/service/lib/index.ts
@@ -17,13 +17,19 @@
* under the License.
*/
-export { SavedObjectsRepository, SavedObjectsRepositoryOptions } from './repository';
+export {
+ ISavedObjectsRepository,
+ SavedObjectsRepository,
+ SavedObjectsRepositoryOptions,
+} from './repository';
+
export {
SavedObjectsClientWrapperFactory,
SavedObjectsClientWrapperOptions,
ISavedObjectsClientProvider,
SavedObjectsClientProvider,
SavedObjectsClientProviderOptions,
+ SavedObjectsClientFactory,
} from './scoped_client_provider';
export { SavedObjectsErrorHelpers } from './errors';
diff --git a/src/core/server/saved_objects/service/lib/repository.mock.ts b/src/core/server/saved_objects/service/lib/repository.mock.ts
new file mode 100644
index 0000000000000..e69c0ff37d1be
--- /dev/null
+++ b/src/core/server/saved_objects/service/lib/repository.mock.ts
@@ -0,0 +1,35 @@
+/*
+ * Licensed to Elasticsearch B.V. under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch B.V. licenses this file to you under
+ * the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { ISavedObjectsRepository } from './repository';
+
+const create = (): jest.Mocked => ({
+ create: jest.fn(),
+ bulkCreate: jest.fn(),
+ bulkUpdate: jest.fn(),
+ delete: jest.fn(),
+ bulkGet: jest.fn(),
+ find: jest.fn(),
+ get: jest.fn(),
+ update: jest.fn(),
+ deleteByNamespace: jest.fn(),
+ incrementCounter: jest.fn(),
+});
+
+export const savedObjectsRepositoryMock = { create };
diff --git a/src/core/server/saved_objects/service/lib/repository.test.js b/src/core/server/saved_objects/service/lib/repository.test.js
index 329ffb5ae8b75..b54a03e9bb435 100644
--- a/src/core/server/saved_objects/service/lib/repository.test.js
+++ b/src/core/server/saved_objects/service/lib/repository.test.js
@@ -16,14 +16,11 @@
* specific language governing permissions and limitations
* under the License.
*/
-
-import { delay } from 'bluebird';
import _ from 'lodash';
import { SavedObjectsRepository } from './repository';
import * as getSearchDslNS from './search_dsl/search_dsl';
import { SavedObjectsErrorHelpers } from './errors';
-import * as legacyElasticsearch from 'elasticsearch';
import { SavedObjectsSchema } from '../../schema';
import { SavedObjectsSerializer } from '../../serialization';
import { getRootPropertiesObjects } from '../../mappings/lib/get_root_properties_objects';
@@ -36,7 +33,6 @@ jest.mock('./search_dsl/search_dsl', () => ({ getSearchDsl: jest.fn() }));
describe('SavedObjectsRepository', () => {
let callAdminCluster;
- let onBeforeWrite;
let savedObjectsRepository;
let migrator;
const mockTimestamp = '2017-08-14T15:49:14.886Z';
@@ -262,7 +258,6 @@ describe('SavedObjectsRepository', () => {
beforeEach(() => {
callAdminCluster = jest.fn();
- onBeforeWrite = jest.fn();
migrator = {
migrateDocument: jest.fn(doc => doc),
runMigrations: async () => ({ status: 'skipped' }),
@@ -280,7 +275,6 @@ describe('SavedObjectsRepository', () => {
schema,
serializer,
allowedTypes,
- onBeforeWrite,
});
savedObjectsRepository._getCurrentTime = jest.fn(() => mockTimestamp);
@@ -359,7 +353,6 @@ describe('SavedObjectsRepository', () => {
expect(callAdminCluster).toHaveBeenCalledTimes(1);
expect(callAdminCluster).toHaveBeenCalledWith('index', expect.any(Object));
- expect(onBeforeWrite).toHaveBeenCalledTimes(1);
});
it('should use default index', async () => {
@@ -368,8 +361,8 @@ describe('SavedObjectsRepository', () => {
title: 'Logstash',
});
- expect(onBeforeWrite).toHaveBeenCalledTimes(1);
- expect(onBeforeWrite).toHaveBeenCalledWith(
+ expect(callAdminCluster).toHaveBeenCalledTimes(1);
+ expect(callAdminCluster).toHaveBeenCalledWith(
'index',
expect.objectContaining({
index: '.kibana-test',
@@ -383,8 +376,8 @@ describe('SavedObjectsRepository', () => {
title: 'Logstash',
});
- expect(onBeforeWrite).toHaveBeenCalledTimes(1);
- expect(onBeforeWrite).toHaveBeenCalledWith(
+ expect(callAdminCluster).toHaveBeenCalledTimes(1);
+ expect(callAdminCluster).toHaveBeenCalledWith(
'index',
expect.objectContaining({
index: 'beats',
@@ -456,7 +449,6 @@ describe('SavedObjectsRepository', () => {
expect(callAdminCluster).toHaveBeenCalledTimes(1);
expect(callAdminCluster).toHaveBeenCalledWith('create', expect.any(Object));
- expect(onBeforeWrite).toHaveBeenCalledTimes(1);
});
it('allows for id to be provided', async () => {
@@ -475,8 +467,6 @@ describe('SavedObjectsRepository', () => {
id: 'index-pattern:logstash-*',
})
);
-
- expect(onBeforeWrite).toHaveBeenCalledTimes(1);
});
it('self-generates an ID', async () => {
@@ -491,8 +481,6 @@ describe('SavedObjectsRepository', () => {
id: expect.objectContaining(/index-pattern:[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}/),
})
);
-
- expect(onBeforeWrite).toHaveBeenCalledTimes(1);
});
it('prepends namespace to the id and adds namespace to body when providing namespace for namespaced type', async () => {
@@ -519,7 +507,6 @@ describe('SavedObjectsRepository', () => {
}),
})
);
- expect(onBeforeWrite).toHaveBeenCalledTimes(1);
});
it(`doesn't prepend namespace to the id or add namespace property when providing no namespace for namespaced type`, async () => {
@@ -544,7 +531,6 @@ describe('SavedObjectsRepository', () => {
}),
})
);
- expect(onBeforeWrite).toHaveBeenCalledTimes(1);
});
it(`doesn't prepend namespace to the id or add namespace property when providing namespace for namespace agnostic type`, async () => {
@@ -570,7 +556,6 @@ describe('SavedObjectsRepository', () => {
}),
})
);
- expect(onBeforeWrite).toHaveBeenCalledTimes(1);
});
it('defaults to empty references array if none are provided', async () => {
@@ -667,8 +652,6 @@ describe('SavedObjectsRepository', () => {
references: [{ name: 'ref_0', type: 'test', id: '2' }],
},
]);
-
- expect(onBeforeWrite).toHaveBeenCalledTimes(1);
});
it('defaults to a refresh setting of `wait_for`', async () => {
@@ -830,10 +813,7 @@ describe('SavedObjectsRepository', () => {
})
);
- expect(onBeforeWrite).toHaveBeenCalledTimes(1);
-
callAdminCluster.mockReset();
- onBeforeWrite.mockReset();
callAdminCluster.mockReturnValue({
items: [{ create: { type: 'foo', id: 'bar', _primary_term: 1, _seq_no: 1 } }],
@@ -853,8 +833,6 @@ describe('SavedObjectsRepository', () => {
],
})
);
-
- expect(onBeforeWrite).toHaveBeenCalledTimes(1);
});
it('mockReturnValue document errors', async () => {
@@ -1012,7 +990,6 @@ describe('SavedObjectsRepository', () => {
],
})
);
- expect(onBeforeWrite).toHaveBeenCalledTimes(1);
});
it(`doesn't prepend namespace to the id or add namespace property when providing no namespace for namespaced type`, async () => {
@@ -1061,7 +1038,6 @@ describe('SavedObjectsRepository', () => {
],
})
);
- expect(onBeforeWrite).toHaveBeenCalledTimes(1);
});
it(`doesn't prepend namespace to the id or add namespace property when providing namespace for namespace agnostic type`, async () => {
@@ -1089,7 +1065,6 @@ describe('SavedObjectsRepository', () => {
],
})
);
- expect(onBeforeWrite).toHaveBeenCalledTimes(1);
});
it('should return objects in the same order regardless of type', () => {});
@@ -1133,8 +1108,6 @@ describe('SavedObjectsRepository', () => {
index: '.kibana-test',
ignore: [404],
});
-
- expect(onBeforeWrite).toHaveBeenCalledTimes(1);
});
it(`doesn't prepend namespace to the id when providing no namespace for namespaced type`, async () => {
@@ -1148,8 +1121,6 @@ describe('SavedObjectsRepository', () => {
index: '.kibana-test',
ignore: [404],
});
-
- expect(onBeforeWrite).toHaveBeenCalledTimes(1);
});
it(`doesn't prepend namespace to the id when providing namespace for namespace agnostic type`, async () => {
@@ -1165,8 +1136,6 @@ describe('SavedObjectsRepository', () => {
index: '.kibana-test',
ignore: [404],
});
-
- expect(onBeforeWrite).toHaveBeenCalledTimes(1);
});
it('defaults to a refresh setting of `wait_for`', async () => {
@@ -1197,7 +1166,6 @@ describe('SavedObjectsRepository', () => {
callAdminCluster.mockReturnValue(deleteByQueryResults);
expect(savedObjectsRepository.deleteByNamespace()).rejects.toThrowErrorMatchingSnapshot();
expect(callAdminCluster).not.toHaveBeenCalled();
- expect(onBeforeWrite).not.toHaveBeenCalled();
});
it('requires namespace to be a string', async () => {
@@ -1206,7 +1174,6 @@ describe('SavedObjectsRepository', () => {
savedObjectsRepository.deleteByNamespace(['namespace-1', 'namespace-2'])
).rejects.toThrowErrorMatchingSnapshot();
expect(callAdminCluster).not.toHaveBeenCalled();
- expect(onBeforeWrite).not.toHaveBeenCalled();
});
it('constructs a deleteByQuery call using all types that are namespace aware', async () => {
@@ -1215,7 +1182,6 @@ describe('SavedObjectsRepository', () => {
expect(result).toEqual(deleteByQueryResults);
expect(callAdminCluster).toHaveBeenCalledTimes(1);
- expect(onBeforeWrite).toHaveBeenCalledTimes(1);
expect(getSearchDslNS.getSearchDsl).toHaveBeenCalledWith(mappings, schema, {
namespace: 'my-namespace',
@@ -1264,7 +1230,6 @@ describe('SavedObjectsRepository', () => {
it('requires type to be defined', async () => {
await expect(savedObjectsRepository.find({})).rejects.toThrow(/options\.type must be/);
expect(callAdminCluster).not.toHaveBeenCalled();
- expect(onBeforeWrite).not.toHaveBeenCalled();
});
it('requires searchFields be an array if defined', async () => {
@@ -1274,7 +1239,6 @@ describe('SavedObjectsRepository', () => {
throw new Error('expected find() to reject');
} catch (error) {
expect(callAdminCluster).not.toHaveBeenCalled();
- expect(onBeforeWrite).not.toHaveBeenCalled();
expect(error.message).toMatch('must be an array');
}
});
@@ -1286,7 +1250,6 @@ describe('SavedObjectsRepository', () => {
throw new Error('expected find() to reject');
} catch (error) {
expect(callAdminCluster).not.toHaveBeenCalled();
- expect(onBeforeWrite).not.toHaveBeenCalled();
expect(error.message).toMatch('must be an array');
}
});
@@ -1388,7 +1351,6 @@ describe('SavedObjectsRepository', () => {
getSearchDslNS.getSearchDsl.mockReturnValue({ query: 1, aggregations: 2 });
await savedObjectsRepository.find({ type: 'foo' });
expect(callAdminCluster).toHaveBeenCalledTimes(1);
- expect(onBeforeWrite).not.toHaveBeenCalled();
expect(callAdminCluster).toHaveBeenCalledWith(
'search',
expect.objectContaining({
@@ -1457,8 +1419,6 @@ describe('SavedObjectsRepository', () => {
from: 50,
})
);
-
- expect(onBeforeWrite).not.toHaveBeenCalled();
});
it('can filter by fields', async () => {
@@ -1480,8 +1440,6 @@ describe('SavedObjectsRepository', () => {
],
})
);
-
- expect(onBeforeWrite).not.toHaveBeenCalled();
});
it('should set rest_total_hits_as_int to true on a request', async () => {
@@ -1535,7 +1493,6 @@ describe('SavedObjectsRepository', () => {
it('formats Elasticsearch response when there is no namespace', async () => {
callAdminCluster.mockResolvedValue(noNamespaceResult);
const response = await savedObjectsRepository.get('index-pattern', 'logstash-*');
- expect(onBeforeWrite).not.toHaveBeenCalled();
expect(response).toEqual({
id: 'logstash-*',
type: 'index-pattern',
@@ -1551,7 +1508,6 @@ describe('SavedObjectsRepository', () => {
it('formats Elasticsearch response when there are namespaces', async () => {
callAdminCluster.mockResolvedValue(namespacedResult);
const response = await savedObjectsRepository.get('index-pattern', 'logstash-*');
- expect(onBeforeWrite).not.toHaveBeenCalled();
expect(response).toEqual({
id: 'logstash-*',
type: 'index-pattern',
@@ -1570,7 +1526,6 @@ describe('SavedObjectsRepository', () => {
namespace: 'foo-namespace',
});
- expect(onBeforeWrite).not.toHaveBeenCalled();
expect(callAdminCluster).toHaveBeenCalledTimes(1);
expect(callAdminCluster).toHaveBeenCalledWith(
expect.any(String),
@@ -1584,7 +1539,6 @@ describe('SavedObjectsRepository', () => {
callAdminCluster.mockResolvedValue(noNamespaceResult);
await savedObjectsRepository.get('index-pattern', 'logstash-*');
- expect(onBeforeWrite).not.toHaveBeenCalled();
expect(callAdminCluster).toHaveBeenCalledTimes(1);
expect(callAdminCluster).toHaveBeenCalledWith(
expect.any(String),
@@ -1600,7 +1554,6 @@ describe('SavedObjectsRepository', () => {
namespace: 'foo-namespace',
});
- expect(onBeforeWrite).not.toHaveBeenCalled();
expect(callAdminCluster).toHaveBeenCalledTimes(1);
expect(callAdminCluster).toHaveBeenCalledWith(
expect.any(String),
@@ -1649,8 +1602,6 @@ describe('SavedObjectsRepository', () => {
},
})
);
-
- expect(onBeforeWrite).not.toHaveBeenCalled();
});
it('prepends namespace and type appropriately to id when getting objects when there is a namespace', async () => {
@@ -1680,8 +1631,6 @@ describe('SavedObjectsRepository', () => {
},
})
);
-
- expect(onBeforeWrite).not.toHaveBeenCalled();
});
it('mockReturnValue early for empty objects argument', async () => {
@@ -1691,7 +1640,6 @@ describe('SavedObjectsRepository', () => {
expect(response.saved_objects).toHaveLength(0);
expect(callAdminCluster).not.toHaveBeenCalled();
- expect(onBeforeWrite).not.toHaveBeenCalled();
});
it('handles missing ids gracefully', async () => {
@@ -1746,7 +1694,6 @@ describe('SavedObjectsRepository', () => {
{ id: 'bad', type: 'config' },
]);
- expect(onBeforeWrite).not.toHaveBeenCalled();
expect(callAdminCluster).toHaveBeenCalledTimes(1);
expect(savedObjects).toHaveLength(2);
@@ -2018,8 +1965,6 @@ describe('SavedObjectsRepository', () => {
refresh: 'wait_for',
index: '.kibana-test',
});
-
- expect(onBeforeWrite).toHaveBeenCalledTimes(1);
});
it(`doesn't prepend namespace to the id or add namespace property when providing no namespace for namespaced type`, async () => {
@@ -2060,8 +2005,6 @@ describe('SavedObjectsRepository', () => {
refresh: 'wait_for',
index: '.kibana-test',
});
-
- expect(onBeforeWrite).toHaveBeenCalledTimes(1);
});
it(`doesn't prepend namespace to the id or add namespace property when providing namespace for namespace agnostic type`, async () => {
@@ -2103,8 +2046,6 @@ describe('SavedObjectsRepository', () => {
refresh: 'wait_for',
index: '.kibana-test',
});
-
- expect(onBeforeWrite).toHaveBeenCalledTimes(1);
});
it('defaults to a refresh setting of `wait_for`', async () => {
@@ -2534,8 +2475,6 @@ describe('SavedObjectsRepository', () => {
},
],
});
-
- expect(onBeforeWrite).toHaveBeenCalledTimes(1);
});
it(`doesn't prepend namespace to the id or add namespace property when providing no namespace for namespaced type`, async () => {
@@ -2592,8 +2531,6 @@ describe('SavedObjectsRepository', () => {
},
],
});
-
- expect(onBeforeWrite).toHaveBeenCalledTimes(1);
});
it(`doesn't prepend namespace to the id or add namespace property when providing namespace for namespace agnostic type`, async () => {
@@ -2755,8 +2692,6 @@ describe('SavedObjectsRepository', () => {
expect(requestDoc.body.script.params.type).toBe('config');
expect(requestDoc.body.upsert.type).toBe('config');
expect(requestDoc).toHaveProperty('body.upsert.config');
-
- expect(onBeforeWrite).toHaveBeenCalledTimes(1);
});
it(`doesn't prepend namespace to the id or add namespace property when providing no namespace for namespaced type`, async () => {
@@ -2769,8 +2704,6 @@ describe('SavedObjectsRepository', () => {
expect(requestDoc.body.script.params.type).toBe('config');
expect(requestDoc.body.upsert.type).toBe('config');
expect(requestDoc).toHaveProperty('body.upsert.config');
-
- expect(onBeforeWrite).toHaveBeenCalledTimes(1);
});
it(`doesn't prepend namespace to the id or add namespace property when providing namespace for namespace agnostic type`, async () => {
@@ -2802,8 +2735,6 @@ describe('SavedObjectsRepository', () => {
expect(requestDoc.body.script.params.type).toBe('globaltype');
expect(requestDoc.body.upsert.type).toBe('globaltype');
expect(requestDoc).toHaveProperty('body.upsert.globaltype');
-
- expect(onBeforeWrite).toHaveBeenCalledTimes(1);
});
it('should assert that the "type" and "counterFieldName" arguments are strings', () => {
@@ -2852,39 +2783,6 @@ describe('SavedObjectsRepository', () => {
});
});
- describe('onBeforeWrite', () => {
- it('blocks calls to callCluster of requests', async () => {
- onBeforeWrite.mockReturnValue(delay(500));
- callAdminCluster.mockReturnValue({ result: 'deleted', found: true });
-
- const deletePromise = savedObjectsRepository.delete('foo', 'id');
- await delay(100);
- expect(onBeforeWrite).toHaveBeenCalledTimes(1);
- expect(callAdminCluster).not.toHaveBeenCalled();
- await deletePromise;
- expect(onBeforeWrite).toHaveBeenCalledTimes(1);
- expect(callAdminCluster).toHaveBeenCalledTimes(1);
- });
-
- it('can throw es errors and have them decorated as SavedObjectsClient errors', async () => {
- expect.assertions(4);
-
- const es401 = new legacyElasticsearch.errors[401]();
- expect(SavedObjectsErrorHelpers.isNotAuthorizedError(es401)).toBe(false);
- onBeforeWrite.mockImplementation(() => {
- throw es401;
- });
-
- try {
- await savedObjectsRepository.delete('foo', 'id');
- } catch (error) {
- expect(onBeforeWrite).toHaveBeenCalledTimes(1);
- expect(error).toBe(es401);
- expect(SavedObjectsErrorHelpers.isNotAuthorizedError(error)).toBe(true);
- }
- });
- });
-
describe('types on custom index', () => {
it('should error when attempting to \'update\' an unsupported type', async () => {
await expect(
diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts
index 3acf25234ec0e..4cd632586997e 100644
--- a/src/core/server/saved_objects/service/lib/repository.ts
+++ b/src/core/server/saved_objects/service/lib/repository.ts
@@ -19,7 +19,6 @@
import { omit } from 'lodash';
import { APICaller } from '../../../elasticsearch/';
-
import { getRootPropertiesObjects, IndexMapping } from '../../mappings';
import { getSearchDsl } from './search_dsl';
import { includedFields } from './included_fields';
@@ -42,7 +41,6 @@ import {
SavedObjectsBulkUpdateObject,
SavedObjectsBulkUpdateOptions,
SavedObjectsDeleteOptions,
- SavedObjectsDeleteByNamespaceOptions,
} from '../saved_objects_client';
import {
SavedObject,
@@ -75,6 +73,7 @@ const isLeft = (either: Either): either is Left => {
export interface SavedObjectsRepositoryOptions {
index: string;
+ /** @deprecated Will be removed once SavedObjectsSchema is exposed from Core */
config: Config;
mappings: IndexMapping;
callCluster: APICaller;
@@ -82,17 +81,38 @@ export interface SavedObjectsRepositoryOptions {
serializer: SavedObjectsSerializer;
migrator: KibanaMigrator;
allowedTypes: string[];
- onBeforeWrite?: (...args: Parameters) => Promise;
}
-export interface IncrementCounterOptions extends SavedObjectsBaseOptions {
+/**
+ * @public
+ */
+export interface SavedObjectsIncrementCounterOptions extends SavedObjectsBaseOptions {
migrationVersion?: SavedObjectsMigrationVersion;
/** The Elasticsearch Refresh setting for this operation */
refresh?: MutatingOperationRefreshSetting;
}
+/**
+ *
+ * @public
+ */
+export interface SavedObjectsDeleteByNamespaceOptions extends SavedObjectsBaseOptions {
+ /** The Elasticsearch Refresh setting for this operation */
+ refresh?: MutatingOperationRefreshSetting;
+}
+
const DEFAULT_REFRESH_SETTING = 'wait_for';
+/**
+ * See {@link SavedObjectsRepository}
+ *
+ * @public
+ */
+export type ISavedObjectsRepository = Pick;
+
+/**
+ * @public
+ */
export class SavedObjectsRepository {
private _migrator: KibanaMigrator;
private _index: string;
@@ -100,11 +120,10 @@ export class SavedObjectsRepository {
private _mappings: IndexMapping;
private _schema: SavedObjectsSchema;
private _allowedTypes: string[];
-
- private _onBeforeWrite: (...args: Parameters) => Promise;
private _unwrappedCallCluster: APICaller;
private _serializer: SavedObjectsSerializer;
+ /** @internal */
constructor(options: SavedObjectsRepositoryOptions) {
const {
index,
@@ -115,7 +134,6 @@ export class SavedObjectsRepository {
serializer,
migrator,
allowedTypes = [],
- onBeforeWrite = () => Promise.resolve(),
} = options;
// It's important that we migrate documents / mark them as up-to-date
@@ -135,8 +153,6 @@ export class SavedObjectsRepository {
}
this._allowedTypes = allowedTypes;
- this._onBeforeWrite = onBeforeWrite;
-
this._unwrappedCallCluster = async (...args: Parameters) => {
await migrator.runMigrations();
return callCluster(...args);
@@ -806,7 +822,7 @@ export class SavedObjectsRepository {
type: string,
id: string,
counterFieldName: string,
- options: IncrementCounterOptions = {}
+ options: SavedObjectsIncrementCounterOptions = {}
) {
if (typeof type !== 'string') {
throw new Error('"type" argument must be a string');
@@ -872,7 +888,6 @@ export class SavedObjectsRepository {
private async _writeToCluster(...args: Parameters) {
try {
- await this._onBeforeWrite(...args);
return await this._callCluster(...args);
} catch (err) {
throw decorateEsError(err);
diff --git a/src/core/server/saved_objects/service/lib/scoped_client_provider.ts b/src/core/server/saved_objects/service/lib/scoped_client_provider.ts
index 87607acd94fc4..0b67727455333 100644
--- a/src/core/server/saved_objects/service/lib/scoped_client_provider.ts
+++ b/src/core/server/saved_objects/service/lib/scoped_client_provider.ts
@@ -55,8 +55,7 @@ export interface SavedObjectsClientProviderOptions {
}
/**
- * @public
- * See {@link SavedObjectsClientProvider}
+ * @internal
*/
export type ISavedObjectsClientProvider = Pick<
SavedObjectsClientProvider,
@@ -66,6 +65,8 @@ export type ISavedObjectsClientProvider = Pick<
/**
* Provider for the Scoped Saved Objects Client.
*
+ * @internal
+ *
* @internalRemarks Because `getClient` is synchronous the Client Provider does
* not support creating factories that react to new ES clients emitted from
* elasticsearch.adminClient$. The Client Provider therefore doesn't support
diff --git a/src/core/server/saved_objects/service/saved_objects_client.ts b/src/core/server/saved_objects/service/saved_objects_client.ts
index 550e8a1de0d80..b0b2633646e10 100644
--- a/src/core/server/saved_objects/service/saved_objects_client.ts
+++ b/src/core/server/saved_objects/service/saved_objects_client.ts
@@ -17,7 +17,7 @@
* under the License.
*/
-import { SavedObjectsRepository } from './lib';
+import { ISavedObjectsRepository } from './lib';
import {
SavedObject,
SavedObjectAttributes,
@@ -126,15 +126,6 @@ export interface SavedObjectsDeleteOptions extends SavedObjectsBaseOptions {
refresh?: MutatingOperationRefreshSetting;
}
-/**
- *
- * @public
- */
-export interface SavedObjectsDeleteByNamespaceOptions extends SavedObjectsBaseOptions {
- /** The Elasticsearch Refresh setting for this operation */
- refresh?: MutatingOperationRefreshSetting;
-}
-
/**
*
* @public
@@ -180,9 +171,10 @@ export class SavedObjectsClient {
public static errors = SavedObjectsErrorHelpers;
public errors = SavedObjectsErrorHelpers;
- private _repository: SavedObjectsRepository;
+ private _repository: ISavedObjectsRepository;
- constructor(repository: SavedObjectsRepository) {
+ /** @internal */
+ constructor(repository: ISavedObjectsRepository) {
this._repository = repository;
}
diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md
index bf4394af5e71a..e8cafe1e1334e 100644
--- a/src/core/server/server.api.md
+++ b/src/core/server/server.api.md
@@ -511,11 +511,15 @@ export interface CoreSetup {
// (undocumented)
http: HttpServiceSetup;
// (undocumented)
+ savedObjects: SavedObjectsServiceSetup;
+ // (undocumented)
uiSettings: UiSettingsServiceSetup;
}
// @public
export interface CoreStart {
+ // (undocumented)
+ savedObjects: SavedObjectsServiceStart;
}
// @public
@@ -729,6 +733,9 @@ export interface IRouter {
// @public
export type IsAuthenticated = (request: KibanaRequest | LegacyRequest) => boolean;
+// @public
+export type ISavedObjectsRepository = Pick;
+
// @public
export type IScopedClusterClient = Pick;
@@ -1223,8 +1230,8 @@ export interface SavedObjectsBulkUpdateResponse(objects: Array>, options?: SavedObjectsCreateOptions): Promise>;
bulkGet(objects?: SavedObjectsBulkGetObject[], options?: SavedObjectsBaseOptions): Promise>;
bulkUpdate(objects: Array>, options?: SavedObjectsBulkUpdateOptions): Promise>;
@@ -1242,6 +1249,11 @@ export class SavedObjectsClient {
// @public
export type SavedObjectsClientContract = Pick;
+// @public
+export type SavedObjectsClientFactory = ({ request, }: {
+ request: Request;
+}) => SavedObjectsClientContract;
+
// @public
export interface SavedObjectsClientProviderOptions {
// (undocumented)
@@ -1269,6 +1281,11 @@ export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions {
refresh?: MutatingOperationRefreshSetting;
}
+// @public (undocumented)
+export interface SavedObjectsDeleteByNamespaceOptions extends SavedObjectsBaseOptions {
+ refresh?: MutatingOperationRefreshSetting;
+}
+
// @public (undocumented)
export interface SavedObjectsDeleteOptions extends SavedObjectsBaseOptions {
refresh?: MutatingOperationRefreshSetting;
@@ -1479,6 +1496,13 @@ export interface SavedObjectsImportUnsupportedTypeError {
type: 'unsupported_type';
}
+// @public (undocumented)
+export interface SavedObjectsIncrementCounterOptions extends SavedObjectsBaseOptions {
+ // (undocumented)
+ migrationVersion?: SavedObjectsMigrationVersion;
+ refresh?: MutatingOperationRefreshSetting;
+}
+
// @internal @deprecated (undocumented)
export interface SavedObjectsLegacyService {
// Warning: (ae-forgotten-export) The symbol "SavedObjectsClientProvider" needs to be exported by the entry point index.d.ts
@@ -1538,6 +1562,32 @@ export interface SavedObjectsRawDoc {
_type?: string;
}
+// @public (undocumented)
+export class SavedObjectsRepository {
+ // Warning: (ae-forgotten-export) The symbol "SavedObjectsRepositoryOptions" needs to be exported by the entry point index.d.ts
+ //
+ // @internal
+ constructor(options: SavedObjectsRepositoryOptions);
+ bulkCreate(objects: Array>, options?: SavedObjectsCreateOptions): Promise>;
+ bulkGet(objects?: SavedObjectsBulkGetObject[], options?: SavedObjectsBaseOptions): Promise>;
+ bulkUpdate(objects: Array>, options?: SavedObjectsBulkUpdateOptions): Promise>;
+ create(type: string, attributes: T, options?: SavedObjectsCreateOptions): Promise>;
+ delete(type: string, id: string, options?: SavedObjectsDeleteOptions): Promise<{}>;
+ deleteByNamespace(namespace: string, options?: SavedObjectsDeleteByNamespaceOptions): Promise;
+ // (undocumented)
+ find({ search, defaultSearchOperator, searchFields, hasReference, page, perPage, sortField, sortOrder, fields, namespace, type, filter, }: SavedObjectsFindOptions): Promise>;
+ get(type: string, id: string, options?: SavedObjectsBaseOptions): Promise>;
+ incrementCounter(type: string, id: string, counterFieldName: string, options?: SavedObjectsIncrementCounterOptions): Promise<{
+ id: string;
+ type: string;
+ updated_at: string;
+ references: any;
+ version: string;
+ attributes: any;
+ }>;
+ update(type: string, id: string, attributes: Partial, options?: SavedObjectsUpdateOptions): Promise>;
+ }
+
// @public
export interface SavedObjectsResolveImportErrorsOptions {
// (undocumented)
@@ -1578,6 +1628,19 @@ export class SavedObjectsSerializer {
savedObjectToRaw(savedObj: SanitizedSavedObjectDoc): SavedObjectsRawDoc;
}
+// @public
+export interface SavedObjectsServiceSetup {
+ addClientWrapper: (priority: number, id: string, factory: SavedObjectsClientWrapperFactory) => void;
+ createInternalRepository: (extraTypes?: string[]) => ISavedObjectsRepository;
+ createScopedRepository: (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository;
+ setClientFactory: (customClientFactory: SavedObjectsClientFactory) => void;
+}
+
+// @public
+export interface SavedObjectsServiceStart {
+ getScopedClient: (req: KibanaRequest, options?: SavedObjectsClientProviderOptions) => SavedObjectsClientContract;
+}
+
// @public (undocumented)
export interface SavedObjectsUpdateOptions extends SavedObjectsBaseOptions {
references?: SavedObjectReference[];
diff --git a/src/core/server/server.test.mocks.ts b/src/core/server/server.test.mocks.ts
index f8eb5e32f4c5a..b378273a7075a 100644
--- a/src/core/server/server.test.mocks.ts
+++ b/src/core/server/server.test.mocks.ts
@@ -35,9 +35,11 @@ jest.doMock('./elasticsearch/elasticsearch_service', () => ({
ElasticsearchService: jest.fn(() => mockElasticsearchService),
}));
-export const mockLegacyService = {
+import { ILegacyService } from './legacy/legacy_service';
+export const mockLegacyService: ILegacyService = {
legacyId: Symbol(),
- setup: jest.fn().mockReturnValue({ uiExports: {} }),
+ discoverPlugins: jest.fn().mockReturnValue({ uiExports: {} }),
+ setup: jest.fn(),
start: jest.fn(),
stop: jest.fn(),
};
diff --git a/src/core/server/server.ts b/src/core/server/server.ts
index 6c38de03f0f2d..b36468b85d7a1 100644
--- a/src/core/server/server.ts
+++ b/src/core/server/server.ts
@@ -38,7 +38,6 @@ import { config as savedObjectsConfig } from './saved_objects';
import { config as uiSettingsConfig } from './ui_settings';
import { mapToObject } from '../utils/';
import { ContextService } from './context';
-import { SavedObjectsServiceSetup } from './saved_objects/saved_objects_service';
import { RequestHandlerContext } from '.';
import { InternalCoreSetup } from './internal_types';
@@ -78,6 +77,7 @@ export class Server {
// Discover any plugins before continuing. This allows other systems to utilize the plugin dependency graph.
const pluginDependencies = await this.plugins.discover();
+ const legacyPlugins = await this.legacy.discoverPlugins();
const contextServiceSetup = this.context.setup({
// We inject a fake "legacy plugin" with dependencies on every plugin so that legacy plugins:
// 1) Can access context from any NP plugin
@@ -103,40 +103,41 @@ export class Server {
http: httpSetup,
});
+ const savedObjectsSetup = await this.savedObjects.setup({
+ elasticsearch: elasticsearchServiceSetup,
+ legacyPlugins,
+ });
+
const coreSetup: InternalCoreSetup = {
context: contextServiceSetup,
elasticsearch: elasticsearchServiceSetup,
http: httpSetup,
uiSettings: uiSettingsSetup,
+ savedObjects: savedObjectsSetup,
};
const pluginsSetup = await this.plugins.setup(coreSetup);
- const legacySetup = await this.legacy.setup({
+ await this.legacy.setup({
core: { ...coreSetup, plugins: pluginsSetup },
plugins: mapToObject(pluginsSetup.contracts),
});
- const savedObjectsSetup = await this.savedObjects.setup({
- elasticsearch: elasticsearchServiceSetup,
- legacy: legacySetup,
- });
-
- this.registerCoreContext(coreSetup, savedObjectsSetup);
+ this.registerCoreContext(coreSetup);
return coreSetup;
}
public async start() {
this.log.debug('starting server');
- const pluginsStart = await this.plugins.start({});
const savedObjectsStart = await this.savedObjects.start({});
+ const pluginsStart = await this.plugins.start({ savedObjects: savedObjectsStart });
+
const coreStart = {
savedObjects: savedObjectsStart,
plugins: pluginsStart,
};
-
await this.legacy.start({
core: coreStart,
plugins: mapToObject(pluginsStart.contracts),
@@ -164,17 +165,14 @@ export class Server {
);
}
- private registerCoreContext(
- coreSetup: InternalCoreSetup,
- savedObjects: SavedObjectsServiceSetup
- ) {
+ private registerCoreContext(coreSetup: InternalCoreSetup) {
coreSetup.http.registerRouteHandlerContext(
coreId,
'core',
async (context, req): Promise => {
const adminClient = await coreSetup.elasticsearch.adminClient$.pipe(take(1)).toPromise();
const dataClient = await coreSetup.elasticsearch.dataClient$.pipe(take(1)).toPromise();
- const savedObjectsClient = savedObjects.clientProvider.getClient(req);
+ const savedObjectsClient = coreSetup.savedObjects.getScopedClient(req);
return {
savedObjects: {
diff --git a/src/es_archiver/lib/indices/kibana_index.js b/src/es_archiver/lib/indices/kibana_index.js
index 49e767135c243..e3c6ba70f1edf 100644
--- a/src/es_archiver/lib/indices/kibana_index.js
+++ b/src/es_archiver/lib/indices/kibana_index.js
@@ -26,6 +26,7 @@ import { toArray } from 'rxjs/operators';
import { deleteIndex } from './delete_index';
import { collectUiExports } from '../../../legacy/ui/ui_exports';
import { KibanaMigrator } from '../../../core/server/saved_objects/migrations';
+import { SavedObjectsSchema } from '../../../core/server/saved_objects';
import { findPluginSpecs } from '../../../legacy/plugin_discovery';
/**
@@ -101,7 +102,7 @@ export async function migrateKibanaIndex({ client, log, kibanaPluginIds }) {
error: log.error.bind(log),
},
version: kibanaVersion,
- savedObjectSchemas: uiExports.savedObjectSchemas,
+ savedObjectSchemas: new SavedObjectsSchema(uiExports.savedObjectSchemas),
savedObjectMappings: uiExports.savedObjectMappings,
savedObjectMigrations: uiExports.savedObjectMigrations,
savedObjectValidations: uiExports.savedObjectValidations,