Source: client/direct-client.js

  1. /*
  2. * Copyright 2023, TeamDev. All rights reserved.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Redistribution and use in source and/or binary forms, with or without
  11. * modification, must retain the above copyright notice and the following
  12. * disclaimer.
  13. *
  14. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  15. * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  16. * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  17. * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  18. * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  19. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  20. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  21. * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  22. * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  23. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  24. * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  25. */
  26. "use strict";
  27. import {ActorRequestFactory} from './actor-request-factory';
  28. import {AnyPacker} from "./any-packer";
  29. import {AbstractClientFactory} from './client-factory';
  30. import {CommandingClient} from "./commanding-client";
  31. import {CompositeClient} from "./composite-client";
  32. import {HttpClient} from './http-client';
  33. import {HttpEndpoint} from './http-endpoint';
  34. import KnownTypes from "./known-types";
  35. import {QueryingClient} from "./querying-client";
  36. import {NoOpSubscribingClient} from "./subscribing-client";
  37. import {Type} from "./typed-message";
  38. import TypeParsers from "./parser/type-parsers";
  39. /**
  40. * An implementation of the `AbstractClientFactory` that creates instances of client which exchanges
  41. * data with the server directly.
  42. *
  43. * Querying is performed by sending a query to the server over HTTP and reading the query response
  44. * from the HTTP response.
  45. *
  46. * This client does not support subscriptions.
  47. */
  48. export class DirectClientFactory extends AbstractClientFactory {
  49. static _clientFor(options) {
  50. const httpClient = this._createHttpClient(options);
  51. const httpResponseHandler = this._createHttpResponseHandler(options);
  52. const endpoint = new HttpEndpoint(httpClient, httpResponseHandler, options.routing);
  53. const requestFactory = ActorRequestFactory.create(options);
  54. const querying = new DirectQueryingClient(endpoint, requestFactory);
  55. const subscribing = new NoOpSubscribingClient(requestFactory);
  56. const commanding = new CommandingClient(endpoint, requestFactory);
  57. return new CompositeClient(querying, subscribing, commanding);
  58. }
  59. static createQuerying(options) {
  60. const httpClient = this._createHttpClient(options);
  61. const httpResponseHandler = this._createHttpResponseHandler(options);
  62. const endpoint = new HttpEndpoint(httpClient, httpResponseHandler, options.routing);
  63. const requestFactory = ActorRequestFactory.create(options);
  64. return new DirectQueryingClient(endpoint, requestFactory);
  65. }
  66. static createSubscribing(options) {
  67. const requestFactory = ActorRequestFactory.create(options);
  68. return new NoOpSubscribingClient(requestFactory);
  69. }
  70. /**
  71. * @override
  72. */
  73. static _ensureOptionsSufficient(options) {
  74. super._ensureOptionsSufficient(options);
  75. const messageForMissing = (option) =>
  76. `Unable to initialize a direct client. The ClientOptions.${option} not specified.`;
  77. if (!options.endpointUrl) {
  78. throw new Error(messageForMissing('endpointUrl'));
  79. }
  80. if (!options.actorProvider) {
  81. throw new Error(messageForMissing('actorProvider'));
  82. }
  83. }
  84. }
  85. /**
  86. * A {@link QueryingClient} which reads entity states directly from the server.
  87. */
  88. class DirectQueryingClient extends QueryingClient {
  89. /**
  90. * An internal `DirectQueryingClient` constructor.
  91. *
  92. * Use `FirebaseClient#usingFirebase()` for instantiation.
  93. *
  94. * @param {!HttpEndpoint} endpoint the server endpoint to execute queries and commands
  95. * @param {!ActorRequestFactory} actorRequestFactory a factory to instantiate the actor requests with
  96. *
  97. * @protected
  98. */
  99. constructor(endpoint, actorRequestFactory) {
  100. super(actorRequestFactory);
  101. this._endpoint = endpoint;
  102. }
  103. read(query) {
  104. const typeUrl = query.getTarget().getType();
  105. const targetClass = KnownTypes.classFor(typeUrl);
  106. const targetType = Type.of(targetClass, typeUrl);
  107. const responseParser = TypeParsers.parserFor('type.spine.io/spine.client.QueryResponse');
  108. return this._endpoint
  109. .query(query)
  110. .then(response => {
  111. const message = responseParser.fromObject(response);
  112. const entityStates = message.getMessageList();
  113. return entityStates.map(entity => DirectQueryingClient._unpack(entity, targetType));
  114. });
  115. }
  116. static _unpack(entity, targetType) {
  117. const unpacker = AnyPacker.unpack(entity.getState());
  118. return unpacker.as(targetType);
  119. }
  120. }