Error: Fallback to JSON serialization supported, but object does not define a type property

I am using the latest js codegen (0.30.0). I have a proto buf that is following the sample provided as the starting point. npm run build runs successfully. npm run start runs successfully. Local docker running OK.

When I go to set some state though, I get an error in the proxy:

 Error handling command 'UpdateUser': Error: Fallback to JSON serialization supported, but object does not define a type property: { active: false }","severity":"ERROR","thread":"akkaserverless-proxy-akka.actor.default-dispatcher-2"}

and in the node app:

Error: Fallback to JSON serialization supported, but object does not define a type property: { active: false }
    at Function.serialize (/Users/jeremypollock/development/lightbend/projects/sample_apps/akka-serverless-demo-javascript-user-persisted-session/node_modules/@lightbend/akkaserverless-javascript-sdk/dist/src/protobuf-any.js:199:27)
    at ValueEntitySupport.serialize (/Users/jeremypollock/development/lightbend/projects/sample_apps/akka-serverless-demo-javascript-user-persisted-session/node_modules/@lightbend/akkaserverless-javascript-sdk/dist/src/value-entity-support.js:40:27)
    at Object.ctx.context.updateState (/Users/jeremypollock/development/lightbend/projects/sample_apps/akka-serverless-demo-javascript-user-persisted-session/node_modules/@lightbend/akkaserverless-javascript-sdk/dist/src/value-entity-support.js:112:55)
    at Object.UpdateUser (file:///Users/jeremypollock/development/lightbend/projects/sample_apps/akka-serverless-demo-javascript-user-persisted-session/src/userpersistedsession.js:42:9)
    at ValueEntityHandler.<anonymous> (/Users/jeremypollock/development/lightbend/projects/sample_apps/akka-serverless-demo-javascript-user-persisted-session/node_modules/@lightbend/akkaserverless-javascript-sdk/dist/src/value-entity-support.js:123:85)
    at Generator.next (<anonymous>)
    at /Users/jeremypollock/development/lightbend/projects/sample_apps/akka-serverless-demo-javascript-user-persisted-session/node_modules/tslib/tslib.js:117:75
    at new Promise (<anonymous>)
    at Object.__awaiter (/Users/jeremypollock/development/lightbend/projects/sample_apps/akka-serverless-demo-javascript-user-persisted-session/node_modules/tslib/tslib.js:113:16)
    at /Users/jeremypollock/development/lightbend/projects/sample_apps/akka-serverless-demo-javascript-user-persisted-session/node_modules/@lightbend/akkaserverless-javascript-sdk/dist/src/value-entity-support.js:88:50

Protobufs:

// This is the public API offered by your entity.
syntax = "proto3";

import "google/protobuf/empty.proto";
import "akkaserverless/annotations.proto";
import "google/api/annotations.proto";
import "user_persisted_session_domain.proto";

package com.example;

message UpdateUserPersistedSession {
    string user_persisted_session_id = 1 [(akkaserverless.field).entity_key = true];
    bool active = 2;
}

message GetOnlineStatus {
    string user_persisted_session_id = 1 [(akkaserverless.field).entity_key = true];
}

message GetActivity {
    string user_persisted_session_id = 1 [(akkaserverless.field).entity_key = true];
}

message GetRecommendations {
    string user_persisted_session_id = 1 [(akkaserverless.field).entity_key = true];
}

message Recommendations {
    repeated com.example.domain.Recommendation recommendation = 1;
}

message OnlineStatus {
    bool active = 1;
}

service UserPersistedSessionService {
    option (akkaserverless.service) = {
        type : SERVICE_TYPE_ENTITY
        component : ".domain.UserPersistedSession"
    };

    rpc UpdateUser(UpdateUserPersistedSession) returns (google.protobuf.Empty);
    rpc GetOnlineStatusForUser(GetOnlineStatus) returns (OnlineStatus);
    rpc GetLastActivityForUser(GetActivity) returns (domain.LastActivity);
    rpc GetRecommendationsForUser(GetRecommendations) returns (Recommendations);
}

syntax = "proto3";

package com.example.domain;
import "akkaserverless/annotations.proto";

option (akkaserverless.file).value_entity = {
    name: "UserPersistedSession"
    entity_type: "user_persisted_session"
    state: "UserPersistedSessionState"
};

message UserPersistedSessionState {
    bool active = 1;
    Profile profile = 2;
    LastActivity lastActivity = 3;
    repeated Recommendation recommendations  = 4;
}

message Profile {
    int32 externalId = 1;
    string firstName = 2;
    string lastName = 3;
}

message LastActivity {
    enum Type {
        WEB = 0;
        MOBILE = 1;
        CHAT = 2;
    };
    Type type = 1;
    int64 timestamp = 2;
}

message Recommendation {
    int32 id = 1;
    string name = 2;
    string description = 3;
    string uri = 4;
}

Service code:

/* This code was initialised by Akka Serverless tooling.
 * As long as this file exists it will not be re-generated.
 * You are free to make changes to this file.
 */

import akkaserverless from "@lightbend/akkaserverless-javascript-sdk";
const ValueEntity = akkaserverless.ValueEntity;

/**
 * Type definitions.
 * These types have been generated based on your proto source.
 * A TypeScript aware editor such as VS Code will be able to leverage them to provide hinting and validation.
 * 
 * State; the serialisable and persistable state of the entity
 * @typedef { import("../lib/generated/userpersistedsessionservice").State } State
 * 
 * UserPersistedSessionService; a strongly typed extension of ValueEntity derived from your proto source
 * @typedef { import("../lib/generated/userpersistedsessionservice").UserPersistedSessionService } UserPersistedSessionService
 */

/**
 * @type UserPersistedSessionService
 */
const entity = new ValueEntity(
  [
    "user_persisted_session_domain.proto",
    "user_persisted_session_api.proto"
  ],
  "com.example.UserPersistedSessionService",
  "user_persisted_session",
  {
    includeDirs: ["./proto"],
    serializeFallbackToJson: true
  }
);

entity.setInitial(entityId => ({}));

entity.setCommandHandlers({
  UpdateUser(command, state, ctx) {
    state.active = command.active;
    ctx.updateState(state);
    return {};
  },
  GetOnlineStatusForUser(command, state, ctx) {
    return { active: state.value };
  },
  GetLastActivityForUser(command, state, ctx) {
    return ctx.fail("The command handler for `GetLastActivityForUser` is not implemented, yet");
  },
  GetRecommendationsForUser(command, state, ctx) {
    return ctx.fail("The command handler for `GetRecommendationsForUser` is not implemented, yet");
  }
});

export default entity;

I think that everything is wired correctly but obviously not. Any pointers on where to look?

Reproduced using unchanged service (from code gen) and using docs. Implementing Value Entities in JavaScript :: Akka Serverless Documentation and Implementing Value Entities in JavaScript :: Akka Serverless Documentation were critical pieces of information.

const CounterState = entity.lookupType("com.example.domain.CounterState");

seems needed.

I added const UserPersistedSessionState = entity.lookupType("com.example.domain.UserPersistedSessionState"); to my service file and now it works.

Actually none of this fixed things. CC: @retgits

I think the issue is in the docs:

function increase(command, state, ctx) {
  if (command.value < 0) {
    ctx.fail(`Increase requires a positive value. It was [${command.value}].`);
  }
  state.value += command.value;
  ctx.updateState(state);
  return {};
}

did not work for me

When you’re using the serializeFallbackToJson flag, the state of your function needs to have an extra field called type with a value. This allows Akka Serverless to serialize and deserialize the state properly.

To make your code work, you can change your function’s return to:

return { type: 'object', active: state.value };

Note that the value entity state is serialized as JSON, as expected, but this will also be the state when subscribing to value entity changes through the eventing system.

To use protobuf types for state:

First remove the serializeFallbackToJson option, so that it doesn’t try to fallback to JSON but warns if the updated state is not a protobuf message.

Look up the protobuf message type:

const UserPersistedSessionState = entity.lookupType("com.example.domain.UserPersistedSessionState");

And then create a protobuf message object for your initial state:

entity.setInitial(entityId => UserPersistedSessionState.create());

It looks like creating protobuf message objects may have been missed. You can also pass objects to the create method to set any values. Described in the docs on using protobuf types and creating initial state.

You can continue to mutate the current state and then persist with updateState as before.