diff --git a/.travis.yml b/.travis.yml index 55037ac18..075c4f3d2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,8 +27,7 @@ script: - ./gradlew check --stacktrace # Run integration tests of `spine-web` library. - # - ./gradlew integrationTest - # TODO:2019-11-11:dmytro.dashenkov: Run integration tests when https://github.com/gretty-gradle-plugin/gretty/issues/130 is resolved. + - ./gradlew integrationTest # The publishing script should be executed in `script` section in order to # fail the Travis build if execution of this script is failed. diff --git a/client-js/main/client/actor-request-factory.js b/client-js/main/client/actor-request-factory.js index f168e5818..9f955c95d 100644 --- a/client-js/main/client/actor-request-factory.js +++ b/client-js/main/client/actor-request-factory.js @@ -814,7 +814,7 @@ class CommandFactory { * Creates a `Command` from the given command message. * * @param {!Message} message a command message - * @return {TypedMessage} a typed representation of the Spine Command + * @return {Command} a Spine Command */ create(message) { const id = CommandFactory._newCommandId(); @@ -825,7 +825,7 @@ class CommandFactory { result.setId(id); result.setMessage(messageAny); result.setContext(context); - return TypedMessage.of(result); + return result; } _commandContext() { diff --git a/client-js/main/client/client-factory.js b/client-js/main/client/client-factory.js index 6034adf41..92baeee10 100644 --- a/client-js/main/client/client-factory.js +++ b/client-js/main/client/client-factory.js @@ -20,11 +20,11 @@ "use strict"; +import {ActorRequestFactory} from "./actor-request-factory"; import {Client} from './client'; +import {CommandingClient} from "./commanding-client"; import {HttpClient} from "./http-client"; import {HttpEndpoint} from "./http-endpoint"; -import {ActorRequestFactory} from "./actor-request-factory"; -import {CommandingClient} from "./composite-client"; /** * @typedef {Object} ClientOptions a type of object for initialization of Spine client diff --git a/client-js/main/client/client-request.js b/client-js/main/client/client-request.js new file mode 100644 index 000000000..dd9d74ddb --- /dev/null +++ b/client-js/main/client/client-request.js @@ -0,0 +1,49 @@ +/* + * Copyright 2019, TeamDev. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +"use strict"; + +/** + * A request from a client to the Spine backend. + * + * @abstract + */ +export class ClientRequest { + + /** + * @protected + */ + constructor(client, requestFactory) { + + /** + * @type Client + * + * @protected + */ + this._client = client; + + /** + * @type ActorRequestFactory + * + * @protected + */ + this._requestFactory = requestFactory; + } +} diff --git a/client-js/main/client/client.js b/client-js/main/client/client.js index d1b978f22..ca63f6947 100644 --- a/client-js/main/client/client.js +++ b/client-js/main/client/client.js @@ -22,8 +22,6 @@ import {Message} from 'google-protobuf'; import {Observable} from 'rxjs'; -import {Query} from '../proto/spine/client/query_pb'; -import {Topic} from '../proto/spine/client/subscription_pb'; /** * The callback that doesn't accept any parameters. @@ -39,38 +37,53 @@ import {Topic} from '../proto/spine/client/subscription_pb'; * @template the type of value passed to the callback */ +/** + * The callback that accepts a single `Event` as a parameter. + * + * @callback eventConsumer + * + * @param {spine.core.Event} the event that is accepted by the callback + */ + /** * @typedef {Object} EntitySubscriptionObject * - * An object representing a result of the subscription to entities state changes. - * The entities that already exist will be initially passed to the `itemAdded` observer. + * An object representing the result of a subscription to entity state changes. * * @property {!Observable} itemAdded emits new items matching the subscription topic * @property {!Observable} itemChanged emits updated items matching the subscription topic * @property {!Observable} itemRemoved emits removed items matching the subscription topic * @property {!parameterlessCallback} unsubscribe a method to be called to cancel the subscription, - * stopping the subscribers from receiving new entities + * stopping the subscribers from receiving new + * entities * * @template a type of the subscription target entities */ /** - * @typedef {Object} SimpleTarget + * @typedef {Object} EventSubscriptionObject * - * An object representing a set of parameters for building a query or a subscription - * topic specifying only a type and identifiers of the target entities. + * An object which represents the result of a subscription to events of a certain type. * - * Target built from this object point either: - * - a single entity of a given type with a given ID; - * - several entities of a given type with given IDs; - * - all entities of a given type if no IDs specified; + * @property > eventEmitted emits new items when the new events + * matching the subscription topic occur in + * the system + * @property {!parameterlessCallback} unsubscribe a method to be called to cancel the subscription, + * stopping the subscribers from receiving new + * entities + */ + +/** + * @typedef AckCallback * - * @property {!Class} entity a class of target entities - * @property {?[] | | Number[] | Number | String[] | String} byIds - * a list of target entities IDs or an ID of a single target entity + * Represents a command acknowledgement callback. * - * @template a class of a query or subscription target entities - * @template a class of a query or subscription target entities identifiers + * @property {!parameterlessCallback} onOk the callback to run when the command is handled properly + * @property {!consumerCallback} onError the callback to run when the command cannot be + * handled due to a technical error + * @property {!consumerCallback} onRejection the callback to run when the command cannot + * be handled properly because of the business + * rejection */ /** @@ -81,6 +94,48 @@ import {Topic} from '../proto/spine/client/subscription_pb'; */ export class Client { + /** + * Creates a query request that allows to configure and post a new query. + * + * @param {!Class} entityType a Protobuf type of the query target entities + * @return {QueryRequest} the builder to construct and post a new query + */ + select(entityType) { + throw new Error('Not implemented in abstract base.'); + } + + /** + * Executes the given `Query` instance specifying the data to be retrieved from + * Spine server fulfilling a returned promise with an array of received objects. + * + * @param {!spine.client.Query} query a query instance to be executed + * @return {Promise<[]>} a promise to be fulfilled with a list of Protobuf + * messages of a given type or with an empty list if no entities matching given query + * were found; rejected with a `SpineError` if error occurs + * + * @template a Protobuf type of entities being the target of a query + */ + read(query) { + throw new Error('Not implemented in abstract base.'); + } + + /** + * Executes the given `Query` instance specifying the data to be retrieved from + * Spine server fulfilling a returned promise with an array of received objects. + * + * @param {!spine.client.Query} query a query instance to be executed + * @return {Promise<[]>} a promise to be fulfilled with a list of Protobuf + * messages of a given type or with an empty list if no entities matching given query + * were found; rejected with a `SpineError` if error occurs; + * + * @template a Protobuf type of entities being the target of a query + * + * @deprecated Please use {@link Client#read()} instead + */ + execute(query) { + return this.read(query); + } + /** * Creates a new {@link QueryFactory} for creating `Query` instances specifying * the data to be retrieved from Spine server. @@ -100,6 +155,8 @@ export class Client { * * To execute the resulting `Query` instance pass it to the {@link Client#execute()}. * + * Alternatively, the `QueryRequest` API can be used. See {@link Client#select()}. + * * @return {QueryFactory} a factory for creating queries to the Spine server * * @see QueryFactory @@ -111,17 +168,53 @@ export class Client { } /** - * Executes the given `Query` instance specifying the data to be retrieved from - * Spine server fulfilling a returned promise with an array of received objects. + * Creates a subscription request that allows to configure and post a new entity subscription. * - * @param {!Query} query a query instance to be executed - * @return {Promise<[]>} a promise to be fulfilled with a list of Protobuf - * messages of a given type or with an empty list if no entities matching given query - * were found; rejected with a `SpineError` if error occurs; + * @param {!Class} entityType a Protobuf type of the target entities + * @return {SubscriptionRequest} the builder for the new entity subscription + */ + subscribeTo(entityType) { + throw new Error('Not implemented in abstract base.'); + } + + /** + * Subscribes to the given `Topic` instance. * - * @template a Protobuf type of entities being the target of a query + * The topic should have an entity type as target. Use {@link #subscribeToEvents} to subscribe to + * the topic that targets events. + * + * @param {!spine.client.Topic} topic a topic to subscribe to + * @return {Promise>} + * the subscription object which exposes entity changes via its callbacks + * + * @template a Protobuf type of entities being the target of a subscription */ - execute(query) { + subscribe(topic) { + throw new Error('Not implemented in abstract base.'); + } + + /** + * Creates an event subscription request that allows to configure and post a new event + * subscription. + * + * @param {!Class} eventType a Protobuf type of the target events + * @return {EventSubscriptionRequest} the builder for the new event subscription + */ + subscribeToEvent(eventType) { + throw new Error('Not implemented in abstract base.'); + } + + /** + * Subscribes to the given `Topic` instance. + * + * The given topic should target an event type. To perform an entity subscription, use + * {@link #subscribe}. + * + * @param {!spine.client.Topic} topic a topic to subscribe to + * + * @return {Promise} + */ + subscribeToEvents(topic) { throw new Error('Not implemented in abstract base.'); } @@ -136,13 +229,16 @@ export class Client { * * @example * // Build a subscription topic for `UserTasks` domain entity, selecting the instances - * // which task count is greater than 3. + * // with over 3 tasks. * newTopic().select(UserTasks) * .where(Filters.gt('tasksCount', 3)) * .build() * * To turn the resulting `Topic` instance into a subscription pass it - * to the {@link Client#subscribeTo()}. + * to the {@link Client#subscribe()}. + * + * Alternatively, the `SubscriptionRequest` API can be used. See {@link Client#subscribeTo()}, + * {@link Client#subscribeToEvents()}. * * @return {TopicFactory} a factory for creating subscription topics to the Spine server * @@ -155,17 +251,23 @@ export class Client { } /** - * Creates a subscription to the topic which is updated with backend changes. Fulfills - * a returning promise with the created subscription. + * Creates a new command request which allows to post a command to the Spine server and + * configures the command handling callbacks. * - * @param {!Topic} topic a topic to subscribe - * @return {Promise>} a promise to be resolved with an object - * representing a result of the subscription to entities state changes; rejected with - * a `SpineError` if error occurs; + * @param {!Message} commandMessage a command to post to the server + * @return {CommandRequest} a new command request + */ + command(commandMessage) { + throw new Error('Not implemented in abstract base.'); + } + + /** + * Posts a given command to the Spine server. * - * @template a Protobuf type of entities being the target of a subscription + * @param {!spine.core.Command} command a Command sent to Spine server + * @param {!AckCallback} onAck a command acknowledgement callback */ - subscribeTo(topic) { + post(command, onAck) { throw new Error('Not implemented in abstract base.'); } @@ -205,86 +307,14 @@ export class Client { * a callback executed if the command was rejected by Spine server * @see CommandHandlingError * @see CommandValidationError - */ - sendCommand(commandMessage, acknowledgedCallback, errorCallback, rejectionCallback) { - throw new Error('Not implemented in abstract base.'); - } - - /** - * Fetches entities of the given class type from the Spine backend. - * - * Optionally accepts entity identifiers targeting the objects to fetch. If no identifiers are - * passed, returns all entities of the selected type. * - * The returned promise is fulfilled an array of objects, each representing an entity. - * - * This API call is a shortcut for {@link Client#newQuery()} followed by {@link Client#execute()}. - * It covers the most common use cases. If a more advanced fetch behaviour is required, the - * `Query` instance should be created and parameterized via {@link Client#newQuery()}. - * - * @example - * // Fetch all `Task` domain entities. Returning promise resolves with a list of entities - * // or with an empty list if no records of the specified type were found. - * fetch({entity: Task}).then(tasks => { ... }) - * - * @example - * // Fetch a single `Task` domain entity by ID. Returning promise resolves with a list containing - * // the target entity or with an empty list if no record with the specified ID was found. - * fetch({entity: Task, byIds: taskId}).then(task => { ... }) - * - * @example - * // Fetch several `Task` domain entities by IDs. Returning promise resolves with a list of - * // entities or with an empty list if no records with the specified IDs were found. - * fetch({entity: Task, byIds: [taskId1, taskId2]}).then(tasks => { ... }) - * - * @param {SimpleTarget} object representing a set of parameters for building a query by target - * entities class type and IDs - * @return {Promise} a promise to be fulfilled with a list of Protobuf messages - * of a given type or with an empty list if no entities matching given class or IDs were - * found; rejected with a `SpineError` if error occurs; - * - * @template a Protobuf type of entities being the fetch target - */ - fetch({entity: cls, byIds: ids}) { - throw new Error('Not implemented in abstract base.'); - } - - /** - * Creates a subscription to changes of entities of given class type from the Spine backend. - * - * Optionally accepts entity identifiers targeting the objects. If no identifiers are - * passed, subscribes to changes of all entities of the selected type. - * - * Fulfills a returning promise with the created subscription. - * - * The entities that already exist will be initially passed to the `itemAdded` observer. - * - * This API call is a shortcut for {@link Client#newTopic()} followed by - * {@link Client#subscribeTo()}. It covers the most common use cases. If a more advanced - * subscription behaviour is required, the `Topic` instance should be created and parameterized - * via {@link Client#newTopic()}. - * - * @example - * // Subscribe to changes of all `UserTasks` domain entities. Returning promise resolves with - * // an object representing a result of the subscription. - * subscribe({entity: UserTasks}).then(subscriptionObject => { ... }) - * - * @example - * // Subscribe to changes of a single `UserTasks` domain entity by ID. - * subscribe({entity: Task, byIds: taskId}).then(subscriptionObject => { ... }) - * - * @example - * // Subscribe to changes of several `UserTasks` domain entities by IDs. - * subscribe({entity: Task, byIds: [taskId1, taskId2]}).then(subscriptionObject => { ... }) - * - * @param {SimpleTarget} object representing a set of parameters for building a subscription - * topic by target entities class type and IDs - * @return {Promise>} a promise of means to observe the changes - * and unsubscribe from the updated - * - * @template a Protobuf type of entities being the subscription target + * @deprecated Please use {@link Client#command()} */ - subscribe({entity: cls, byIds: ids}) { - throw new Error('Not implemented in abstract base.'); + sendCommand(commandMessage, acknowledgedCallback, errorCallback, rejectionCallback) { + this.command(commandMessage) + .onOk(acknowledgedCallback) + .onError(errorCallback) + .onRejection(rejectionCallback) + .post(); } } diff --git a/client-js/main/client/command-request.js b/client-js/main/client/command-request.js new file mode 100644 index 000000000..cd3bbf0d1 --- /dev/null +++ b/client-js/main/client/command-request.js @@ -0,0 +1,181 @@ +/* + * Copyright 2019, TeamDev. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import {CommandId} from "../proto/spine/core/command_pb"; +import {MessageId, Origin} from "../proto/spine/core/diagnostics_pb"; +import {Filters} from "./actor-request-factory"; +import {AnyPacker} from "./any-packer"; +import {ClientRequest} from "./client-request"; +import {Type} from "./typed-message"; + +const NOOP_CALLBACK = () => {}; + +/** + * A request to post a command to the Spine backend. + * + * Optionally allows to subscribe to events that are the immediate results of handling the command. + * + * A usage example: + * ``` + * client.command(logInUser) + * .onOk(_logOk) + * .onError(_logError) + * .observe(UserLoggedIn.class, ({subscribe, unsubscribe}) => { + * subscribe(event => _logAndUnsubscribe(event, unsubscribe)); + * setTimeout(unsubscribe, EVENT_WAIT_TIMEOUT); + * }) + * .observe(UserAlreadyLoggedIn.class, (({subscribe, unsubscribe}) => { + * subscribe(event => _warnAboutAndUnsubscribe(event, unsubscribe)); + * setTimeout(unsubscribe, EVENT_WAIT_TIMEOUT); + * }) + * .post(); + * ``` + * + * The `subscribe` callback provided to the consumer allows to configure an event receival process + * while the `unsubscribe` callback allows to cancel the subscription. + * + * Please note that in the example above we make sure the subscription is cancelled even if the + * specified event type is never received. + */ +export class CommandRequest extends ClientRequest { + + /** + * @param {!Message} commandMessage the command to post + * @param {!Client} client the client which initiated the request + * @param {!ActorRequestFactory} actorRequestFactory the request factory + */ + constructor(commandMessage, client, actorRequestFactory) { + super(client, actorRequestFactory); + this._commandMessage = commandMessage; + this._onAck = NOOP_CALLBACK; + this._onError = NOOP_CALLBACK; + this._onRejection = NOOP_CALLBACK; + this._observedTypes = []; + } + + /** + * Runs the callback if the command is successfully handled by the Spine server. + * + * @param {!parameterlessCallback} callback the callback to run + * @return {this} self for method chaining + */ + onOk(callback) { + this._onAck = callback; + return this; + } + + /** + * Runs the callback if the command could not be handled by the Spine server due to the + * technical error. + * + * @param {!consumerCallback} callback the callback to run + * @return {this} self for method chaining + */ + onError(callback) { + this._onError = callback; + return this; + } + + /** + * Runs the callback if the server responded with the `rejection` status on a command. + * + * Note that with the current Spine server implementation it's rare for the command to be + * rejected right away. In most cases, the command will be acknowledged with the `OK` status and + * only then lead to a business rejection. You can check this scenario using the `observe` method. + * + * @param {!consumerCallback} callback + * @return {this} self for method chaining + */ + onRejection(callback) { + this._onRejection = callback; + return this; + } + + /** + * Adds the event type to the list of observed command handling results. + * + * @param {!Class} eventType a type of the observed events + * @param {!consumerCallback} consumer + * a consumer of the `subscribe` and `unsubscribe` callbacks which are responsible for + * accepting the incoming events and cancelling the subscription respectively + * @return {this} self for method chaining + */ + observe(eventType, consumer) { + this._observedTypes.push({type: eventType, consumer: consumer}); + return this; + } + + /** + * Posts the command to the server and subscribes to all observed types. + * + * @return {Promise} a promise that signals if the command posting was done successfully, + * may be ignored + */ + post() { + const command = this._requestFactory.command().create(this._commandMessage); + const onAck = {onOk: this._onAck, onError: this._onError, onRejection: this._onRejection}; + const promises = []; + this._observedTypes.forEach(({type, consumer}) => { + const originFilter = Filters.eq("context.past_message", this._asOrigin(command)); + const promise = this._client.subscribeToEvent(type) + .where(originFilter) + .post() + .then(({eventEmitted, unsubscribe}) => { + const subscribe = eventConsumer => { + eventEmitted.subscribe({ + next: eventConsumer + }); + }; + consumer({subscribe, unsubscribe}); + }); + promises.push(promise); + }); + const subscriptionPromise = Promise.all(promises); + return subscriptionPromise.then(() => this._client.post(command, onAck)); + } + + /** + * @param {!Command} command + * @return {Origin} + * + * @private + */ + _asOrigin(command) { + const result = new Origin(); + + const messageId = new MessageId(); + const commandIdType = Type.forClass(CommandId); + const packedId = AnyPacker.pack(command.getId()).as(commandIdType); + messageId.setId(packedId); + const typeUrl = command.getMessage().getTypeUrl(); + messageId.setTypeUrl(typeUrl); + result.setMessage(messageId); + + let grandOrigin = command.getContext().getOrigin(); + if (!grandOrigin) { + grandOrigin = new Origin(); + } + result.setGrandOrigin(grandOrigin); + + const actorContext = command.getContext().getActorContext(); + result.setActorContext(actorContext); + return result; + } +} diff --git a/client-js/main/client/commanding-client.js b/client-js/main/client/commanding-client.js new file mode 100644 index 000000000..07f37adc1 --- /dev/null +++ b/client-js/main/client/commanding-client.js @@ -0,0 +1,93 @@ +/* + * Copyright 2019, TeamDev. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import {Status} from "../proto/spine/core/response_pb"; +import {CommandRequest} from "./command-request"; +import {CommandHandlingError, CommandValidationError, SpineError} from "./errors"; +import ObjectToProto from "./object-to-proto"; +import {TypedMessage} from "./typed-message"; + +const _statusType = Status.typeUrl(); + +/** + * A client which posts commands. + * + * This class has a default implementation but is intended to be overridden as necessary if it's + * required to change the behavior. + */ +export class CommandingClient { + + constructor(endpoint, requestFactory) { + this._requestFactory = requestFactory; + this._endpoint = endpoint; + } + + /** + * Creates a new command request. + * + * @param {!Message} commandMessage the command to send to the server + * @param {!Client} client the client which initiated the request + * @return {CommandRequest} a new command request + */ + command(commandMessage, client) { + return new CommandRequest(commandMessage, client, this._requestFactory); + } + + /** + * Posts a given command to the Spine server. + * + * @param {!spine.core.Command} command a Command sent to Spine server + * @param {!AckCallback} onAck a command acknowledgement callback + */ + post(command, onAck) { + const cmd = TypedMessage.of(command); + this._endpoint.command(cmd) + .then(ack => this._onAck(ack, onAck)) + .catch(error => { + onAck.onError(new CommandHandlingError(error.message, error)); + }); + } + + _onAck(ack, onAck) { + const responseStatus = ack.status; + const responseStatusProto = ObjectToProto.convert(responseStatus, _statusType); + const responseStatusCase = responseStatusProto.getStatusCase(); + + switch (responseStatusCase) { + case Status.StatusCase.OK: + onAck.onOk(); + break; + case Status.StatusCase.ERROR: + const error = responseStatusProto.getError(); + const message = error.getMessage(); + onAck.onError(error.hasValidationError() + ? new CommandValidationError(message, error) + : new CommandHandlingError(message, error)); + break; + case Status.StatusCase.REJECTION: + onAck.onRejection(responseStatusProto.getRejection()); + break; + default: + onAck.onError( + new SpineError(`Unknown response status case ${responseStatusCase}`) + ); + } + } +} diff --git a/client-js/main/client/composite-client.js b/client-js/main/client/composite-client.js index 1f1c72c8c..4cf9d6218 100644 --- a/client-js/main/client/composite-client.js +++ b/client-js/main/client/composite-client.js @@ -20,9 +20,6 @@ "use strict"; -import ObjectToProto from "./object-to-proto"; -import {CommandHandlingError, CommandValidationError, SpineError} from "./errors"; -import {Status} from '../proto/spine/core/response_pb'; import {Client} from "./client"; /** @@ -34,226 +31,98 @@ import {Client} from "./client"; */ export class CompositeClient extends Client { - constructor(querying, subscribing, commanding) { - super(); - - /** - * @type QueryingClient - * - * @private - */ - this._querying = querying; - - /** - * @type SubscribingClient - * - * @private - */ - this._subscribing = subscribing; - - /** - * @type CommandingClient - * - * @private - */ - this._commanding = commanding; - } - - newQuery() { - return this._querying.newQuery(); - } - - execute(query) { - return this._querying.execute(query); - } - - fetch({entity: cls, byIds: ids}) { - return this._querying.fetch(cls, ids); - } - - newTopic() { - return this._subscribing.newTopic(); - } - - subscribeTo(topic) { - return this._subscribing.subscribeTo(topic); - } - - subscribe({entity: cls, byIds: ids}) { - return this._subscribing.subscribe(cls, ids); - } - - sendCommand(commandMessage, acknowledgedCallback, errorCallback, rejectionCallback) { - return this._commanding.sendCommand(commandMessage, - acknowledgedCallback, - errorCallback, - rejectionCallback); - } -} - -/** - * A client which performs entity state queries. - * - * @abstract - */ -export class QueryingClient { + constructor(querying, subscribing, commanding) { + super(); /** - * @param {!ActorRequestFactory} actorRequestFactory - * a request factory to build requests to Spine server + * @type QueryingClient + * + * @private */ - constructor(actorRequestFactory) { - this._requestFactory = actorRequestFactory; - } + this._querying = querying; /** - * Executes the given `Query` instance specifying the data to be retrieved from - * Spine server fulfilling a returned promise with an array of received objects. + * @type SubscribingClient * - * @param {!Query} query a query instance to be executed - * @return {Promise<[]>} a promise to be fulfilled with a list of Protobuf - * messages of a given type or with an empty list if no entities matching given query - * were found; rejected with a `SpineError` if error occurs - * - * @template a Protobuf type of entities being the target of a query + * @private */ - execute(query) { - throw new Error('Not implemented in abstract base.'); - } - - newQuery() { - return this._requestFactory.query(); - } - - fetch(entityClass, ids) { - const queryBuilder = this.newQuery().select(entityClass); - const query = _buildTarget(queryBuilder, ids); - return this.execute(query); - } -} - -/** - * A client which manages entity state and event subscriptions. - * - * @abstract - */ -export class SubscribingClient { + this._subscribing = subscribing; /** - * @param {!ActorRequestFactory} actorRequestFactory - * a request factory to build requests to Spine server - */ - constructor(actorRequestFactory) { - this._requestFactory = actorRequestFactory; - } - - subscribeTo(topic) { - throw new Error('Not implemented in abstract base.'); - } - - newTopic() { - return this._requestFactory.topic(); - } - - subscribe(entityClass, ids) { - const topicBuilder = this.newTopic().select(entityClass); - const topic = _buildTarget(topicBuilder, ids); - - return this.subscribeTo(topic); - } -} - -/** - * A {@link SubscribingClient} which does not create subscriptions. - */ -export class NoOpSubscribingClient extends SubscribingClient { - - constructor(actorRequestFactory) { - super(actorRequestFactory) - } - - /** - * Always throws an error. + * @type CommandingClient * - * @override + * @private */ - subscribeTo(topic) { - throw new Error('Entity subscription is not supported.'); - } -} - -const _statusType = Status.typeUrl(); - -/** - * A client which posts commands. - * - * This class has a default implementation. Override it to change the behaviour. - */ -export class CommandingClient { - - constructor(endpoint, requestFactory) { - this._requestFactory = requestFactory; - this._endpoint = endpoint; - } - - sendCommand(commandMessage, acknowledgedCallback, errorCallback, rejectionCallback) { - const command = this._requestFactory.command().create(commandMessage); - this._endpoint.command(command) - .then(ack => this._onAck(ack, acknowledgedCallback, errorCallback, rejectionCallback)) - .catch(error => { - errorCallback(new CommandHandlingError(error.message, error)); - }); - } - - _onAck(ack, acknowledgedCallback, errorCallback, rejectionCallback) { - const responseStatus = ack.status; - const responseStatusProto = ObjectToProto.convert(responseStatus, _statusType); - const responseStatusCase = responseStatusProto.getStatusCase(); - - switch (responseStatusCase) { - case Status.StatusCase.OK: - acknowledgedCallback(); - break; - case Status.StatusCase.ERROR: - const error = responseStatusProto.getError(); - const message = error.getMessage(); - errorCallback(error.hasValidationError() - ? new CommandValidationError(message, error) - : new CommandHandlingError(message, error)); - break; - case Status.StatusCase.REJECTION: - rejectionCallback(responseStatusProto.getRejection()); - break; - default: - errorCallback( - new SpineError(`Unknown response status case ${responseStatusCase}`) - ); - } - } -} - -/** - * Builds target from the given target builder specifying the set - * of target entities. - * - * The resulting target points to: - * - the particular entities with the given IDs; - * - the all entities if no IDs specified. - * - * @param {AbstractTargetBuilder} targetBuilder - * a builder for creating `Query` or `Topic` instances. - * @param {?[] | | Number[] | Number | String[] | String} ids - * a list of target entities IDs or an ID of a single target entity - * @return {Query|Topic} the built target - * - * @template a class of identifiers, corresponding to the query/subscription targets - * @private - */ -function _buildTarget(targetBuilder, ids) { - if (Array.isArray(ids)) { - targetBuilder.byIds(ids); - } else if (!!ids) { - targetBuilder.byIds([ids]); - } - return targetBuilder.build(); + this._commanding = commanding; + } + + /** + * @override + */ + select(entityType) { + return this._querying.select(entityType, this); + } + + /** + * @override + */ + read(query) { + return this._querying.read(query); + } + + /** + * @override + */ + newQuery() { + return this._querying.newQuery(); + } + + /** + * @override + */ + subscribeTo(entityType) { + return this._subscribing.subscribeTo(entityType, this); + } + + /** + * @override + */ + subscribeToEvent(eventType) { + return this._subscribing.subscribeToEvent(eventType, this); + } + + /** + * @override + */ + subscribe(topic) { + return this._subscribing.subscribe(topic) + } + + /** + * @override + */ + subscribeToEvents(topic) { + return this._subscribing.subscribeToEvents(topic); + } + + /** + * @override + */ + newTopic() { + return this._subscribing.newTopic(); + } + + /** + * @override + */ + command(commandMessage) { + return this._commanding.command(commandMessage, this); + } + + /** + * @override + */ + post(command, onAck) { + this._commanding.post(command, onAck); + } } diff --git a/client-js/main/client/direct-client.js b/client-js/main/client/direct-client.js index 27221e8af..739282aa0 100644 --- a/client-js/main/client/direct-client.js +++ b/client-js/main/client/direct-client.js @@ -20,18 +20,16 @@ "use strict"; +import {ActorRequestFactory} from './actor-request-factory'; +import {AnyPacker} from "./any-packer"; import {AbstractClientFactory} from './client-factory'; +import {CommandingClient} from "./commanding-client"; +import {CompositeClient} from "./composite-client"; import {HttpClient} from './http-client'; import {HttpEndpoint} from './http-endpoint'; -import {ActorRequestFactory} from './actor-request-factory'; -import { - CommandingClient, - CompositeClient, - QueryingClient, - NoOpSubscribingClient -} from "./composite-client"; import KnownTypes from "./known-types"; -import {AnyPacker} from "./any-packer"; +import {QueryingClient} from "./querying-client"; +import {NoOpSubscribingClient} from "./subscribing-client"; import {Type} from "./typed-message"; import TypeParsers from "./parser/type-parsers"; @@ -102,7 +100,7 @@ class DirectQueryingClient extends QueryingClient { this._endpoint = endpoint; } - execute(query) { + read(query) { const typeUrl = query.getTarget().getType(); const targetClass = KnownTypes.classFor(typeUrl); const targetType = Type.of(targetClass, typeUrl); diff --git a/client-js/main/client/field-paths.js b/client-js/main/client/field-paths.js index c101344a0..563d80aaf 100644 --- a/client-js/main/client/field-paths.js +++ b/client-js/main/client/field-paths.js @@ -27,22 +27,22 @@ import {FieldPath} from "../proto/spine/base/field_path_pb"; */ export class FieldPaths { - constructor() { - throw new Error('Instantiating utility FieldPaths class.'); - } + constructor() { + throw new Error('Instantiating utility FieldPaths class.'); + } - /** - * Creates a new `FieldPath` from the given string. - * - * String examples: "owner.userId", "employeeCount". - */ - static parse(stringPath) { - if (!stringPath) { - throw new Error('Constructing FieldPath from the invalid string'); - } - const fieldPath = new FieldPath(); - const pathElements = stringPath.split('.'); - fieldPath.setFieldNameList(pathElements); - return fieldPath; + /** + * Creates a new `FieldPath` from the given string. + * + * String examples: "owner.userId", "employee_count". + */ + static parse(stringPath) { + if (!stringPath) { + throw new Error('Constructing FieldPath from the invalid string'); } + const fieldPath = new FieldPath(); + const pathElements = stringPath.split('.'); + fieldPath.setFieldNameList(pathElements); + return fieldPath; + } } diff --git a/client-js/main/client/filtering-request.js b/client-js/main/client/filtering-request.js new file mode 100644 index 000000000..d154b9426 --- /dev/null +++ b/client-js/main/client/filtering-request.js @@ -0,0 +1,142 @@ +/* + * Copyright 2019, TeamDev. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import {ClientRequest} from "./client-request"; + +/** + * An abstract base for client requests that filter messages by certain criteria. + * + * @abstract + * + * @template the type of the builder wrapped by this request + * @template the type of the messages that store the request data + */ +export class FilteringRequest extends ClientRequest { + + /** + * @param {!Class} targetType the target type of the request + * @param {!Client} client the client which initiated the request + * @param {!ActorRequestFactory} actorRequestFactory the request factory + * + * @protected + */ + constructor(targetType, client, actorRequestFactory) { + super(client, actorRequestFactory); + this.targetType = targetType; + } + + /** + * Adds filtering by IDs to the built request. + * + * @param ids {!|Number|String|[]|Number[]|String[]} + * the IDs of interest + * @return {this} self for method chaining + */ + byId(ids) { + ids = FilteringRequest._ensureArray(ids); + this._builder().byIds(ids); + return this._self(); + } + + /** + * Adds filtering by predicates to the built request. + * + * Filters specified in a list are considered to be joined using `AND` operator. + * + * @param {!Filter|CompositeFilter|Filter[]|CompositeFilter[]} predicates the filters + * @return {this} self for method chaining + */ + where(predicates) { + predicates = FilteringRequest._ensureArray(predicates); + this._builder().where(predicates); + return this._self(); + } + + /** + * Applies a field mask to the request results. + * + * The names of the fields must be formatted according to the `google.protobuf.FieldMask` + * specification. + * + * @param {!String|String[]} fieldPaths the fields to include in the mask + * @return {this} self for method chaining + */ + withMask(fieldPaths) { + fieldPaths = FilteringRequest._ensureArray(fieldPaths); + this._builder().withMask(fieldPaths); + return this._self(); + } + + /** + * Returns the builder for messages that store request data. + * + * @return {AbstractTargetBuilder} the builder instance + * + * @protected + */ + _builder() { + if (!this._builderInstance) { + const newBuilderFn = this._newBuilderFn(); + this._builderInstance = newBuilderFn(this._requestFactory); + } + return this._builderInstance; + } + + /** + * Returns the function with which the {@link _builderInstance} can be created. + * + * @abstract + * @return {Function} + * + * @protected + */ + _newBuilderFn() { + throw new Error('Not implemented in abstract base.'); + } + + /** + * @abstract + * @return {this} + * + * @protected + */ + _self() { + throw new Error('Not implemented in abstract base.'); + } + + /** + * Wraps the passed argument into array if it's not an array already. + * + * The `null` values are converted into an empty array. + * + * @return {Array} the passed argument as an array + * + * @private + */ + static _ensureArray(values) { + if (!values) { + return []; + } + if (!(values instanceof Array)) { + return [values] + } + return values; + } +} diff --git a/client-js/main/client/firebase-client.js b/client-js/main/client/firebase-client.js index 0cfaa7166..532195ad5 100644 --- a/client-js/main/client/firebase-client.js +++ b/client-js/main/client/firebase-client.js @@ -22,47 +22,43 @@ import {Observable, Subject, Subscription} from 'rxjs'; import { - Subscription as SpineSubscription, + Subscription as SubscriptionObject, SubscriptionId } from '../proto/spine/client/subscription_pb'; +import {ActorRequestFactory} from './actor-request-factory'; import {AbstractClientFactory} from './client-factory'; -import ObjectToProto from './object-to-proto'; +import {CommandingClient} from "./commanding-client"; +import {CompositeClient} from "./composite-client"; import {HttpClient} from './http-client'; import {HttpEndpoint} from './http-endpoint'; import {FirebaseDatabaseClient} from './firebase-database-client'; -import {ActorRequestFactory} from './actor-request-factory'; import {FirebaseSubscriptionService} from './firebase-subscription-service'; -import { - CommandingClient, - CompositeClient, - QueryingClient, - SubscribingClient -} from "./composite-client"; +import ObjectToProto from './object-to-proto'; +import {QueryingClient} from "./querying-client"; +import {SubscribingClient} from "./subscribing-client"; /** - * A subscription to entity changes on application backend. + * An abstract base for subscription objects. + * + * @abstract */ -class EntitySubscription extends Subscription { +class SpineSubscription extends Subscription { /** - * @param {Function} unsubscribe - * @param {{itemAdded: Observable, itemChanged: Observable, itemRemoved: Observable}} observables - * @param {SpineSubscription} subscription + * @param {Function} unsubscribe the callbacks that allows to cancel the subscription + * @param {SubscriptionObject} subscription the wrapped subscription object + * + * @protected */ - constructor({ - unsubscribedBy: unsubscribe, - withObservables: observables, - forInternal: subscription - }) { + constructor(unsubscribe, subscription) { super(unsubscribe); - this._observables = observables; this._subscription = subscription; } /** * An internal Spine subscription which includes the topic the updates are received for. * - * @return {SpineSubscription} a `spine.client.Subscription` instance + * @return {SubscriptionObject} a `spine.client.Subscription` instance */ internal() { return this._subscription; @@ -74,12 +70,62 @@ class EntitySubscription extends Subscription { id() { return this.internal().getId().getValue(); } +} + +/** + * A subscription to entity changes on application backend. + */ +class EntitySubscription extends SpineSubscription { + + /** + * @param {Function} unsubscribe the callbacks that allows to cancel the subscription + * @param {{itemAdded: Observable, itemChanged: Observable, itemRemoved: Observable}} observables + * the observables for entity changes + * @param {SubscriptionObject} subscription the wrapped subscription object + */ + constructor({ + unsubscribedBy: unsubscribe, + withObservables: observables, + forInternal: subscription + }) { + super(unsubscribe, subscription); + this._observables = observables; + } /** * @return {EntitySubscriptionObject} a plain object with observables and unsubscribe method */ toObject() { - return Object.assign({}, this._observables, {unsubscribe: () => this.unsubscribe()}) + return Object.assign({}, this._observables, {unsubscribe: () => this.unsubscribe()}); + } +} + +/** + * A subscription to events that occur in the system. + */ +class EventSubscription extends SpineSubscription { + + /** + * @param {Function} unsubscribe the callbacks that allows to cancel the subscription + * @param {Observable} eventEmitted the observable for the emitted events + * @param {SubscriptionObject} subscription the wrapped subscription object + */ + constructor({ + unsubscribedBy: unsubscribe, + withObservable: observable, + forInternal: subscription + }) { + super(unsubscribe, subscription); + this._observable = observable; + } + + /** + * @return {EventSubscriptionObject} a plain object with observables and unsubscribe method + */ + toObject() { + return Object.assign( + {}, {eventEmitted: this._observable}, {unsubscribe: () => this.unsubscribe()} + ); } } @@ -98,7 +144,7 @@ class FirebaseQueryingClient extends QueryingClient { this._firebase = firebaseDatabase; } - execute(query) { + read(query) { return new Promise((resolve, reject) => { this._endpoint.query(query) .then(({path}) => this._firebase.getValues(path, values => { @@ -111,6 +157,8 @@ class FirebaseQueryingClient extends QueryingClient { } } +const EVENT_TYPE_URL = 'type.spine.io/spine.core.Event'; + /** * A {@link SubscribingClient} which receives entity state updates and events from * a Firebase Realtime Database. @@ -139,46 +187,86 @@ class FirebaseSubscribingClient extends SubscribingClient { /** * @inheritDoc */ - subscribeTo(topic) { + subscribe(topic) { + return this._doSubscribe(topic, this._entitySubscription); + } + + /** + * @inheritDoc + */ + subscribeToEvents(topic) { + return this._doSubscribe(topic, this._eventSubscription); + } + + /** + * @private + */ + _doSubscribe(topic, createSubscriptionFn) { return new Promise((resolve, reject) => { - const typeUrl = topic.getTarget().getType(); this._endpoint.subscribeTo(topic) .then(response => { const path = response.nodePath.value; - - const itemAdded = new Subject(); - const itemChanged = new Subject(); - const itemRemoved = new Subject(); - - const pathSubscriptions = [ - this._firebase - .onChildAdded(path, itemAdded.next.bind(itemAdded)), - this._firebase - .onChildChanged(path, itemChanged.next.bind(itemChanged)), - this._firebase - .onChildRemoved(path, itemRemoved.next.bind(itemRemoved)) - ]; - const internalSubscription = FirebaseSubscribingClient.internalSubscription(path, topic); - const entitySubscription = new EntitySubscription({ - unsubscribedBy: () => { - FirebaseSubscribingClient._unsubscribe(pathSubscriptions); - }, - withObservables: { - itemAdded: ObjectToProto.map(itemAdded.asObservable(), typeUrl), - itemChanged: ObjectToProto.map(itemChanged.asObservable(), typeUrl), - itemRemoved: ObjectToProto.map(itemRemoved.asObservable(), typeUrl) - }, - forInternal: internalSubscription - }); - resolve(entitySubscription.toObject()); - this._subscriptionService.add(entitySubscription); + + const subscription = createSubscriptionFn.call(this, path, internalSubscription); + + resolve(subscription.toObject()); + this._subscriptionService.add(subscription); }) .catch(reject); }); } + /** + * @private + */ + _entitySubscription(path, subscription) { + const itemAdded = new Subject(); + const itemChanged = new Subject(); + const itemRemoved = new Subject(); + + const pathSubscriptions = [ + this._firebase + .onChildAdded(path, itemAdded.next.bind(itemAdded)), + this._firebase + .onChildChanged(path, itemChanged.next.bind(itemChanged)), + this._firebase + .onChildRemoved(path, itemRemoved.next.bind(itemRemoved)) + ]; + + const typeUrl = subscription.getTopic().getTarget().getType(); + + return new EntitySubscription({ + unsubscribedBy: () => { + FirebaseSubscribingClient._unsubscribe(pathSubscriptions); + }, + withObservables: { + itemAdded: ObjectToProto.map(itemAdded.asObservable(), typeUrl), + itemChanged: ObjectToProto.map(itemChanged.asObservable(), typeUrl), + itemRemoved: ObjectToProto.map(itemRemoved.asObservable(), typeUrl) + }, + forInternal: subscription + }); + } + + /** + * @private + */ + _eventSubscription(path, subscription) { + const itemAdded = new Subject(); + const pathSubscription = + this._firebase.onChildAdded(path, itemAdded.next.bind(itemAdded)); + + return new EventSubscription({ + unsubscribedBy: () => { + FirebaseSubscribingClient._unsubscribe([pathSubscription]); + }, + withObservable: ObjectToProto.map(itemAdded.asObservable(), EVENT_TYPE_URL), + forInternal: subscription + }); + } + /** * Unsubscribes the provided Firebase subscriptions. * @@ -194,14 +282,14 @@ class FirebaseSubscribingClient extends SubscribingClient { } /** - * Creates a `SpineSubscription` instance to communicate with Spine server. + * Creates a `SubscriptionObject` instance to communicate with Spine server. * * @param {!String} path a path to object which gets updated in Firebase * @param {!spine.client.Topic} topic a topic for which the Subscription gets updates - * @return {SpineSubscription} a `SpineSubscription` instance to communicate with Spine server + * @return {SubscriptionObject} a `SubscriptionObject` instance to communicate with Spine server */ static internalSubscription(path, topic) { - const subscription = new SpineSubscription(); + const subscription = new SubscriptionObject(); const id = new SubscriptionId(); id.setValue(path); subscription.setId(id); diff --git a/client-js/main/client/firebase-subscription-service.js b/client-js/main/client/firebase-subscription-service.js index aabc4fc3e..9901b9520 100644 --- a/client-js/main/client/firebase-subscription-service.js +++ b/client-js/main/client/firebase-subscription-service.js @@ -21,11 +21,15 @@ "use strict"; import {Duration} from './time-utils'; +import ObjectToProto from "./object-to-proto"; +import {Status} from '../proto/spine/core/response_pb'; + const SUBSCRIPTION_KEEP_UP_INTERVAL = new Duration({minutes: 2}); /** - * A service that manages the subscriptions periodically sending requests to keep them running. + * A service that manages the active subscriptions periodically sending requests to keep them + * running. */ export class FirebaseSubscriptionService { /** @@ -33,7 +37,7 @@ export class FirebaseSubscriptionService { */ constructor(endpoint) { /** - * @type {EntitySubscription[]} + * @type {SpineSubscription[]} * @private */ this._subscriptions = []; @@ -48,7 +52,7 @@ export class FirebaseSubscriptionService { * Add a subscription to the service to handle the keep-up requests and cancel in * case of unsubscribe. * - * @param {EntitySubscription} subscription an entity subscription to keep running + * @param {SpineSubscription} subscription an active subscription to keep running */ add(subscription) { if (this._isRegistered(subscription)) { @@ -70,6 +74,15 @@ export class FirebaseSubscriptionService { }, SUBSCRIPTION_KEEP_UP_INTERVAL.inMs()); } + /** + * Sends the "keep-up" request for all active subscriptions. + * + * The non-`OK` response status means the subscription has already been canceled on the server, + * most likely due to a timeout. So, in such case, the subscription is removed from the list of + * active ones. + * + * @private + */ _keepUpSubscriptions() { this._subscriptions.forEach(subscription => { const spineSubscription = subscription.internal(); @@ -78,7 +91,13 @@ export class FirebaseSubscriptionService { this._removeSubscription(subscription); }); } else { - this._endpoint.keepUpSubscription(spineSubscription); + this._endpoint.keepUpSubscription(spineSubscription).then(response => { + const responseStatus = response.status; + const responseStatusProto = ObjectToProto.convert(responseStatus, _statusType); + if (responseStatusProto.getStatusCase() !== Status.StatusCase.OK) { + this._removeSubscription(subscription) + } + }); } }); } diff --git a/client-js/main/client/http-endpoint.js b/client-js/main/client/http-endpoint.js index 0d08f404a..386fe623e 100644 --- a/client-js/main/client/http-endpoint.js +++ b/client-js/main/client/http-endpoint.js @@ -50,7 +50,7 @@ class Endpoint { /** * Sends off a command to the endpoint. * - * @param {!TypedMessage} command a Command send to Spine server + * @param {!TypedMessage} command a Command to send to the Spine server * @return {Promise} a promise of a successful server response, rejected if * an error occurs */ @@ -110,7 +110,7 @@ class Endpoint { /** - * @param {!TypedMessage} command a Command send to Spine server + * @param {!TypedMessage} command a Command to send to the Spine server * @return {Promise} a promise of a successful server response, rejected if * an error occurs * @protected @@ -184,9 +184,10 @@ export class HttpEndpoint extends Endpoint { /** * Sends off a command to the endpoint. * - * @param {!TypedMessage} command a Command send to Spine server - * @return {Promise} a promise of a successful server response JSON data, rejected if - * the client response is not 2xx or a connection error occurs + * @param {!TypedMessage} command a Command to send to the Spine server + * @return {Promise} a promise of a successful server response JSON data, + * rejected if the client response is not 2xx or a + * connection error occurs * @protected */ _executeCommand(command) { @@ -198,8 +199,9 @@ export class HttpEndpoint extends Endpoint { * Sends off a query to the endpoint. * * @param {!TypedMessage} query a Query to Spine server to retrieve some domain entities - * @return {Promise} a promise of a successful server response JSON data, rejected if - * the client response is not 2xx or a connection error occurs + * @return {Promise} a promise of a successful server response JSON data, + * rejected if the client response is not 2xx or a + * connection error occurs * @protected */ _performQuery(query) { @@ -211,8 +213,9 @@ export class HttpEndpoint extends Endpoint { * Sends off a request to create a subscription for a topic. * * @param {!TypedMessage} topic a topic to subscribe to - * @return {Promise} a promise of a successful server response JSON data, rejected if - * the client response is not 2xx or a connection error occurs + * @return {Promise} a promise of a successful server response JSON data, + * rejected if the client response is not 2xx or a + * connection error occurs * @protected */ _subscribeTo(topic) { @@ -227,7 +230,8 @@ export class HttpEndpoint extends Endpoint { * @param {!TypedMessage} subscription a subscription that is prevented * from being closed by server * @return {Promise} a promise of a successful server response JSON data, - * rejected if the client response is not 2xx or a connection error occurs + * rejected if the client response is not 2xx or a + * connection error occurs * @protected */ _keepUp(subscription) { @@ -241,7 +245,8 @@ export class HttpEndpoint extends Endpoint { * * @param {!TypedMessage} subscription a subscription to be canceled * @return {Promise} a promise of a successful server response JSON data, - * rejected if the client response is not 2xx or a connection error occurs + * rejected if the client response is not 2xx or a + * connection error occurs * @protected */ _cancel(subscription) { @@ -255,8 +260,9 @@ export class HttpEndpoint extends Endpoint { * * @param {!string} endpoint an endpoint to send the message to * @param {!TypedMessage} message a message to send, as a {@link TypedMessage} - * @return {Promise} a promise of a successful server response JSON data, rejected if - * the client response is not 2xx or a connection error occurs + * @return {Promise} a promise of a successful server response JSON data, + * rejected if the client response is not 2xx or a + * connection error occurs * @private */ _sendMessage(endpoint, message) { @@ -273,8 +279,9 @@ export class HttpEndpoint extends Endpoint { * with a respective error otherwise. * * @param {!Response} response an HTTP request response - * @return {Promise} a promise of a successful server response JSON data, rejected if - * the client response is not 2xx or if JSON parsing fails + * @return {Promise} a promise of a successful server response JSON data, + * rejected if the client response is not 2xx or if JSON + * parsing fails * @private */ static _jsonOrError(response) { @@ -294,8 +301,9 @@ export class HttpEndpoint extends Endpoint { * Parses the given response JSON data, rejects if parsing fails. * * @param {!Response} response an HTTP request response - * @return {Promise} a promise of a server response parsing to be fulfilled with a JSON - * data or rejected with {@link SpineError} if JSON parsing fails. + * @return {Promise} a promise of a server response parsing to be fulfilled + * with a JSON data or rejected with {@link SpineError} if + * JSON parsing fails. * @private */ static _parseJson(response) { diff --git a/client-js/main/client/query-request.js b/client-js/main/client/query-request.js new file mode 100644 index 000000000..94f77aa83 --- /dev/null +++ b/client-js/main/client/query-request.js @@ -0,0 +1,120 @@ +/* + * Copyright 2019, TeamDev. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import {OrderBy} from "../proto/spine/client/query_pb"; +import {FilteringRequest} from "./filtering-request"; + +/** + * A request to retrieve entities of the given type. + * + * Allows to post the query data to the Spine backend and receive the entity states as `Promise`. + * + * A usage example: + * ``` + * const customers = + * client.select(Customer.class) + * .byId(westCoastCustomerIds()) + * .withMask("name", "address", "email") + * .where([Filters.eq("type", "permanent"), + * Filters.eq("discount_percent", 10), + * Filters.eq("company_size", Company.Size.SMALL)]) + * .orderBy("name", OrderBy.Direction.ASCENDING) + * .limit(20) + * .run(); // The returned type is `Promise`. + * ``` + * + * All of the called filtering methods are optional. If none of them are specified, all entities + * of type will be retrieved. + * + * @template the query target type + */ +export class QueryRequest extends FilteringRequest { + + /** + * @param {!Class} targetType the target type of entities + * @param {!Client} client the client which initiated the request + * @param {!ActorRequestFactory} actorRequestFactory the request factory + */ + constructor(targetType, client, actorRequestFactory) { + super(targetType, client, actorRequestFactory) + } + + /** + * Sets the sorting order for the retrieved results. + * + * @param {!String} column the column to order by + * @param {!OrderBy.Direction} direction the ascending/descending direction + * @return {this} self for method chaining + */ + orderBy(column, direction) { + if (direction === OrderBy.Direction.ASCENDING) { + this._builder().orderAscendingBy(column); + } else { + this._builder().orderDescendingBy(column); + } + return this._self(); + } + + /** + * Sets the maximum number of returned entities. + * + * Can only be used in conjunction with the {@link #orderBy} condition. + * + * @param {number} count the max number of response entities + * @return {this} self for method chaining + */ + limit(count) { + this._builder().limit(count); + return this._self(); + } + + /** + * Builds a `Query` instance based on currently specified filters. + * + * @return {spine.client.Query} a `Query` instance + */ + query() { + return this._builder().build(); + } + + /** + * Runs the query and obtains the results. + * + * @return {Promise<[]>} the asynchronously resolved query results + */ + run() { + const query = this.query(); + return this._client.read(query); + } + + /** + * @inheritDoc + */ + _newBuilderFn() { + return requestFactory => requestFactory.query().select(this.targetType); + } + + /** + * @inheritDoc + */ + _self() { + return this; + } +} diff --git a/client-js/main/client/querying-client.js b/client-js/main/client/querying-client.js new file mode 100644 index 000000000..220a35a1c --- /dev/null +++ b/client-js/main/client/querying-client.js @@ -0,0 +1,72 @@ +/* + * Copyright 2019, TeamDev. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import {QueryRequest} from "./query-request"; + +/** + * A client which performs entity state queries. + * + * @abstract + */ +export class QueryingClient { + + /** + * @param {!ActorRequestFactory} actorRequestFactory + * a request factory to build requests to Spine server + */ + constructor(actorRequestFactory) { + this._requestFactory = actorRequestFactory; + } + + /** + * Creates a new query request. + * + * @param {!Class} entityType the target entity type + * @param {!Client} client the client which initiated the request + * @return {QueryRequest} a new query request + */ + select(entityType, client) { + return new QueryRequest(entityType, client, this._requestFactory); + } + + /** + * Executes the given `Query` instance specifying the data to be retrieved from + * Spine server fulfilling a returned promise with an array of received objects. + * + * @param {!spine.client.Query} query a query instance to be executed + * @return {Promise<[]>} a promise to be fulfilled with a list of Protobuf + * messages of a given type or with an empty list if no entities matching given query + * were found; rejected with a `SpineError` if error occurs + * + * @template a Protobuf type of entities being the target of a query + */ + read(query) { + throw new Error('Not implemented in abstract base.'); + } + + /** + * Creates a new query factory instance which can be further used for the `Query` creation. + * + * @return {QueryFactory} + */ + newQuery() { + return this._requestFactory.query(); + } +} diff --git a/client-js/main/client/subscribing-client.js b/client-js/main/client/subscribing-client.js new file mode 100644 index 000000000..766ba071d --- /dev/null +++ b/client-js/main/client/subscribing-client.js @@ -0,0 +1,120 @@ +/* + * Copyright 2019, TeamDev. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import {EventSubscriptionRequest, SubscriptionRequest} from "./subscribing-request"; + +/** + * A client which manages entity state and event subscriptions. + * + * @abstract + */ +export class SubscribingClient { + + /** + * @param {!ActorRequestFactory} actorRequestFactory + * a request factory to build requests to Spine server + */ + constructor(actorRequestFactory) { + this._requestFactory = actorRequestFactory; + } + + /** + * Creates a new subscription request. + * + * @param {!Class} type the target entity type + * @param {!Client} client the client that initiated the request + * @return {SubscriptionRequest} a new subscription request + */ + subscribeTo(type, client) { + return new SubscriptionRequest(type, client, this._requestFactory); + } + + /** + * Subscribes to a given topic which targets an entity type. + * + * @param {!spine.client.Topic} topic a topic to subscribe to + * @return {Promise>} a subscription object + * + * @template a Protobuf type of entities being the target of a subscription + */ + subscribe(topic) { + throw new Error('Not implemented in abstract base.'); + } + + /** + * Creates a new event subscription request. + * + * @param {!Class} type the target event type + * @param {!Client} client the client that initiated the request + * @return {EventSubscriptionRequest} a new event subscription request + */ + subscribeToEvent(type, client) { + return new EventSubscriptionRequest(type, client, this._requestFactory); + } + + /** + * Subscribes to the given topic which targets an event type. + * + * @param {!spine.client.Topic} topic a topic to subscribe to + * @return {Promise} a subscription object + */ + subscribeToEvents(topic) { + throw new Error('Not implemented in abstract base.'); + } + + /** + * Returns a new topic factory instance which can be further used for the `Topic` creation. + * + * @return {TopicFactory} + */ + newTopic() { + return this._requestFactory.topic(); + } +} + +const SUBSCRIPTIONS_NOT_SUPPORTED = 'Subscriptions are not supported.'; + +/** + * A {@link SubscribingClient} which does not create subscriptions. + */ +export class NoOpSubscribingClient extends SubscribingClient { + + constructor(actorRequestFactory) { + super(actorRequestFactory) + } + + /** + * Always throws an error. + * + * @override + */ + subscribe(topic) { + throw new Error(SUBSCRIPTIONS_NOT_SUPPORTED); + } + + /** + * Always throws an error. + * + * @override + */ + subscribeToEvents(topic) { + throw new Error(SUBSCRIPTIONS_NOT_SUPPORTED); + } +} diff --git a/client-js/main/client/subscribing-request.js b/client-js/main/client/subscribing-request.js new file mode 100644 index 000000000..eaf09457f --- /dev/null +++ b/client-js/main/client/subscribing-request.js @@ -0,0 +1,189 @@ +/* + * Copyright 2019, TeamDev. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import {FilteringRequest} from "./filtering-request"; + +/** + * @typedef EventSubscriptionCallbacks + * + * A pair of callbacks that allow to add an event consumer to the subscription and to cancel it + * respectively. + * + * @property {consumerCallback} subscribe the callback which allows to setup an + * event consumer to use for the subscription + * @property {parameterlessCallback} unsubscribe the callback which allows to cancel the + * subscription + */ + +/** + * An abstract base for requests that subscribe to messages of a certain type. + * + * @abstract + * @template the target type of messages, for events the type is always `spine.core.Event` + */ +class SubscribingRequest extends FilteringRequest { + + /** + * Builds a `Topic` instance based on the currently specified filters. + * + * @return {spine.client.Topic} a `Topic` instance + */ + topic() { + return this._builder().build(); + } + + /** + * Posts a subscription request and returns the result as `Promise`. + * + * @return {Promise | EventSubscriptionObject>} + * the asynchronously resolved subscription object + */ + post() { + const topic = this.topic(); + return this._subscribe(topic); + } + + /** + * @inheritDoc + */ + _newBuilderFn() { + return requestFactory => requestFactory.topic().select(this.targetType); + } + + /** + * @abstract + * @return {Promise | EventSubscriptionObject>} + * + * @protected + */ + _subscribe(topic) { + throw new Error('Not implemented in abstract base.'); + } +} + +/** + * A request to subscribe to updates of entity states of a certain type. + * + * Allows to obtain the `EntitySubscriptionObject` which exposes the entity changes in a form of + * callbacks which can be subscribed to. + * + * A usage example: + * ``` + * client.subscribeTo(Task.class) + * .where(Filters.eq("status", Task.Status.ACTIVE)) + * // Additional filtering can be done here. + * .post() + * .then(({itemAdded, itemChanged, itemRemoved, unsubscribe}) => { + * itemAdded.subscribe(_addDisplayedTask); + * itemChanged.subscribe(_changeDisplayedTask); + * itemRemoved.subscribe(_removeDisplayedTask); + * }); + * ``` + * + * If the entity matched the subscription criteria at one point, but stopped to do so, the + * `itemRemoved` callback will be triggered for it. The callback will contain the last entity state + * that matched the subscription. + * + * Please note that the subscription object should be manually unsubscribed when it's no longer + * needed to receive the updates. This can be done with the help of `unsubscribe` callback. + * + * @template the target entity type + */ +export class SubscriptionRequest extends SubscribingRequest { + + /** + * @param {!Class} entityType the target entity type + * @param {!Client} client the client which initiated the request + * @param {!ActorRequestFactory} actorRequestFactory the request factory + */ + constructor(entityType, client, actorRequestFactory) { + super(entityType, client, actorRequestFactory) + } + + /** + * @inheritDoc + * + * @return {Promise>} + */ + _subscribe(topic) { + return this._client.subscribe(topic); + } + + /** + * @inheritDoc + */ + _self() { + return this; + } +} + +/** + * A request to subscribe to events of a certain type. + * + * Allows to obtain the `EventSubscriptionObject` which reflects the events that happened in the + * system and match the subscription criteria. + * + * A usage example: + * ``` + * client.subscribeToEvent(TaskCreated.class) + * .where([Filters.eq("task_priority", Task.Priority.HIGH), + * Filters.eq("context.past_message.actor_context.actor", userId)]) + * .post() + * .then(({eventEmitted, unsubscribe}) => { + * eventEmitted.subscribe(_logEvent); + * }); + * ``` + * + * The fields specified to the `where` filters should either be a part of the event message or + * have a `context.` prefix and address one of the fields of the `EventContext` type. + * + * The `eventEmitted` observable reflects all events that occurred in the system and match the + * subscription criteria, in a form of `spine.core.Event`. + * + * Please note that the subscription object should be manually unsubscribed when it's no longer + * needed to receive the updates. This can be done with the help of `unsubscribe` callback. + */ +export class EventSubscriptionRequest extends SubscribingRequest { + + /** + * @param {!Class} eventType the target event type + * @param {!Client} client the client which initiated the request + * @param {!ActorRequestFactory} actorRequestFactory the request factory + */ + constructor(eventType, client, actorRequestFactory) { + super(eventType, client, actorRequestFactory) + } + + /** + * @inheritDoc + * + * @return {Promise} + */ + _subscribe(topic) { + return this._client.subscribeToEvents(topic); + } + + /** + * @inheritDoc + */ + _self() { + return this; + } +} diff --git a/client-js/main/client/time-utils.js b/client-js/main/client/time-utils.js index e4cc6bd0e..1a1a07998 100644 --- a/client-js/main/client/time-utils.js +++ b/client-js/main/client/time-utils.js @@ -76,12 +76,12 @@ export function convertDateToTimestamp(date) { if (isNaN(date.getTime())) { throw new Error(errorMessage(`The given "${date}" is invalid.`)); } - const millis = date.getTime(); + const millis = date.getTime(); - const timestamp = new Timestamp(); - timestamp.setSeconds(Math.trunc(millis / 1000)); - timestamp.setNanos((millis % 1000) * 1000000); - return timestamp; + const timestamp = new Timestamp(); + timestamp.setSeconds(Math.trunc(millis / 1000)); + timestamp.setNanos((millis % 1000) * 1000000); + return timestamp; } diff --git a/client-js/main/index.js b/client-js/main/index.js index c4b922177..cf65268b5 100644 --- a/client-js/main/index.js +++ b/client-js/main/index.js @@ -26,5 +26,4 @@ export {FirebaseDatabaseClient} from './client/firebase-database-client'; export {HttpClient} from './client/http-client'; export {Client} from './client/client'; export {init} from './client/spine'; -export {FirebaseClient} from './client/firebase-client' export * from './client/errors'; diff --git a/client-js/package.json b/client-js/package.json index b3a3a8b13..6315be937 100644 --- a/client-js/package.json +++ b/client-js/package.json @@ -1,6 +1,6 @@ { "name": "spine-web", - "version": "1.2.1", + "version": "1.2.9", "license": "Apache-2.0", "description": "A JS client for interacting with Spine applications.", "homepage": "https://spine.io", diff --git a/client-js/test/client/client-factory-test.js b/client-js/test/client/client-factory-test.js index 737af52e0..4051690da 100644 --- a/client-js/test/client/client-factory-test.js +++ b/client-js/test/client/client-factory-test.js @@ -31,29 +31,29 @@ class TestClient extends Client {} describe('Client factory should', () => { - it('create composite client', done => { - const endpoint = 'example.com'; - const userId = new UserId(); - userId.value = 'me'; - const client = init({ - protoIndexFiles: [types, testTypes], - forQueries: { - endpointUrl: `${endpoint}/q/`, - actorProvider: new ActorProvider(userId) - }, - forSubscriptions: { - endpointUrl: `${endpoint}/q/`, - actorProvider: new ActorProvider(userId), - firebaseDatabase: "mock database" - }, - forCommands: { - endpointUrl: `${endpoint}/c/`, - actorProvider: new ActorProvider(userId), - implementation: new TestClient() - } - }); - assert.ok(client instanceof CompositeClient); - assert.ok(client._commanding instanceof TestClient); - done(); + it('create composite client', done => { + const endpoint = 'example.com'; + const userId = new UserId(); + userId.value = 'me'; + const client = init({ + protoIndexFiles: [types, testTypes], + forQueries: { + endpointUrl: `${endpoint}/q/`, + actorProvider: new ActorProvider(userId) + }, + forSubscriptions: { + endpointUrl: `${endpoint}/q/`, + actorProvider: new ActorProvider(userId), + firebaseDatabase: "mock database" + }, + forCommands: { + endpointUrl: `${endpoint}/c/`, + actorProvider: new ActorProvider(userId), + implementation: new TestClient() + } }); + assert.ok(client instanceof CompositeClient); + assert.ok(client._commanding instanceof TestClient); + done(); + }); }); diff --git a/client-js/test/client/direct-client-test.js b/client-js/test/client/direct-client-test.js index ffe959c2a..b3ca0c250 100644 --- a/client-js/test/client/direct-client-test.js +++ b/client-js/test/client/direct-client-test.js @@ -27,18 +27,18 @@ import {ActorProvider} from "../../main"; describe('Direct client should', () => { - it('not support subscriptions', done => { - const client = DirectClientFactory.createClient({ - endpointUrl: 'example.org', - protoIndexFiles: [types], - actorProvider: ActorProvider.ANONYMOUS - }); - try { - client.subscribeTo(client.newTopic().select(TaskCreated).build()); - assert.fail(); - } catch (e) { - assert.ok(e instanceof Error); - done(); - } + it('not support subscriptions', done => { + const client = DirectClientFactory.createClient({ + endpointUrl: 'example.org', + protoIndexFiles: [types], + actorProvider: ActorProvider.ANONYMOUS }); + try { + client.subscribeTo(TaskCreated).post(); + assert.fail(); + } catch (e) { + assert.ok(e instanceof Error); + done(); + } + }); }); diff --git a/client-js/test/client/filtering-request-test.js b/client-js/test/client/filtering-request-test.js new file mode 100644 index 000000000..765d6420e --- /dev/null +++ b/client-js/test/client/filtering-request-test.js @@ -0,0 +1,244 @@ +/* + * Copyright 2019, TeamDev. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import assert from 'assert'; +import uuid from 'uuid'; +import {ActorProvider, ActorRequestFactory, Filters} from '@lib/client/actor-request-factory'; +import {AnyPacker} from '@lib/client/any-packer'; +import {Type} from "@lib/client/typed-message"; +import {Task, TaskId} from '@testProto/spine/test/js/task_pb'; +import {MockClient} from "./test-helpers"; + +export class Given { + + constructor() { + throw new Error('A utility `Given` class cannot be instantiated.'); + } + + static targetType() { + return Task; + } + + static client() { + return new MockClient(); + } + + static actorRequestFactory() { + return new ActorRequestFactory(new ActorProvider()); + } +} + +/** + * Emulates an abstract test. + * + * To run, call the function inside the actual test with the necessary callbacks provided. + * + * @param newRequest a callback which accepts a target type, client and request factory and creates + * a new filtering request + * @param buildResult a callback which accepts a filtering request and builds the message + * that is sent to the Spine server (e.g. `Query`, `Topic`) + * @param getTarget a callback which extracts the target from the result message + * @param getFieldMask a callback which extracts the field mask from the result message + */ +export function filteringRequestTest(newRequest, buildResult, getTarget, getFieldMask) { + + const targetType = Given.targetType(); + const client = Given.client(); + const actorRequestFactory = Given.actorRequestFactory(); + + const targetTypeUrl = Type.forClass(targetType).url().value(); + + let request; + + beforeEach(done => { + request = newRequest(targetType, client, actorRequestFactory); + done(); + }); + + it('creates a `select all` target', done => { + const result = buildResult(request); + const target = getTarget(result); + assertIsIncludeAll(target); + done(); + }); + + it('creates a target filtering entities by a single ID', done => { + const taskId = new TaskId(); + const idValue = uuid.v4(); + taskId.setValue(idValue); + request.byId(taskId); + + const result = buildResult(request); + const target = getTarget(result); + assertTargetTypeEquals(target); + const idFilter = target.getFilters().getIdFilter(); + const idList = idFilter.getIdList(); + const length = idList.length; + assert.equal( + 1, length, + `Expected the ID list to contain a single ID, the actual length: '${length}'.` + ); + const taskIdType = Type.forClass(TaskId); + const targetId = AnyPacker.unpack(idList[0]).as(taskIdType); + const actualId = targetId.getValue(); + assert.equal( + idValue, actualId, + `Unexpected target ID '${actualId}', expected: '${idValue}'.` + ); + done(); + }); + + it('creates a target filtering entities by a group of IDs', done => { + const taskId1 = new TaskId(); + const idValue1 = uuid.v4(); + taskId1.setValue(idValue1); + const taskId2 = new TaskId(); + const idValue2 = uuid.v4(); + taskId2.setValue(idValue2); + + request.byId([taskId1, taskId2]); + const result = buildResult(request); + const target = getTarget(result); + assertTargetTypeEquals(target); + const idFilter = target.getFilters().getIdFilter(); + const idList = idFilter.getIdList(); + const length = idList.length; + assert.equal( + 2, length, + `Expected the ID list to contain two IDs, the actual length: '${length}'.` + ); + done(); + }); + + it('ignores a `null` ID specified to the `byId` method', done => { + request.byId(null); + const result = buildResult(request); + const target = getTarget(result); + assertIsIncludeAll(target); + done(); + }); + + it('ignores an empty array of IDs specified to the `byId` method', done => { + request.byId([]); + const result = buildResult(request); + const target = getTarget(result); + assertIsIncludeAll(target); + done(); + }); + + it('creates a target filtering entities by a single filter', done => { + const filter = Filters.eq('name', 'some task name'); + request.where(filter); + const result = buildResult(request); + const target = getTarget(result); + const compositeFilters = target.getFilters().getFilterList(); + const compositeFiltersLength = compositeFilters.length; + assert.equal( + 1, compositeFiltersLength, + `Expected the composite filter list to contain a single filter, the actual length: + '${compositeFiltersLength}'.` + ); + const filters = compositeFilters[0].getFilterList(); + const length = filters.length; + assert.equal( + 1, length, + `Expected the filter list to contain a single filter, the actual length: '${length}'.` + ); + const targetFilter = filters[0]; + assert.equal( + filter, targetFilter, + `Unexpected filter value '${targetFilter}', expected: '${filter}'.` + ); + done(); + }); + + it('creates a target filtering entities by a group of filters', done => { + const filter1 = Filters.eq('name', 'some task name'); + const filter2 = Filters.eq('description', 'some task description'); + request.where([filter1, filter2]); + const result = buildResult(request); + const target = getTarget(result); + const compositeFilters = target.getFilters().getFilterList(); + const compositeFiltersLength = compositeFilters.length; + assert.equal( + 1, compositeFiltersLength, + `Expected the composite filter list to contain a single filter, the actual + length: '${compositeFiltersLength}'.` + ); + const filters = compositeFilters[0].getFilterList(); + const length = filters.length; + assert.equal( + 2, length, + `Expected the filter list to contain two filters, the actual length: '${length}'.` + ); + done(); + }); + + it('ignores a `null` filter specified to the `where` method', done => { + request.where(null); + const result = buildResult(request); + const target = getTarget(result); + assertIsIncludeAll(target); + done(); + }); + + it('ignores an empty filter list specified to the `where` method', done => { + request.where([]); + const result = buildResult(request); + const target = getTarget(result); + assertIsIncludeAll(target); + done(); + }); + + it('allows to specify a field mask', done => { + const fields = ['id', 'name', 'description']; + request.withMask(fields); + const result = buildResult(request); + const fieldMask = getFieldMask(result); + const pathList = fieldMask.getPathsList(); + assert.equal( + fields, pathList, + `Unexpected list of fields in the field mask: '${pathList}', expected: '${fields}'.` + ); + done(); + }); + + /** + * @param {!spine.client.Target} target + */ + function assertIsIncludeAll(target) { + assertTargetTypeEquals(target); + assert.equal( + true, target.getIncludeAll(), + 'Expected `target.include_all` to be `true`.' + ); + } + + /** + * @param {!spine.client.Target} target + */ + function assertTargetTypeEquals(target) { + const actualType = target.getType(); + assert.equal( + targetTypeUrl, actualType, + `The unexpected target type '${actualType}', expected: '${targetTypeUrl}'.` + ); + } +} diff --git a/client-js/test/client/http-endpoint-test.js b/client-js/test/client/http-endpoint-test.js index c57ba69b1..cbb4da658 100644 --- a/client-js/test/client/http-endpoint-test.js +++ b/client-js/test/client/http-endpoint-test.js @@ -25,12 +25,7 @@ import {HttpEndpoint} from '@lib/client/http-endpoint'; import {HttpClient} from '@lib/client/http-client'; import {TypedMessage} from '@lib/client/typed-message'; import {CreateTask} from '@testProto/spine/test/js/commands_pb'; -import { - SpineError, - ConnectionError, - ClientError, - ServerError -} from '@lib/client/errors'; +import {ClientError, ConnectionError, ServerError, SpineError} from '@lib/client/errors'; import {Duration} from '@lib/client/time-utils'; import {fail} from './test-helpers'; @@ -68,24 +63,24 @@ class Given { static response() { return this._mockResponse() - .withStatus(Given.HTTP_RESPONSE.STATUS.OK) - .withBodyContent(Given.HTTP_RESPONSE.BODY_CONTENT); + .withStatus(Given.HTTP_RESPONSE.STATUS.OK) + .withBodyContent(Given.HTTP_RESPONSE.BODY_CONTENT); } static responseWithClientError() { return this._mockResponse() - .withStatus(Given.HTTP_RESPONSE.STATUS.CLIENT_ERROR); + .withStatus(Given.HTTP_RESPONSE.STATUS.CLIENT_ERROR); } static responseWithServerError() { return this._mockResponse() - .withStatus(Given.HTTP_RESPONSE.STATUS.SERVER_ERROR); + .withStatus(Given.HTTP_RESPONSE.STATUS.SERVER_ERROR); } static responseWithMalformedBody() { return this._mockResponse() - .withStatus(Given.HTTP_RESPONSE.STATUS.OK) - .withMalformedBodyContent(); + .withStatus(Given.HTTP_RESPONSE.STATUS.OK) + .withMalformedBodyContent(); } static _mockResponse() { @@ -131,11 +126,11 @@ describe('HttpEndpoint.command', function () { httpClientBehavior.resolves(Given.response()); sendCommand() - .then(responseParsedValue => { - assert.equal(responseParsedValue, Given.HTTP_RESPONSE.BODY_CONTENT); - done(); - }) - .catch(fail(done, 'A message sending failed when it was expected to complete.')); + .then(responseParsedValue => { + assert.equal(responseParsedValue, Given.HTTP_RESPONSE.BODY_CONTENT); + done(); + }) + .catch(fail(done, 'A message sending failed when it was expected to complete.')); }); it('rejects with `SpineError` when response body parsing fails', done => { @@ -143,26 +138,26 @@ describe('HttpEndpoint.command', function () { httpClientBehavior.resolves(malformedResponse); sendCommand() - .then(fail(done, 'A message sending was completed when it was expected to fail.')) - .catch(error => { - assert.ok(error instanceof SpineError); - assert.ok(error.getCause() instanceof Error); - assert.equal(error.message, 'Failed to parse response JSON'); - done(); - }); + .then(fail(done, 'A message sending was completed when it was expected to fail.')) + .catch(error => { + assert.ok(error instanceof SpineError); + assert.ok(error.getCause() instanceof Error); + assert.equal(error.message, 'Failed to parse response JSON'); + done(); + }); }); it('rejects with `ConnectionError` when message sending fails', done => { httpClientBehavior.rejects(Given.CONNECTION_ERROR); sendCommand() - .then(fail(done, 'A message sending was completed when it was expected to fail.')) - .catch(error => { - assert.ok(error instanceof ConnectionError); - assert.ok(error.getCause() instanceof Error); - assert.equal(error.message, Given.CONNECTION_ERROR.message); - done(); - }); + .then(fail(done, 'A message sending was completed when it was expected to fail.')) + .catch(error => { + assert.ok(error instanceof ConnectionError); + assert.ok(error.getCause() instanceof Error); + assert.equal(error.message, Given.CONNECTION_ERROR.message); + done(); + }); }); it('rejects with `ClientError` when response with status 400 received', done => { @@ -170,13 +165,13 @@ describe('HttpEndpoint.command', function () { httpClientBehavior.resolves(responseWithClientError); sendCommand() - .then(fail(done, 'A message sending was completed when it was expected to fail.')) - .catch(error => { - assert.ok(error instanceof ClientError); - assert.equal(error.message, MOCK_RESPONSE_STATUS_TEXT); - assert.equal(error.getCause(), responseWithClientError); - done(); - }); + .then(fail(done, 'A message sending was completed when it was expected to fail.')) + .catch(error => { + assert.ok(error instanceof ClientError); + assert.equal(error.message, MOCK_RESPONSE_STATUS_TEXT); + assert.equal(error.getCause(), responseWithClientError); + done(); + }); }); it('rejects with `ServerError` when response with status 500 received', done => { @@ -184,12 +179,12 @@ describe('HttpEndpoint.command', function () { httpClientBehavior.resolves(responseWithServerError); sendCommand() - .then(fail(done, 'A message sending was completed when it was expected to fail.')) - .catch(error => { - assert.ok(error instanceof ServerError); - assert.equal(error.message, MOCK_RESPONSE_STATUS_TEXT); - assert.equal(error.getCause(), responseWithServerError); - done(); - }); + .then(fail(done, 'A message sending was completed when it was expected to fail.')) + .catch(error => { + assert.ok(error instanceof ServerError); + assert.equal(error.message, MOCK_RESPONSE_STATUS_TEXT); + assert.equal(error.getCause(), responseWithServerError); + done(); + }); }); }); diff --git a/client-js/test/client/query-request-test.js b/client-js/test/client/query-request-test.js new file mode 100644 index 000000000..88d08057e --- /dev/null +++ b/client-js/test/client/query-request-test.js @@ -0,0 +1,73 @@ +/* + * Copyright 2019, TeamDev. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import assert from 'assert'; +import {QueryRequest} from "@lib/client/query-request"; +import {Duration} from "@lib/client/time-utils"; +import {OrderBy} from '@proto/spine/client/query_pb'; +import {filteringRequestTest, Given} from "./filtering-request-test"; + +describe('QueryRequest', function () { + + const timeoutDuration = new Duration({seconds: 5}); + this.timeout(timeoutDuration.inMs()); + + filteringRequestTest(newQueryRequest, buildQuery, getTarget, getFieldMask); + + it('allows to set ordering and limit', done => { + const request = + newQueryRequest(Given.targetType(), Given.client(), Given.actorRequestFactory()); + const column = 'name'; + const direction = OrderBy.Direction.ASCENDING; + const query = request.orderBy(column, direction) + .limit(2) + .query(); + const responseFormat = query.getFormat(); + const orderBy = responseFormat.getOrderBy(); + const orderByColumn = orderBy.getColumn(); + assert.equal( + column, orderByColumn, + `Unexpected column specified in the order by: '${orderByColumn}', expected: '${column}'.` + ); + const orderByDirection = orderBy.getDirection(); + assert.equal( + direction, orderByDirection, + `Unexpected direction specified in the order by: '${orderByDirection}', + expected: '${direction}'.` + ); + done(); + }); + + function newQueryRequest(targetType, clientStub, actorRequestFactory) { + return new QueryRequest(targetType, clientStub, actorRequestFactory); + } + + function buildQuery(request) { + return request.query(); + } + + function getTarget(query) { + return query.getTarget(); + } + + function getFieldMask(query) { + return query.getFormat().getFieldMask(); + } +}); diff --git a/client-js/test/client/subscription-request-test.js b/client-js/test/client/subscription-request-test.js new file mode 100644 index 000000000..5e1ae0cee --- /dev/null +++ b/client-js/test/client/subscription-request-test.js @@ -0,0 +1,47 @@ +/* + * Copyright 2019, TeamDev. All rights reserved. + * + * Redistribution and use in source and/or binary forms, with or without + * modification, must retain the above copyright notice and the following + * disclaimer. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import {SubscriptionRequest} from "@lib/client/subscribing-request"; +import {Duration} from "@lib/client/time-utils"; +import {filteringRequestTest} from "./filtering-request-test"; + +describe('SubscriptionRequest', function () { + + const timeoutDuration = new Duration({seconds: 5}); + this.timeout(timeoutDuration.inMs()); + + filteringRequestTest(newSubscriptionRequest, buildTopic, getTarget, getFieldMask); + + function newSubscriptionRequest(targetType, clientStub, actorRequestFactory) { + return new SubscriptionRequest(targetType, clientStub, actorRequestFactory); + } + + function buildTopic(request) { + return request.topic(); + } + + function getTarget(topic) { + return topic.getTarget(); + } + + function getFieldMask(topic) { + return topic.getFieldMask(); + } +}); diff --git a/client-js/test/client/time-utils-test.js b/client-js/test/client/time-utils-test.js index 48ad81a04..02344f754 100644 --- a/client-js/test/client/time-utils-test.js +++ b/client-js/test/client/time-utils-test.js @@ -23,33 +23,33 @@ import {convertDateToTimestamp} from '@lib/client/time-utils'; describe(`"convertDateToTimestamp" function`, () => { - const errorPrefix = 'Cannot convert to Timestamp.'; - - it('throws a respective error when non-Date value passed', () => { - const nonDateValue = 'today at 14 pm'; - const expectedErrorMessage = - `${errorPrefix} The given "${nonDateValue}" isn't of Date type.`; - assert.throws(() => convertDateToTimestamp(nonDateValue), null, expectedErrorMessage); - }); - - it('throws a respective error when invalid Date value passed', () => { - const invalidDate = new Date('the day when I get rich'); - const expectedErrorMessage = - `${errorPrefix} The given "${invalidDate}" is invalid.`; - - assert.throws(() => convertDateToTimestamp(invalidDate), null, expectedErrorMessage); - }); - - it('converts to Timestamp correctly', () => { - const now = new Date(Date.now()); - const timestamp = convertDateToTimestamp(now); - - const actualSeconds = timestamp.getSeconds(); - const actualNanos = timestamp.getNanos(); - const expectedSeconds = Math.trunc(now.getTime() / 1000); - const expectedNanos = (now.getTime() % 1000) * 1000000; - - assert.equal(actualSeconds, expectedSeconds); - assert.equal(actualNanos, expectedNanos); - }); + const errorPrefix = 'Cannot convert to Timestamp.'; + + it('throws a respective error when non-Date value passed', () => { + const nonDateValue = 'today at 14 pm'; + const expectedErrorMessage = + `${errorPrefix} The given "${nonDateValue}" isn't of Date type.`; + assert.throws(() => convertDateToTimestamp(nonDateValue), null, expectedErrorMessage); + }); + + it('throws a respective error when invalid Date value passed', () => { + const invalidDate = new Date('the day when I get rich'); + const expectedErrorMessage = + `${errorPrefix} The given "${invalidDate}" is invalid.`; + + assert.throws(() => convertDateToTimestamp(invalidDate), null, expectedErrorMessage); + }); + + it('converts to Timestamp correctly', () => { + const now = new Date(Date.now()); + const timestamp = convertDateToTimestamp(now); + + const actualSeconds = timestamp.getSeconds(); + const actualNanos = timestamp.getNanos(); + const expectedSeconds = Math.trunc(now.getTime() / 1000); + const expectedNanos = (now.getTime() % 1000) * 1000000; + + assert.equal(actualSeconds, expectedSeconds); + assert.equal(actualNanos, expectedNanos); + }); }); diff --git a/config b/config index 5dfa89584..d0c113735 160000 --- a/config +++ b/config @@ -1 +1 @@ -Subproject commit 5dfa8958451663696ebecd2c978fe0122b926417 +Subproject commit d0c113735c2297f5d4eec8bd6a8660ee4a9bee2e diff --git a/firebase-web/src/main/java/io/spine/web/firebase/StoredJson.java b/firebase-web/src/main/java/io/spine/web/firebase/StoredJson.java index 717541109..eede722ed 100644 --- a/firebase-web/src/main/java/io/spine/web/firebase/StoredJson.java +++ b/firebase-web/src/main/java/io/spine/web/firebase/StoredJson.java @@ -28,7 +28,6 @@ import com.google.protobuf.Message; import io.spine.protobuf.AnyPacker; import io.spine.value.StringTypeValue; -import org.checkerframework.checker.nullness.qual.Nullable; import static com.google.common.base.Preconditions.checkNotNull; import static io.spine.json.Json.toCompactJson; @@ -62,20 +61,6 @@ public static StoredJson from(String value) { : new StoredJson(value); } - /** - * Tries to encode the given message into a {@code StoredJson}. - * - *

