@azure-utils/durable-functions
Version:
Utilities for Azure Durable functions.
263 lines (259 loc) • 10.2 kB
JavaScript
//#region rolldown:runtime
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __commonJS = (cb, mod) => function() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
key = keys[i];
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
get: ((k) => from[k]).bind(null, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
value: mod,
enumerable: true
}) : target, mod));
//#endregion
const durable_functions = __toESM(require("durable-functions"));
//#region ../../node_modules/deepmerge/dist/cjs.js
var require_cjs = __commonJS({ "../../node_modules/deepmerge/dist/cjs.js"(exports, module) {
var isMergeableObject = function isMergeableObject$1(value) {
return isNonNullObject(value) && !isSpecial(value);
};
function isNonNullObject(value) {
return !!value && typeof value === "object";
}
function isSpecial(value) {
var stringValue = Object.prototype.toString.call(value);
return stringValue === "[object RegExp]" || stringValue === "[object Date]" || isReactElement(value);
}
var canUseSymbol = typeof Symbol === "function" && Symbol.for;
var REACT_ELEMENT_TYPE = canUseSymbol ? Symbol.for("react.element") : 60103;
function isReactElement(value) {
return value.$$typeof === REACT_ELEMENT_TYPE;
}
function emptyTarget(val) {
return Array.isArray(val) ? [] : {};
}
function cloneUnlessOtherwiseSpecified(value, options) {
return options.clone !== false && options.isMergeableObject(value) ? deepmerge(emptyTarget(value), value, options) : value;
}
function defaultArrayMerge(target, source, options) {
return target.concat(source).map(function(element) {
return cloneUnlessOtherwiseSpecified(element, options);
});
}
function getMergeFunction(key, options) {
if (!options.customMerge) return deepmerge;
var customMerge = options.customMerge(key);
return typeof customMerge === "function" ? customMerge : deepmerge;
}
function getEnumerableOwnPropertySymbols(target) {
return Object.getOwnPropertySymbols ? Object.getOwnPropertySymbols(target).filter(function(symbol) {
return Object.propertyIsEnumerable.call(target, symbol);
}) : [];
}
function getKeys(target) {
return Object.keys(target).concat(getEnumerableOwnPropertySymbols(target));
}
function propertyIsOnObject(object, property) {
try {
return property in object;
} catch (_) {
return false;
}
}
function propertyIsUnsafe(target, key) {
return propertyIsOnObject(target, key) && !(Object.hasOwnProperty.call(target, key) && Object.propertyIsEnumerable.call(target, key));
}
function mergeObject(target, source, options) {
var destination = {};
if (options.isMergeableObject(target)) getKeys(target).forEach(function(key) {
destination[key] = cloneUnlessOtherwiseSpecified(target[key], options);
});
getKeys(source).forEach(function(key) {
if (propertyIsUnsafe(target, key)) return;
if (propertyIsOnObject(target, key) && options.isMergeableObject(source[key])) destination[key] = getMergeFunction(key, options)(target[key], source[key], options);
else destination[key] = cloneUnlessOtherwiseSpecified(source[key], options);
});
return destination;
}
function deepmerge(target, source, options) {
options = options || {};
options.arrayMerge = options.arrayMerge || defaultArrayMerge;
options.isMergeableObject = options.isMergeableObject || isMergeableObject;
options.cloneUnlessOtherwiseSpecified = cloneUnlessOtherwiseSpecified;
var sourceIsArray = Array.isArray(source);
var targetIsArray = Array.isArray(target);
var sourceAndTargetTypesMatch = sourceIsArray === targetIsArray;
if (!sourceAndTargetTypesMatch) return cloneUnlessOtherwiseSpecified(source, options);
else if (sourceIsArray) return options.arrayMerge(target, source, options);
else return mergeObject(target, source, options);
}
deepmerge.all = function deepmergeAll(array, options) {
if (!Array.isArray(array)) throw new Error("first argument should be an array");
return array.reduce(function(prev, next) {
return deepmerge(prev, next, options);
}, {});
};
var deepmerge_1 = deepmerge;
module.exports = deepmerge_1;
} });
//#endregion
//#region src/DurableEntity.ts
var import_cjs = __toESM(require_cjs(), 1);
/**
* DurableEntity is a wrapper around Durable Functions entities.
*
* It provides a simple way to define an entity with a default state and operations.
* It also provides methods to get, reset, and update the state of the entity.
* The entity is registered with the Durable framework when the class is instantiated.
* The entity is identified by a name and a key.
*
* Note: keep Entity state small, limit to 16kB for reliable read operations.
*
* @example
* ```ts
* type MyEntityState = { foo: string; bar: number };
* const defaultState: MyEntityState = { foo: "bar", bar: 0 };
* const myEntity = new DurableEntity("my-entity", defaultState, {
* updateFoo: (input: { foo: string }) => ({ foo: input.foo }),
* updateBar: (input: { bar: number }) => ({ bar: input.bar }),
* })
* ```
*/
var DurableEntity = class DurableEntity {
name;
#defaultState;
#operations;
constructor(entityName, defaultState, operations) {
this.name = entityName;
this.#defaultState = defaultState;
this.#operations = operations;
durable_functions.app.entity(entityName, (context) => {
const state = context.df.getState(() => this.#defaultState);
const operationName = context.df.operationName;
context.info(`[${context.functionName}] Operation: ${operationName}`);
switch (operationName) {
case "get": return context.df.return(state);
case "reset": return context.df.setState(this.#defaultState);
case "update": {
const operationInput = context.df.getInput();
const newState = (0, import_cjs.default)(state, operationInput ?? {});
return context.df.setState(newState);
}
default: {
context.error(`[${context.functionName}] Unknown operation: ${operationName}`);
return;
}
}
});
}
/**
* Converts a string key to an EntityId.
* If the key is already an EntityId, it returns it as is.
*/
entityId(key) {
if (typeof key === "string") return new durable_functions.EntityId(this.name, key.includes("@") ? this.getKey(key) : key);
else return key;
}
getState(context, keyOrEntityId) {
const client = getDfClientFromContext(context);
const entityId = this.entityId(keyOrEntityId);
if ("callEntity" in client) return client.callEntity(entityId, "get");
return client.readEntityState(entityId).then((result) => result.entityState ?? this.#defaultState);
}
/**
* Resets the state of the entity to the default state.
*/
async resetState(context, keyOrEntityId) {
const client = getDfClientFromContext(context);
const entityId = this.entityId(keyOrEntityId);
client.signalEntity(entityId, "reset");
return this.#defaultState;
}
/**
* Updates the state of the entity with the new state.
* The new state is deeply-merged with the current state.
*/
async updateState(context, keyOrEntityId, newState) {
const client = getDfClientFromContext(context);
const entityId = this.entityId(keyOrEntityId);
client.signalEntity(entityId, "update", newState);
}
/**
* Returns the key of the entity.
* If the entityId is a string, it extracts the key from it.
* If the entityId is an EntityId, it returns the key property.
*/
getKey(entityId) {
if (typeof entityId === "string") return entityId.split("@").pop() ?? entityId;
else return entityId.key;
}
/**
* Returns a map of operations that can be used to update the state of the entity.
* The operations are defined in the constructor and are deeply-merged with the current state.
*/
operations(context, keyOrEntityId) {
const updateOperations = {};
Object.keys(this.#operations).forEach((operationName) => {
context.debug("Update Entity state", {
caller: context.functionName,
entity: this.entityId(keyOrEntityId),
operation: operationName
});
updateOperations[operationName] = (input) => this.updateState(context, keyOrEntityId, this.#operations[operationName](input));
});
return updateOperations;
}
/**
* Lists all instances of the entity.
*/
async listInstances(context, filter) {
return await DurableEntity.listInstances(context, this.name, filter);
}
static async listInstances(context, entityName, filter) {
try {
const client = getDfClientFromContext(context);
const instances = await client.getStatusBy({
...filter,
runtimeStatus: filter?.runtimeStatus ?? [durable_functions.OrchestrationRuntimeStatus.Running]
});
const filteredInstances = instances.filter((instance) => instance.instanceId.startsWith(entityName ? `@${entityName}@` : "@"));
return filteredInstances.map((instance) => {
const input = instance.input;
return {
name: instance.name,
instanceId: instance.instanceId,
createdTime: instance.createdTime,
lastUpdatedTime: instance.lastUpdatedTime,
key: instance.instanceId.split("@").pop() ?? instance.instanceId,
state: input.exists && input.state && typeof input.state === "string" ? JSON.parse(String(input.state)) : null
};
});
} catch (error) {
context.error("Error listing instances", {
error,
entityName,
filter
});
return [];
}
}
};
function getDfClientFromContext(context) {
if ("df" in context) return context.df;
else return durable_functions.getClient(context);
}
//#endregion
exports.DurableEntity = DurableEntity;
//# sourceMappingURL=DurableEntity.cjs.map