UNPKG

@lightbend/akkaserverless-javascript-sdk

Version:
231 lines (206 loc) 8.63 kB
/* * Copyright 2021 Lightbend Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ const fs = require('fs'); const protobufHelper = require('./protobuf-helper'); const grpc = require('@grpc/grpc-js'); const protoLoader = require('@grpc/proto-loader'); const EventSourcedEntityServices = require('./event-sourced-entity-support'); const AkkaServerless = require('./akkaserverless'); const { GrpcUtil } = require('./grpc-util'); const eventSourcedEntityServices = new EventSourcedEntityServices(); /** * An event sourced entity command handler. * * @callback module:akkaserverless.EventSourcedEntity~commandHandler * @param {Object} command The command message, this will be of the type of the gRPC service call input type. * @param {module:akkaserverless.Serializable} state The entity state. * @param {module:akkaserverless.EventSourcedEntity.EventSourcedEntityCommandContext} context The command context. * @returns {undefined|Object|module:akkaserverless.replies.Reply} The message to reply with, it must match the gRPC service call output type for this * command, or if a Reply is returned, contain an object that matches the output type. */ /** * An event sourced entity event handler. * * @callback module:akkaserverless.EventSourcedEntity~eventHandler * @param {module:akkaserverless.Serializable} event The event. * @param {module:akkaserverless.Serializable} state The entity state. * @returns {module:akkaserverless.Serializable} The new entity state. */ /** * An event sourced entity behavior. * * @typedef module:akkaserverless.EventSourcedEntity~behavior * @property {Object<string, module:akkaserverless.EventSourcedEntity~commandHandler>} commandHandlers The command handlers. * * The names of the properties must match the names of the service calls specified in the gRPC descriptor for this * event sourced entities service. * @property {Object<string, module:akkaserverless.EventSourcedEntity~eventHandler>} eventHandlers The event handlers. * * The names of the properties must match the short names of the events. */ /** * An event sourced entity behavior callback. * * This callback takes the current entity state, and returns a set of handlers to handle commands and events for it. * * @callback module:akkaserverless.EventSourcedEntity~behaviorCallback * @param {module:akkaserverless.Serializable} state The entity state. * @returns {module:akkaserverless.EventSourcedEntity~behavior} The new entity state. */ /** * Initial state callback. * * This is invoked if the entity is started with no snapshot. * * @callback module:akkaserverless.EventSourcedEntity~initialCallback * @param {string} entityId The entity id. * @returns {module:akkaserverless.Serializable} The entity state. */ /** * Options for an event sourced entity. * * @typedef module:akkaserverless.EventSourcedEntity~options * @property {number} [snapshotEvery=100] A snapshot will be persisted every time this many events are emitted. * It is strongly recommended to not disable snapshotting unless it is known that * event sourced entities will never have more than 100 events (in which case * the default will anyway not trigger any snapshots) * @property {array<string>} [includeDirs=["."]] The directories to include when looking up imported protobuf files. * @property {boolean} [serializeAllowPrimitives=false] Whether serialization of primitives should be supported when * serializing events and snapshots. * @property {boolean} [serializeFallbackToJson=false] Whether serialization should fallback to using JSON if an event * or snapshot can't be serialized as a protobuf. * @property {array<string>} [forwardHeaders=[]] request headers to be forwarded as metadata to the event sourced entity * @property {module:akkaserverless.EventSourcedEntity~entityPassivationStrategy} [entityPassivationStrategy] Entity passivation strategy to use. */ /** * Entity passivation strategy for an event sourced entity. * * @typedef module:akkaserverless.EventSourcedEntity~entityPassivationStrategy * @property {number} [timeout] Passivation timeout (in milliseconds). */ /** * An event sourced entity. * * @memberOf module:akkaserverless * @implements module:akkaserverless.Entity */ class EventSourcedEntity { /** * Create a new event sourced entity. * * @constructs * @param {string|string[]} desc A descriptor or list of descriptors to parse, containing the service to serve. * @param {string} serviceName The fully qualified name of the service that provides this entities interface. * @param {string} entityType The entity type name for all event source entities of this type. This will be prefixed * onto the entityId when storing the events for this entity. Be aware that the * chosen name must be stable through the entity lifecycle. Never change it after deploying * a service that stored data of this type * @param {module:akkaserverless.EventSourcedEntity~options=} options The options for this event sourced entity */ constructor(desc, serviceName, entityType, options) { /** * @type {module:akkaserverless.EventSourcedEntity~options} */ this.options = { ...{ entityType: entityType, snapshotEvery: 100, includeDirs: ['.'], serializeAllowPrimitives: false, serializeFallbackToJson: false, }, ...options, }; if (!entityType) throw Error('EntityType must contain a name'); const allIncludeDirs = protobufHelper.moduleIncludeDirs.concat( this.options.includeDirs, ); this.root = protobufHelper.loadSync(desc, allIncludeDirs); /** * @type {string} */ this.serviceName = serviceName; // Eagerly lookup the service to fail early /** * @type {protobuf.Service} */ this.service = this.root.lookupService(serviceName); const packageDefinition = protoLoader.loadSync(desc, { includeDirs: allIncludeDirs, }); this.grpc = grpc.loadPackageDefinition(packageDefinition); /** * Access to gRPC clients (with promisified unary methods). * * @type module:akkaserverless.GrpcClientLookup */ this.clients = GrpcUtil.clientCreators(this.root, this.grpc); } /** * @return {string} event sourced entity component type. */ componentType() { return eventSourcedEntityServices.componentType(); } /** * Lookup a protobuf message type. * * This is provided as a convenience to lookup protobuf message types for use with events and snapshots. * * @param {string} messageType The fully qualified name of the type to lookup. * @return {protobuf.Type} The protobuf message type. */ lookupType(messageType) { return this.root.lookupType(messageType); } /** * The initial state callback. * * @member module:akkaserverless.EventSourcedEntity#initial * @type module:akkaserverless.EventSourcedEntity~initialCallback */ /** * Set the initial state callback. * * @param {module:akkaserverless.EventSourcedEntity~initialCallback} callback The initial state callback. * @return {module:akkaserverless.EventSourcedEntity} This entity. */ setInitial(callback) { this.initial = callback; return this; } /** * The behavior callback. * * @member module:akkaserverless.EventSourcedEntity#behavior * @type module:akkaserverless.EventSourcedEntity~behaviorCallback */ /** * Set the behavior callback. * * @param {module:akkaserverless.EventSourcedEntity~behaviorCallback} callback The behavior callback. * @return {module:akkaserverless.EventSourcedEntity} This entity. */ setBehavior(callback) { this.behavior = callback; return this; } register(allComponents) { eventSourcedEntityServices.addService(this, allComponents); return eventSourcedEntityServices; } } module.exports = EventSourcedEntity;