Returns the {@code null} JSON value if the given message is {@code null}; - * - * @param message - * message to encode - */ - public static StoredJson encodeOrNull(@Nullable Message message) { - return message != null - ? encode(message) - : NULL_JSON; - } - /** * Encodes the given message into a {@code StoredJson}. * diff --git a/firebase-web/src/main/java/io/spine/web/firebase/subscription/UpdatePayload.java b/firebase-web/src/main/java/io/spine/web/firebase/subscription/UpdatePayload.java index 60881d774..32d055141 100644 --- a/firebase-web/src/main/java/io/spine/web/firebase/subscription/UpdatePayload.java +++ b/firebase-web/src/main/java/io/spine/web/firebase/subscription/UpdatePayload.java @@ -24,6 +24,7 @@ import com.google.common.hash.HashCode; import com.google.common.hash.HashFunction; import com.google.protobuf.Any; +import com.google.protobuf.Empty; import com.google.protobuf.Message; import io.spine.client.EntityStateUpdate; import io.spine.client.SubscriptionUpdate; @@ -48,6 +49,9 @@ * *

Consists of entities or events, which ought to be written into the Firebase database under * a certain path. + * + *

The updates about {@linkplain EntityStateUpdate#getNoLongerMatching() no longer matching} + * entity states are represented as {@link Empty}. */ final class UpdatePayload { @@ -63,9 +67,9 @@ final class UpdatePayload { */ private static final HashFunction keyHashFunction = murmur3_128(); - private final ImmutableMap messages; + private final ImmutableMap messages; - private UpdatePayload(ImmutableMap messages) { + private UpdatePayload(ImmutableMap messages) { this.messages = messages; } @@ -83,15 +87,15 @@ private static UpdatePayload entityUpdates(SubscriptionUpdate update) { .getEntityUpdates() .getUpdateList() .stream() - .collect(toHashTable(EntityStateUpdate::getId, UpdatePayload::messageOrNull)); + .collect(toHashTable(EntityStateUpdate::getId, UpdatePayload::messageOrEmpty)); checkArgument(!messages.isEmpty(), "Empty subscription update: %s", update); return new UpdatePayload(messages); } - private static @Nullable Message messageOrNull(EntityStateUpdate update) { + private static Message messageOrEmpty(EntityStateUpdate update) { return update.hasState() ? unpack(update.getState()) - : null; + : Empty.getDefaultInstance(); } private static UpdatePayload eventUpdates(SubscriptionUpdate update) { @@ -99,7 +103,7 @@ private static UpdatePayload eventUpdates(SubscriptionUpdate update) { .getEventUpdates() .getEventList() .stream() - .collect(toHashTable(Event::id, Event::enclosedMessage)); + .collect(toHashTable(Event::id, identity())); return new UpdatePayload(messages); } @@ -136,7 +140,33 @@ private static String key(Message id) { */ NodeValue asNodeValue() { NodeValue node = NodeValue.empty(); - messages.forEach((id, message) -> node.addChild(id, StoredJson.encodeOrNull(message))); + messages.forEach((id, message) -> addChildOrNull(node, id, message)); return node; } + + /** + * Adds an {@code ID -> message} key-value pair as a child of the given node. + * + *

If the message is {@link Empty}, adds a {@code null} child under the given key, + * effectively deleting it from the database. + */ + private static void addChildOrNull(NodeValue node, String id, Message message) { + boolean isEmpty = Empty.getDefaultInstance() + .equals(message); + if (isEmpty) { + node.addNullChild(id); + } else { + node.addChild(id, StoredJson.encode(message)); + } + } + + /** + * An identity {@link Event}-to-{@link Message} conversion. + * + *

The standard {@link Function#identity()} can't be applied because it requires having the + * exact same type as input and output. + */ + private static Function identity() { + return event -> event; + } } diff --git a/integration-tests/js-tests/package.json b/integration-tests/js-tests/package.json index 0f238828f..7ba3e694d 100644 --- a/integration-tests/js-tests/package.json +++ b/integration-tests/js-tests/package.json @@ -1,6 +1,6 @@ { "name": "client-js-tests", - "version": "1.2.1", + "version": "1.2.9", "license": "Apache-2.0", "description": "Tests of a `spine-web` JS library against the Spine-based application.", "scripts": { diff --git a/integration-tests/js-tests/test/direct-client/given/direct-client.js b/integration-tests/js-tests/test/direct-client/given/direct-client.js index 54586f9a9..3d81c3cae 100644 --- a/integration-tests/js-tests/test/direct-client/given/direct-client.js +++ b/integration-tests/js-tests/test/direct-client/given/direct-client.js @@ -23,25 +23,25 @@ import * as spineWeb from '@lib/index'; import {ActorProvider} from '@lib/client/actor-request-factory'; /** - * Initializes the {@link DirectClient client} that interacts with Gretty-based + * Initializes the {@link DirectClientFactory client} that interacts with Gretty-based * local backend server. * * See `integration-tests/README.MD` for details. */ export function initClient() { - return spineWeb.init({ - protoIndexFiles: [testProtobuf], - endpointUrl: 'http://localhost:8080', - actorProvider: new ActorProvider(), - routing: { - query: '/direct-query' - } - }); + return spineWeb.init({ + protoIndexFiles: [testProtobuf], + endpointUrl: 'http://localhost:8080', + actorProvider: new ActorProvider(), + routing: { + query: '/direct-query' + } + }); } /** - * A {@link DirectClient client} instance for tests. + * A client instance for tests. * - * @type {DirectClient} + * @type {Client} */ export const client = initClient(); diff --git a/integration-tests/js-tests/test/direct-client/query-test.js b/integration-tests/js-tests/test/direct-client/query-test.js index 286d7aa5d..410e912bf 100644 --- a/integration-tests/js-tests/test/direct-client/query-test.js +++ b/integration-tests/js-tests/test/direct-client/query-test.js @@ -26,48 +26,45 @@ import {UserTasks} from '@testProto/spine/web/test/given/user_tasks_pb'; describe('DirectClient executes query built', function () { - let users; + let users; - /** - * Prepares environment, where four users have one, two, three, and four tasks - * assigned respectively. - */ - before(function(done) { - // Big timeout allows to complete environment setup. - this.timeout(10 * 1000); + /** + * Prepares environment, where four users have one, two, three, and four tasks + * assigned respectively. + */ + before(function (done) { + // Big timeout allows to complete environment setup. + this.timeout(10 * 1000); - users = [ - TestEnvironment.newUser('query-tester1'), - TestEnvironment.newUser('query-tester2'), - TestEnvironment.newUser('query-tester3'), - TestEnvironment.newUser('query-tester4') - ]; + users = [ + TestEnvironment.newUser('query-tester1'), + TestEnvironment.newUser('query-tester2'), + TestEnvironment.newUser('query-tester3'), + TestEnvironment.newUser('query-tester4') + ]; - const createTasksPromises = []; - users.forEach((user, index) => { - const tasksCount = index + 1; - const promise = TestEnvironment.createTaskFor(user, tasksCount, client); - createTasksPromises.push(promise); - }); - Promise.all(createTasksPromises) - .then(() => { - // Gives time for the model state to be updated - setTimeout(done, 500); - }); + const createTasksPromises = []; + users.forEach((user, index) => { + const tasksCount = index + 1; + const promise = TestEnvironment.createTaskFor(user, tasksCount, client); + createTasksPromises.push(promise); }); + Promise.all(createTasksPromises) + .then(() => { + // Gives time for the model state to be updated + setTimeout(done, 500); + }); + }); - it(`by IDs and returns correct values`, done => { - const ids = users.map(user => user.id); - const query = client.newQuery() - .select(UserTasks) - .byIds(ids) - .build(); - - client.execute(query) - .then(userTasksList => { - assert.ok(ensureUserTasks(userTasksList, users)); - done(); - }) - .catch(fail(done)); - }); + it(`by IDs and returns correct values`, done => { + const ids = users.map(user => user.id); + client.select(UserTasks) + .byId(ids) + .run() + .then(userTasksList => { + assert.ok(ensureUserTasks(userTasksList, users)); + done(); + }) + .catch(fail(done)); + }); }); diff --git a/integration-tests/js-tests/test/firebase-client/fetch-test.js b/integration-tests/js-tests/test/firebase-client/fetch-test.js index db2bfdd18..37161f3fd 100644 --- a/integration-tests/js-tests/test/firebase-client/fetch-test.js +++ b/integration-tests/js-tests/test/firebase-client/fetch-test.js @@ -27,142 +27,171 @@ import {Project} from '@testProto/spine/web/test/given/project_pb'; import {client} from './given/firebase-client'; describe('FirebaseClient "fetch"', function () { - let taskIds; - - /** - * Prepares the environment for `FirebaseClient#fetch()` tests where - * two tasks are created. - */ - before(function(done) { - // Big timeout allows complete environment setup. - this.timeout(20 * 1000); - - const createTaskCommands = [ - TestEnvironment.createTaskCommand({withPrefix: 'spine-web-fetch-test-task-1'}), - TestEnvironment.createTaskCommand({withPrefix: 'spine-web-fetch-test-task-2'}), - ]; - - taskIds = createTaskCommands.map(command => command.getId()); - - const createTasksPromises = []; - createTaskCommands.forEach(command => { - let reportTaskCreated; - const promise = new Promise(resolve => reportTaskCreated = resolve); - createTasksPromises.push(promise); - - client.sendCommand(command, () => reportTaskCreated(), fail(done), fail(done)) - }); - - Promise.all(createTasksPromises) - .then(() => { - // Gives time for the model state to be updated - setTimeout(done, 500); - }); - }); - - it('returns correct value by single ID', done => { - const id = taskIds[0]; - client.fetch({entity: Task, byIds: id}) - .then(data => { - assert.ok(Array.isArray(data)); - assert.equal(data.length, 1); - const item = data[0]; - assert.ok(item.getId().getValue() === id.getValue()); - done(); - }, fail(done)); - }); - - it('ignores `byIds` parameter when empty list specified', done => { - client.fetch({entity: Task, byIds: []}) - .then(data => { - assert.ok(Array.isArray(data)); - assert.ok(data.length >= taskIds.length); - done(); - }, fail(done)); - }); - - it('ignores `byIds` parameter when `null` value specified', done => { - client.fetch({entity: Task, byIds: null}) - .then(data => { - assert.ok(Array.isArray(data)); - assert.ok(data.length >= taskIds.length); - done(); - }, fail(done)); - }); - - it('ignores `byIds` parameter when empty list specified', done => { - client.fetch({entity: Task, byIds: []}) - .then(data => { - assert.ok(Array.isArray(data)); - assert.ok(data.length >= taskIds.length); - done(); - }, fail(done)); - }); - - - it('returns empty list when fetches entity by single ID that is missing', done => { - const taskId = TestEnvironment.taskId({}); - - client.fetch({entity: Task, byIds: taskId}) - .then(data => { - assert.ok(Array.isArray(data)); - assert.ok(data.length === 0); - done(); - }, fail(done)); + let taskIds; + + /** + * Prepares the environment for `FirebaseClient#fetch()` tests where + * two tasks are created. + */ + before(function (done) { + // Big timeout allows complete environment setup. + this.timeout(20 * 1000); + + const createTaskCommands = [ + TestEnvironment.createTaskCommand({withPrefix: 'spine-web-fetch-test-task-1'}), + TestEnvironment.createTaskCommand({withPrefix: 'spine-web-fetch-test-task-2'}), + ]; + + taskIds = createTaskCommands.map(command => command.getId()); + + const createTasksPromises = []; + createTaskCommands.forEach(command => { + let reportTaskCreated; + const promise = new Promise(resolve => reportTaskCreated = resolve); + createTasksPromises.push(promise); + + client.command(command) + .onOk(() => reportTaskCreated()) + .onError(fail(done)) + .onRejection(fail(done)) + .post(); }); - it('returns correct values by IDs', done => { - client.fetch({entity: Task, byIds: taskIds}) - .then(data => { - assert.ok(Array.isArray(data)); - assert.equal(data.length, taskIds.length); - taskIds.forEach(taskId => { - const targetObject = data.find(item => item.getId().getValue() === taskId.getValue()); - assert.ok(targetObject); - }); - - done(); - }, fail(done)); - }); - - it('retrieves the existing entities of given type when no IDs specified', done => { - client.fetch({entity: Task}) - .then(data => { - taskIds.forEach(taskId => { - const targetObject = data.find(item => item.getId().getValue() === taskId.getValue()); - assert.ok(targetObject); - }); - - done(); - }, fail(done)); - }); - - it('retrieves an empty list for entity that does not get created', done => { - client.fetch({entity: Project}) - .then(data => { - assert.ok(data.length === 0); - done(); - }, fail(done)); - }); - - it('fails a malformed query', done => { - const command = TestEnvironment.createTaskCommand({withPrefix: 'spine-web-test-malformed-query'}); - - const Unknown = class { - static typeUrl() { - return 'spine.web/fails.malformed.type' - } - }; - - client.sendCommand(command, () => { - - client.fetch({entity: Unknown}) - .then(fail(done), error => { - assert.ok(error instanceof ServerError); - assert.equal(error.message, 'Server Error'); - done(); - }); - - }, fail(done), fail(done)); - }); + Promise.all(createTasksPromises) + .then(() => { + // Gives time for the model state to be updated + setTimeout(done, 500); + }); + }); + + it('returns all values of a type', done => { + client.select(Task) + .run() + .then(data => { + taskIds.forEach(taskId => { + const targetObject = data.find( + item => item.getId().getValue() === taskId.getValue() + ); + assert.ok(targetObject); + }); + + done(); + }, fail(done)); + }); + + it('returns the correct value by a single ID', done => { + const id = taskIds[0]; + client.select(Task) + .byId(id) + .run() + .then(data => { + assert.ok(Array.isArray(data)); + assert.equal(data.length, 1); + const item = data[0]; + assert.ok(item.getId().getValue() === id.getValue()); + done(); + }, fail(done)); + }); + + it('ignores `byId` parameter when an empty list is specified', done => { + client.select(Task) + .byId([]) + .run() + .then(data => { + assert.ok(Array.isArray(data)); + assert.ok(data.length >= taskIds.length); + done(); + }, fail(done)); + }); + + it('ignores `byId` parameter when a `null` value is specified', done => { + client.select(Task) + .byId(null) + .run() + .then(data => { + assert.ok(Array.isArray(data)); + assert.ok(data.length >= taskIds.length); + done(); + }, fail(done)); + }); + + it('returns empty list when fetches entity by a single ID that is missing', done => { + const taskId = TestEnvironment.taskId({}); + + client.select(Task) + .byId(taskId) + .run() + .then(data => { + assert.ok(Array.isArray(data)); + assert.ok(data.length === 0); + done(); + }, fail(done)); + }); + + it('returns correct values by multiple IDs', done => { + client.select(Task) + .byId(taskIds) + .run() + .then(data => { + assert.ok(Array.isArray(data)); + assert.equal(data.length, taskIds.length); + taskIds.forEach(taskId => { + const targetObject = data.find(item => item.getId().getValue() === taskId.getValue()); + assert.ok(targetObject); + }); + + done(); + }, fail(done)); + }); + + it('retrieves an empty list for an entity type that does not get instantiated', done => { + client.select(Project) + .run() + .then(data => { + assert.ok(data.length === 0); + done(); + }, fail(done)); + }); + + it('fetches entities using a manually created `Query`', done => { + const query = client.newQuery() + .select(Task) + .byIds(taskIds) + .build(); + client.read(query) + .then(data => { + assert.ok(Array.isArray(data)); + assert.equal(data.length, taskIds.length); + taskIds.forEach(taskId => { + const targetObject = data.find(item => item.getId().getValue() === taskId.getValue()); + assert.ok(targetObject); + }); + + done(); + }) + .catch(() => fail(done)); + }); + + it('fails a malformed query', done => { + const command = + TestEnvironment.createTaskCommand({withPrefix: 'spine-web-test-malformed-query'}); + + const Unknown = class { + static typeUrl() { + return 'spine.web/fails.malformed.type' + } + }; + const selectAndCheckFailed = () => client.select(Unknown) + .run() + .then(fail(done), error => { + assert.ok(error instanceof ServerError); + assert.equal(error.message, 'Server Error'); + done(); + }); + client.command(command) + .onOk(selectAndCheckFailed) + .onError(fail(done)) + .onRejection(fail(done)) + .post(); + }); }); diff --git a/integration-tests/js-tests/test/firebase-client/given/firebase-client.js b/integration-tests/js-tests/test/firebase-client/given/firebase-client.js index 8bcd94499..10548707f 100644 --- a/integration-tests/js-tests/test/firebase-client/given/firebase-client.js +++ b/integration-tests/js-tests/test/firebase-client/given/firebase-client.js @@ -33,12 +33,12 @@ import {ActorProvider} from '@lib/client/actor-request-factory'; * @return {FirebaseClient} the Firebase client instance */ export function initClient(endpointUrl = 'http://localhost:8080') { - return spineWeb.init({ - protoIndexFiles: [testProtobuf], - endpointUrl: endpointUrl, - firebaseDatabase: firebaseDatabase, - actorProvider: new ActorProvider() - }); + return spineWeb.init({ + protoIndexFiles: [testProtobuf], + endpointUrl: endpointUrl, + firebaseDatabase: firebaseDatabase, + actorProvider: new ActorProvider() + }); } /** diff --git a/integration-tests/js-tests/test/firebase-client/given/firebase-database.js b/integration-tests/js-tests/test/firebase-client/given/firebase-database.js index 4f33eaac9..3d07f7693 100644 --- a/integration-tests/js-tests/test/firebase-client/given/firebase-database.js +++ b/integration-tests/js-tests/test/firebase-client/given/firebase-database.js @@ -25,8 +25,8 @@ import firebase from 'firebase'; * see `integration-tests/README.MD`. */ const config = { - databaseURL: 'https://spine-dev.firebaseio.com/', - authDomain: 'https://spine-dev.firebaseio.com/' + databaseURL: 'https://spine-dev.firebaseio.com/', + authDomain: 'https://spine-dev.firebaseio.com/' }; /** diff --git a/integration-tests/js-tests/test/firebase-client/given/test-environment.js b/integration-tests/js-tests/test/firebase-client/given/test-environment.js index 9d9ba8c96..188faf5b6 100644 --- a/integration-tests/js-tests/test/firebase-client/given/test-environment.js +++ b/integration-tests/js-tests/test/firebase-client/given/test-environment.js @@ -37,88 +37,90 @@ import {UserId} from '@testProto/spine/core/user_id_pb'; */ export default class TestEnvironment { - constructor() { - throw new Error('A utility TestEnvironment class cannot be instantiated.'); + constructor() { + throw new Error('A utility `TestEnvironment` class cannot be instantiated.'); + } + + /** + * @param {{ + * withId?: String, + * withPrefix?: String, + * named?: String, + * describedAs: String, + * assignedTo: UserId + * }} + * + * @return {CreateTask} + */ + static createTaskCommand({ + withId: id, + withPrefix: idPrefix, + named: name, + describedAs: description, + assignedTo: userId + }) { + const taskId = this.taskId({value: id, withPrefix: idPrefix}); + + name = typeof name === 'undefined' ? this.DEFAULT_TASK_NAME : name; + description = typeof description === 'undefined' + ? this.DEFAULT_TASK_DESCRIPTION + : description; + + const command = new CreateTask(); + command.setId(taskId); + command.setName(name); + command.setDescription(description); + + if (!!userId) { + command.setAssignee(userId); } - /** - * @param {{ - * withId?: String, - * withPrefix?: String, - * named?: String, - * describedAs: String, - * assignedTo: UserId - * }} - * - * @return {CreateTask} - */ - static createTaskCommand({ - withId: id, - withPrefix: idPrefix, - named: name, - describedAs: description, - assignedTo: userId - }) { - const taskId = this.taskId({value: id, withPrefix: idPrefix}); - - name = typeof name === 'undefined' ? this.DEFAULT_TASK_NAME : name; - description = typeof description === 'undefined' ? this.DEFAULT_TASK_DESCRIPTION : description; - - const command = new CreateTask(); - command.setId(taskId); - command.setName(name); - command.setDescription(description); - - if (!!userId) { - command.setAssignee(userId); - } - - return command; + return command; + } + + /** + * @param {{ + * withId!: String, + * to!: String + * }} + * + * @return {RenameTask} + */ + static renameTaskCommand({withId: id, to: newName}) { + const taskId = this.taskId({value: id}); + + const command = new RenameTask(); + command.setId(taskId); + command.setName(newName); + + return command; + + } + + /** + * @param value + * @param withPrefix + * + * @return {TaskId} + */ + static taskId({value, withPrefix: prefix}) { + if (typeof value === 'undefined') { + value = uuid.v4(); } - - /** - * @param {{ - * withId!: String, - * to!: String - * }} - * - * @return {RenameTask} - */ - static renameTaskCommand({withId: id, to: newName}) { - const taskId = this.taskId({value: id}); - - const command = new RenameTask(); - command.setId(taskId); - command.setName(newName); - - return command; - - } - - /** - * @param value - * @param withPrefix - * - * @return {TaskId} - */ - static taskId({value, withPrefix: prefix}) { - if (typeof value === 'undefined') { - value = uuid.v4(); - } - if (typeof prefix !== 'undefined') { - value = `${prefix}-${value}`; - } - const taskId = new TaskId(); - taskId.setValue(value); - return taskId; - } - - /** - * A function that does nothing. - */ - static noop() { - // Do nothing. + if (typeof prefix !== 'undefined') { + value = `${prefix}-${value}`; } + const taskId = new TaskId(); + taskId.setValue(value); + return taskId; + } + + /** + * A function that does nothing. + */ + static noop() { + // Do nothing. + } } TestEnvironment.DEFAULT_TASK_NAME = 'Get to Mount Doom'; diff --git a/integration-tests/js-tests/test/firebase-client/query-test.js b/integration-tests/js-tests/test/firebase-client/query-test.js index a7013d3a0..8d9b5513f 100644 --- a/integration-tests/js-tests/test/firebase-client/query-test.js +++ b/integration-tests/js-tests/test/firebase-client/query-test.js @@ -39,180 +39,168 @@ import {UserTasks} from '@testProto/spine/web/test/given/user_tasks_pb'; */ describe('FirebaseClient executes query built', function () { - let users; - - function toUserIds(users) { - return users.map(user => user.id); - } - - /** - * Prepares environment, where four users have one, two, three, and four tasks - * assigned respectively. - */ - before(function(done) { - // Big timeout allows complete environment setup. - this.timeout(10 * 1000); - - users = [ - TestEnvironment.newUser('query-tester1'), - TestEnvironment.newUser('query-tester2'), - TestEnvironment.newUser('query-tester3'), - TestEnvironment.newUser('query-tester4') - ]; - - const createTasksPromises = []; - users.forEach((user, index) => { - const tasksCount = index + 1; - const promise = TestEnvironment.createTaskFor(user, tasksCount, client); - createTasksPromises.push(promise); - }); - Promise.all(createTasksPromises) - .then(() => { - // Gives time for the model state to be updated - setTimeout(done, 500); - }); - }); - - /** - * @type {QueryTest[]} - */ - const tests = [ - { - message: 'by ID', - ids: () => toUserIds(users.slice(0, 1)), - expectedUsers: () => users.slice(0, 1) - }, - { - message: 'by IDs', - ids: () => toUserIds(users.slice(0, 2)), - expectedUsers: () => users.slice(0, 2) - }, - { - message: 'by missing ID', - ids: () => [ - TestEnvironment.userId('user-without-tasks-assigned') - ], - expectedUsers: () => users.slice(0, 2) - }, - { - message: 'with `eq` filter', - filters: [ - Filters.eq('task_count', 3) - ], - expectedUsers: () => users.filter(user => user.tasks.length === 3) - }, - { - message: 'with `lt` filter', - filters: [ - Filters.lt('task_count', 3) - ], - expectedUsers: () => users.filter(user => user.tasks.length < 3) - }, - { - message: 'with `gt` filter', - filters: [ - Filters.gt('task_count', 3) - ], - expectedUsers: () => users.filter(user => user.tasks.length > 3) - }, - { - message: 'with `le` filter', - filters: [ - Filters.le('task_count', TypedMessage.int32(3)) - ], - expectedUsers: () => users.filter(user => user.tasks.length <= 3) - }, - { - message: 'with `ge` filter', - filters: [ - Filters.ge('task_count', 3) - ], - expectedUsers: () => users.filter(user => user.tasks.length >= 3) - }, - { - message: 'with several filters applied to the same column', - filters: [ - Filters.gt('task_count', 1), - Filters.lt('task_count', 3) - ], - expectedUsers: () => users.filter(user => user.tasks.length > 1 && user.tasks.length < 3) - }, - { - message: 'with several filters applied to different column', - filters: [ - Filters.gt('task_count', 1), - Filters.lt('is_overloaded', new BoolValue([true])) - ], - expectedUsers: () => users.filter(user => user.tasks.length > 1) - }, - { - message: 'with inappropriate filter', - filters: [ - Filters.ge('task_count', 100) - ], - expectedUsers: () => [] - } + let users; + + function toUserIds(users) { + return users.map(user => user.id); + } + + /** + * Prepares environment, where four users have one, two, three, and four tasks + * assigned respectively. + */ + before(function (done) { + // Big timeout allows complete environment setup. + this.timeout(10 * 1000); + + users = [ + TestEnvironment.newUser('query-tester1'), + TestEnvironment.newUser('query-tester2'), + TestEnvironment.newUser('query-tester3'), + TestEnvironment.newUser('query-tester4') ]; - function buildQueryFor({ids, filters}) { - const queryBuilder = client.newQuery() - .select(UserTasks) - .byIds(ids ? ids : toUserIds(users)); - - if (!!filters) { - queryBuilder.where(filters) - } - - return queryBuilder.build(); - } - - tests.forEach(test => { - it(`${test.message} and returns correct values`, done => { - const ids = test.ids ? test.ids() : undefined; - const filters = test.filters; - const query = buildQueryFor({ids: ids, filters: filters}); - - client.execute(query) - .then(userTasksList => { - assert.ok(ensureUserTasks(userTasksList, test.expectedUsers())); - done(); - }) - .catch(() => fail(done)); - }); + const createTasksPromises = []; + users.forEach((user, index) => { + const tasksCount = index + 1; + const promise = TestEnvironment.createTaskFor(user, tasksCount, client); + createTasksPromises.push(promise); }); - - it('with Date-based filter and returns correct values', (done) => { - const userIds = toUserIds(users); - - client.fetch({entity: UserTasks, byIds: userIds}) - .then(data => { - assert.ok(Array.isArray(data)); - assert.equal(data.length, userIds.length); - - const firstUserTasks = data[0]; - - const lastUpdatedTimestamp = firstUserTasks.getLastUpdated(); - const seconds = lastUpdatedTimestamp.getSeconds(); - const nanos = lastUpdatedTimestamp.getNanos(); - const millis = seconds * 1000 + nanos / 1000000; - const whenFirstUserGotTask = new Date(millis); - - const query = buildQueryFor({ - filters: [ - Filters.eq('last_updated', whenFirstUserGotTask), - ] - }); - - client.execute(query) - .then(userTasksList => { - assert.ok(Array.isArray(userTasksList)); - assert.equal(userTasksList.length, 1); - - const actualUserId = userTasksList[0].getId(); - assert.equal(actualUserId.getValue(), firstUserTasks.getId().getValue()); - done(); - }) - .catch(() => fail(done)); - }); + Promise.all(createTasksPromises) + .then(() => { + // Gives time for the model state to be updated + setTimeout(done, 500); + }); + }); + + /** + * @type {QueryTest[]} + */ + const tests = [ + { + message: 'by ID', + ids: () => toUserIds(users.slice(0, 1)), + expectedUsers: () => users.slice(0, 1) + }, + { + message: 'by IDs', + ids: () => toUserIds(users.slice(0, 2)), + expectedUsers: () => users.slice(0, 2) + }, + { + message: 'by missing ID', + ids: () => [ + TestEnvironment.userId('user-without-tasks-assigned') + ], + expectedUsers: () => users.slice(0, 2) + }, + { + message: 'with `eq` filter', + filters: [ + Filters.eq('task_count', 3) + ], + expectedUsers: () => users.filter(user => user.tasks.length === 3) + }, + { + message: 'with `lt` filter', + filters: [ + Filters.lt('task_count', 3) + ], + expectedUsers: () => users.filter(user => user.tasks.length < 3) + }, + { + message: 'with `gt` filter', + filters: [ + Filters.gt('task_count', 3) + ], + expectedUsers: () => users.filter(user => user.tasks.length > 3) + }, + { + message: 'with `le` filter', + filters: [ + Filters.le('task_count', TypedMessage.int32(3)) + ], + expectedUsers: () => users.filter(user => user.tasks.length <= 3) + }, + { + message: 'with `ge` filter', + filters: [ + Filters.ge('task_count', 3) + ], + expectedUsers: () => users.filter(user => user.tasks.length >= 3) + }, + { + message: 'with several filters applied to the same column', + filters: [ + Filters.gt('task_count', 1), + Filters.lt('task_count', 3) + ], + expectedUsers: () => users.filter(user => user.tasks.length > 1 && user.tasks.length < 3) + }, + { + message: 'with several filters applied to different column', + filters: [ + Filters.gt('task_count', 1), + Filters.lt('is_overloaded', new BoolValue([true])) + ], + expectedUsers: () => users.filter(user => user.tasks.length > 1) + }, + { + message: 'with inappropriate filter', + filters: [ + Filters.ge('task_count', 100) + ], + expectedUsers: () => [] + } + ]; + + tests.forEach(test => { + it(`${test.message} and returns correct values`, done => { + const ids = test.ids ? test.ids() : toUserIds(users); + const filters = test.filters; + + client.select(UserTasks) + .byId(ids) + .where(filters) + .run() + .then(userTasksList => { + assert.ok(ensureUserTasks(userTasksList, test.expectedUsers())); + done(); + }) + .catch(() => fail(done)); }); + }); + + it('with `Date`-based filter and returns correct values', (done) => { + const userIds = toUserIds(users); + + client.select(UserTasks) + .byId(userIds) + .run() + .then(data => { + assert.ok(Array.isArray(data)); + assert.equal(data.length, userIds.length); + + const firstUserTasks = data[0]; + + const lastUpdatedTimestamp = firstUserTasks.getLastUpdated(); + const seconds = lastUpdatedTimestamp.getSeconds(); + const nanos = lastUpdatedTimestamp.getNanos(); + const millis = seconds * 1000 + nanos / 1000000; + const whenFirstUserGotTask = new Date(millis); + + client.select(UserTasks) + .where(Filters.eq('last_updated', whenFirstUserGotTask)) + .run() + .then(userTasksList => { + assert.ok(Array.isArray(userTasksList)); + assert.equal(userTasksList.length, 1); + + const actualUserId = userTasksList[0].getId(); + assert.equal(actualUserId.getValue(), firstUserTasks.getId().getValue()); + done(); + }) + .catch(() => fail(done)); + }); + }); }); diff --git a/integration-tests/js-tests/test/firebase-client/send-command-test.js b/integration-tests/js-tests/test/firebase-client/send-command-test.js index 341e866df..c84268155 100644 --- a/integration-tests/js-tests/test/firebase-client/send-command-test.js +++ b/integration-tests/js-tests/test/firebase-client/send-command-test.js @@ -21,90 +21,175 @@ import assert from 'assert'; import TestEnvironment from './given/test-environment'; import {CommandHandlingError, CommandValidationError, ConnectionError} from '@lib/index'; +import {CreateTask} from '@testProto/spine/web/test/given/commands_pb'; +import {TaskCreated} from '@testProto/spine/web/test/given/events_pb'; import {Task} from '@testProto/spine/web/test/given/task_pb'; import {fail} from '../test-helpers'; import {client, initClient} from './given/firebase-client'; +import {AnyPacker} from '@lib/client/any-packer'; +import {Type} from '@lib/client/typed-message'; describe('FirebaseClient command sending', function () { - // Big timeout allows to receive model state changes during tests. - this.timeout(5000); + // Big timeout allows to receive model state changes during tests. + this.timeout(5000); - it('completes with success', done => { + it('completes with success', done => { - const command = TestEnvironment.createTaskCommand({ - withPrefix: 'spine-web-test-send-command', - named: 'Implement Spine Web JS client tests', - describedAs: 'Spine Web need integration tests' - }); - - const taskId = command.getId(); - - client.sendCommand(command, () => { - - client.fetch({entity: Task, byIds: taskId}) - .then(data => { - assert.equal(data.length, 1); - const item = data[0]; - assert.equal(item.getId().getValue(), taskId); - assert.equal(item.getName(), command.getName()); - assert.equal(item.getDescription(), command.getDescription()); - - done(); - - }, fail(done)); + const command = TestEnvironment.createTaskCommand({ + withPrefix: 'spine-web-test-send-command', + named: 'Implement Spine Web JS client tests', + describedAs: 'Spine Web need integration tests' + }); - }, fail(done), fail(done)); + const taskId = command.getId(); + + const fetchAndCheck = () => { + client.select(Task) + .byId(taskId) + .run() + .then(data => { + assert.equal(data.length, 1); + const item = data[0]; + assert.equal(item.getId().getValue(), taskId); + assert.equal(item.getName(), command.getName()); + assert.equal(item.getDescription(), command.getDescription()); + + done(); + + }, fail(done)); + }; + + client.command(command) + .onOk(fetchAndCheck) + .onError(fail(done)) + .onRejection(fail(done)) + .post(); + }); + + it('fails when wrong server endpoint specified', done => { + const fakeBaseUrl = 'https://malformed-server-endpoint.com'; + const malformedBackendClient = initClient(fakeBaseUrl); + const command = TestEnvironment.createTaskCommand({ + withPrefix: 'spine-web-test-send-command', + named: 'Implement Spine Web JS client tests', + describedAs: 'Spine Web need integration tests' }); - it('fails when wrong server endpoint specified', done => { - const fakeBaseUrl = 'https://malformed-server-endpoint.com'; - const malformedBackendClient = initClient(fakeBaseUrl); - const command = TestEnvironment.createTaskCommand({ - withPrefix: 'spine-web-test-send-command', - named: 'Implement Spine Web JS client tests', - describedAs: 'Spine Web need integration tests' - }); - - malformedBackendClient.sendCommand( - command, - fail(done, 'A command was acknowledged when it was expected to fail.'), - error => { - try { - assert.ok(error instanceof CommandHandlingError); - assert.ok(error.message.startsWith(`request to ${fakeBaseUrl}/command failed`)); - const connectionError = error.getCause(); - assert.ok(connectionError instanceof ConnectionError); - done(); - } catch (e) { - fail(done, e.message) - } - }, - fail(done, 'A command was rejected when an error was expected.')); + const checkError = error => { + try { + assert.ok(error instanceof CommandHandlingError); + assert.ok(error.message.startsWith(`request to ${fakeBaseUrl}/command failed`)); + const connectionError = error.getCause(); + assert.ok(connectionError instanceof ConnectionError); + done(); + } catch (e) { + fail(done, e.message) + } + }; + malformedBackendClient.command(command) + .onOk(fail(done, 'A command was acknowledged when it was expected to fail.')) + .onError(checkError) + .onRejection(fail(done, 'A command was rejected when an error was expected.')) + .post(); + }); + + it('fails with `CommandValidationError` for invalid command message', done => { + const command = TestEnvironment.createTaskCommand({withId: null}); + + const checkError = error => { + try { + assert.ok(error instanceof CommandValidationError); + assert.ok(error.validationError()); + assert.ok(error.assuresCommandNeglected()); + + const cause = error.getCause(); + assert.ok(cause); + assert.equal(cause.getCode(), 2); + assert.equal(cause.getType(), 'spine.core.CommandValidationError'); + done(); + } catch (e) { + fail(done, e.message) + } + }; + + client.command(command) + .onOk(fail(done, 'A command was acknowledged when it was expected to fail.')) + .onError(checkError) + .onRejection(fail(done, 'A command was rejected when an error was expected.')) + .post(); + }); + + it('allows to observe the produced events of a given type', done => { + const taskName = 'Implement Spine Web JS client tests'; + const command = TestEnvironment.createTaskCommand({ + withPrefix: 'spine-web-test-send-command', + named: taskName, + describedAs: 'Spine Web need integration tests' }); - it('fails with `CommandValidationError` for invalid command message', done => { - const command = TestEnvironment.createTaskCommand({withId: null}); - - client.sendCommand( - command, - fail(done, 'A command was acknowledged when it was expected to fail.'), - error => { - try { - assert.ok(error instanceof CommandValidationError); - assert.ok(error.validationError()); - // TODO:2019-06-05:yegor.udovchenko: Find the reason of failing assertion - // assert.ok(error.assuresCommandNeglected()); - - const cause = error.getCause(); - assert.ok(cause); - assert.equal(cause.getCode(), 2); - assert.equal(cause.getType(), 'spine.core.CommandValidationError'); - done(); - } catch (e) { - fail(done, e.message) - } - }, - fail(done, 'A command was rejected when an error was expected.')); + const taskId = command.getId(); + + client.command(command) + .onError(fail(done)) + .onRejection(fail(done)) + .observe(TaskCreated, ({subscribe, unsubscribe}) => { + subscribe(event => { + const packedMessage = event.getMessage(); + const taskCreatedType = Type.forClass(TaskCreated); + const message = AnyPacker.unpack(packedMessage).as(taskCreatedType); + const theTaskId = message.getId().getValue(); + assert.equal( + taskId, theTaskId, + `Expected the task ID to be '${taskId}', got '${theTaskId}' instead.` + ); + const theTaskName = message.getName(); + assert.equal( + taskName, theTaskName, + `Expected the task name to be '${taskName}', got '${theTaskName}' instead.` + ); + const origin = event.getContext().getPastMessage().getMessage(); + const originType = origin.getTypeUrl(); + const createTaskType = Type.forClass(CreateTask); + const expectedOriginType = createTaskType.url().value(); + assert.equal( + expectedOriginType, originType, + `Expected origin to be of type '${expectedOriginType}', got + '${originType}' instead.` + ); + unsubscribe(); + done(); + }); + }) + .post() + }); + + it('fails when trying to observe a malformed event type', done => { + const Unknown = class { + static typeUrl() { + return 'spine.web/fails.malformed.type' + } + }; + + const command = TestEnvironment.createTaskCommand({ + withPrefix: 'spine-web-test-send-command', + named: 'Implement Spine Web JS client tests', + describedAs: 'Spine Web need integration tests' }); + + client.command(command) + .onError(fail(done)) + .onRejection(fail(done)) + .observe(Unknown) + .post() + .then(() => { + done(new Error('An attempt to observe a malformed event type did not lead to an ' + + 'error.')); + }) + .catch(() => { + assert.ok(true); + done(); + } + ); + }); }); diff --git a/integration-tests/js-tests/test/firebase-client/subscribe-test.js b/integration-tests/js-tests/test/firebase-client/subscribe-test.js index 0229cc9f4..da8f81527 100644 --- a/integration-tests/js-tests/test/firebase-client/subscribe-test.js +++ b/integration-tests/js-tests/test/firebase-client/subscribe-test.js @@ -21,204 +21,356 @@ import assert from 'assert'; import {fail} from '../test-helpers'; import TestEnvironment from './given/test-environment'; +import {TaskRenamed} from '@testProto/spine/web/test/given/events_pb'; import {Task} from '@testProto/spine/web/test/given/task_pb'; import {client} from './given/firebase-client'; +import {Filters} from '@lib/client/actor-request-factory'; +import {AnyPacker} from '@lib/client/any-packer'; +import {Type} from '@lib/client/typed-message'; describe('FirebaseClient subscription', function () { - // Big timeout allows to receive model state changes during tests. - this.timeout(120 * 1000); + // Big timeout allows to receive model state changes during tests. + this.timeout(120 * 1000); - it('retrieves new entities', done => { - const names = ['Task #1', 'Task #2', 'Task #3']; - const newTasksCount = names.length; - let receivedCount = 0; + it('retrieves new entities', done => { + const names = ['Task #1', 'Task #2', 'Task #3']; + const newTasksCount = names.length; + let receivedCount = 0; - const commands = names.map(name => TestEnvironment.createTaskCommand({ - withPrefix: 'spine-web-test-subscribe', - named: name - })); - const taskIds = commands.map(command => command.getId().getValue()); - - client.subscribe({entity: Task}) - .then(({itemAdded, itemChanged, itemRemoved, unsubscribe}) => { - itemAdded.subscribe({ - next: task => { - const id = task.getId().getValue(); - console.log(`Retrieved task '${id}'`); - if (taskIds.includes(id)) { - receivedCount++; - if (receivedCount === newTasksCount) { - unsubscribe(); - done(); - } - } - } - }); - itemRemoved.subscribe({ - next: fail(done, 'Unexpected entity remove during entity create subscription test.') - }); - itemChanged.subscribe({ - next: fail(done, 'Unexpected entity change during entity create subscription test.') - }); - commands.forEach(command => { - client.sendCommand(command, TestEnvironment.noop, fail(done), fail(done)); - }); - }) - .catch(fail(done)); - }); + const commands = names.map(name => TestEnvironment.createTaskCommand({ + withPrefix: 'spine-web-test-subscribe', + named: name + })); + const taskIds = commands.map(command => command.getId().getValue()); - it('retrieves updates when subscribed by type', done => { - const INITIAL_TASK_NAME = "Task to test entity updates"; - const UPDATED_TASK_NAME = "RENAMED Task to test entity updates"; - let taskId; - client.subscribe({entity: Task}) - .then(({itemAdded, itemChanged, itemRemoved, unsubscribe}) => { - itemAdded.subscribe({ - next: item => { - const id = item.getId().getValue(); - console.log(`Retrieved new task '${id}'.`); - if (taskId === id) { - assert.equal( - INITIAL_TASK_NAME, item.getName(), - `Task is named "${item.getName()}", expected "${INITIAL_TASK_NAME}"` - ); - const renameCommand = TestEnvironment.renameTaskCommand({ - withId: taskId, - to: UPDATED_TASK_NAME - }); - client.sendCommand( - renameCommand, - () => console.log(`Task '${taskId}' renamed.`), - fail(done, 'Unexpected error while renaming a task.'), - fail(done, 'Unexpected rejection while renaming a task.') - ); - } - } - }); - itemRemoved.subscribe({ - next: fail(done, 'Task was removed in a test of entity changes subscription.') - }); - itemChanged.subscribe({ - next: item => { - const id = item.getId().getValue(); - if (taskId === id) { - assert.equal( - item.getName(), UPDATED_TASK_NAME, - `Task is named "${item.getName()}", expected "${UPDATED_TASK_NAME}"` - ); - console.log(`Got task changes for ${id}.`); - unsubscribe(); - done(); - } - } - }); - // Create task. - const createCommand = TestEnvironment.createTaskCommand({ - withPrefix: 'spine-web-test-subscribe', - named: INITIAL_TASK_NAME - }); - taskId = createCommand.getId().getValue(); + client.subscribeTo(Task) + .post() + .then(({itemAdded, itemChanged, itemRemoved, unsubscribe}) => { + itemAdded.subscribe({ + next: task => { + const id = task.getId().getValue(); + console.log(`Retrieved task '${id}'.`); + if (taskIds.includes(id)) { + receivedCount++; + if (receivedCount === newTasksCount) { + unsubscribe(); + done(); + } + } + } + }); + itemRemoved.subscribe({ + next: fail(done, 'Unexpected entity remove during entity create subscription test.') + }); + itemChanged.subscribe({ + next: fail(done, 'Unexpected entity change during entity create subscription test.') + }); + commands.forEach(command => { + client.command(command) + .onError(fail(done)) + .onRejection(fail(done)) + .post(); + }); + }) + .catch(fail(done)); + }); - client.sendCommand( - createCommand, - () => console.log(`Task '${createCommand.getId().getValue()}' created.`), - fail(done, 'Unexpected error while creating a task.'), - fail(done, 'Unexpected rejection while creating a task.') + it('retrieves updates when subscribed by type', done => { + const INITIAL_TASK_NAME = "Task to test entity updates"; + const UPDATED_TASK_NAME = "RENAMED Task to test entity updates"; + let taskId; + client.subscribeTo(Task) + .post() + .then(({itemAdded, itemChanged, itemRemoved, unsubscribe}) => { + itemAdded.subscribe({ + next: item => { + const id = item.getId().getValue(); + console.log(`Retrieved new task '${id}'.`); + if (taskId === id) { + assert.equal( + INITIAL_TASK_NAME, item.getName(), + `Task is named "${item.getName()}", expected "${INITIAL_TASK_NAME}".` ); - }) - .catch(fail(done)); + const renameCommand = TestEnvironment.renameTaskCommand({ + withId: taskId, + to: UPDATED_TASK_NAME + }); + client.command(renameCommand) + .onOk(() => console.log(`Task '${taskId}' renamed.`)) + .onError(fail(done, 'Unexpected error while renaming a task.')) + .onRejection(fail(done, 'Unexpected rejection while renaming a task.')) + .post(); + } + } + }); + itemRemoved.subscribe({ + next: fail(done, 'Task was removed in a test of entity changes subscription.') + }); + itemChanged.subscribe({ + next: item => { + const id = item.getId().getValue(); + if (taskId === id) { + assert.equal( + item.getName(), UPDATED_TASK_NAME, + `Task is named "${item.getName()}", expected "${UPDATED_TASK_NAME}".` + ); + console.log(`Got task changes for '${id}'.`); + unsubscribe(); + done(); + } + } + }); + // Create task. + const createCommand = TestEnvironment.createTaskCommand({ + withPrefix: 'spine-web-test-subscribe', + named: INITIAL_TASK_NAME + }); + taskId = createCommand.getId().getValue(); + + client.command(createCommand) + .onOk(() => console.log(`Task '${createCommand.getId().getValue()}' created.`)) + .onError(fail(done, 'Unexpected error while creating a task.')) + .onRejection(fail(done, 'Unexpected rejection while creating a task.')) + .post(); + }) + .catch(fail(done)); + }); + + it('retrieves updates by ID', done => { + const expectedChangesCount = 2; + const INITIAL_TASK_NAME = 'Initial task name'; + const UPDATED_NAMES = ['Renamed once', 'Renamed twice']; + + const createCommand = TestEnvironment.createTaskCommand({ + withPrefix: 'spine-web-test-subscribe', + named: INITIAL_TASK_NAME }); + const taskId = createCommand.getId(); + const taskIdValue = taskId.getValue(); - it('retrieves updates by id', done => { - const expectedChangesCount = 2; - const INITIAL_TASK_NAME = 'Initial task name'; - const UPDATED_NAMES = ['Renamed once', 'Renamed twice']; + let changesCount = 0; + client.subscribeTo(Task) + .byId(taskId) + .post() + .then(({itemAdded, itemChanged, itemRemoved, unsubscribe}) => { + itemAdded.subscribe({ + next: item => { + const id = item.getId().getValue(); + console.log(`Retrieved new task '${id}'.`); + if (taskIdValue === id) { + assert.equal( + item.getName(), INITIAL_TASK_NAME, + `Task is named "${item.getName()}", expected "${INITIAL_TASK_NAME}".` + ); + } + const renameCommand = TestEnvironment.renameTaskCommand({ + withId: taskIdValue, + to: UPDATED_NAMES[0] + }); + client.command(renameCommand) + .onOk(() => console.log(`Task '${taskIdValue}' renamed for the first time.`)) + .onError(fail(done, 'Unexpected error while renaming a task.')) + .onRejection(fail(done, 'Unexpected rejection while renaming a task.')) + .post(); + } + }); + itemRemoved.subscribe({ + next: fail(done, 'Task was removed in a test of entity changes subscription.') + }); + itemChanged.subscribe({ + next: item => { + const id = item.getId().getValue(); + if (taskIdValue === id) { + console.log(`Got task changes for ${id}.`); + assert.equal(item.getName(), UPDATED_NAMES[changesCount]); + changesCount++; + if (changesCount === expectedChangesCount) { + unsubscribe(); + done(); + } else { + const renameCommand = TestEnvironment.renameTaskCommand({ + withId: taskIdValue, + to: UPDATED_NAMES[1] + }); + client.command(renameCommand) + .onOk(() => console.log(`Task '${taskIdValue}' renamed for the second time.`)) + .onError(fail(done, + 'Unexpected error while renaming a task.')) + .onRejection(fail(done, + 'Unexpected rejection while renaming a task.')) + .post(); + } + } + } + }); + client.command(createCommand) + .onOk(() => console.log(`Task '${taskIdValue}' created.`)) + .onError(fail(done, 'Unexpected error while creating a task.')) + .onRejection(fail(done, 'Unexpected rejection while creating a task.')) + .post(); + }) + .catch(fail(done)); + }); - const createCommand = TestEnvironment.createTaskCommand({ - withPrefix: 'spine-web-test-subscribe', - named: INITIAL_TASK_NAME - }); - const taskId = createCommand.getId(); - const taskIdValue = createCommand.getId().getValue(); - - let changesCount = 0; - client.subscribe({entity: Task, byIds: taskId}) - .then(({itemAdded, itemChanged, itemRemoved, unsubscribe}) => { - itemAdded.subscribe({ - next: item => { - const id = item.getId().getValue(); - console.log(`Retrieved new task '${id}'.`); - if (taskIdValue === id) { - assert.equal( - item.getName(), INITIAL_TASK_NAME, - `Task is named "${item.getName()}", expected "${INITIAL_TASK_NAME}"` - ); - } - const renameCommand = TestEnvironment.renameTaskCommand({ - withId: taskIdValue, - to: UPDATED_NAMES[0] - }); - client.sendCommand( - renameCommand, - () => console.log(`Task '${taskIdValue}' renamed for the first time.`), - fail(done, 'Unexpected error while renaming a task.'), - fail(done, 'Unexpected rejection while renaming a task.') - ); - } - }); - itemRemoved.subscribe({ - next: fail(done, 'Task was removed in a test of entity changes subscription.') - }); - itemChanged.subscribe({ - next: item => { - const id = item.getId().getValue(); - if (taskIdValue === id) { - console.log(`Got task changes for ${id}.`); - assert.equal(item.getName(), UPDATED_NAMES[changesCount]); - changesCount++; - if (changesCount === expectedChangesCount) { - unsubscribe(); - done(); - } else { - const renameCommand = TestEnvironment.renameTaskCommand({ - withId: taskIdValue, - to: UPDATED_NAMES[1] - }); - client.sendCommand( - renameCommand, - () => console.log(`Task '${taskIdValue}' renamed for the second time.`), - fail(done, 'Unexpected error while renaming a task.'), - fail(done, 'Unexpected rejection while renaming a task.') - ); - } - } - } - }); - client.sendCommand( - createCommand, - () => console.log(`Task '${taskIdValue}' created.`), - fail(done, 'Unexpected error while creating a task.'), - fail(done, 'Unexpected rejection while creating a task.') + it('is notified when the entity no longer matches the subscription criteria', done => { + const initialTaskName = 'Initial task name'; + const nameAfterRenamed = 'Renamed task'; + + const createCommand = TestEnvironment.createTaskCommand({ + withPrefix: 'spine-web-test-subscribe', + named: initialTaskName + }); + const taskIdValue = createCommand.getId().getValue(); + + client.subscribeTo(Task) + .where(Filters.eq("name", initialTaskName)) + .post() + .then(({itemAdded, itemChanged, itemRemoved, unsubscribe}) => { + itemAdded.subscribe({ + next: item => { + const id = item.getId().getValue(); + console.log(`Retrieved new task '${id}'.`); + if (taskIdValue === id) { + assert.equal( + initialTaskName, item.getName(), + `Task is named "${item.getName()}", expected "${initialTaskName}".` ); - }) - .catch(fail(done)); + } + const renameCommand = TestEnvironment.renameTaskCommand({ + withId: taskIdValue, + to: nameAfterRenamed + }); + client.command(renameCommand) + .onOk(() => + console.log(`Task '${taskIdValue}' is renamed to '${nameAfterRenamed}'.`)) + .onError(fail(done, 'Unexpected error while renaming a task.')) + .onRejection(fail(done, 'Unexpected rejection while renaming a task.')) + .post(); + } + }); + itemRemoved.subscribe({ + next: item => { + const id = item.getId().getValue(); + console.log('Task removed'); + assert.equal( + taskIdValue, id, + `A wrong Task item is removed, expected the task with ID "${taskIdValue}", + received the task with ID "${id}".` + ); + unsubscribe(); + done(); + } + }); + itemChanged.subscribe({ + next: fail(done, 'The `itemChanged` call is unexpected within this test.') + }); + client.command(createCommand) + .onOk(() => console.log(`Task '${taskIdValue}' created.`)) + .onError(fail(done, 'Unexpected error while creating a task.')) + .onRejection(fail(done, 'Unexpected rejection while creating a task.')) + .post(); + }) + .catch(fail(done)); + }); + + it('retrieves event updates', done => { + const initialTaskName = "The initial task name"; + const updatedTaskName = "Renamed task"; + + let taskId; + const createCommand = TestEnvironment.createTaskCommand({ + withPrefix: 'spine-web-test-subscribe', + named: initialTaskName }); + taskId = createCommand.getId().getValue(); - it('fails for a malformed entity type', done => { - const Unknown = class { - static typeUrl() { - return 'spine.web/fails.malformed.type' + client.subscribeToEvent(TaskRenamed) + .where([Filters.eq("id.value", taskId), Filters.eq("name", updatedTaskName)]) + .post() + .then(({eventEmitted, unsubscribe}) => { + eventEmitted.subscribe({ + next: event => { + const packedMessage = event.getMessage(); + const taskRenamedType = Type.forClass(TaskRenamed); + const message = AnyPacker.unpack(packedMessage).as(taskRenamedType); + const theTaskId = message.getId().getValue(); + assert.equal( + taskId, theTaskId, + `Expected the task ID to be ${taskId}, got ${theTaskId} instead.` + ); + const newTaskName = message.getName(); + assert.equal( + updatedTaskName, newTaskName, + `Expected the new task name to be ${updatedTaskName}, got ${newTaskName} instead.` + ); + unsubscribe(); + done(); } - }; - - client.subscribe({entity: Unknown}) - .then(() => { - done(new Error('A malformed subscription should not yield results.')); - }) - .catch(error => { - assert.ok(true); - done(); - }); + }); + }); + client.command(createCommand) + .onOk(() => console.log(`Task '${createCommand.getId().getValue()}' created.`)) + .onError(fail(done, 'Unexpected error while creating a task.')) + .onRejection(fail(done, 'Unexpected rejection while creating a task.')) + .post(); + + const renameCommand = TestEnvironment.renameTaskCommand({ + withId: taskId, + to: updatedTaskName + }); + client.command(renameCommand) + .onOk(() => console.log(`Task '${taskId}' renamed.`)) + .onError(fail(done, 'Unexpected error while renaming a task.')) + .onRejection(fail(done, 'Unexpected rejection while renaming a task.')) + .post(); + }); + + it('subscribes to entities using a manually created `Topic`', done => { + const TASK_NAME = 'Task name'; + + const command = TestEnvironment.createTaskCommand({ + withPrefix: 'spine-web-test-subscribe', + named: TASK_NAME }); + const taskId = command.getId(); + const topic = client.newTopic() + .select(Task) + .byIds([taskId]) + .build(); + client.subscribe(topic) + .then(({itemAdded, itemChanged, itemRemoved, unsubscribe}) => { + itemAdded.subscribe({ + next: item => { + if (taskId === item.getId()) { + assert.equal( + item.getName(), TASK_NAME, + `Task is named '${item.getName()}', expected '${TASK_NAME}'.` + ); + } + unsubscribe(); + done(); + } + }); + client.command(command).post(); + }); + }); + + it('fails for a malformed type', done => { + const Unknown = class { + static typeUrl() { + return 'spine.web/fails.malformed.type' + } + }; + + client.subscribeTo(Unknown) + .post() + .then(() => { + done(new Error('A malformed subscription should not yield results.')); + }) + .catch(error => { + assert.ok(true); + done(); + }); + }); }); diff --git a/integration-tests/js-tests/test/firebase-client/topic-test.js b/integration-tests/js-tests/test/firebase-client/topic-test.js index 2010829c7..2fac2fa4b 100644 --- a/integration-tests/js-tests/test/firebase-client/topic-test.js +++ b/integration-tests/js-tests/test/firebase-client/topic-test.js @@ -50,232 +50,210 @@ import {UserTasks} from '@testProto/spine/web/test/given/user_tasks_pb'; */ class UserTasksFlow { - constructor(userTasksList$) { - this.userTasksList$ = userTasksList$; - this._states = []; - this._transitions = []; - } + constructor(userTasksList$) { + this.userTasksList$ = userTasksList$; + this._states = []; + this._transitions = []; + } + + static for(userTasksList$) { + return new UserTasksFlow(userTasksList$); + } - static for(userTasksList$) { - return new UserTasksFlow(userTasksList$); + /** + * Adds a transition that does nothing if the next state is expected to be received + * without any actions. This allows to handle such cases: + * ``` + * ... + * .waitFor([]) // The NoOp transition will be performed when this state matches + * .waitFor([ + * { id: user1.id, tasksCount: 1 } + * ]) + * ... + * ``` + * @private + */ + static _equalize(states, transitions) { + if (states.length === transitions.length) { + return; + } + if (states.length === transitions.length + 1) { + transitions.push(UserTasksFlow.doNothing); + return; } - /** - * Adds a transition that does nothing if the next state is expected to be received - * without any actions. This allows to handle such cases: - * ``` - * ... - * .waitFor([]) // The NoOp transition will be performed when this state matches - * .waitFor([ - * { id: user1.id, tasksCount: 1 } - * ]) - * ... - * ``` - * @private - */ - static _equalize(states, transitions) { - if (states.length === transitions.length) { - return; - } - if (states.length === transitions.length + 1) { - transitions.push(UserTasksFlow.doNothing); - return; - } + throw new Error('The consistency of the flow is broken. Ensure each action is' + + 'followed by the next expected state.'); + } - throw new Error('The consistency of the flow is broken. Ensure each action is' + - 'followed by the next expected state.'); - } + static doNothing() { + // NO-OP. + } - static doNothing() { - // NoOp - } + waitFor(state) { + UserTasksFlow._equalize(this._states, this._transitions); + this._states.push(state); + return this; + } - waitFor(state) { - UserTasksFlow._equalize(this._states, this._transitions); - this._states.push(state); - return this; - } + then(perform) { + this._transitions.push(perform); + return this; + } - then(perform) { - this._transitions.push(perform); - return this; - } + /** + * Starts to observe the user tasks list and perform actions when the + * list state matches the next expected state. + * + * @return {Promise} a promise to be resolved when all expected states were + * received and all followed + */ + start() { + return new Promise((resolve, reject) => { + if (!this._states.length) { + reject('The list of states must not be empty.'); + } + UserTasksFlow._equalize(this._states, this._transitions); + const subscriptionEnd$ = new Subject(); + this.userTasksList$ + .pipe(takeUntil(subscriptionEnd$)) + .subscribe(userTasksList => { + const nextState = this._states[0]; - /** - * Starts to observe the user tasks list and perform actions when the - * list state matches the next expected state. - * - * @return {Promise} a promise to be resolved when all expected states were - * received and all followed - */ - start() { - return new Promise((resolve, reject) => { - if (!this._states.length) { - reject('The list of states must not be empty.'); + const nextStateMatches = ensureUserTasksCount(userTasksList, nextState); + try { + this._transitIf(nextStateMatches); + } catch (e) { + reject(e); } - UserTasksFlow._equalize(this._states, this._transitions); - const subscriptionEnd$ = new Subject(); - this.userTasksList$ - .pipe(takeUntil(subscriptionEnd$)) - .subscribe(userTasksList => { - const nextState = this._states[0]; - const nextStateMatches = ensureUserTasksCount(userTasksList, nextState); - try { - this._transitIf(nextStateMatches); - } catch (e) { - reject(e); - } - - if (this._hasNoMoreStates()) { - subscriptionEnd$.next(); - subscriptionEnd$.complete(); - resolve(); - } - }); - }); - } + if (this._hasNoMoreStates()) { + subscriptionEnd$.next(); + subscriptionEnd$.complete(); + resolve(); + } + }); + }); + } - _transitIf(stateMatches) { - if (stateMatches) { - this._states.shift(); - const performTransition = this._transitions.shift(); - performTransition(); - } + _transitIf(stateMatches) { + if (stateMatches) { + this._states.shift(); + const performTransition = this._transitions.shift(); + performTransition(); } + } - _hasNoMoreStates() { - return !this._states.length; - } + _hasNoMoreStates() { + return !this._states.length; + } } describe('FirebaseClient subscribes to topic', function () { - // Big timeout allows to receive model state changes during tests. - this.timeout(120 * 1000); - const compareUserTasks = (userTasks1, userTasks2) => - userTasks1.getId().getValue() === userTasks2.getId().getValue(); + // Big timeout allows to receive model state changes during tests. + this.timeout(120 * 1000); + const compareUserTasks = (userTasks1, userTasks2) => + userTasks1.getId().getValue() === userTasks2.getId().getValue(); - let user1; - let user2; - let teardownSubscription = () => { - }; + let user1; + let user2; + let teardownSubscription = () => { + }; - /** - * Prepares environment where two users have two tasks assigned each. - */ - beforeEach((done) => { - user1 = TestEnvironment.newUser('topic-tester-1'); - user2 = TestEnvironment.newUser('topic-tester-2'); - teardownSubscription = () => {}; + /** + * Prepares environment where two users have two tasks assigned each. + */ + beforeEach((done) => { + user1 = TestEnvironment.newUser('topic-tester-1'); + user2 = TestEnvironment.newUser('topic-tester-2'); + teardownSubscription = () => {}; - const createTasksPromises = []; - [user1, user2].forEach(user => { - const promise = TestEnvironment.createTaskFor(user, 2, client); - createTasksPromises.push(promise); - }); - Promise.all(createTasksPromises) - .then(() => done()); + const createTasksPromises = []; + [user1, user2].forEach(user => { + const promise = TestEnvironment.createTaskFor(user, 2, client); + createTasksPromises.push(promise); }); + Promise.all(createTasksPromises) + .then(() => done()); + }); - afterEach(() => { - teardownSubscription(); - }); + afterEach(() => { + teardownSubscription(); + }); - function buildTopicFor({ids, filters}) { - const topicBuilder = client.newTopic() - .select(UserTasks) - .byIds(ids); + it('built by IDs and retrieves correct data', (done) => { + client.subscribeTo(UserTasks) + .byId([user1.id, user2.id]) + .post() + .then(subscription => { + teardownSubscription = subscription.unsubscribe; + const userTasksList$ = toListObservable(subscription, compareUserTasks); + const userTasksFlow = UserTasksFlow.for(userTasksList$); - if (!!filters) { - topicBuilder.where(filters) - } + userTasksFlow + .waitFor([ + {id: user1.id, tasksCount: 2}, + {id: user2.id, tasksCount: 2} + ]) + .start() + .then(done) + .catch(e => fail(done, e)()) + }) + }); - return topicBuilder.build(); - } + it('built by IDs and filters and retrieves correct data', (done) => { + client.subscribeTo(UserTasks) + .byId([user1.id, user2.id]) + .where(Filters.eq('task_count', 2)) + .post() + .then(subscription => { + teardownSubscription = subscription.unsubscribe; + const userTasksList$ = toListObservable(subscription, compareUserTasks); + const userTasksFlow = UserTasksFlow.for(userTasksList$); - it('built by IDs and retrieves correct data', (done) => { - const topic = buildTopicFor({ids: [user1.id, user2.id]}); + userTasksFlow + .waitFor([ + {id: user1.id, tasksCount: 2}, + {id: user2.id, tasksCount: 2} + ]) + .start() + .then(done) + .catch(e => fail(done, e)()) + }) + }); - client.subscribeTo(topic) - .then(subscription => { - teardownSubscription = subscription.unsubscribe; - const userTasksList$ = toListObservable(subscription, compareUserTasks); - const userTasksFlow = UserTasksFlow.for(userTasksList$); + it('built by IDs and filters and updates data correctly when state changes', (done) => { + client.subscribeTo(UserTasks) + .byId([user1.id, user2.id]) + .where(Filters.ge('task_count', 2)) + .post() + .then(subscription => { + teardownSubscription = subscription.unsubscribe; + const userTasksList$ = toListObservable(subscription, compareUserTasks); + const userTasksFlow = UserTasksFlow.for(userTasksList$); - userTasksFlow - .waitFor([ - { id: user1.id, tasksCount: 2 }, - { id: user2.id, tasksCount: 2 } - ]) - .start() - .then(done) - .catch(e => fail(done, e)()) - }) - }); - - it('built by IDs and filters and retrieves correct data', (done) => { - const topic = buildTopicFor({ - ids: [user1.id, user2.id], - filters: [ - Filters.eq('task_count', 2) - ] + userTasksFlow + .waitFor([ + {id: user1.id, tasksCount: 2}, + {id: user2.id, tasksCount: 2} + ]) + .then(() => { + const taskToReassign = user1.tasks[0]; + TestEnvironment.reassignTask(taskToReassign, user2.id, client); + }) + .waitFor([ + {id: user2.id, tasksCount: 3} + ]) + .then(() => { + const taskToReassign = user1.tasks[1]; + TestEnvironment.reassignTask(taskToReassign, user2.id, client); + }) + .waitFor([ + {id: user2.id, tasksCount: 4} + ]) + .start() + .then(done) + .catch(e => fail(done, e)()) }); - - client.subscribeTo(topic) - .then(subscription => { - teardownSubscription = subscription.unsubscribe; - const userTasksList$ = toListObservable(subscription, compareUserTasks); - const userTasksFlow = UserTasksFlow.for(userTasksList$); - - userTasksFlow - .waitFor([ - { id: user1.id, tasksCount: 2 }, - { id: user2.id, tasksCount: 2 } - ]) - .start() - .then(done) - .catch(e => fail(done, e)()) - }) - }); - - it('built by IDs and filters and updates data correctly when state changes', (done) => { - // TODO:2019-08-01:dmytro.dashenkov: Re-enable the test when columns filtering is implemented. - done(); - const topic = buildTopicFor({ - ids: [user1.id, user2.id], - filters: [ - Filters.ge('task_count', 2) - ] - }); - - client.subscribeTo(topic) - .then(subscription => { - teardownSubscription = subscription.unsubscribe; - const userTasksList$ = toListObservable(subscription, compareUserTasks); - const userTasksFlow = UserTasksFlow.for(userTasksList$); - - userTasksFlow - .waitFor([ - { id: user1.id, tasksCount: 2 }, - { id: user2.id, tasksCount: 2 } - ]) - .then(() => { - const taskToReassign = user1.tasks[0]; - TestEnvironment.reassignTask(taskToReassign, user2.id, client); - }) - .waitFor([ - { id: user2.id, tasksCount: 3 } - ]) - .then(() => { - const taskToReassign = user1.tasks[1]; - TestEnvironment.reassignTask(taskToReassign, user2.id, client); - }) - .waitFor([ - { id: user2.id, tasksCount: 4 } - ]) - .start() - .then(done) - .catch(e => fail(done, e)()) - }); - }); + }); }); diff --git a/integration-tests/js-tests/test/given/users-test-environment.js b/integration-tests/js-tests/test/given/users-test-environment.js index a8a520dbe..1a39b74bd 100644 --- a/integration-tests/js-tests/test/given/users-test-environment.js +++ b/integration-tests/js-tests/test/given/users-test-environment.js @@ -30,77 +30,86 @@ import TestEnvironment from '../firebase-client/given/test-environment'; */ export class UserTasksTestEnvironment extends TestEnvironment { - /** - * Creates requested amount of tasks assigned to the given user. - * - * @param {User} user a user to create tasks for - * @param {number} taskCount an amount of tasks to be created and assigned - * @param {Client} client a Spine client to send commands - * @return {Promise} a promise to be resolved when all `CreateTask` commands acknowledged; - * rejected if an error occurs; - */ - static createTaskFor(user, taskCount, client) { - const createTaskPromises = []; - for (let i = 0; i < taskCount; i++) { - const command = super.createTaskCommand({ - named: `task#${i + 1}-for-${user.name}`, - assignedTo: user.id - }); - const taskId = command.getId(); + /** + * Creates requested amount of tasks assigned to the given user. + * + * @param {User} user a user to create tasks for + * @param {number} taskCount an amount of tasks to be created and assigned + * @param {Client} client a Spine client to send commands + * @return {Promise} a promise to be resolved when all `CreateTask` commands acknowledged; + * rejected if an error occurs; + */ + static createTaskFor(user, taskCount, client) { + const createTaskPromises = []; + for (let i = 0; i < taskCount; i++) { + const command = super.createTaskCommand({ + named: `task#${i + 1}-for-${user.name}`, + assignedTo: user.id + }); + const taskId = command.getId(); - let createTaskAcknowledged; - let createTaskFailed; + let createTaskAcknowledged; + let createTaskFailed; - const promise = new Promise((resolve, reject) => { - createTaskAcknowledged = resolve; - createTaskFailed = reject; - }); - createTaskPromises.push(promise); - client.sendCommand(command, () => { - user.tasks.push(taskId); - createTaskAcknowledged(); - }, createTaskFailed); - } - - return Promise.all(createTaskPromises); + const promise = new Promise((resolve, reject) => { + createTaskAcknowledged = resolve; + createTaskFailed = reject; + }); + createTaskPromises.push(promise); + client.command(command) + .onOk(() => { + user.tasks.push(taskId); + createTaskAcknowledged(); + }) + .onError(createTaskFailed) + .onRejection(createTaskFailed) + .post(); } - /** - * Sends a command to reassign the given task to the given user. - * - * @param {!TaskId} taskId - * @param {!UserId} newAssignee - * @param {!Client} client - * @return {Promise} - */ - static reassignTask(taskId, newAssignee, client) { - return new Promise((resolve, reject) => { - const command = new ReassignTask(); - command.setId(taskId); - command.setNewAssignee(newAssignee); - client.sendCommand(command, () => resolve(), () => reject()); - }) - } + return Promise.all(createTaskPromises); + } - /** - * @param {?String} withPrefix - * @return {UserId} - */ - static userId(withPrefix) { - const id = new UserId(); - id.setValue(`${withPrefix ? withPrefix : 'ANONYMOUS'}-${uuid.v4()}`); - return id; - } + /** + * Sends a command to reassign the given task to the given user. + * + * @param {!TaskId} taskId + * @param {!UserId} newAssignee + * @param {!Client} client + * @return {Promise} + */ + static reassignTask(taskId, newAssignee, client) { + return new Promise((resolve, reject) => { + const command = new ReassignTask(); + command.setId(taskId); + command.setNewAssignee(newAssignee); + + client.command(command) + .onOk(resolve) + .onError(reject) + .onRejection(reject) + .post(); + }) + } + + /** + * @param {?String} withPrefix + * @return {UserId} + */ + static userId(withPrefix) { + const id = new UserId(); + id.setValue(`${withPrefix ? withPrefix : 'ANONYMOUS'}-${uuid.v4()}`); + return id; + } - /** - * @param {?String} withName - * @return {User} - */ - static newUser(withName) { - return { - name: withName, - id: UserTasksTestEnvironment.userId(withName), - tasks: [] - } + /** + * @param {?String} withName + * @return {User} + */ + static newUser(withName) { + return { + name: withName, + id: UserTasksTestEnvironment.userId(withName), + tasks: [] } + } } diff --git a/integration-tests/js-tests/test/test-helpers.js b/integration-tests/js-tests/test/test-helpers.js index 6dd484db3..f051b9447 100644 --- a/integration-tests/js-tests/test/test-helpers.js +++ b/integration-tests/js-tests/test/test-helpers.js @@ -50,9 +50,9 @@ import {BehaviorSubject, Observable} from 'rxjs'; export function fail(done, message = '') { return cause => { if (message) { - done(new Error(`Test failed. Cause: ${message}`)); + done(new Error(`Test failed. Cause: ${message}`)); } else { - done(new Error(`Test failed. Cause: ${cause ? cause : 'not identified'}`)); + done(new Error(`Test failed. Cause: ${cause ? cause : 'not identified'}`)); } }; } @@ -64,8 +64,8 @@ export function fail(done, message = '') { * @param {UserId[]} expected */ export function ensureUserIds(actual, expected) { - return arraysEqualDeep(actual, expected, (userId1, userId2) => - userId1.getValue() === userId2.getValue()); + return arraysEqualDeep(actual, expected, (userId1, userId2) => + userId1.getValue() === userId2.getValue()); } /** @@ -78,9 +78,9 @@ export function ensureUserIds(actual, expected) { * }[]} expectedUsers */ export function ensureUserTasks(actualUserTasks, expectedUsers) { - const actualUserIds = actualUserTasks.map(userTasks => userTasks.getId()); - const expectedUserIds = expectedUsers.map(user => user.id); - return ensureUserIds(actualUserIds, expectedUserIds); + const actualUserIds = actualUserTasks.map(userTasks => userTasks.getId()); + const expectedUserIds = expectedUsers.map(user => user.id); + return ensureUserIds(actualUserIds, expectedUserIds); } /** @@ -94,12 +94,12 @@ export function ensureUserTasks(actualUserTasks, expectedUsers) { * }[]} expectedUsers */ export function ensureUserTasksCount(actualUserTasks, expectedUsers) { - return arraysEqualDeep(actualUserTasks, - expectedUsers, - (userTasks, expected) => - userTasks.getId().getValue() === expected.id.getValue() - && userTasks.getTasksList().length === expected.tasksCount - ); + return arraysEqualDeep(actualUserTasks, + expectedUsers, + (userTasks, expected) => + userTasks.getId().getValue() === expected.id.getValue() + && userTasks.getTasksList().length === expected.tasksCount + ); } /** @@ -112,19 +112,19 @@ export function ensureUserTasksCount(actualUserTasks, expectedUsers) { * @return {boolean} `true` if arrays are equal; `false` otherwise; */ function arraysEqualDeep(arr1, arr2, compare) { - if (!Array.isArray(arr1) || !Array.isArray(arr2)) { - throw new Error('Unable to compare equality of non-array objects.'); - } + if (!Array.isArray(arr1) || !Array.isArray(arr2)) { + throw new Error('Unable to compare equality of non-array objects.'); + } - if (arr1.length === 0 && arr2.length === 0) { - return true; - } + if (arr1.length === 0 && arr2.length === 0) { + return true; + } - const intersection = arr1.filter(value1 => { - return arr2.findIndex(value2 => compare(value1, value2)) > -1; - }); + const intersection = arr1.filter(value1 => { + return arr2.findIndex(value2 => compare(value1, value2)) > -1; + }); - return intersection.length === arr1.length + return intersection.length === arr1.length } /** @@ -139,41 +139,41 @@ function arraysEqualDeep(arr1, arr2, compare) { * @template a class of a subscription target entities */ export function toListObservable(subscription, compare) { - const list$ = new BehaviorSubject([]); - const {itemAdded, itemChanged, itemRemoved} = subscription; + const list$ = new BehaviorSubject([]); + const {itemAdded, itemChanged, itemRemoved} = subscription; - itemAdded.subscribe({ - next: addedItem => { - const currentList = list$.getValue(); - list$.next([...currentList, addedItem]); - } - }); + itemAdded.subscribe({ + next: addedItem => { + const currentList = list$.getValue(); + list$.next([...currentList, addedItem]); + } + }); - itemChanged.subscribe({ - next: changedItem => { - const currentList = list$.getValue(); - const changedItemIndex =_indexOf(changedItem, currentList, compare); - const updatedList = currentList.slice(); - updatedList[changedItemIndex] = changedItem; - list$.next(updatedList); - } - }); + itemChanged.subscribe({ + next: changedItem => { + const currentList = list$.getValue(); + const changedItemIndex = _indexOf(changedItem, currentList, compare); + const updatedList = currentList.slice(); + updatedList[changedItemIndex] = changedItem; + list$.next(updatedList); + } + }); - itemRemoved.subscribe({ - next: removedItem => { - const currentList = list$.getValue(); - const removedItemIndex = _indexOf(removedItem, currentList, compare); - const updatedList = [ - ...currentList.slice(0, removedItemIndex), - ...currentList.slice(removedItemIndex + 1) - ]; - list$.next(updatedList); - } - }); + itemRemoved.subscribe({ + next: removedItem => { + const currentList = list$.getValue(); + const removedItemIndex = _indexOf(removedItem, currentList, compare); + const updatedList = [ + ...currentList.slice(0, removedItemIndex), + ...currentList.slice(removedItemIndex + 1) + ]; + list$.next(updatedList); + } + }); - return list$.asObservable(); + return list$.asObservable(); } function _indexOf(item, items, compare) { - return items.findIndex(value => compare(value, item)); + return items.findIndex(value => compare(value, item)); } diff --git a/integration-tests/test-app/build.gradle b/integration-tests/test-app/build.gradle index 624e3cbef..6119ad0b1 100644 --- a/integration-tests/test-app/build.gradle +++ b/integration-tests/test-app/build.gradle @@ -20,7 +20,7 @@ plugins { id 'java' - id 'org.gretty' version '2.3.1' + id 'org.gretty' version '3.0.1' id "com.github.psxpaul.execfork" version '0.1.12' } diff --git a/license-report.md b/license-report.md index bcd1dc6dc..449fa9245 100644 --- a/license-report.md +++ b/license-report.md @@ -1,6 +1,6 @@ -# Dependencies of `io.spine:spine-client-js:1.2.1` +# Dependencies of `io.spine:spine-client-js:1.2.9` ## Runtime 1. **Group:** com.google.code.findbugs **Name:** jsr305 **Version:** 3.0.2 @@ -376,10 +376,10 @@ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Thu Dec 05 13:15:44 EET 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -#NPM dependencies of `spine-web@1.2.1` +#NPM dependencies of `spine-web@1.2.9` ## `Production` dependencies: @@ -389,7 +389,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **encoding@0.1.12** * Licenses: MIT * Repository: [https://github.com/andris9/encoding](https://github.com/andris9/encoding) -1. **google-protobuf@3.10.0** +1. **google-protobuf@3.9.1** * Licenses: BSD-3-Clause * Repository: [https://github.com/protocolbuffers/protobuf/tree/master/js](https://github.com/protocolbuffers/protobuf/tree/master/js) 1. **iconv-lite@0.4.24** @@ -404,19 +404,19 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **node-fetch@1.7.3** * Licenses: MIT * Repository: [https://github.com/bitinn/node-fetch](https://github.com/bitinn/node-fetch) -1. **rxjs@6.5.3** +1. **rxjs@6.5.2** * Licenses: Apache-2.0 * Repository: [https://github.com/reactivex/rxjs](https://github.com/reactivex/rxjs) 1. **safer-buffer@2.1.2** * Licenses: MIT * Repository: [https://github.com/ChALkeR/safer-buffer](https://github.com/ChALkeR/safer-buffer) -1. **spine-web@1.2.1** +1. **spine-web@1.2.9** * Licenses: Apache-2.0 * Repository: [https://github.com/SpineEventEngine/web](https://github.com/SpineEventEngine/web) 1. **tslib@1.10.0** * Licenses: Apache-2.0 * Repository: [https://github.com/Microsoft/tslib](https://github.com/Microsoft/tslib) -1. **uuid@3.3.3** +1. **uuid@3.3.2** * Licenses: MIT * Repository: [https://github.com/kelektiv/node-uuid](https://github.com/kelektiv/node-uuid) 1. **whatwg-fetch@3.0.0** @@ -427,55 +427,52 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice ## `Development` dependencies: -1. **@babel/cli@7.7.0** +1. **@babel/cli@7.5.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-cli](https://github.com/babel/babel/tree/master/packages/babel-cli) 1. **@babel/code-frame@7.5.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-code-frame](https://github.com/babel/babel/tree/master/packages/babel-code-frame) -1. **@babel/core@7.7.2** +1. **@babel/core@7.5.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-core](https://github.com/babel/babel/tree/master/packages/babel-core) -1. **@babel/generator@7.7.2** +1. **@babel/generator@7.5.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-generator](https://github.com/babel/babel/tree/master/packages/babel-generator) -1. **@babel/helper-annotate-as-pure@7.7.0** +1. **@babel/helper-annotate-as-pure@7.0.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helper-annotate-as-pure](https://github.com/babel/babel/tree/master/packages/babel-helper-annotate-as-pure) -1. **@babel/helper-builder-binary-assignment-operator-visitor@7.7.0** +1. **@babel/helper-builder-binary-assignment-operator-visitor@7.1.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helper-builder-binary-assignment-operator-visitor](https://github.com/babel/babel/tree/master/packages/babel-helper-builder-binary-assignment-operator-visitor) -1. **@babel/helper-call-delegate@7.7.0** +1. **@babel/helper-call-delegate@7.4.4** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helper-call-delegate](https://github.com/babel/babel/tree/master/packages/babel-helper-call-delegate) -1. **@babel/helper-create-regexp-features-plugin@7.7.2** - * Licenses: MIT - * Repository: [https://github.com/babel/babel](https://github.com/babel/babel) -1. **@babel/helper-define-map@7.7.0** +1. **@babel/helper-define-map@7.5.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helper-define-map](https://github.com/babel/babel/tree/master/packages/babel-helper-define-map) -1. **@babel/helper-explode-assignable-expression@7.7.0** +1. **@babel/helper-explode-assignable-expression@7.1.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helper-explode-assignable-expression](https://github.com/babel/babel/tree/master/packages/babel-helper-explode-assignable-expression) -1. **@babel/helper-function-name@7.7.0** +1. **@babel/helper-function-name@7.1.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helper-function-name](https://github.com/babel/babel/tree/master/packages/babel-helper-function-name) -1. **@babel/helper-get-function-arity@7.7.0** +1. **@babel/helper-get-function-arity@7.0.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helper-get-function-arity](https://github.com/babel/babel/tree/master/packages/babel-helper-get-function-arity) -1. **@babel/helper-hoist-variables@7.7.0** +1. **@babel/helper-hoist-variables@7.4.4** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helper-hoist-variables](https://github.com/babel/babel/tree/master/packages/babel-helper-hoist-variables) -1. **@babel/helper-member-expression-to-functions@7.7.0** +1. **@babel/helper-member-expression-to-functions@7.5.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helper-member-expression-to-functions](https://github.com/babel/babel/tree/master/packages/babel-helper-member-expression-to-functions) -1. **@babel/helper-module-imports@7.7.0** +1. **@babel/helper-module-imports@7.0.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helper-module-imports](https://github.com/babel/babel/tree/master/packages/babel-helper-module-imports) -1. **@babel/helper-module-transforms@7.7.0** +1. **@babel/helper-module-transforms@7.5.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helper-module-transforms](https://github.com/babel/babel/tree/master/packages/babel-helper-module-transforms) -1. **@babel/helper-optimise-call-expression@7.7.0** +1. **@babel/helper-optimise-call-expression@7.0.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helper-optimise-call-expression](https://github.com/babel/babel/tree/master/packages/babel-helper-optimise-call-expression) 1. **@babel/helper-plugin-utils@7.0.0** @@ -484,46 +481,46 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **@babel/helper-regex@7.5.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helper-regex](https://github.com/babel/babel/tree/master/packages/babel-helper-regex) -1. **@babel/helper-remap-async-to-generator@7.7.0** +1. **@babel/helper-remap-async-to-generator@7.1.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helper-remap-async-to-generator](https://github.com/babel/babel/tree/master/packages/babel-helper-remap-async-to-generator) -1. **@babel/helper-replace-supers@7.7.0** +1. **@babel/helper-replace-supers@7.5.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helper-replace-supers](https://github.com/babel/babel/tree/master/packages/babel-helper-replace-supers) -1. **@babel/helper-simple-access@7.7.0** +1. **@babel/helper-simple-access@7.1.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helper-simple-access](https://github.com/babel/babel/tree/master/packages/babel-helper-simple-access) -1. **@babel/helper-split-export-declaration@7.7.0** +1. **@babel/helper-split-export-declaration@7.4.4** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helper-split-export-declaration](https://github.com/babel/babel/tree/master/packages/babel-helper-split-export-declaration) -1. **@babel/helper-wrap-function@7.7.0** +1. **@babel/helper-wrap-function@7.2.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helper-wrap-function](https://github.com/babel/babel/tree/master/packages/babel-helper-wrap-function) -1. **@babel/helpers@7.7.0** +1. **@babel/helpers@7.5.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-helpers](https://github.com/babel/babel/tree/master/packages/babel-helpers) 1. **@babel/highlight@7.5.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-highlight](https://github.com/babel/babel/tree/master/packages/babel-highlight) -1. **@babel/parser@7.7.3** +1. **@babel/parser@7.5.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-parser](https://github.com/babel/babel/tree/master/packages/babel-parser) -1. **@babel/plugin-proposal-async-generator-functions@7.7.0** +1. **@babel/plugin-proposal-async-generator-functions@7.2.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-proposal-async-generator-functions](https://github.com/babel/babel/tree/master/packages/babel-plugin-proposal-async-generator-functions) -1. **@babel/plugin-proposal-dynamic-import@7.7.0** +1. **@babel/plugin-proposal-dynamic-import@7.5.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-proposal-dynamic-import](https://github.com/babel/babel/tree/master/packages/babel-plugin-proposal-dynamic-import) 1. **@babel/plugin-proposal-json-strings@7.2.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-proposal-json-strings](https://github.com/babel/babel/tree/master/packages/babel-plugin-proposal-json-strings) -1. **@babel/plugin-proposal-object-rest-spread@7.6.2** +1. **@babel/plugin-proposal-object-rest-spread@7.5.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-proposal-object-rest-spread](https://github.com/babel/babel/tree/master/packages/babel-plugin-proposal-object-rest-spread) 1. **@babel/plugin-proposal-optional-catch-binding@7.2.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-proposal-optional-catch-binding](https://github.com/babel/babel/tree/master/packages/babel-plugin-proposal-optional-catch-binding) -1. **@babel/plugin-proposal-unicode-property-regex@7.7.0** +1. **@babel/plugin-proposal-unicode-property-regex@7.4.4** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-proposal-unicode-property-regex](https://github.com/babel/babel/tree/master/packages/babel-plugin-proposal-unicode-property-regex) 1. **@babel/plugin-syntax-async-generators@7.2.0** @@ -541,31 +538,28 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **@babel/plugin-syntax-optional-catch-binding@7.2.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-syntax-optional-catch-binding](https://github.com/babel/babel/tree/master/packages/babel-plugin-syntax-optional-catch-binding) -1. **@babel/plugin-syntax-top-level-await@7.7.0** - * Licenses: MIT - * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-syntax-top-level-await](https://github.com/babel/babel/tree/master/packages/babel-plugin-syntax-top-level-await) 1. **@babel/plugin-transform-arrow-functions@7.2.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-arrow-functions](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-arrow-functions) -1. **@babel/plugin-transform-async-to-generator@7.7.0** +1. **@babel/plugin-transform-async-to-generator@7.5.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-async-to-generator](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-async-to-generator) 1. **@babel/plugin-transform-block-scoped-functions@7.2.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-block-scoped-functions](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-block-scoped-functions) -1. **@babel/plugin-transform-block-scoping@7.6.3** +1. **@babel/plugin-transform-block-scoping@7.5.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-block-scoping](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-block-scoping) -1. **@babel/plugin-transform-classes@7.7.0** +1. **@babel/plugin-transform-classes@7.5.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-classes](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-classes) 1. **@babel/plugin-transform-computed-properties@7.2.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-computed-properties](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-computed-properties) -1. **@babel/plugin-transform-destructuring@7.6.0** +1. **@babel/plugin-transform-destructuring@7.5.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-destructuring](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-destructuring) -1. **@babel/plugin-transform-dotall-regex@7.7.0** +1. **@babel/plugin-transform-dotall-regex@7.4.4** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-dotall-regex](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-dotall-regex) 1. **@babel/plugin-transform-duplicate-keys@7.5.0** @@ -577,7 +571,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **@babel/plugin-transform-for-of@7.4.4** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-for-of](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-for-of) -1. **@babel/plugin-transform-function-name@7.7.0** +1. **@babel/plugin-transform-function-name@7.4.4** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-function-name](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-function-name) 1. **@babel/plugin-transform-literals@7.2.0** @@ -589,18 +583,18 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **@babel/plugin-transform-modules-amd@7.5.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-modules-amd](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-modules-amd) -1. **@babel/plugin-transform-modules-commonjs@7.7.0** +1. **@babel/plugin-transform-modules-commonjs@7.5.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-modules-commonjs](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-modules-commonjs) -1. **@babel/plugin-transform-modules-systemjs@7.7.0** +1. **@babel/plugin-transform-modules-systemjs@7.5.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-modules-systemjs](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-modules-systemjs) -1. **@babel/plugin-transform-modules-umd@7.7.0** +1. **@babel/plugin-transform-modules-umd@7.2.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-modules-umd](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-modules-umd) -1. **@babel/plugin-transform-named-capturing-groups-regex@7.7.0** +1. **@babel/plugin-transform-named-capturing-groups-regex@7.4.5** * Licenses: MIT - * Repository: [https://github.com/babel/babel](https://github.com/babel/babel) + * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-named-capturing-groups-regex](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-named-capturing-groups-regex) 1. **@babel/plugin-transform-new-target@7.4.4** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-new-target](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-new-target) @@ -613,7 +607,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **@babel/plugin-transform-property-literals@7.2.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-property-literals](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-property-literals) -1. **@babel/plugin-transform-regenerator@7.7.0** +1. **@babel/plugin-transform-regenerator@7.4.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-regenerator](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-regenerator) 1. **@babel/plugin-transform-reserved-words@7.2.0** @@ -622,7 +616,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **@babel/plugin-transform-shorthand-properties@7.2.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-shorthand-properties](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-shorthand-properties) -1. **@babel/plugin-transform-spread@7.6.2** +1. **@babel/plugin-transform-spread@7.2.2** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-spread](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-spread) 1. **@babel/plugin-transform-sticky-regex@7.2.0** @@ -634,91 +628,91 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **@babel/plugin-transform-typeof-symbol@7.2.0** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-typeof-symbol](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-typeof-symbol) -1. **@babel/plugin-transform-unicode-regex@7.7.0** +1. **@babel/plugin-transform-unicode-regex@7.4.4** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-unicode-regex](https://github.com/babel/babel/tree/master/packages/babel-plugin-transform-unicode-regex) -1. **@babel/preset-env@7.7.1** +1. **@babel/preset-env@7.5.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-preset-env](https://github.com/babel/babel/tree/master/packages/babel-preset-env) -1. **@babel/register@7.7.0** +1. **@babel/register@7.5.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-register](https://github.com/babel/babel/tree/master/packages/babel-register) -1. **@babel/template@7.7.0** +1. **@babel/template@7.4.4** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-template](https://github.com/babel/babel/tree/master/packages/babel-template) -1. **@babel/traverse@7.7.2** +1. **@babel/traverse@7.5.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-traverse](https://github.com/babel/babel/tree/master/packages/babel-traverse) -1. **@babel/types@7.7.2** +1. **@babel/types@7.5.5** * Licenses: MIT * Repository: [https://github.com/babel/babel/tree/master/packages/babel-types](https://github.com/babel/babel/tree/master/packages/babel-types) 1. **@firebase/app-types@0.4.3** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) -1. **@firebase/app@0.4.17** +1. **@firebase/app@0.4.14** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) -1. **@firebase/auth-types@0.8.0** +1. **@firebase/auth-types@0.7.2** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) -1. **@firebase/auth@0.12.0** +1. **@firebase/auth@0.11.8** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) -1. **@firebase/database-types@0.4.3** +1. **@firebase/database-types@0.4.2** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) -1. **@firebase/database@0.5.4** +1. **@firebase/database@0.4.12** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) -1. **@firebase/firestore-types@1.5.0** +1. **@firebase/firestore-types@1.4.4** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) -1. **@firebase/firestore@1.5.3** +1. **@firebase/firestore@1.4.9** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) 1. **@firebase/functions-types@0.3.8** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) -1. **@firebase/functions@0.4.18** +1. **@firebase/functions@0.4.15** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) 1. **@firebase/installations-types@0.1.2** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) -1. **@firebase/installations@0.2.7** +1. **@firebase/installations@0.2.4** * Licenses: Apache-2.0 * Repository: unknown -1. **@firebase/logger@0.1.25** +1. **@firebase/logger@0.1.22** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) 1. **@firebase/messaging-types@0.3.2** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) -1. **@firebase/messaging@0.4.11** +1. **@firebase/messaging@0.4.8** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) 1. **@firebase/performance-types@0.0.3** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk/tree/master/packages/performance-types](https://github.com/firebase/firebase-js-sdk/tree/master/packages/performance-types) -1. **@firebase/performance@0.2.19** +1. **@firebase/performance@0.2.15** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk/tree/master/packages/performance](https://github.com/firebase/firebase-js-sdk/tree/master/packages/performance) -1. **@firebase/polyfill@0.3.22** +1. **@firebase/polyfill@0.3.19** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) 1. **@firebase/storage-types@0.3.3** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) -1. **@firebase/storage@0.3.12** +1. **@firebase/storage@0.3.9** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) -1. **@firebase/util@0.2.28** +1. **@firebase/util@0.2.25** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) -1. **@firebase/webchannel-wrapper@0.2.26** +1. **@firebase/webchannel-wrapper@0.2.24** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) -1. **@grpc/proto-loader@0.5.3** +1. **@grpc/proto-loader@0.5.1** * Licenses: Apache-2.0 * Repository: [https://github.com/grpc/grpc-node](https://github.com/grpc/grpc-node) 1. **@protobufjs/aspromise@1.1.2** @@ -751,25 +745,22 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **@protobufjs/utf8@1.1.0** * Licenses: BSD-3-Clause * Repository: [https://github.com/dcodeIO/protobuf.js](https://github.com/dcodeIO/protobuf.js) -1. **@sinonjs/commons@1.6.0** +1. **@sinonjs/commons@1.4.0** * Licenses: BSD-3-Clause * Repository: [https://github.com/sinonjs/commons](https://github.com/sinonjs/commons) -1. **@sinonjs/formatio@3.2.2** +1. **@sinonjs/formatio@3.2.1** * Licenses: BSD-3-Clause * Repository: [https://github.com/sinonjs/formatio](https://github.com/sinonjs/formatio) -1. **@sinonjs/samsam@3.3.3** +1. **@sinonjs/samsam@3.3.2** * Licenses: BSD-3-Clause * Repository: [https://github.com/sinonjs/samsam](https://github.com/sinonjs/samsam) 1. **@sinonjs/text-encoding@0.7.1** * Licenses: (Unlicense OR Apache-2.0) * Repository: [https://github.com/inexorabletash/text-encoding](https://github.com/inexorabletash/text-encoding) -1. **@types/bytebuffer@5.0.40** - * Licenses: MIT - * Repository: [https://github.com/DefinitelyTyped/DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped) 1. **@types/long@4.0.0** * Licenses: MIT * Repository: [https://github.com/DefinitelyTyped/DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped) -1. **@types/node@10.17.5** +1. **@types/node@10.14.15** * Licenses: MIT * Repository: [https://github.com/DefinitelyTyped/DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped) 1. **@webassemblyjs/ast@1.8.5** @@ -967,7 +958,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **binary-extensions@1.13.1** * Licenses: MIT * Repository: [https://github.com/sindresorhus/binary-extensions](https://github.com/sindresorhus/binary-extensions) -1. **bluebird@3.7.1** +1. **bluebird@3.5.5** * Licenses: MIT * Repository: [https://github.com/petkaantonov/bluebird](https://github.com/petkaantonov/bluebird) 1. **bn.js@4.11.8** @@ -1003,7 +994,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **browserify-zlib@0.2.0** * Licenses: MIT * Repository: [https://github.com/devongovett/browserify-zlib](https://github.com/devongovett/browserify-zlib) -1. **browserslist@4.7.2** +1. **browserslist@4.6.6** * Licenses: MIT * Repository: [https://github.com/browserslist/browserslist](https://github.com/browserslist/browserslist) 1. **buffer-from@1.1.1** @@ -1012,7 +1003,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **buffer-xor@1.0.3** * Licenses: MIT * Repository: [https://github.com/crypto-browserify/buffer-xor](https://github.com/crypto-browserify/buffer-xor) -1. **buffer@4.9.2** +1. **buffer@4.9.1** * Licenses: MIT * Repository: [https://github.com/feross/buffer](https://github.com/feross/buffer) 1. **builtin-status-codes@3.0.0** @@ -1021,7 +1012,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **bytebuffer@5.0.1** * Licenses: Apache-2.0 * Repository: [https://github.com/dcodeIO/bytebuffer.js](https://github.com/dcodeIO/bytebuffer.js) -1. **cacache@12.0.3** +1. **cacache@12.0.2** * Licenses: ISC * Repository: [https://github.com/npm/cacache](https://github.com/npm/cacache) 1. **cache-base@1.0.1** @@ -1036,7 +1027,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **camelcase@5.3.1** * Licenses: MIT * Repository: [https://github.com/sindresorhus/camelcase](https://github.com/sindresorhus/camelcase) -1. **caniuse-lite@1.0.30001008** +1. **caniuse-lite@1.0.30000989** * Licenses: CC-BY-4.0 * Repository: [https://github.com/ben-eb/caniuse-lite](https://github.com/ben-eb/caniuse-lite) 1. **chalk@1.1.3** @@ -1045,7 +1036,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **chalk@2.4.2** * Licenses: MIT * Repository: [https://github.com/chalk/chalk](https://github.com/chalk/chalk) -1. **chokidar@2.1.8** +1. **chokidar@2.1.6** * Licenses: MIT * Repository: [https://github.com/paulmillr/chokidar](https://github.com/paulmillr/chokidar) 1. **chownr@1.1.1** @@ -1054,9 +1045,6 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **chownr@1.1.2** * Licenses: ISC * Repository: [https://github.com/isaacs/chownr](https://github.com/isaacs/chownr) -1. **chownr@1.1.3** - * Licenses: ISC - * Repository: [https://github.com/isaacs/chownr](https://github.com/isaacs/chownr) 1. **chrome-trace-event@1.0.2** * Licenses: MIT * Repository: [github.com:samccone/chrome-trace-event](github.com:samccone/chrome-trace-event) @@ -1075,7 +1063,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **code-point-at@1.1.0** * Licenses: MIT * Repository: [https://github.com/sindresorhus/code-point-at](https://github.com/sindresorhus/code-point-at) -1. **codecov@3.6.1** +1. **codecov@3.5.0** * Licenses: MIT * Repository: [https://github.com/codecov/codecov-node](https://github.com/codecov/codecov-node) 1. **collection-visit@1.0.0** @@ -1093,7 +1081,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **commander@2.15.1** * Licenses: MIT * Repository: [https://github.com/tj/commander.js](https://github.com/tj/commander.js) -1. **commander@2.20.3** +1. **commander@2.20.0** * Licenses: MIT * Repository: [https://github.com/tj/commander.js](https://github.com/tj/commander.js) 1. **commondir@1.0.1** @@ -1108,16 +1096,16 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **concat-stream@1.6.2** * Licenses: MIT * Repository: [https://github.com/maxogden/concat-stream](https://github.com/maxogden/concat-stream) -1. **console-browserify@1.2.0** +1. **console-browserify@1.1.0** * Licenses: MIT - * Repository: [https://github.com/browserify/console-browserify](https://github.com/browserify/console-browserify) + * Repository: [https://github.com/Raynos/console-browserify](https://github.com/Raynos/console-browserify) 1. **console-control-strings@1.1.0** * Licenses: ISC * Repository: [https://github.com/iarna/console-control-strings](https://github.com/iarna/console-control-strings) 1. **constants-browserify@1.0.0** * Licenses: MIT * Repository: [https://github.com/juliangruber/constants-browserify](https://github.com/juliangruber/constants-browserify) -1. **convert-source-map@1.7.0** +1. **convert-source-map@1.6.0** * Licenses: MIT * Repository: [https://github.com/thlorenz/convert-source-map](https://github.com/thlorenz/convert-source-map) 1. **copy-concurrently@1.0.5** @@ -1126,13 +1114,16 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **copy-descriptor@0.1.1** * Licenses: MIT * Repository: [https://github.com/jonschlinkert/copy-descriptor](https://github.com/jonschlinkert/copy-descriptor) -1. **core-js-compat@3.4.0** +1. **core-js-compat@3.2.0** + * Licenses: MIT + * Repository: [https://github.com/zloirock/core-js](https://github.com/zloirock/core-js) +1. **core-js@2.6.9** * Licenses: MIT * Repository: [https://github.com/zloirock/core-js](https://github.com/zloirock/core-js) -1. **core-js@2.6.10** +1. **core-js@3.1.4** * Licenses: MIT * Repository: [https://github.com/zloirock/core-js](https://github.com/zloirock/core-js) -1. **core-js@3.2.1** +1. **core-js@3.2.0** * Licenses: MIT * Repository: [https://github.com/zloirock/core-js](https://github.com/zloirock/core-js) 1. **core-util-is@1.0.2** @@ -1159,9 +1150,12 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **crypto-browserify@3.12.0** * Licenses: MIT * Repository: [https://github.com/crypto-browserify/crypto-browserify](https://github.com/crypto-browserify/crypto-browserify) -1. **cyclist@1.0.1** - * Licenses: MIT +1. **cyclist@0.2.2** + * Licenses: MIT* * Repository: [https://github.com/mafintosh/cyclist](https://github.com/mafintosh/cyclist) +1. **date-now@0.1.4** + * Licenses: MIT + * Repository: [https://github.com/Colingo/date-now](https://github.com/Colingo/date-now) 1. **debug@2.6.9** * Licenses: MIT * Repository: [https://github.com/visionmedia/debug](https://github.com/visionmedia/debug) @@ -1231,10 +1225,10 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **duplexify@3.7.1** * Licenses: MIT * Repository: [https://github.com/mafintosh/duplexify](https://github.com/mafintosh/duplexify) -1. **electron-to-chromium@1.3.306** +1. **electron-to-chromium@1.3.224** * Licenses: ISC * Repository: [https://github.com/kilian/electron-to-chromium](https://github.com/kilian/electron-to-chromium) -1. **elliptic@6.5.1** +1. **elliptic@6.5.0** * Licenses: MIT * Repository: [https://github.com/indutny/elliptic](https://github.com/indutny/elliptic) 1. **emoji-regex@7.0.3** @@ -1243,15 +1237,12 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **emojis-list@2.1.0** * Licenses: MIT * Repository: [https://github.com/kikobeats/emojis-list](https://github.com/kikobeats/emojis-list) -1. **end-of-stream@1.4.4** +1. **end-of-stream@1.4.1** * Licenses: MIT * Repository: [https://github.com/mafintosh/end-of-stream](https://github.com/mafintosh/end-of-stream) 1. **enhanced-resolve@4.1.0** * Licenses: MIT * Repository: [https://github.com/webpack/enhanced-resolve](https://github.com/webpack/enhanced-resolve) -1. **enhanced-resolve@4.1.1** - * Licenses: MIT - * Repository: [https://github.com/webpack/enhanced-resolve](https://github.com/webpack/enhanced-resolve) 1. **errno@0.1.7** * Licenses: MIT * Repository: [https://github.com/rvagg/node-errno](https://github.com/rvagg/node-errno) @@ -1279,7 +1270,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **esrecurse@4.2.1** * Licenses: BSD-2-Clause * Repository: [https://github.com/estools/esrecurse](https://github.com/estools/esrecurse) -1. **estraverse@4.3.0** +1. **estraverse@4.2.0** * Licenses: BSD-2-Clause * Repository: [https://github.com/estools/estraverse](https://github.com/estools/estraverse) 1. **esutils@2.0.3** @@ -1339,7 +1330,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **findup-sync@3.0.0** * Licenses: MIT * Repository: [https://github.com/gulpjs/findup-sync](https://github.com/gulpjs/findup-sync) -1. **firebase@6.6.2** +1. **firebase@6.3.5** * Licenses: Apache-2.0 * Repository: [https://github.com/firebase/firebase-js-sdk](https://github.com/firebase/firebase-js-sdk) 1. **flush-write-stream@1.1.1** @@ -1357,9 +1348,6 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **from2@2.3.0** * Licenses: MIT * Repository: [https://github.com/hughsk/from2](https://github.com/hughsk/from2) -1. **fs-minipass@1.2.5** - * Licenses: ISC - * Repository: [https://github.com/npm/fs-minipass](https://github.com/npm/fs-minipass) 1. **fs-minipass@1.2.6** * Licenses: ISC * Repository: [https://github.com/npm/fs-minipass](https://github.com/npm/fs-minipass) @@ -1372,9 +1360,6 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **fs.realpath@1.0.0** * Licenses: ISC * Repository: [https://github.com/isaacs/fs.realpath](https://github.com/isaacs/fs.realpath) -1. **fsevents@1.2.9** - * Licenses: MIT - * Repository: [https://github.com/strongloop/fsevents](https://github.com/strongloop/fsevents) 1. **function-bind@1.1.1** * Licenses: MIT * Repository: [https://github.com/Raynos/function-bind](https://github.com/Raynos/function-bind) @@ -1396,15 +1381,9 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **glob@7.1.2** * Licenses: ISC * Repository: [https://github.com/isaacs/node-glob](https://github.com/isaacs/node-glob) -1. **glob@7.1.3** - * Licenses: ISC - * Repository: [https://github.com/isaacs/node-glob](https://github.com/isaacs/node-glob) 1. **glob@7.1.4** * Licenses: ISC * Repository: [https://github.com/isaacs/node-glob](https://github.com/isaacs/node-glob) -1. **glob@7.1.6** - * Licenses: ISC - * Repository: [https://github.com/isaacs/node-glob](https://github.com/isaacs/node-glob) 1. **global-modules@1.0.0** * Licenses: MIT * Repository: [https://github.com/jonschlinkert/global-modules](https://github.com/jonschlinkert/global-modules) @@ -1423,16 +1402,16 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **globals@9.18.0** * Licenses: MIT * Repository: [https://github.com/sindresorhus/globals](https://github.com/sindresorhus/globals) -1. **graceful-fs@4.2.3** +1. **graceful-fs@4.2.1** * Licenses: ISC * Repository: [https://github.com/isaacs/node-graceful-fs](https://github.com/isaacs/node-graceful-fs) 1. **growl@1.10.5** * Licenses: MIT * Repository: [https://github.com/tj/node-growl](https://github.com/tj/node-growl) -1. **grpc@1.23.3** +1. **grpc@1.22.2** * Licenses: Apache-2.0 * Repository: [https://github.com/grpc/grpc-node](https://github.com/grpc/grpc-node) -1. **handlebars@4.5.1** +1. **handlebars@4.3.1** * Licenses: MIT * Repository: [https://github.com/wycats/handlebars.js](https://github.com/wycats/handlebars.js) 1. **has-ansi@2.0.0** @@ -1477,7 +1456,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **homedir-polyfill@1.0.3** * Licenses: MIT * Repository: [https://github.com/doowb/homedir-polyfill](https://github.com/doowb/homedir-polyfill) -1. **hosted-git-info@2.8.5** +1. **hosted-git-info@2.8.2** * Licenses: ISC * Repository: [https://github.com/npm/hosted-git-info](https://github.com/npm/hosted-git-info) 1. **http-parser-js@0.4.10** @@ -1486,10 +1465,10 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **https-browserify@1.0.0** * Licenses: MIT * Repository: [https://github.com/substack/https-browserify](https://github.com/substack/https-browserify) -1. **https-proxy-agent@2.2.4** +1. **https-proxy-agent@2.2.2** * Licenses: MIT * Repository: [https://github.com/TooTallNate/node-https-proxy-agent](https://github.com/TooTallNate/node-https-proxy-agent) -1. **iconv-lite@0.4.24** +1. **iconv-lite@0.4.23** * Licenses: MIT * Repository: [https://github.com/ashtuchkin/iconv-lite](https://github.com/ashtuchkin/iconv-lite) 1. **idb@3.0.2** @@ -1504,9 +1483,6 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **ignore-walk@3.0.1** * Licenses: ISC * Repository: [https://github.com/isaacs/ignore-walk](https://github.com/isaacs/ignore-walk) -1. **ignore-walk@3.0.3** - * Licenses: ISC - * Repository: [https://github.com/isaacs/ignore-walk](https://github.com/isaacs/ignore-walk) 1. **import-local@2.0.0** * Licenses: MIT * Repository: [https://github.com/sindresorhus/import-local](https://github.com/sindresorhus/import-local) @@ -1594,6 +1570,9 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **is-number@3.0.0** * Licenses: MIT * Repository: [https://github.com/jonschlinkert/is-number](https://github.com/jonschlinkert/is-number) +1. **is-plain-obj@1.1.0** + * Licenses: MIT + * Repository: [https://github.com/sindresorhus/is-plain-obj](https://github.com/sindresorhus/is-plain-obj) 1. **is-plain-object@2.0.4** * Licenses: MIT * Repository: [https://github.com/jonschlinkert/is-plain-object](https://github.com/jonschlinkert/is-plain-object) @@ -1666,7 +1645,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **json5@1.0.1** * Licenses: MIT * Repository: [https://github.com/json5/json5](https://github.com/json5/json5) -1. **json5@2.1.1** +1. **json5@2.1.0** * Licenses: MIT * Repository: [https://github.com/json5/json5](https://github.com/json5/json5) 1. **just-extend@4.0.2** @@ -1762,9 +1741,6 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **memory-fs@0.4.1** * Licenses: MIT * Repository: [https://github.com/webpack/memory-fs](https://github.com/webpack/memory-fs) -1. **memory-fs@0.5.0** - * Licenses: MIT - * Repository: [https://github.com/webpack/memory-fs](https://github.com/webpack/memory-fs) 1. **merge-source-map@1.1.0** * Licenses: MIT * Repository: [https://github.com/keik/merge-source-map](https://github.com/keik/merge-source-map) @@ -1786,9 +1762,6 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **minimatch@3.0.4** * Licenses: ISC * Repository: [https://github.com/isaacs/minimatch](https://github.com/isaacs/minimatch) -1. **minimist@0.0.10** - * Licenses: MIT - * Repository: [https://github.com/substack/minimist](https://github.com/substack/minimist) 1. **minimist@0.0.8** * Licenses: MIT * Repository: [https://github.com/substack/minimist](https://github.com/substack/minimist) @@ -1819,9 +1792,6 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **ms@2.0.0** * Licenses: MIT * Repository: [https://github.com/zeit/ms](https://github.com/zeit/ms) -1. **ms@2.1.1** - * Licenses: MIT - * Repository: [https://github.com/zeit/ms](https://github.com/zeit/ms) 1. **ms@2.1.2** * Licenses: MIT * Repository: [https://github.com/zeit/ms](https://github.com/zeit/ms) @@ -1831,9 +1801,6 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **nanomatch@1.2.13** * Licenses: MIT * Repository: [https://github.com/micromatch/nanomatch](https://github.com/micromatch/nanomatch) -1. **needle@2.3.0** - * Licenses: MIT - * Repository: [https://github.com/tomas/needle](https://github.com/tomas/needle) 1. **needle@2.4.0** * Licenses: MIT * Repository: [https://github.com/tomas/needle](https://github.com/tomas/needle) @@ -1846,7 +1813,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **nice-try@1.0.5** * Licenses: MIT * Repository: [https://github.com/electerious/nice-try](https://github.com/electerious/nice-try) -1. **nise@1.5.2** +1. **nise@1.5.1** * Licenses: BSD-3-Clause * Repository: [https://github.com/sinonjs/nise](https://github.com/sinonjs/nise) 1. **node-fetch@2.6.0** @@ -1858,13 +1825,10 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **node-modules-regexp@1.0.0** * Licenses: MIT * Repository: [https://github.com/jamestalmage/node-modules-regexp](https://github.com/jamestalmage/node-modules-regexp) -1. **node-pre-gyp@0.12.0** - * Licenses: BSD-3-Clause - * Repository: [https://github.com/mapbox/node-pre-gyp](https://github.com/mapbox/node-pre-gyp) 1. **node-pre-gyp@0.13.0** * Licenses: BSD-3-Clause * Repository: [https://github.com/mapbox/node-pre-gyp](https://github.com/mapbox/node-pre-gyp) -1. **node-releases@1.1.39** +1. **node-releases@1.1.27** * Licenses: MIT * Repository: [https://github.com/chicoxyzzy/node-releases](https://github.com/chicoxyzzy/node-releases) 1. **nopt@4.0.1** @@ -1885,9 +1849,6 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **npm-packlist@1.4.1** * Licenses: ISC * Repository: [https://github.com/npm/npm-packlist](https://github.com/npm/npm-packlist) -1. **npm-packlist@1.4.4** - * Licenses: ISC - * Repository: [https://github.com/npm/npm-packlist](https://github.com/npm/npm-packlist) 1. **npm-run-path@2.0.2** * Licenses: MIT * Repository: [https://github.com/sindresorhus/npm-run-path](https://github.com/sindresorhus/npm-run-path) @@ -1945,6 +1906,9 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **osenv@0.1.5** * Licenses: ISC * Repository: [https://github.com/npm/osenv](https://github.com/npm/osenv) +1. **output-file-sync@2.0.1** + * Licenses: ISC + * Repository: [https://github.com/shinnn/output-file-sync](https://github.com/shinnn/output-file-sync) 1. **p-defer@1.0.0** * Licenses: MIT * Repository: [https://github.com/sindresorhus/p-defer](https://github.com/sindresorhus/p-defer) @@ -1957,7 +1921,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **p-limit@1.3.0** * Licenses: MIT * Repository: [https://github.com/sindresorhus/p-limit](https://github.com/sindresorhus/p-limit) -1. **p-limit@2.2.1** +1. **p-limit@2.2.0** * Licenses: MIT * Repository: [https://github.com/sindresorhus/p-limit](https://github.com/sindresorhus/p-limit) 1. **p-locate@2.0.0** @@ -1978,10 +1942,10 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **pako@1.0.10** * Licenses: (MIT AND Zlib) * Repository: [https://github.com/nodeca/pako](https://github.com/nodeca/pako) -1. **parallel-transform@1.2.0** +1. **parallel-transform@1.1.0** * Licenses: MIT * Repository: [https://github.com/mafintosh/parallel-transform](https://github.com/mafintosh/parallel-transform) -1. **parse-asn1@5.1.5** +1. **parse-asn1@5.1.4** * Licenses: ISC * Repository: [https://github.com/crypto-browserify/parse-asn1](https://github.com/crypto-browserify/parse-asn1) 1. **parse-json@4.0.0** @@ -2011,7 +1975,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **path-parse@1.0.6** * Licenses: MIT * Repository: [https://github.com/jbgutierrez/path-parse](https://github.com/jbgutierrez/path-parse) -1. **path-to-regexp@1.8.0** +1. **path-to-regexp@1.7.0** * Licenses: MIT * Repository: [https://github.com/pillarjs/path-to-regexp](https://github.com/pillarjs/path-to-regexp) 1. **path-type@3.0.0** @@ -2041,9 +2005,6 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **private@0.1.8** * Licenses: MIT * Repository: [https://github.com/benjamn/private](https://github.com/benjamn/private) -1. **process-nextick-args@2.0.0** - * Licenses: MIT - * Repository: [https://github.com/calvinmetcalf/process-nextick-args](https://github.com/calvinmetcalf/process-nextick-args) 1. **process-nextick-args@2.0.1** * Licenses: MIT * Repository: [https://github.com/calvinmetcalf/process-nextick-args](https://github.com/calvinmetcalf/process-nextick-args) @@ -2107,7 +2068,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **read-installed@4.0.3** * Licenses: ISC * Repository: [https://github.com/isaacs/read-installed](https://github.com/isaacs/read-installed) -1. **read-package-json@2.1.0** +1. **read-package-json@2.0.13** * Licenses: ISC * Repository: [https://github.com/npm/read-package-json](https://github.com/npm/read-package-json) 1. **read-pkg-up@4.0.0** @@ -2140,10 +2101,13 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **regex-not@1.0.2** * Licenses: MIT * Repository: [https://github.com/jonschlinkert/regex-not](https://github.com/jonschlinkert/regex-not) -1. **regexpu-core@4.6.0** +1. **regexp-tree@0.1.11** + * Licenses: MIT + * Repository: [https://github.com/DmitrySoshnikov/regexp-tree](https://github.com/DmitrySoshnikov/regexp-tree) +1. **regexpu-core@4.5.5** * Licenses: MIT * Repository: [https://github.com/mathiasbynens/regexpu-core](https://github.com/mathiasbynens/regexpu-core) -1. **regjsgen@0.5.1** +1. **regjsgen@0.5.0** * Licenses: MIT * Repository: [https://github.com/bnjmnt4n/regjsgen](https://github.com/bnjmnt4n/regjsgen) 1. **regjsparser@0.6.0** @@ -2194,9 +2158,6 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **rimraf@2.6.3** * Licenses: ISC * Repository: [https://github.com/isaacs/rimraf](https://github.com/isaacs/rimraf) -1. **rimraf@2.7.1** - * Licenses: ISC - * Repository: [https://github.com/isaacs/rimraf](https://github.com/isaacs/rimraf) 1. **ripemd160@2.0.2** * Licenses: MIT * Repository: [https://github.com/crypto-browserify/ripemd160](https://github.com/crypto-browserify/ripemd160) @@ -2221,13 +2182,10 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **semver@5.7.0** * Licenses: ISC * Repository: [https://github.com/npm/node-semver](https://github.com/npm/node-semver) -1. **semver@5.7.1** - * Licenses: ISC - * Repository: [https://github.com/npm/node-semver](https://github.com/npm/node-semver) 1. **semver@6.3.0** * Licenses: ISC * Repository: [https://github.com/npm/node-semver](https://github.com/npm/node-semver) -1. **serialize-javascript@1.9.1** +1. **serialize-javascript@1.7.0** * Licenses: BSD-3-Clause * Repository: [https://github.com/yahoo/serialize-javascript](https://github.com/yahoo/serialize-javascript) 1. **set-blocking@2.0.0** @@ -2248,10 +2206,13 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **shebang-regex@1.0.0** * Licenses: MIT * Repository: [https://github.com/sindresorhus/shebang-regex](https://github.com/sindresorhus/shebang-regex) +1. **signal-exit@3.0.1** + * Licenses: ISC + * Repository: [https://github.com/tapjs/signal-exit](https://github.com/tapjs/signal-exit) 1. **signal-exit@3.0.2** * Licenses: ISC * Repository: [https://github.com/tapjs/signal-exit](https://github.com/tapjs/signal-exit) -1. **sinon@7.5.0** +1. **sinon@7.4.1** * Licenses: BSD-3-Clause * Repository: [https://github.com/sinonjs/sinon](https://github.com/sinonjs/sinon) 1. **slash@1.0.0** @@ -2278,7 +2239,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **source-map-resolve@0.5.2** * Licenses: MIT * Repository: [https://github.com/lydell/source-map-resolve](https://github.com/lydell/source-map-resolve) -1. **source-map-support@0.5.16** +1. **source-map-support@0.5.13** * Licenses: MIT * Repository: [https://github.com/evanw/node-source-map-support](https://github.com/evanw/node-source-map-support) 1. **source-map-url@0.4.0** @@ -2314,7 +2275,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **spdx-satisfies@4.0.1** * Licenses: MIT * Repository: [https://github.com/kemitchell/spdx-satisfies.js](https://github.com/kemitchell/spdx-satisfies.js) -1. **spine-web@1.2.1** +1. **spine-web@1.2.9** * Licenses: Apache-2.0 * Repository: [https://github.com/SpineEventEngine/web](https://github.com/SpineEventEngine/web) 1. **split-string@3.1.0** @@ -2383,18 +2344,15 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **tar@4.4.10** * Licenses: ISC * Repository: [https://github.com/npm/node-tar](https://github.com/npm/node-tar) -1. **tar@4.4.8** - * Licenses: ISC - * Repository: [https://github.com/npm/node-tar](https://github.com/npm/node-tar) 1. **teeny-request@3.11.3** * Licenses: Apache-2.0 * Repository: [https://github.com/fhinkel/teeny-request](https://github.com/fhinkel/teeny-request) 1. **terser-webpack-plugin@1.4.1** * Licenses: MIT * Repository: [https://github.com/webpack-contrib/terser-webpack-plugin](https://github.com/webpack-contrib/terser-webpack-plugin) -1. **terser@4.4.0** +1. **terser@4.1.4** * Licenses: BSD-2-Clause - * Repository: [https://github.com/terser/terser](https://github.com/terser/terser) + * Repository: [https://github.com/fabiosantoscode/terser](https://github.com/fabiosantoscode/terser) 1. **test-exclude@5.2.3** * Licenses: ISC * Repository: [https://github.com/istanbuljs/istanbuljs](https://github.com/istanbuljs/istanbuljs) @@ -2425,6 +2383,9 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **treeify@1.1.0** * Licenses: MIT * Repository: [https://github.com/notatestuser/treeify](https://github.com/notatestuser/treeify) +1. **trim-right@1.0.1** + * Licenses: MIT + * Repository: [https://github.com/sindresorhus/trim-right](https://github.com/sindresorhus/trim-right) 1. **tty-browserify@0.0.0** * Licenses: MIT * Repository: [https://github.com/substack/tty-browserify](https://github.com/substack/tty-browserify) @@ -2434,7 +2395,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **typedarray@0.0.6** * Licenses: MIT * Repository: [https://github.com/substack/typedarray](https://github.com/substack/typedarray) -1. **uglify-js@3.6.8** +1. **uglify-js@3.6.0** * Licenses: BSD-2-Clause * Repository: [https://github.com/mishoo/UglifyJS2](https://github.com/mishoo/UglifyJS2) 1. **unicode-canonical-property-names-ecmascript@1.0.4** @@ -2461,7 +2422,7 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **unset-value@1.0.0** * Licenses: MIT * Repository: [https://github.com/jonschlinkert/unset-value](https://github.com/jonschlinkert/unset-value) -1. **upath@1.2.0** +1. **upath@1.1.2** * Licenses: MIT * Repository: [https://github.com/anodynos/upath](https://github.com/anodynos/upath) 1. **uri-js@4.2.2** @@ -2497,22 +2458,22 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **validate-npm-package-license@3.0.4** * Licenses: Apache-2.0 * Repository: [https://github.com/kemitchell/validate-npm-package-license.js](https://github.com/kemitchell/validate-npm-package-license.js) -1. **vm-browserify@1.1.2** +1. **vm-browserify@1.1.0** * Licenses: MIT * Repository: [https://github.com/substack/vm-browserify](https://github.com/substack/vm-browserify) 1. **watchpack@1.6.0** * Licenses: MIT * Repository: [https://github.com/webpack/watchpack](https://github.com/webpack/watchpack) -1. **webpack-cli@3.3.10** +1. **webpack-cli@3.3.6** * Licenses: MIT * Repository: [https://github.com/webpack/webpack-cli](https://github.com/webpack/webpack-cli) -1. **webpack-merge@4.2.2** +1. **webpack-merge@4.2.1** * Licenses: MIT * Repository: [https://github.com/survivejs/webpack-merge](https://github.com/survivejs/webpack-merge) 1. **webpack-sources@1.4.3** * Licenses: MIT * Repository: [https://github.com/webpack/webpack-sources](https://github.com/webpack/webpack-sources) -1. **webpack@4.41.2** +1. **webpack@4.39.1** * Licenses: MIT * Repository: [https://github.com/webpack/webpack](https://github.com/webpack/webpack) 1. **websocket-driver@0.7.3** @@ -2572,9 +2533,6 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice 1. **yallist@3.0.3** * Licenses: ISC * Repository: [https://github.com/isaacs/yallist](https://github.com/isaacs/yallist) -1. **yallist@3.1.1** - * Licenses: ISC - * Repository: [https://github.com/isaacs/yallist](https://github.com/isaacs/yallist) 1. **yargs-parser@13.1.1** * Licenses: ISC * Repository: [https://github.com/yargs/yargs-parser](https://github.com/yargs/yargs-parser) @@ -2589,12 +2547,12 @@ This report was generated on **Mon Nov 11 18:27:19 EET 2019** using [Gradle-Lice * Repository: [https://github.com/bcoe/yargs](https://github.com/bcoe/yargs) -This report was generated on **Mon Nov 11 2019 18:27:21 GMT+0200 (EET)** using [NPM License Checker](https://github.com/davglass/license-checker) library. +This report was generated on **Thu Dec 05 2019 13:15:47 GMT+0200 (Eastern European Standard Time)** using [NPM License Checker](https://github.com/davglass/license-checker) library. -# Dependencies of `io.spine.gcloud:spine-firebase-web:1.2.1` +# Dependencies of `io.spine.gcloud:spine-firebase-web:1.2.9` ## Runtime 1. **Group:** com.fasterxml.jackson.core **Name:** jackson-annotations **Version:** 2.9.10 @@ -3437,12 +3395,12 @@ This report was generated on **Mon Nov 11 2019 18:27:21 GMT+0200 (EET)** using [ The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Nov 11 18:27:22 EET 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Thu Dec 05 13:16:39 EET 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-js-tests:1.2.1` +# Dependencies of `io.spine:spine-js-tests:1.2.9` ## Runtime 1. **Group:** com.google.code.findbugs **Name:** jsr305 **Version:** 3.0.2 @@ -3848,12 +3806,12 @@ This report was generated on **Mon Nov 11 18:27:22 EET 2019** using [Gradle-Lice The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Nov 11 18:27:23 EET 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Thu Dec 05 13:17:11 EET 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-test-app:1.2.1` +# Dependencies of `io.spine:spine-test-app:1.2.9` ## Runtime 1. **Group:** com.fasterxml.jackson.core **Name:** jackson-annotations **Version:** 2.9.10 @@ -4389,9 +4347,9 @@ This report was generated on **Mon Nov 11 18:27:23 EET 2019** using [Gradle-Lice * **POM Project URL:** [http://github.com/square/javapoet/](http://github.com/square/javapoet/) * **POM License: Apache 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** commons-cli **Name:** commons-cli **Version:** 1.2 - * **Project URL:** [http://commons.apache.org/cli/](http://commons.apache.org/cli/) - * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) +1. **Group:** commons-cli **Name:** commons-cli **Version:** 1.4 + * **Project URL:** [http://commons.apache.org/proper/commons-cli/](http://commons.apache.org/proper/commons-cli/) + * **POM License: Apache License, Version 2.0** - [https://www.apache.org/licenses/LICENSE-2.0.txt](https://www.apache.org/licenses/LICENSE-2.0.txt) 1. **Group:** commons-codec **Name:** commons-codec **Version:** 1.10 * **Project URL:** [http://commons.apache.org/proper/commons-codec/](http://commons.apache.org/proper/commons-codec/) @@ -4606,18 +4564,6 @@ This report was generated on **Mon Nov 11 18:27:23 EET 2019** using [Gradle-Lice 1. **Group:** org.apache.maven **Name:** maven-plugin-api **Version:** 3.2.1 * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** org.apache.servicemix.bundles **Name:** org.apache.servicemix.bundles.bcprov-jdk16 **Version:** 1.46_3 - * **Manifest Project URL:** [http://www.apache.org/](http://www.apache.org/) - * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group:** org.apache.tomcat **Name:** tomcat-annotations-api **Version:** 7.0.92 - * **POM Project URL:** [https://tomcat.apache.org/](https://tomcat.apache.org/) - * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group:** org.apache.tomcat **Name:** tomcat-annotations-api **Version:** 8.0.53 - * **POM Project URL:** [https://tomcat.apache.org/](https://tomcat.apache.org/) - * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group:** org.apache.tomcat **Name:** tomcat-annotations-api **Version:** 8.5.35 * **POM Project URL:** [https://tomcat.apache.org/](https://tomcat.apache.org/) * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -4626,14 +4572,6 @@ This report was generated on **Mon Nov 11 18:27:23 EET 2019** using [Gradle-Lice * **POM Project URL:** [https://tomcat.apache.org/](https://tomcat.apache.org/) * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** org.apache.tomcat.embed **Name:** tomcat-embed-core **Version:** 7.0.92 - * **POM Project URL:** [https://tomcat.apache.org/](https://tomcat.apache.org/) - * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group:** org.apache.tomcat.embed **Name:** tomcat-embed-core **Version:** 8.0.53 - * **POM Project URL:** [https://tomcat.apache.org/](https://tomcat.apache.org/) - * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group:** org.apache.tomcat.embed **Name:** tomcat-embed-core **Version:** 8.5.35 * **POM Project URL:** [https://tomcat.apache.org/](https://tomcat.apache.org/) * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -4642,14 +4580,6 @@ This report was generated on **Mon Nov 11 18:27:23 EET 2019** using [Gradle-Lice * **POM Project URL:** [https://tomcat.apache.org/](https://tomcat.apache.org/) * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** org.apache.tomcat.embed **Name:** tomcat-embed-el **Version:** 7.0.92 - * **POM Project URL:** [https://tomcat.apache.org/](https://tomcat.apache.org/) - * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group:** org.apache.tomcat.embed **Name:** tomcat-embed-el **Version:** 8.0.53 - * **POM Project URL:** [https://tomcat.apache.org/](https://tomcat.apache.org/) - * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group:** org.apache.tomcat.embed **Name:** tomcat-embed-el **Version:** 8.5.35 * **POM Project URL:** [https://tomcat.apache.org/](https://tomcat.apache.org/) * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -4658,14 +4588,6 @@ This report was generated on **Mon Nov 11 18:27:23 EET 2019** using [Gradle-Lice * **POM Project URL:** [https://tomcat.apache.org/](https://tomcat.apache.org/) * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** org.apache.tomcat.embed **Name:** tomcat-embed-jasper **Version:** 7.0.92 - * **POM Project URL:** [https://tomcat.apache.org/](https://tomcat.apache.org/) - * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group:** org.apache.tomcat.embed **Name:** tomcat-embed-jasper **Version:** 8.0.53 - * **POM Project URL:** [https://tomcat.apache.org/](https://tomcat.apache.org/) - * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group:** org.apache.tomcat.embed **Name:** tomcat-embed-jasper **Version:** 8.5.35 * **POM Project URL:** [https://tomcat.apache.org/](https://tomcat.apache.org/) * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -4674,18 +4596,6 @@ This report was generated on **Mon Nov 11 18:27:23 EET 2019** using [Gradle-Lice * **POM Project URL:** [https://tomcat.apache.org/](https://tomcat.apache.org/) * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** org.apache.tomcat.embed **Name:** tomcat-embed-logging-log4j **Version:** 7.0.92 - * **POM Project URL:** [http://tomcat.apache.org/](http://tomcat.apache.org/) - * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group:** org.apache.tomcat.embed **Name:** tomcat-embed-websocket **Version:** 7.0.92 - * **POM Project URL:** [http://tomcat.apache.org/](http://tomcat.apache.org/) - * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - -1. **Group:** org.apache.tomcat.embed **Name:** tomcat-embed-websocket **Version:** 8.0.53 - * **POM Project URL:** [https://tomcat.apache.org/](https://tomcat.apache.org/) - * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group:** org.apache.tomcat.embed **Name:** tomcat-embed-websocket **Version:** 8.5.35 * **POM Project URL:** [https://tomcat.apache.org/](https://tomcat.apache.org/) * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) @@ -4698,6 +4608,10 @@ This report was generated on **Mon Nov 11 18:27:23 EET 2019** using [Gradle-Lice * **POM Project URL:** [https://github.com/apiguardian-team/apiguardian](https://github.com/apiguardian-team/apiguardian) * **POM License: The Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) +1. **Group:** org.bouncycastle **Name:** bcprov-jdk15on **Version:** 1.60 + * **POM Project URL:** [http://www.bouncycastle.org/java.html](http://www.bouncycastle.org/java.html) + * **POM License: Bouncy Castle Licence** - [http://www.bouncycastle.org/licence.html](http://www.bouncycastle.org/licence.html) + 1. **Group:** org.checkerframework **Name:** checker-compat-qual **Version:** 2.5.3 * **POM Project URL:** [https://checkerframework.org](https://checkerframework.org) * **POM License: GNU General Public License, version 2 (GPL2), with the classpath exception** - [http://www.gnu.org/software/classpath/license.html](http://www.gnu.org/software/classpath/license.html) @@ -4718,12 +4632,16 @@ This report was generated on **Mon Nov 11 18:27:23 EET 2019** using [Gradle-Lice * **POM License: GNU General Public License, version 2 (GPL2), with the classpath exception** - [http://www.gnu.org/software/classpath/license.html](http://www.gnu.org/software/classpath/license.html) * **POM License: The MIT License** - [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT) -1. **Group:** org.codehaus.groovy **Name:** groovy **Version:** 2.4.15 - * **POM Project URL:** [http://groovy-lang.org](http://groovy-lang.org) +1. **Group:** org.codehaus.groovy **Name:** groovy **Version:** 2.5.8 + * **POM Project URL:** [https://groovy-lang.org](https://groovy-lang.org) + * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) + +1. **Group:** org.codehaus.groovy **Name:** groovy-cli-commons **Version:** 2.5.8 + * **POM Project URL:** [https://groovy-lang.org](https://groovy-lang.org) * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** org.codehaus.groovy **Name:** groovy-json **Version:** 2.4.15 - * **POM Project URL:** [http://groovy-lang.org](http://groovy-lang.org) +1. **Group:** org.codehaus.groovy **Name:** groovy-json **Version:** 2.5.8 + * **POM Project URL:** [https://groovy-lang.org](https://groovy-lang.org) * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) 1. **Group:** org.codehaus.mojo **Name:** animal-sniffer-annotations **Version:** 1.18 @@ -5265,63 +5183,55 @@ This report was generated on **Mon Nov 11 18:27:23 EET 2019** using [Gradle-Lice * **POM Project URL:** [http://jstl.java.net](http://jstl.java.net) * **POM License: CDDL + GPLv2 with classpath exception** - [http://glassfish.dev.java.net/nonav/public/CDDL+GPL.html](http://glassfish.dev.java.net/nonav/public/CDDL+GPL.html) -1. **Group:** org.gretty **Name:** gretty-core **Version:** 2.3.1 +1. **Group:** org.gretty **Name:** gretty-core **Version:** 3.0.1 * **POM Project URL:** [https://github.com/gretty-gradle-plugin/gretty](https://github.com/gretty-gradle-plugin/gretty) * **POM License: MIT** - [https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE](https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE) -1. **Group:** org.gretty **Name:** gretty-runner **Version:** 2.3.1 +1. **Group:** org.gretty **Name:** gretty-runner **Version:** 3.0.1 * **POM Project URL:** [https://github.com/gretty-gradle-plugin/gretty](https://github.com/gretty-gradle-plugin/gretty) * **POM License: MIT** - [https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE](https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE) -1. **Group:** org.gretty **Name:** gretty-runner-jetty **Version:** 2.3.1 +1. **Group:** org.gretty **Name:** gretty-runner-jetty **Version:** 3.0.1 * **POM Project URL:** [https://github.com/gretty-gradle-plugin/gretty](https://github.com/gretty-gradle-plugin/gretty) * **POM License: MIT** - [https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE](https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE) -1. **Group:** org.gretty **Name:** gretty-runner-jetty7 **Version:** 2.3.1 +1. **Group:** org.gretty **Name:** gretty-runner-jetty7 **Version:** 3.0.1 * **POM Project URL:** [https://github.com/gretty-gradle-plugin/gretty](https://github.com/gretty-gradle-plugin/gretty) * **POM License: MIT** - [https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE](https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE) -1. **Group:** org.gretty **Name:** gretty-runner-jetty8 **Version:** 2.3.1 +1. **Group:** org.gretty **Name:** gretty-runner-jetty8 **Version:** 3.0.1 * **POM Project URL:** [https://github.com/gretty-gradle-plugin/gretty](https://github.com/gretty-gradle-plugin/gretty) * **POM License: MIT** - [https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE](https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE) -1. **Group:** org.gretty **Name:** gretty-runner-jetty9 **Version:** 2.3.1 +1. **Group:** org.gretty **Name:** gretty-runner-jetty9 **Version:** 3.0.1 * **POM Project URL:** [https://github.com/gretty-gradle-plugin/gretty](https://github.com/gretty-gradle-plugin/gretty) * **POM License: MIT** - [https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE](https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE) -1. **Group:** org.gretty **Name:** gretty-runner-jetty93 **Version:** 2.3.1 +1. **Group:** org.gretty **Name:** gretty-runner-jetty93 **Version:** 3.0.1 * **POM Project URL:** [https://github.com/gretty-gradle-plugin/gretty](https://github.com/gretty-gradle-plugin/gretty) * **POM License: MIT** - [https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE](https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE) -1. **Group:** org.gretty **Name:** gretty-runner-jetty94 **Version:** 2.3.1 +1. **Group:** org.gretty **Name:** gretty-runner-jetty94 **Version:** 3.0.1 * **POM Project URL:** [https://github.com/gretty-gradle-plugin/gretty](https://github.com/gretty-gradle-plugin/gretty) * **POM License: MIT** - [https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE](https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE) -1. **Group:** org.gretty **Name:** gretty-runner-tomcat **Version:** 2.3.1 +1. **Group:** org.gretty **Name:** gretty-runner-tomcat **Version:** 3.0.1 * **POM Project URL:** [https://github.com/gretty-gradle-plugin/gretty](https://github.com/gretty-gradle-plugin/gretty) * **POM License: MIT** - [https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE](https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE) -1. **Group:** org.gretty **Name:** gretty-runner-tomcat7 **Version:** 2.3.1 +1. **Group:** org.gretty **Name:** gretty-runner-tomcat85 **Version:** 3.0.1 * **POM Project URL:** [https://github.com/gretty-gradle-plugin/gretty](https://github.com/gretty-gradle-plugin/gretty) * **POM License: MIT** - [https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE](https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE) -1. **Group:** org.gretty **Name:** gretty-runner-tomcat8 **Version:** 2.3.1 +1. **Group:** org.gretty **Name:** gretty-runner-tomcat8plus **Version:** 3.0.1 * **POM Project URL:** [https://github.com/gretty-gradle-plugin/gretty](https://github.com/gretty-gradle-plugin/gretty) * **POM License: MIT** - [https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE](https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE) -1. **Group:** org.gretty **Name:** gretty-runner-tomcat85 **Version:** 2.3.1 +1. **Group:** org.gretty **Name:** gretty-runner-tomcat9 **Version:** 3.0.1 * **POM Project URL:** [https://github.com/gretty-gradle-plugin/gretty](https://github.com/gretty-gradle-plugin/gretty) * **POM License: MIT** - [https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE](https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE) -1. **Group:** org.gretty **Name:** gretty-runner-tomcat8plus **Version:** 2.3.1 - * **POM Project URL:** [https://github.com/gretty-gradle-plugin/gretty](https://github.com/gretty-gradle-plugin/gretty) - * **POM License: MIT** - [https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE](https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE) - -1. **Group:** org.gretty **Name:** gretty-runner-tomcat9 **Version:** 2.3.1 - * **POM Project URL:** [https://github.com/gretty-gradle-plugin/gretty](https://github.com/gretty-gradle-plugin/gretty) - * **POM License: MIT** - [https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE](https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE) - -1. **Group:** org.gretty **Name:** gretty-starter **Version:** 2.3.1 +1. **Group:** org.gretty **Name:** gretty-starter **Version:** 3.0.1 * **POM Project URL:** [https://github.com/gretty-gradle-plugin/gretty](https://github.com/gretty-gradle-plugin/gretty) * **POM License: MIT** - [https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE](https://raw.github.com/gretty-gradle-plugin/gretty/master/LICENSE) @@ -5394,12 +5304,6 @@ This report was generated on **Mon Nov 11 18:27:23 EET 2019** using [Gradle-Lice * **POM Project URL:** [https://github.com/ota4j-team/opentest4j](https://github.com/ota4j-team/opentest4j) * **POM License: The Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** org.ow2.asm **Name:** asm **Version:** 7.0 - * **Manifest Project URL:** [http://asm.ow2.org](http://asm.ow2.org) - * **POM Project URL:** [http://asm.ow2.org/](http://asm.ow2.org/) - * **POM License: BSD** - [http://asm.ow2.org/license.html](http://asm.ow2.org/license.html) - * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group:** org.ow2.asm **Name:** asm **Version:** 7.1 * **Manifest Project URL:** [http://asm.ow2.org](http://asm.ow2.org) * **POM Project URL:** [http://asm.ow2.org/](http://asm.ow2.org/) @@ -5413,12 +5317,6 @@ This report was generated on **Mon Nov 11 18:27:23 EET 2019** using [Gradle-Lice * **POM License: BSD-3-Clause** - [https://asm.ow2.io/license.html](https://asm.ow2.io/license.html) * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** org.ow2.asm **Name:** asm-analysis **Version:** 7.0 - * **Manifest Project URL:** [http://asm.ow2.org](http://asm.ow2.org) - * **POM Project URL:** [http://asm.ow2.org/](http://asm.ow2.org/) - * **POM License: BSD** - [http://asm.ow2.org/license.html](http://asm.ow2.org/license.html) - * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group:** org.ow2.asm **Name:** asm-analysis **Version:** 7.2 * **Manifest Project URL:** [http://asm.ow2.org](http://asm.ow2.org) * **Manifest License:** BSD-3-Clause;link=https://asm.ow2.io/LICENSE.txt (Not packaged) @@ -5426,12 +5324,6 @@ This report was generated on **Mon Nov 11 18:27:23 EET 2019** using [Gradle-Lice * **POM License: BSD-3-Clause** - [https://asm.ow2.io/license.html](https://asm.ow2.io/license.html) * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** org.ow2.asm **Name:** asm-commons **Version:** 7.0 - * **Manifest Project URL:** [http://asm.ow2.org](http://asm.ow2.org) - * **POM Project URL:** [http://asm.ow2.org/](http://asm.ow2.org/) - * **POM License: BSD** - [http://asm.ow2.org/license.html](http://asm.ow2.org/license.html) - * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group:** org.ow2.asm **Name:** asm-commons **Version:** 7.2 * **Manifest Project URL:** [http://asm.ow2.org](http://asm.ow2.org) * **Manifest License:** BSD-3-Clause;link=https://asm.ow2.io/LICENSE.txt (Not packaged) @@ -5439,12 +5331,6 @@ This report was generated on **Mon Nov 11 18:27:23 EET 2019** using [Gradle-Lice * **POM License: BSD-3-Clause** - [https://asm.ow2.io/license.html](https://asm.ow2.io/license.html) * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** org.ow2.asm **Name:** asm-tree **Version:** 7.0 - * **Manifest Project URL:** [http://asm.ow2.org](http://asm.ow2.org) - * **POM Project URL:** [http://asm.ow2.org/](http://asm.ow2.org/) - * **POM License: BSD** - [http://asm.ow2.org/license.html](http://asm.ow2.org/license.html) - * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) - 1. **Group:** org.ow2.asm **Name:** asm-tree **Version:** 7.2 * **Manifest Project URL:** [http://asm.ow2.org](http://asm.ow2.org) * **Manifest License:** BSD-3-Clause;link=https://asm.ow2.io/LICENSE.txt (Not packaged) @@ -5473,23 +5359,27 @@ This report was generated on **Mon Nov 11 18:27:23 EET 2019** using [Gradle-Lice * **Manifest Project URL:** [http://code.google.com/p/google-guice/](http://code.google.com/p/google-guice/) * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** org.springframework **Name:** spring-aop **Version:** 4.3.16.RELEASE +1. **Group:** org.springframework **Name:** spring-aop **Version:** 5.0.6.RELEASE + * **POM Project URL:** [https://github.com/spring-projects/spring-framework](https://github.com/spring-projects/spring-framework) + * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) + +1. **Group:** org.springframework **Name:** spring-beans **Version:** 5.0.6.RELEASE * **POM Project URL:** [https://github.com/spring-projects/spring-framework](https://github.com/spring-projects/spring-framework) * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -1. **Group:** org.springframework **Name:** spring-beans **Version:** 4.3.16.RELEASE +1. **Group:** org.springframework **Name:** spring-context **Version:** 5.0.6.RELEASE * **POM Project URL:** [https://github.com/spring-projects/spring-framework](https://github.com/spring-projects/spring-framework) * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -1. **Group:** org.springframework **Name:** spring-context **Version:** 4.3.16.RELEASE +1. **Group:** org.springframework **Name:** spring-core **Version:** 5.0.6.RELEASE * **POM Project URL:** [https://github.com/spring-projects/spring-framework](https://github.com/spring-projects/spring-framework) * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -1. **Group:** org.springframework **Name:** spring-core **Version:** 4.3.16.RELEASE +1. **Group:** org.springframework **Name:** spring-expression **Version:** 5.0.6.RELEASE * **POM Project URL:** [https://github.com/spring-projects/spring-framework](https://github.com/spring-projects/spring-framework) * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -1. **Group:** org.springframework **Name:** spring-expression **Version:** 4.3.16.RELEASE +1. **Group:** org.springframework **Name:** spring-jcl **Version:** 5.0.6.RELEASE * **POM Project URL:** [https://github.com/spring-projects/spring-framework](https://github.com/spring-projects/spring-framework) * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) @@ -5497,16 +5387,16 @@ This report was generated on **Mon Nov 11 18:27:23 EET 2019** using [Gradle-Lice * **POM Project URL:** [https://github.com/spring-projects/spring-loaded](https://github.com/spring-projects/spring-loaded) * **POM License: The Apache Software License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0.txt](http://www.apache.org/licenses/LICENSE-2.0.txt) -1. **Group:** org.springframework.boot **Name:** spring-boot **Version:** 1.5.12.RELEASE - * **POM Project URL:** [http://projects.spring.io/spring-boot/](http://projects.spring.io/spring-boot/) +1. **Group:** org.springframework.boot **Name:** spring-boot **Version:** 2.0.2.RELEASE + * **POM Project URL:** [https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot](https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot) * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -1. **Group:** org.springframework.boot **Name:** spring-boot-autoconfigure **Version:** 1.5.12.RELEASE - * **POM Project URL:** [http://projects.spring.io/spring-boot/](http://projects.spring.io/spring-boot/) +1. **Group:** org.springframework.boot **Name:** spring-boot-autoconfigure **Version:** 2.0.2.RELEASE + * **POM Project URL:** [https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-autoconfigure](https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-autoconfigure) * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) -1. **Group:** org.springframework.boot **Name:** spring-boot-devtools **Version:** 1.5.12.RELEASE - * **POM Project URL:** [http://projects.spring.io/spring-boot/](http://projects.spring.io/spring-boot/) +1. **Group:** org.springframework.boot **Name:** spring-boot-devtools **Version:** 2.0.2.RELEASE + * **POM Project URL:** [https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-devtools](https://projects.spring.io/spring-boot/#/spring-boot-parent/spring-boot-devtools) * **POM License: Apache License, Version 2.0** - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) 1. **Group:** org.threeten **Name:** threetenbp **Version:** 1.3.3 @@ -5519,12 +5409,12 @@ This report was generated on **Mon Nov 11 18:27:23 EET 2019** using [Gradle-Lice The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Nov 11 18:28:07 EET 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Thu Dec 05 13:18:19 EET 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-testutil-web:1.2.1` +# Dependencies of `io.spine:spine-testutil-web:1.2.9` ## Runtime 1. **Group:** com.google.android **Name:** annotations **Version:** 4.1.1.4 @@ -5991,12 +5881,12 @@ This report was generated on **Mon Nov 11 18:28:07 EET 2019** using [Gradle-Lice The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Nov 11 18:28:08 EET 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). +This report was generated on **Thu Dec 05 13:18:28 EET 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). -# Dependencies of `io.spine:spine-web:1.2.1` +# Dependencies of `io.spine:spine-web:1.2.9` ## Runtime 1. **Group:** com.google.android **Name:** annotations **Version:** 4.1.1.4 @@ -6539,4 +6429,4 @@ This report was generated on **Mon Nov 11 18:28:08 EET 2019** using [Gradle-Lice The dependencies distributed under several licenses, are used according their commercial-use-friendly license. -This report was generated on **Mon Nov 11 18:28:08 EET 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file +This report was generated on **Thu Dec 05 13:18:39 EET 2019** using [Gradle-License-Report plugin](https://github.com/jk1/Gradle-License-Report) by Evgeny Naumenko, licensed under [Apache 2.0 License](https://github.com/jk1/Gradle-License-Report/blob/master/LICENSE). \ No newline at end of file diff --git a/pom.xml b/pom.xml index c475e0b95..e11a8c23a 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ all modules and does not describe the project structure per-subproject. io.spine spine-web -1.2.1 +1.2.9 2015 @@ -76,7 +76,7 @@ all modules and does not describe the project structure per-subproject. io.spine spine-server - 1.2.1 + 1.2.9 compile @@ -100,13 +100,13 @@ all modules and does not describe the project structure per-subproject. io.spine spine-testutil-client - 1.2.1 + 1.2.9 test io.spine.tools spine-mute-logging - 1.2.1 + 1.2.3 test @@ -168,17 +168,17 @@ all modules and does not describe the project structure per-subproject. io.spine spine-client - 1.2.1 + 1.2.9 io.spine.tools spine-errorprone-checks - 1.2.1 + 1.2.3 io.spine.tools spine-protoc-plugin - 1.2.1 + 1.2.3 javax.servlet @@ -198,52 +198,42 @@ all modules and does not describe the project structure per-subproject. org.gretty gretty-runner-jetty7 - 2.3.1 + 3.0.1 org.gretty gretty-runner-jetty8 - 2.3.1 + 3.0.1 org.gretty gretty-runner-jetty9 - 2.3.1 + 3.0.1 org.gretty gretty-runner-jetty93 - 2.3.1 + 3.0.1 org.gretty gretty-runner-jetty94 - 2.3.1 - - - org.gretty - gretty-runner-tomcat7 - 2.3.1 - - - org.gretty - gretty-runner-tomcat8 - 2.3.1 + 3.0.1 org.gretty gretty-runner-tomcat85 - 2.3.1 + 3.0.1 org.gretty gretty-runner-tomcat9 - 2.3.1 + 3.0.1 org.gretty gretty-starter - 2.3.1 + 3.0.1 org.jacoco diff --git a/version.gradle b/version.gradle index c2d596979..ba0c60158 100644 --- a/version.gradle +++ b/version.gradle @@ -19,10 +19,10 @@ */ ext { - spineVersion = '1.2.1' - spineBaseVersion = '1.2.1' + spineVersion = '1.2.9' + spineBaseVersion = '1.2.3' - versionToPublish = '1.2.1' + versionToPublish = '1.2.9' versionToPublishJs = versionToPublish servletApiVersion = '3.1.0'