UNPKG

@azure-utils/durable-functions

Version:
1 lines 18.8 kB
{"version":3,"file":"DurableEntity.cjs","names":["isMergeableObject","entityName: string","defaultState: State","operations: Operations","#defaultState","#operations","context: df.EntityContext<State>","state: State","key: string | df.EntityId","df","context: df.OrchestrationContext | InvocationContext","keyOrEntityId: string | df.EntityId","context: InvocationContext | df.OrchestrationContext","newState: DeepPartial<State>","entityId: string | df.EntityId","updateOperations: Record<string, (input: never) => Promise<void>>","context: InvocationContext","filter?: df.OrchestrationFilter","entityName?: string"],"sources":["../../../node_modules/deepmerge/dist/cjs.js","../src/DurableEntity.ts"],"sourcesContent":["'use strict';\n\nvar isMergeableObject = function isMergeableObject(value) {\n\treturn isNonNullObject(value)\n\t\t&& !isSpecial(value)\n};\n\nfunction isNonNullObject(value) {\n\treturn !!value && typeof value === 'object'\n}\n\nfunction isSpecial(value) {\n\tvar stringValue = Object.prototype.toString.call(value);\n\n\treturn stringValue === '[object RegExp]'\n\t\t|| stringValue === '[object Date]'\n\t\t|| isReactElement(value)\n}\n\n// see https://github.com/facebook/react/blob/b5ac963fb791d1298e7f396236383bc955f916c1/src/isomorphic/classic/element/ReactElement.js#L21-L25\nvar canUseSymbol = typeof Symbol === 'function' && Symbol.for;\nvar REACT_ELEMENT_TYPE = canUseSymbol ? Symbol.for('react.element') : 0xeac7;\n\nfunction isReactElement(value) {\n\treturn value.$$typeof === REACT_ELEMENT_TYPE\n}\n\nfunction emptyTarget(val) {\n\treturn Array.isArray(val) ? [] : {}\n}\n\nfunction cloneUnlessOtherwiseSpecified(value, options) {\n\treturn (options.clone !== false && options.isMergeableObject(value))\n\t\t? deepmerge(emptyTarget(value), value, options)\n\t\t: value\n}\n\nfunction defaultArrayMerge(target, source, options) {\n\treturn target.concat(source).map(function(element) {\n\t\treturn cloneUnlessOtherwiseSpecified(element, options)\n\t})\n}\n\nfunction getMergeFunction(key, options) {\n\tif (!options.customMerge) {\n\t\treturn deepmerge\n\t}\n\tvar customMerge = options.customMerge(key);\n\treturn typeof customMerge === 'function' ? customMerge : deepmerge\n}\n\nfunction getEnumerableOwnPropertySymbols(target) {\n\treturn Object.getOwnPropertySymbols\n\t\t? Object.getOwnPropertySymbols(target).filter(function(symbol) {\n\t\t\treturn Object.propertyIsEnumerable.call(target, symbol)\n\t\t})\n\t\t: []\n}\n\nfunction getKeys(target) {\n\treturn Object.keys(target).concat(getEnumerableOwnPropertySymbols(target))\n}\n\nfunction propertyIsOnObject(object, property) {\n\ttry {\n\t\treturn property in object\n\t} catch(_) {\n\t\treturn false\n\t}\n}\n\n// Protects from prototype poisoning and unexpected merging up the prototype chain.\nfunction propertyIsUnsafe(target, key) {\n\treturn propertyIsOnObject(target, key) // Properties are safe to merge if they don't exist in the target yet,\n\t\t&& !(Object.hasOwnProperty.call(target, key) // unsafe if they exist up the prototype chain,\n\t\t\t&& Object.propertyIsEnumerable.call(target, key)) // and also unsafe if they're nonenumerable.\n}\n\nfunction mergeObject(target, source, options) {\n\tvar destination = {};\n\tif (options.isMergeableObject(target)) {\n\t\tgetKeys(target).forEach(function(key) {\n\t\t\tdestination[key] = cloneUnlessOtherwiseSpecified(target[key], options);\n\t\t});\n\t}\n\tgetKeys(source).forEach(function(key) {\n\t\tif (propertyIsUnsafe(target, key)) {\n\t\t\treturn\n\t\t}\n\n\t\tif (propertyIsOnObject(target, key) && options.isMergeableObject(source[key])) {\n\t\t\tdestination[key] = getMergeFunction(key, options)(target[key], source[key], options);\n\t\t} else {\n\t\t\tdestination[key] = cloneUnlessOtherwiseSpecified(source[key], options);\n\t\t}\n\t});\n\treturn destination\n}\n\nfunction deepmerge(target, source, options) {\n\toptions = options || {};\n\toptions.arrayMerge = options.arrayMerge || defaultArrayMerge;\n\toptions.isMergeableObject = options.isMergeableObject || isMergeableObject;\n\t// cloneUnlessOtherwiseSpecified is added to `options` so that custom arrayMerge()\n\t// implementations can use it. The caller may not replace it.\n\toptions.cloneUnlessOtherwiseSpecified = cloneUnlessOtherwiseSpecified;\n\n\tvar sourceIsArray = Array.isArray(source);\n\tvar targetIsArray = Array.isArray(target);\n\tvar sourceAndTargetTypesMatch = sourceIsArray === targetIsArray;\n\n\tif (!sourceAndTargetTypesMatch) {\n\t\treturn cloneUnlessOtherwiseSpecified(source, options)\n\t} else if (sourceIsArray) {\n\t\treturn options.arrayMerge(target, source, options)\n\t} else {\n\t\treturn mergeObject(target, source, options)\n\t}\n}\n\ndeepmerge.all = function deepmergeAll(array, options) {\n\tif (!Array.isArray(array)) {\n\t\tthrow new Error('first argument should be an array')\n\t}\n\n\treturn array.reduce(function(prev, next) {\n\t\treturn deepmerge(prev, next, options)\n\t}, {})\n};\n\nvar deepmerge_1 = deepmerge;\n\nmodule.exports = deepmerge_1;\n","/**\n * @module\n * Exports a class to create, register and manage a Durable Entity.\n *\n * > Note: This module is to be used inside Azure Functions app only.\n */\n\nimport type { InvocationContext } from \"@azure/functions\";\nimport * as df from \"durable-functions\";\nimport deepMerge from \"deepmerge\";\n\n/**\n * DurableEntity is a wrapper around Durable Functions entities.\n *\n * It provides a simple way to define an entity with a default state and operations.\n * It also provides methods to get, reset, and update the state of the entity.\n * The entity is registered with the Durable framework when the class is instantiated.\n * The entity is identified by a name and a key.\n *\n * Note: keep Entity state small, limit to 16kB for reliable read operations.\n *\n * @example\n * ```ts\n * type MyEntityState = { foo: string; bar: number };\n * const defaultState: MyEntityState = { foo: \"bar\", bar: 0 };\n * const myEntity = new DurableEntity(\"my-entity\", defaultState, {\n * updateFoo: (input: { foo: string }) => ({ foo: input.foo }),\n * updateBar: (input: { bar: number }) => ({ bar: input.bar }),\n * })\n * ```\n */\nexport class DurableEntity<\n State extends Record<string, unknown>,\n Operations extends NoInfer<\n Readonly<Record<string, (input: never) => DeepPartial<State>>>\n >\n> {\n name: string;\n #defaultState: State;\n #operations: Operations;\n\n constructor(entityName: string, defaultState: State, operations: Operations) {\n this.name = entityName;\n this.#defaultState = defaultState;\n this.#operations = operations;\n\n // Register the entity with the Durable framework\n df.app.entity(entityName, (context: df.EntityContext<State>) => {\n const state: State = context.df.getState(() => this.#defaultState)!;\n const operationName = context.df.operationName;\n\n context.info(`[${context.functionName}] Operation: ${operationName}`);\n\n switch (operationName) {\n case \"get\":\n return context.df.return(state);\n case \"reset\":\n return context.df.setState(this.#defaultState);\n case \"update\": {\n const operationInput = context.df.getInput();\n const newState = deepMerge(state, operationInput ?? {}) as State;\n return context.df.setState(newState);\n }\n default: {\n context.error(\n `[${context.functionName}] Unknown operation: ${operationName}`\n );\n return;\n }\n }\n });\n }\n\n /**\n * Converts a string key to an EntityId.\n * If the key is already an EntityId, it returns it as is.\n */\n entityId(key: string | df.EntityId): df.EntityId {\n if (typeof key === \"string\") {\n return new df.EntityId(\n this.name,\n key.includes(\"@\") ? this.getKey(key) : key\n );\n } else {\n return key;\n }\n }\n\n /**\n * Gets the durable task which yields the current state of the entity.\n *\n * Note: Can be only used in orchestrator functions.\n * The yielded value is not type-safe but can be safely casted to the Entity's state type.\n */\n getState(\n context: df.OrchestrationContext,\n keyOrEntityId: string | df.EntityId\n ): df.Task;\n /**\n * Gets the current state of the entity.\n * If the entity does not exists, it returns the default state.\n *\n * Note: Can be only used in client functions (HTTP triggers, etc.)\n */\n getState(\n context: InvocationContext,\n keyOrEntityId: string | df.EntityId\n ): Promise<State>;\n getState(\n context: df.OrchestrationContext | InvocationContext,\n keyOrEntityId: string | df.EntityId\n ): df.Task | Promise<State> {\n const client = getDfClientFromContext(context);\n const entityId = this.entityId(keyOrEntityId);\n\n if (\"callEntity\" in client) {\n return (client as unknown as df.DurableOrchestrationContext).callEntity(\n entityId,\n \"get\"\n );\n }\n\n return client\n .readEntityState<State>(entityId)\n .then((result) => result.entityState ?? this.#defaultState);\n }\n\n /**\n * Resets the state of the entity to the default state.\n */\n async resetState(\n context: InvocationContext | df.OrchestrationContext,\n keyOrEntityId: string | df.EntityId\n ): Promise<State> {\n const client = getDfClientFromContext(context);\n const entityId = this.entityId(keyOrEntityId);\n client.signalEntity(entityId, \"reset\");\n\n return this.#defaultState;\n }\n\n /**\n * Updates the state of the entity with the new state.\n * The new state is deeply-merged with the current state.\n */\n async updateState(\n context: InvocationContext | df.OrchestrationContext,\n keyOrEntityId: string | df.EntityId,\n newState: DeepPartial<State>\n ): Promise<void> {\n const client = getDfClientFromContext(context);\n const entityId = this.entityId(keyOrEntityId);\n client.signalEntity(entityId, \"update\", newState);\n }\n\n /**\n * Returns the key of the entity.\n * If the entityId is a string, it extracts the key from it.\n * If the entityId is an EntityId, it returns the key property.\n */\n getKey(entityId: string | df.EntityId): string {\n if (typeof entityId === \"string\") {\n return entityId.split(\"@\").pop() ?? entityId;\n } else {\n return entityId.key;\n }\n }\n\n /**\n * Returns a map of operations that can be used to update the state of the entity.\n * The operations are defined in the constructor and are deeply-merged with the current state.\n */\n operations(\n context: InvocationContext | df.OrchestrationContext,\n keyOrEntityId: string | df.EntityId\n ): TransformedOperations<Operations> {\n const updateOperations: Record<string, (input: never) => Promise<void>> =\n {};\n\n Object.keys(this.#operations).forEach((operationName) => {\n context.debug(\"Update Entity state\", {\n caller: context.functionName,\n entity: this.entityId(keyOrEntityId),\n operation: operationName,\n });\n updateOperations[operationName] = (input) =>\n this.updateState(\n context,\n keyOrEntityId,\n this.#operations[operationName]!(input)\n );\n });\n\n return updateOperations as TransformedOperations<Operations>;\n }\n\n /**\n * Lists all instances of the entity.\n */\n async listInstances(\n context: InvocationContext,\n filter?: df.OrchestrationFilter\n ): Promise<Array<DurableEntityStatus<State>>> {\n return await DurableEntity.listInstances<State>(context, this.name, filter);\n }\n\n static async listInstances<T = unknown>(\n context: InvocationContext,\n entityName?: string,\n filter?: df.OrchestrationFilter\n ): Promise<DurableEntityStatus<T>[]> {\n try {\n const client = getDfClientFromContext(context);\n const instances = await client.getStatusBy({\n ...filter,\n runtimeStatus: filter?.runtimeStatus ?? [\n df.OrchestrationRuntimeStatus.Running,\n ],\n });\n const filteredInstances = instances.filter((instance) =>\n instance.instanceId.startsWith(entityName ? `@${entityName}@` : \"@\")\n );\n\n return filteredInstances.map((instance) => {\n const input = instance.input as { state: T; exists: boolean };\n return {\n name: instance.name,\n instanceId: instance.instanceId,\n createdTime: instance.createdTime,\n lastUpdatedTime: instance.lastUpdatedTime,\n key: instance.instanceId.split(\"@\").pop() ?? instance.instanceId,\n state:\n input.exists && input.state && typeof input.state === \"string\"\n ? JSON.parse(String(input.state))\n : null,\n };\n });\n } catch (error) {\n context.error(\"Error listing instances\", { error, entityName, filter });\n return [];\n }\n }\n}\n\n/**\n * DurableEntityStatus is the status of a Durable Entity.\n */\nexport type DurableEntityStatus<State> = Pick<\n df.DurableOrchestrationStatus,\n \"name\" | \"instanceId\" | \"createdTime\" | \"lastUpdatedTime\"\n> & { state: State | null; key: string };\n\n/**\n * Helper function to get the Durable Functions client from the context.\n */\nfunction getDfClientFromContext(\n context: df.OrchestrationContext\n): df.DurableOrchestrationContext;\nfunction getDfClientFromContext(context: InvocationContext): df.DurableClient;\nfunction getDfClientFromContext(\n context: InvocationContext | df.OrchestrationContext\n) {\n if (\"df\" in context) {\n return context.df;\n } else {\n return df.getClient(context);\n }\n}\n\ntype TransformedOperations<\n T extends Readonly<Record<string, (input: never) => unknown>>\n> = {\n [Prop in keyof T]: T[Prop] extends () => unknown\n ? () => Promise<void>\n : T[Prop] extends (input: infer I) => unknown\n ? (input: I) => Promise<void>\n : never;\n};\n\ntype DeepPartial<T> = T extends object\n ? { [P in keyof T]?: DeepPartial<T[P]> }\n : Partial<T>;\n"],"x_google_ignoreList":[0],"mappings":"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"}