diff --git a/platform/commonUI/browse/bundle.json b/platform/commonUI/browse/bundle.json index 9e7772c66d..0d4c1eedd6 100644 --- a/platform/commonUI/browse/bundle.json +++ b/platform/commonUI/browse/bundle.json @@ -1,9 +1,4 @@ { - "configuration": { - "paths": { - "uuid": "uuid" - } - }, "extensions": { "routes": [ { @@ -159,7 +154,7 @@ "provides": "creationService", "type": "provider", "implementation": "creation/CreationService.js", - "depends": [ "persistenceService", "now", "$q", "$log" ] + "depends": [ "$q", "$log" ] } ], "runs": [ @@ -178,16 +173,6 @@ "copyright": "Copyright (c) Sindre Sorhus (sindresorhus.com)", "license": "license-mit", "link": "https://github.com/sindresorhus/screenfull.js/blob/gh-pages/license" - }, - { - "name": "Math.uuid.js", - "version": "1.4", - "description": "Unique identifer generation (code adapted.)", - "author": "Robert Kieffer", - "website": "https://github.com/broofa/node-uuid", - "copyright": "Copyright (c) 2010 Robert Kieffer", - "license": "license-mit", - "link": "http://opensource.org/licenses/MIT" } ] } diff --git a/platform/commonUI/browse/src/creation/CreationService.js b/platform/commonUI/browse/src/creation/CreationService.js index 2b059724b3..4b7c0119c9 100644 --- a/platform/commonUI/browse/src/creation/CreationService.js +++ b/platform/commonUI/browse/src/creation/CreationService.js @@ -25,8 +25,8 @@ * Module defining CreateService. Created by vwoeltje on 11/10/14. */ define( - ["uuid"], - function (uuid) { + [], + function () { "use strict"; var NON_PERSISTENT_WARNING = @@ -42,11 +42,9 @@ define( * @memberof platform/commonUI/browse * @constructor */ - function CreationService(persistenceService, now, $q, $log) { - this.persistenceService = persistenceService; + function CreationService($q, $log) { this.$q = $q; this.$log = $log; - this.now = now; } /** @@ -70,26 +68,17 @@ define( */ CreationService.prototype.createObject = function (model, parent) { var persistence = parent.getCapability("persistence"), + newObject = parent.useCapability("instantiation", model), + newObjectPersistence = newObject.getCapability("persistence"), self = this; - // Persist the new domain object's model; it will be fully - // constituted as a domain object when loaded back, as all - // domain object models are. - function doPersist(space, id, model) { - return self.persistenceService.createObject( - space, - id, - model - ).then(function () { return id; }); - } - // Add the newly-created object's id to the parent's // composition, so that it will subsequently appear // as a child contained by that parent. - function addToComposition(id, parent, parentPersistence) { + function addToComposition() { var compositionCapability = parent.getCapability('composition'), addResult = compositionCapability && - compositionCapability.add(id); + compositionCapability.add(newObject); return self.$q.when(addResult).then(function (result) { if (!result) { @@ -97,7 +86,7 @@ define( return undefined; } - return parentPersistence.persist().then(function () { + return persistence.persist().then(function () { return result; }); }); @@ -105,21 +94,13 @@ define( // We need the parent's persistence capability to determine // what space to create the new object's model in. - if (!persistence) { + if (!persistence || !newObjectPersistence) { self.$log.warn(NON_PERSISTENT_WARNING); return self.$q.reject(new Error(NON_PERSISTENT_WARNING)); } - // We create a new domain object in three sequential steps: - // 1. Get a new UUID for the object - // 2. Create a model with that ID in the persistence space - // 3. Add that ID to - return self.$q.when(uuid()).then(function (id) { - model.persisted = self.now(); - return doPersist(persistence.getSpace(), id, model); - }).then(function (id) { - return addToComposition(id, parent, persistence); - }); + // Persist the new object, then add it to composition. + return newObjectPersistence.persist().then(addToComposition); }; diff --git a/platform/commonUI/browse/test/creation/CreationServiceSpec.js b/platform/commonUI/browse/test/creation/CreationServiceSpec.js index 20da8ae886..e0704ba702 100644 --- a/platform/commonUI/browse/test/creation/CreationServiceSpec.js +++ b/platform/commonUI/browse/test/creation/CreationServiceSpec.js @@ -30,9 +30,7 @@ define( "use strict"; describe("The creation service", function () { - var mockPersistenceService, - mockNow, - mockQ, + var mockQ, mockLog, mockParentObject, mockNewObject, @@ -40,7 +38,9 @@ define( mockPersistenceCapability, mockCompositionCapability, mockContextCapability, + mockCreationCapability, mockCapabilities, + mockNewPersistenceCapability, creationService; function mockPromise(value) { @@ -60,11 +60,6 @@ define( } beforeEach(function () { - mockPersistenceService = jasmine.createSpyObj( - "persistenceService", - [ "createObject" ] - ); - mockNow = jasmine.createSpy('now'); mockQ = { when: mockPromise, reject: mockReject }; mockLog = jasmine.createSpyObj( "$log", @@ -76,7 +71,7 @@ define( ); mockNewObject = jasmine.createSpyObj( "newObject", - [ "getId" ] + [ "getId", "getCapability", "useCapability" ] ); mockMutationCapability = jasmine.createSpyObj( "mutation", @@ -94,19 +89,22 @@ define( "context", ["getPath"] ); + mockCreationCapability = jasmine.createSpyObj( + "creation", + ["instantiate", "invoke"] + ); mockCapabilities = { mutation: mockMutationCapability, persistence: mockPersistenceCapability, composition: mockCompositionCapability, - context: mockContextCapability + context: mockContextCapability, + instantiation: mockCreationCapability }; - - mockPersistenceService.createObject.andReturn( - mockPromise(true) + mockNewPersistenceCapability = jasmine.createSpyObj( + "new-persistence", + [ "persist", "getSpace" ] ); - mockNow.andReturn(12321); - mockParentObject.getCapability.andCallFake(function (key) { return mockCapabilities[key]; }); @@ -115,9 +113,16 @@ define( }); mockParentObject.getId.andReturn('parentId'); - mockPersistenceCapability.persist.andReturn( - mockPromise(true) - ); + mockNewObject.getId.andReturn('newId'); + mockNewObject.getCapability.andCallFake(function (c) { + return c === 'persistence' ? + mockNewPersistenceCapability : undefined; + }); + + mockPersistenceCapability.persist + .andReturn(mockPromise(true)); + mockNewPersistenceCapability.persist + .andReturn(mockPromise(true)); mockMutationCapability.invoke.andReturn(mockPromise(true)); mockPersistenceCapability.getSpace.andReturn("testSpace"); @@ -125,10 +130,12 @@ define( mockPromise([mockNewObject]) ); mockCompositionCapability.add.andReturn(mockPromise(true)); + mockCreationCapability.instantiate.andReturn(mockNewObject); + mockCreationCapability.invoke.andCallFake(function (model) { + return mockCreationCapability.instantiate(model); + }); creationService = new CreationService( - mockPersistenceService, - mockNow, mockQ, mockLog ); @@ -137,21 +144,18 @@ define( it("allows new objects to be created", function () { var model = { someKey: "some value" }; creationService.createObject(model, mockParentObject); - expect(mockPersistenceService.createObject).toHaveBeenCalledWith( - "testSpace", - jasmine.any(String), // the object id; generated UUID - model - ); + expect(mockCreationCapability.instantiate) + .toHaveBeenCalledWith(model); }); - it("adds new id's to the parent's composition", function () { + it("adds new objects to the parent's composition", function () { var model = { someKey: "some value" }, parentModel = { composition: ["notAnyUUID"] }; creationService.createObject(model, mockParentObject); // Verify that a new ID was added expect(mockCompositionCapability.add) - .toHaveBeenCalledWith(jasmine.any(String)); + .toHaveBeenCalledWith(mockNewObject); }); it("provides the newly-created object", function () { @@ -207,11 +211,6 @@ define( expect(mockLog.error).toHaveBeenCalled(); }); - it("attaches a 'persisted' timestamp", function () { - var model = { someKey: "some value" }; - creationService.createObject(model, mockParentObject); - expect(model.persisted).toEqual(mockNow()); - }); }); } diff --git a/platform/core/bundle.json b/platform/core/bundle.json index 330ac1c2b9..89c059eee8 100644 --- a/platform/core/bundle.json +++ b/platform/core/bundle.json @@ -2,6 +2,11 @@ "name": "Open MCT Web Core", "description": "Defines core concepts of Open MCT Web.", "sources": "src", + "configuration": { + "paths": { + "uuid": "uuid" + } + }, "extensions": { "versions": [ { @@ -33,7 +38,7 @@ "provides": "objectService", "type": "provider", "implementation": "objects/DomainObjectProvider.js", - "depends": [ "modelService", "capabilityService", "$q" ] + "depends": [ "modelService", "instantiate" ] }, { "provides": "capabilityService", @@ -193,6 +198,11 @@ "key": "delegation", "implementation": "capabilities/DelegationCapability.js", "depends": [ "$q" ] + }, + { + "key": "instantiation", + "implementation": "capabilities/InstantiationCapability.js", + "depends": [ "$injector" ] } ], "services": [ @@ -213,6 +223,11 @@ "key": "contextualize", "implementation": "services/Contextualize.js", "depends": [ "$log" ] + }, + { + "key": "instantiate", + "implementation": "services/Instantiate.js", + "depends": [ "capabilityService" ] } ], "roots": [ @@ -235,6 +250,18 @@ "value": [], "description": "An array of additional persistence spaces to load models from." } + ], + "licenses": [ + { + "name": "Math.uuid.js", + "version": "1.4", + "description": "Unique identifer generation (code adapted.)", + "author": "Robert Kieffer", + "website": "https://github.com/broofa/node-uuid", + "copyright": "Copyright (c) 2010 Robert Kieffer", + "license": "license-mit", + "link": "http://opensource.org/licenses/MIT" + } ] } } diff --git a/platform/commonUI/browse/lib/uuid.js b/platform/core/lib/uuid.js similarity index 100% rename from platform/commonUI/browse/lib/uuid.js rename to platform/core/lib/uuid.js diff --git a/platform/core/src/capabilities/InstantiationCapability.js b/platform/core/src/capabilities/InstantiationCapability.js new file mode 100644 index 0000000000..52384e993e --- /dev/null +++ b/platform/core/src/capabilities/InstantiationCapability.js @@ -0,0 +1,67 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed 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. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define,Promise*/ + +define( + ['../objects/DomainObjectImpl', 'uuid'], + function (DomainObjectImpl, uuid) { + 'use strict'; + + /** + * Implements the `instantiation` capability. This allows new domain + * objects to be instantiated. + * + * @constructor + * @memberof platform/core + * @param $injector Angular's `$injector` + */ + function InstantiationCapability($injector) { + this.$injector = $injector; + } + + /** + * Instantiate a new domain object with the provided model. + * + * This domain object will have been simply instantiated; it will not + * have been persisted, nor will it have been added to the + * composition of the object which exposed this capability. + * + * @returns {DomainObject} the new domain object + */ + InstantiationCapability.prototype.instantiate = function (model) { + // Lazily initialize; instantiate depends on capabilityService, + // which depends on all capabilities, including this one. + this.instantiateFn = this.instantiateFn || + this.$injector.get("instantiate"); + return this.instantiateFn(model); + }; + + /** + * Alias of `create`. + * @see {platform/core.CreationCapability#create} + */ + InstantiationCapability.prototype.invoke = + InstantiationCapability.prototype.instantiate; + + return InstantiationCapability; + } +); diff --git a/platform/core/src/objects/DomainObjectProvider.js b/platform/core/src/objects/DomainObjectProvider.js index c846cbf665..800d31f5d5 100644 --- a/platform/core/src/objects/DomainObjectProvider.js +++ b/platform/core/src/objects/DomainObjectProvider.js @@ -27,8 +27,8 @@ * @namespace platform/core */ define( - ["./DomainObjectImpl"], - function (DomainObjectImpl) { + [], + function () { "use strict"; /** @@ -57,62 +57,36 @@ define( * * @param {ModelService} modelService the service which shall * provide models (persistent state) for domain objects - * @param {CapabilityService} capabilityService the service - * which provides capabilities (dynamic behavior) - * for domain objects. + * @param {Function} instantiate a service to instantiate new + * domain object instances * @param $q Angular's $q, for promise consolidation * @memberof platform/core * @constructor */ - function DomainObjectProvider(modelService, capabilityService, $q) { + function DomainObjectProvider(modelService, instantiate, $q) { this.modelService = modelService; - this.capabilityService = capabilityService; - this.$q = $q; + this.instantiate = instantiate; } DomainObjectProvider.prototype.getObjects = function getObjects(ids) { var modelService = this.modelService, - capabilityService = this.capabilityService, - $q = this.$q; - - // Given a models object (containing key-value id-model pairs) - // create a function that will look up from the capability - // service based on id; for handy mapping below. - function capabilityResolver(models) { - return function (id) { - var model = models[id]; - return model ? - capabilityService.getCapabilities(model) : - undefined; - }; - } + instantiate = this.instantiate; // Assemble the results from the model service and the // capability service into one value, suitable to return - // from this service. Note that ids are matched to capabilities - // by index. - function assembleResult(ids, models, capabilities) { + // from this service. + function assembleResult(models) { var result = {}; ids.forEach(function (id, index) { if (models[id]) { // Create the domain object - result[id] = new DomainObjectImpl( - id, - models[id], - capabilities[index] - ); + result[id] = instantiate(models[id], id); } }); return result; } - return modelService.getModels(ids).then(function (models) { - return $q.all( - ids.map(capabilityResolver(models)) - ).then(function (capabilities) { - return assembleResult(ids, models, capabilities); - }); - }); + return modelService.getModels(ids).then(assembleResult); }; return DomainObjectProvider; diff --git a/platform/core/src/services/Instantiate.js b/platform/core/src/services/Instantiate.js new file mode 100644 index 0000000000..f59916c938 --- /dev/null +++ b/platform/core/src/services/Instantiate.js @@ -0,0 +1,54 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed 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. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define,Promise*/ + +define( + ['../objects/DomainObjectImpl', 'uuid'], + function (DomainObjectImpl, uuid) { + 'use strict'; + + /** + * The `instantiate` service allows new domain object instances to be + * created. These objects are not persisted to any back-end or + * placed anywhere in the object hierarchy by default. + * + * Usage: `instantiate(model, [id])` + * + * ...returns a new instance of a domain object with the specified + * model. An identifier may be provided; if omitted, one will be + * generated instead. + * + * @constructor + * @memberof platform/core + * @param $injector Angular's `$injector` + */ + function Instantiate(capabilityService) { + return function (model, id) { + var capabilities = capabilityService.getCapabilities(model); + id = id || uuid(); + return new DomainObjectImpl(id, model, capabilities); + }; + } + + return Instantiate; + } +); diff --git a/platform/core/test/capabilities/InstantiationCapabilitySpec.js b/platform/core/test/capabilities/InstantiationCapabilitySpec.js new file mode 100644 index 0000000000..0798a68f0c --- /dev/null +++ b/platform/core/test/capabilities/InstantiationCapabilitySpec.js @@ -0,0 +1,67 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed 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. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define,Promise,describe,it,xdescribe,expect,beforeEach,waitsFor,jasmine*/ + +define( + ["../../src/capabilities/InstantiationCapability"], + function (InstantiationCapability) { + 'use strict'; + + describe("The 'instantiation' capability", function () { + var mockInjector, + mockInstantiate, + instantiation; + + beforeEach(function () { + mockInjector = jasmine.createSpyObj("$injector", ["get"]); + mockInstantiate = jasmine.createSpy("instantiate"); + + mockInjector.get.andCallFake(function (key) { + return key === 'instantiate' ? + mockInstantiate : undefined; + }); + + instantiation = new InstantiationCapability(mockInjector); + }); + + + it("aliases 'instantiate' as 'invoke'", function () { + expect(instantiation.invoke).toBe(instantiation.instantiate); + }); + + it("uses the instantiate service to create domain objects", function () { + var mockDomainObject = jasmine.createSpyObj('domainObject', [ + 'getId', + 'getModel', + 'getCapability', + 'useCapability', + 'hasCapability' + ]), testModel = { someKey: "some value" }; + mockInstantiate.andReturn(mockDomainObject); + expect(instantiation.instantiate(testModel)) + .toBe(mockDomainObject); + expect(mockInstantiate).toHaveBeenCalledWith(testModel); + }); + + }); + } +); diff --git a/platform/core/test/objects/DomainObjectProviderSpec.js b/platform/core/test/objects/DomainObjectProviderSpec.js index 3aca982260..438c91f103 100644 --- a/platform/core/test/objects/DomainObjectProviderSpec.js +++ b/platform/core/test/objects/DomainObjectProviderSpec.js @@ -25,14 +25,16 @@ * DomainObjectProviderSpec. Created by vwoeltje on 11/6/14. */ define( - ["../../src/objects/DomainObjectProvider"], - function (DomainObjectProvider) { + [ + "../../src/objects/DomainObjectProvider", + "../../src/objects/DomainObjectImpl" + ], + function (DomainObjectProvider, DomainObjectImpl) { "use strict"; describe("The domain object provider", function () { var mockModelService, - mockCapabilityService, - mockQ, + mockInstantiate, provider; function mockPromise(value) { @@ -57,18 +59,15 @@ define( "modelService", [ "getModels" ] ); - mockCapabilityService = jasmine.createSpyObj( - "capabilityService", - [ "getCapabilities" ] - ); - mockQ = { - when: mockPromise, - all: mockAll - }; + mockInstantiate = jasmine.createSpy("instantiate"); + + mockInstantiate.andCallFake(function (model, id) { + return new DomainObjectImpl(id, model, {}); + }); + provider = new DomainObjectProvider( mockModelService, - mockCapabilityService, - mockQ + mockInstantiate ); }); @@ -86,10 +85,11 @@ define( result; mockModelService.getModels.andReturn(mockPromise({ a: model })); result = provider.getObjects(ids).testValue; + expect(mockInstantiate).toHaveBeenCalledWith(model, 'a'); expect(result.a.getId()).toEqual("a"); expect(result.a.getModel()).toEqual(model); }); }); } -); \ No newline at end of file +); diff --git a/platform/core/test/services/InstantiateSpec.js b/platform/core/test/services/InstantiateSpec.js new file mode 100644 index 0000000000..31a5731dd3 --- /dev/null +++ b/platform/core/test/services/InstantiateSpec.js @@ -0,0 +1,81 @@ +/***************************************************************************** + * Open MCT Web, Copyright (c) 2014-2015, United States Government + * as represented by the Administrator of the National Aeronautics and Space + * Administration. All rights reserved. + * + * Open MCT Web is licensed 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. + * + * Open MCT Web includes source code licensed under additional open source + * licenses. See the Open Source Licenses file (LICENSES.md) included with + * this source code distribution or the Licensing information page available + * at runtime from the About dialog for additional information. + *****************************************************************************/ +/*global define,Promise,describe,it,xdescribe,expect,beforeEach,waitsFor,jasmine*/ + +define( + ["../../src/services/Instantiate"], + function (Instantiate) { + 'use strict'; + + describe("The 'instantiate' service", function () { + + var mockCapabilityService, + mockCapabilityConstructor, + mockCapabilityInstance, + mockCapabilities, + testModel, + instantiate, + domainObject; + + beforeEach(function () { + mockCapabilityService = jasmine.createSpyObj( + 'capabilityService', + ['getCapabilities'] + ); + mockCapabilityConstructor = jasmine.createSpy('capability'); + mockCapabilityInstance = {}; + mockCapabilityService.getCapabilities.andReturn({ + something: mockCapabilityConstructor + }); + mockCapabilityConstructor.andReturn(mockCapabilityInstance); + + testModel = { someKey: "some value" }; + + instantiate = new Instantiate(mockCapabilityService); + domainObject = instantiate(testModel); + }); + + it("loads capabilities from the capability service", function () { + expect(mockCapabilityService.getCapabilities) + .toHaveBeenCalledWith(testModel); + }); + + it("exposes loaded capabilities from the created object", function () { + expect(domainObject.getCapability('something')) + .toBe(mockCapabilityInstance); + expect(mockCapabilityConstructor) + .toHaveBeenCalledWith(domainObject); + }); + + it("exposes the provided model", function () { + expect(domainObject.getModel()).toEqual(testModel); + }); + + it("provides unique identifiers", function () { + expect(domainObject.getId()).toEqual(jasmine.any(String)); + expect(instantiate(testModel).getId()) + .not.toEqual(domainObject.getId()); + }); + }); + + } +); diff --git a/platform/core/test/suite.json b/platform/core/test/suite.json index 012a1b3503..d6afc373ef 100644 --- a/platform/core/test/suite.json +++ b/platform/core/test/suite.json @@ -9,6 +9,7 @@ "capabilities/ContextualDomainObject", "capabilities/CoreCapabilityProvider", "capabilities/DelegationCapability", + "capabilities/InstantiationCapability", "capabilities/MetadataCapability", "capabilities/MutationCapability", "capabilities/PersistenceCapability", @@ -25,6 +26,7 @@ "objects/DomainObjectProvider", "services/Contextualize", + "services/Instantiate", "services/Now", "services/Throttle", "services/Topic", diff --git a/test-main.js b/test-main.js index 77f6bb4d86..2822b8cad8 100644 --- a/test-main.js +++ b/test-main.js @@ -46,7 +46,7 @@ require.config({ 'es6-promise': 'platform/framework/lib/es6-promise-2.0.0.min', 'moment': 'platform/telemetry/lib/moment.min', 'moment-duration-format': 'platform/features/clock/lib/moment-duration-format', - 'uuid': 'platform/commonUI/browse/lib/uuid' + 'uuid': 'platform/core/lib/uuid' }, shim: {