blob: 8ca76b4bf7e3644615ea33e65e7f45faf4be920e [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
"use strict";
import "./generated/request-reply_pb";
import {TypedValueSupport} from "./types";
import {Type, ValueSpec} from "./core";
const MutationType = proto.io.statefun.sdk.reqreply.FromFunction.PersistedValueMutation.MutationType;
// noinspection JSValidateJSDoc
export class Value<T> {
readonly name: string;
readonly #type: Type<T>;
#box: proto.io.statefun.sdk.reqreply.TypedValue | null;
#mutated: boolean;
#deleted: boolean;
/**
*
* @param {string} name
* @param {Type} type
* @param {proto.io.statefun.sdk.reqreply.TypedValue} box
*/
constructor(name: string, type: Type<T>, box: proto.io.statefun.sdk.reqreply.TypedValue | null) {
this.name = name;
this.#type = type;
this.#box = box;
this.#mutated = false;
this.#deleted = false;
}
getValue(): T | null {
if (this.#deleted) {
return null;
}
return TypedValueSupport.parseTypedValue(this.#box, this.#type);
}
setValue(jsObject: T | null) {
if (jsObject === undefined || jsObject === null) {
this.#mutated = true;
this.#deleted = true;
this.#box = null;
} else {
this.#mutated = true;
this.#deleted = false;
this.#box = TypedValueSupport.toTypedValue(jsObject, this.#type);
}
}
// internal helpers
asMutation(): proto.io.statefun.sdk.reqreply.FromFunction.PersistedValueMutation | null {
if (!this.#mutated) {
return null;
}
const mutation = new proto.io.statefun.sdk.reqreply.FromFunction.PersistedValueMutation();
mutation.setStateName(this.name);
if (this.#deleted) {
mutation.setMutationType(MutationType['DELETE']);
} else {
mutation.setMutationType(MutationType['MODIFY']);
mutation.setStateValue(this.#box);
}
return mutation;
}
static fromState<U>(persistedValue: proto.io.statefun.sdk.reqreply.ToFunction.PersistedValue, type: Type<U>) {
const name = persistedValue.getStateName();
return new Value<U>(name, type, persistedValue.getStateValue());
}
}
// noinspection JSValidateJSDoc
export class AddressScopedStorageFactory {
/**
* Tries to create an AddressScopedStorage. An object that contains each known state as a property on that object.
*
* @param {proto.io.statefun.sdk.reqreply.ToFunction.InvocationBatchRequest} invocationBatchRequest
* @param { [ValueSpec] } knownStates
* @returns either a list of missing ValueSpecs or a list of Values and an AddressScopedStorage.
*/
static tryCreateAddressScopedStorage(
invocationBatchRequest: proto.io.statefun.sdk.reqreply.ToFunction.InvocationBatchRequest,
knownStates: ValueSpec[]
) {
const receivedState = AddressScopedStorageFactory.indexActualState(invocationBatchRequest);
const {found, missing} = AddressScopedStorageFactory.extractKnownStates(knownStates, receivedState);
if (missing.length > 0) {
// the caller needs to respond with an IncompleteInvocationResponse,
// that contains all the missing specs.
return {
missing: missing,
values: null,
storage: null
}
}
// TODO: consider caching and setting the newly received states by calling setValue on each individual value.
const storage = AddressScopedStorageFactory.create(found);
return {
missing: null,
values: found,
storage: storage,
};
}
static extractKnownStates(knownStates: ValueSpec[], receivedState: Record<string, any>) {
const found = [];
const missing = [];
for (const spec of knownStates) {
if (!receivedState.hasOwnProperty(spec.name)) {
missing.push(spec);
continue;
}
const persistedValue = receivedState[spec.name];
found.push(Value.fromState(persistedValue, spec.type));
}
return {found, missing};
}
static indexActualState(
batchRequest: proto.io.statefun.sdk.reqreply.ToFunction.InvocationBatchRequest
): Record<string, any> {
const states = batchRequest.getStateList();
const gotState: Record<string, any> = {};
for (const state of states) {
gotState[state.getStateName()] = state;
}
return gotState;
}
/**
* @param {[Value]} values a list of initialize values
*/
static create(values: Value<unknown>[]) {
const storage = Object.create(null);
for (const v of values) {
Object.defineProperty(storage, v.name, {
get: () => v.getValue(),
set: (newValue) => v.setValue(newValue)
})
}
return Object.seal(storage);
}
static collectMutations(values: Value<unknown>[]): proto.io.statefun.sdk.reqreply.FromFunction.PersistedValueMutation[] {
return <proto.io.statefun.sdk.reqreply.FromFunction.PersistedValueMutation[]>values
.map(value => value.asMutation())
.filter(mutation => mutation !== null);
}
}