import { JsonPointerString, JsonPointerStructureArray, MetaInfo } from './MetaInfoProducer.js'; import { Levels, StatedLogger } from "./ConsoleLogger.js"; import { TimerManager } from "./TimerManager.js"; import { debounce } from "./utils/debounce.js"; import { rateLimit } from "./utils/rateLimit.js"; import { ExecutionStatus } from "./ExecutionStatus.js"; import { env } from "./utils/env.js"; import { LifecycleManager } from "./LifecycleManager.js"; import { DataFlowNode, FlowOpt } from "./DataFlow.js"; import { ExecutionPlan, Planner, SerializableExecutionPlan } from "./Planner.js"; export type MetaInfoMap = Record; export type Snapshot = { template: object; output: any; options: {}; mvcc: any; metaInfoByJsonPointer: Record; plans: SerializableExecutionPlan[]; }; export type StatedError = { error: { message: string; name?: string; stack?: string | null; }; }; export type Op = "initialize" | "set" | "delete" | "eval" | "forceSetInternal" | "noop"; export type Fork = { forkId: ForkId; output: object; }; export type ForkId = string; export type PlanStep = { jsonPtr: JsonPointerString; data?: any; op?: Op; output: object; forkStack: Fork[]; forkId: string; didUpdate: boolean; circular?: boolean; }; export type Mutation = { jsonPtr: JsonPointerString; op: Op; data: any; }; export type Transaction = { op: "transaction"; mutations: Mutation[]; }; /** * a FunctionGenerator is used to generate functions that need the context of which expression they were called from * which is made available to them in the MetaInf */ export type FunctionGenerator = (context: T, templateProcessor?: TemplateProcessor) => Promise<(...args: any[]) => Promise> | ((...args: any[]) => any); /** * A callback function that is triggered when data changes. * * This callback supports both the legacy `removed` boolean parameter and the new `op` parameter * to avoid breaking existing clients while allowing for more descriptive operations. * * - When `removed` is provided, it indicates whether the data was removed (`true` for delete, `false` for set). * - When `op` is provided, it specifies the operation performed on the data: * - `"set"`: The data was set or updated. * - `"delete"`: The data was deleted. * - `"forceSetInternal"`: A forced internal set operation was performed. * * Both `removed` and `op` are optional. If both are provided, `op` takes precedence in interpreting the operation. * * @param data - The data that was changed and is pointed to by ptr * @param ptr - The JSON pointer string indicating where in the root object the change occurred. In the case of callbacks * registered on "/", ptr will be an array of JSON pointers into the `data` field represents the root object. * @param removed - (optional) A boolean indicating whether the data was removed. `true` for delete, `false` for set. * @param op - (optional) A string describing the operation. Can be `"set"`, `"delete"`, or `"forceSetInternal"`. */ export type DataChangeCallback = (data: any, ptr: JsonPointerString | JsonPointerString[], removed?: boolean, op?: Op) => void; /** * This is the main TemplateProcessor class. * * @remarks * The TemplateProcessor class is responsible for processing templates and interfacing with your program that may * provide changing inputs over time and react to changes with callbacks. Many examples can be found in * `src/test/TemplateProcessor.test.js` * * @example Initialize a simple template stored in local object 'o' * ``` * //initialize a simple template stored in local object 'o' * test("test 6", async () => { * const o = { * "a": 10, * "b": [ * "../${a}", * ] * }; * const tp = new TemplateProcessor(o); * await tp.initialize(); * expect(o).toEqual({ * "a": 10, * "b": [10] * }); * }); * ``` * @example Pass the TemplateProcessor a context containing a function named `nozzle` and a variable named `ZOINK` * ``` * //Pass the TemplateProcessor a context containing a function named `nozzle` and a variable named `ZOINK` * test("context", async () => { * const nozzle = (something) => "nozzle got some " + something; * const context = {"nozzle": nozzle, "ZOINK": "ZOINK"} * const tp = new TemplateProcessor({ * "a": "${$nozzle($ZOINK)}" * }, context); * await tp.initialize(); * expect(tp.output).toEqual( * { * "a": "nozzle got some ZOINK", * } * ); * }); * ``` * @example Parse template from JSON or YAML * ``` * it('should correctly identify and parse JSON string', async () => { * const jsonString = '{"key": "value"}'; * const instance = TemplateProcessor.fromString(jsonString); * await instance.initialize(); * expect(instance).toBeInstanceOf(TemplateProcessor); * expect(instance.output).toEqual({ key: "value" }); // Assuming parsedObject is publicly accessible * }); * * it('should correctly identify and parse YAML string using ---', async () => { * const yamlString = `--- * key: value`; * const instance = TemplateProcessor.fromString(yamlString); * await instance.initialize(); * expect(instance).toBeInstanceOf(TemplateProcessor); * expect(instance.output).toEqual({ key: "value" }); * }); * ``` * @example React to changes using data change callbacks on various locations in the template * ``` * test("test 1", async () => { * const tp = new TemplateProcessor({ * "a": "aaa", * "b": "${a}" * }); * await tp.initialize(); * const received = []; * tp.setDataChangeCallback("/a", (data, jsonPtr) => { * received.push({data, jsonPtr}) * }); * tp.setDataChangeCallback("/b", (data, jsonPtr) => { * received.push({data, jsonPtr}) * }); * tp.setDataChangeCallback("/", (data, jsonPtr) => { * received.push({data, jsonPtr}) * }); * await tp.setData("/a", 42); * expect(received).toEqual([ * { * "data": 42, * "jsonPtr": "/a" * }, * { * "data": 42, * "jsonPtr": "/b" * }, * { * "data": { * "a": 42, * "b": 42 * }, * "jsonPtr": [ * "/a", * "/b" * ] * } * ]); * }); * ``` */ export default class TemplateProcessor { static NOOP: symbol; private isExecutingPlan; private providedContext; /** * Loads a template and initializes a new template processor instance. * * @static * @param {Object} template - The template data to be processed. * @param {Object} [context={}] - Optional context data for the template. * @param options * @returns {Promise} Returns an initialized instance of `TemplateProcessor`. */ static load(template: object, context?: {}, options?: object): Promise; /** * Default set of functions provided for the template processor. * * @remarks * These functions are commonly used utilities available for * usage within the template processor's context. You can replace set this to * determine which functions are available from templates * * @static * @type {{ * fetch: typeof fetch, * clearInterval: typeof clearInterval, * setTimeout: typeof setTimeout, * setInterval: typeof setInterval, * console: Console, * debounce: typeof debounce * Date: Date * rateLimit: typeof rateLimit * env: typeof env * }} */ static DEFAULT_FUNCTIONS: { fetch: (url: string, opts: object) => Promise; console: Console; debounce: typeof debounce; Date: DateConstructor; rateLimit: typeof rateLimit; env: typeof env; }; private static _isNodeJS; /** * An instance of the `Planner` interface used to manage execution plans. * * The `planner` is responsible for generating and executing `ExecutionPlan`s, * which define the steps necessary to process templates. It provides methods * to initialize plans based on metadata and execute those plans. The planner * can be replaced by any valid Planner. We have SerialPlanner and ParallelPlanner * * @see Planner * @see ExecutionPlan */ planner: Planner; /** Represents the logger used within the template processor. */ logger: StatedLogger; /** Contextual data for the template processing. */ context: any; /** Contains the processed output after template processing. */ output: {}; /** Represents the raw input for the template processor. */ input: any; /** This object mirrors the template output in structure but where the output contains actual data, * this object contains MetaInfo nodes that track metadata on the actual nodes */ templateMeta: Record; /** List of warnings generated during template processing. */ warnings: any[]; /** Maps JSON pointers of import paths to their associated meta information. * So, for example the key "/" -> MetaInfo[]. the MetaInfo are in no particular order * HOWEVER the individual MetaInfo objects are the same objects as memory as those in * the templateMeta tree. Therefore, from any MetaInfo, you can navigate to it children * as its children are simply field of the object that don't end in "__". This explains * why we name the MetaInfo fields with "__" suffix, so they can be differentiated from * 'real' fields of the template output nodes. * */ metaInfoByJsonPointer: MetaInfoMap; /** A set of tags associated with the template. */ tagSet: Set; /** Configuration options for the template processor. */ options: any; /** Debugger utility for the template processor. */ debugger: any; /** Contains any errors encountered during template processing. */ errorReport: { [key: JsonPointerString]: any; }; /** Execution plans 'from' a given JSON Pointer. So key is JSON Pointer and value is array of JSON * pointers (a plan) */ private executionPlans; /** A queue of execution plans awaiting processing. */ private readonly executionQueue; /** function generators can be provided by a caller when functions need to be * created in such a way that they are somehow 'responsive' or dependent on their * location inside the template. Both the generator function, and the function * it generates are asynchronous functions (ie they return a promise). * $import is an example of this kind of behavior. * When $import('http://mytemplate.com/foo.json') is called, the import function * is actually generated on the fly, using knowledge of the json path that it was * called at, to replace the content of the template at that path with the downloaded * content.*/ functionGenerators: Map>; planStepFunctionGenerators: Map>; /** for every json pointer, we have multiple callbacks that are stored in a Set * @private */ private changeCallbacks; /** Flag indicating if the template processor is currently initializing. */ private isInitializing; /** A unique string identifier for the template processor instance like '3b12f1df-5232-4e1f-9c1b-3c6fc5ac7d3f'. */ uniqueId: string; private tempVars; timerManager: TimerManager; private generatorManager; /** Allows caller to set a callback to propagate initialization into their framework * @deprecated use lifecycleManager instead * */ readonly onInitialize: Map Promise | void>; /** * Allows a caller to receive a callback after the template is evaluated, but before any temporary variables are * removed. This function is slated to be replaced with a map of functions like onInitialize * @deprecated use lifecycleManager instead */ postInitialize: () => Promise; readonly lifecycleManager: LifecycleManager; executionStatus: ExecutionStatus; isClosed: boolean; static fromString(template: string, context?: {}, options?: {}): TemplateProcessor; constructor(template?: {}, context?: {}, options?: {}); private resetTemplate; setupContext(context: {}): void; reInitializeContext(userProvidedContext: object): Promise; /** * Template processor initialize can be called from 2 major use cases * 1. initialize a new importedSubtemplate processor importedSubtemplate * 2. $import a new importedSubtemplate for an existing importedSubtemplate processor * in the second case we need to reset the importedSubtemplate processor data holders * @param importedSubtemplate - the object representing the importedSubtemplate * @param jsonPtr - defaults to "/" which is to say, this importedSubtemplate is the root importedSubtemplate. When we $import a importedSubtemplate inside an existing importedSubtemplate, then we must provide a path other than root to import into. Typically, we would use the json pointer of the expression where the $import function is used. * @param snapshottedOutput - if provided, output is set to this initial value * */ initialize(importedSubtemplate?: {} | undefined, jsonPtr?: string, executionStatusSnapshot?: Snapshot | undefined): Promise; private shouldResetInitialization; private resetInitialization; private shouldResetTemplate; private isConcurrentInitialization; private runInitializationPlugins; private setupLogger; private processInitialization; private parseJsonPointer; private getCompilationTarget; private handleMetaInfoCreation; private initializeImportedTemplate; close(): Promise; private queueInitializationPlan; private withErrorHandling; /** * allows direct injection of ${expression} into template at given jsonPointer. * @param expression * @param jsonPointer */ setExpression(expression: string, jsonPointer: JsonPointerString): Promise; import(template: object | string, jsonPtrImportPath: JsonPointerString): Promise; private getImport; private parseURL; private fetchFromURL; private extractFragmentIfNeeded; private validateAsJSON; private setContentInTemplate; private createMetaInfos; private sortMetaInfos; private populateTemplateMeta; private static compileToJsonPointer; private setupDependees; private makeDepsAbsolute; private removeLeadingDollarsFromDependencies; private propagateTags; /** * temp vars are in scope if all tags are present OR the expression's fieldname ends in !, which makes * it an absolutely temporary variable since. * @param metaInfo * @private */ private isTempVarInScope; private cacheTmpVarLocations; private removeTemporaryVariables; /** * Sets or deletes data based on the specified operation. * @async * @param {string} jsonPtr - The JSON pointer indicating where to apply the operation. * @param {*} [data=null] - The data to be used with the set or setDeferred operation. * @param {"set"|"delete"|"setDeferred"} [op="set"] - The operation to perform - setDeferred is for internal use * @returns {Promise<} A promise with the list of json pointers touched by the plan */ setData(jsonPtr: JsonPointerString, data?: any, op?: Op): Promise; /** * Calling setDataForked allows the mutation and its reaction (fromPlan) to begin executing immediately without * queuing/seriealizing/blocking on other plans. This is possible because a forked planStep contains a write-safe * copy of this.output (essentially a 'snapshot' in MVCC terminology) and therefore the mutation and propagation * of the fromPlan are isolated, just like snapshot isolation levels on Postres or other MVCC databases. So, do not * await this method. Just let 'er rip. * @param forkedPlanStep */ setDataForked(forkedPlanStep: PlanStep): Promise; private drainExecutionQueue; /** * Applies a transaction by processing each mutation within the transaction. * * For each mutation, this method applies the specified operation (`set` or `delete`) * to the `output` object based on the `jsonPtr` (JSON pointer). * It also triggers data change callbacks after each mutation. * * @param transaction - The transaction object containing a list of mutations to apply. * @throws {Error} If the operation (`op`) is neither `"set"` nor `"delete"`. * * The transaction is processed as follows: * - `"set"`: Sets the value at the location specified by `jsonPtr` using `jp.set`. * - `"delete"`: Removes the value at the location specified by `jsonPtr` using `jp.remove`. * * After each mutation, `callDataChangeCallbacks` is called to notify of the change. * Finally, a batch data change callback is triggered for all affected JSON pointers. * * @private */ private applyTransaction; /** * Registers a transaction callback to handle batched data changes. * * When setData is called, a set of changes (a DAG) is calculated and the changes are sequentially applied. These * changes can be 'bundled' into a single Transaction for the purpose of capturing a single set of changes that * if atomically applied, has the exact same effect as the DAG propagation. Therefore, a Transaction can be a * less chatty way to capture and apply changes from one template instance A to template instance B without * incurring the cost of for B to compute the change DAG. * * @param cb - A callback function that handles a `Transaction` object. The callback is expected * to return a `Promise`. * * @throws {Error} If the callback is registered for any path other than `'/'`. * * @public */ setTransactionCallback(cb: (transaction: Transaction) => Promise): void; /** * Removes a previously registered transaction callback. * * This method removes the callback that was registered with `setTransactionCallback` * for the root path `'/'`. * * @param cb - The callback function to remove, which should match the previously registered callback. * * @public */ removeTransactionCallback(cb: DataChangeCallback): void; isEnabled(logLevel: Levels): boolean; private logOutput; executePlan(plan: ExecutionPlan): Promise; mutate(planStep: PlanStep): Promise; evaluateNode(step: PlanStep): Promise; private _evaluateExpression; private _strictChecks; private setDataIntoTrackedLocation; private setUntrackedLocation; private _evaluateExprNode; private populateContextWithSelf; private setupFunctionGenerators; /** * Certain functions callable in a JSONata expression must be dynamically generated. They cannot be static * generated because the function instance needs to hold a reference to some kind of runtime state, either * a MetaInfo or a PlanStep (see FunctionGenerator type). This method, for a given list of function names, * generates the function by finding and calling the corresponding FunctionGenerator. * @param context * @param functionNames * @param metaInf * @param planStep * @private */ private populateContextWithGeneratedFunctions; private allTagsPresent; private _setData; from(jsonPtr: JsonPointerString): string[]; getDependents(jsonPtr: JsonPointerString): string[] | JsonPointerStructureArray[]; getDependencies(jsonPtr: JsonPointerString): string[] | JsonPointerStructureArray[]; to(jsonPtr: JsonPointerString): string[]; /** * Controls the flow of data and retrieves root nodes based on the specified level. * * @param {FlowOpt} level - The level specifying the granularity of the data flow. * @return {DataFlowNode[]} An array of root nodes that are computed based on the specified level. */ flow(level: FlowOpt): DataFlowNode[]; /** * Sets a data change callback function that will be called whenever the value at the json pointer has changed * @param jsonPtr * @param cbFn of form (data, ptr:JsonPointerString, removed?:boolean)=>void */ setDataChangeCallback(jsonPtr: JsonPointerString, cbFn: DataChangeCallback): void; removeDataChangeCallback(jsonPtr: JsonPointerString, cbFn?: DataChangeCallback): void; callDataChangeCallbacks(data: any, jsonPointer: JsonPointerString | JsonPointerString[], removed?: boolean, op?: Op): Promise; plan(): Promise; private static dependsOnImportedTemplate; out(jsonPointer: JsonPointerString): object | null; private localImport; static wrapInOrdinaryFunction(jsonataLambda: any): { (...args: any[]): any; _stated_function__: boolean; apply(_this: any, args: any[]): any; }; private generateErrorReportFunction; private generateDeferFunction; /** * Creates a stringified snapshot of the current state of the TemplateProcessor instance, * including its execution status, input, output, and options. * * @returns {string} A JSON string representing the snapshot of the TemplateProcessor's * current state, including template input, processed output, and options. * * @example * const tp = new TemplateProcessor(template, context, options); * const snapshotString = await tp.snapshot(); * // snapshotString contains a JSON string with the execution plans, mvcc, template, output, and options of the * TemplateProcessor */ snapshot(): Promise; static fromSnapshot(snapshot: string, context?: {}): Promise; /** * Constructs a new TemplateProcessor instance from a given snapshot object, but does NOT initialize it. * This method allows the caller the opportunity to register dataChangeCallbacks and so forth before * template evaluation begins, providing more control over the initialization process. * * @param {object} snapshot - A snapshot object containing template, options, and output data for initializing the TemplateProcessor. * @param {object} [context={}] - An optional context object to be used by the TemplateProcessor. * @returns {TemplateProcessor} A new TemplateProcessor instance constructed from the snapshot data, not yet initialized. * * @example * const snapshot = {"template":"...", "options":{}, "output":"..."}; * const tp = TemplateProcessor.constructFromSnapshot(snapshot); * // Register callbacks or perform other setup operations here * await tp.initialize(); */ static constructFromSnapshotObject(snapshot: Snapshot, context?: {}): TemplateProcessor; restore(executionStatusStr: string): Promise; restoreFromSnapshotObject(snapshotObject: Snapshot): Promise; private compileMetaInfo; private restoreFunctions; /** * When $forked is called, it must push the current output onto the forkStack so it can be restored on * $joined, and it must replace the output with a copy of the output. * @private * @param planStep */ generateForked: (planStep: PlanStep) => (jsonPtr: JsonPointerString, data: any, op?: Op) => Promise; static simpleUniqueId(): string; /** * The $set(/foo, data) command may be operating inside the context of a $forked. If this is the case * then $setData is intercepted here and we use the setDataForked function which applies changes to * forked output * @param planStep * @private */ private generateSet; private generateChange; /** * The $joined(/foo, data) function pops the forkstack and can return us to ordinary * non-forked operation if the pop operation empties the fork stack * @param planStep * @private */ private generateJoined; /** * this function is used to make a deep copy of the output so that when we $fork we are operating * on a copy of the output, not co-mutating the original * @param output */ private static deepCopy; /** * Sometimes we need to import a simple expression string that is not nested in an object. * for example if we {"msg":"$import('${'hello ' & to }')"), then we are importing an expression directly * into the parent, not nesting in an object. In this case we must slice off the last element of the * rootJsonPointer, because to not slice it off would imply that the target of the expression is inside * the msg field, but the intention when we import a simple expression is target the parent object which * holds the msg field. * @param template * @param rootJsonPtr * @returns either the original rootJsonPointer, or one that has been trimmed to point to the parent of rootJsonPtr * @private */ private adjustRootForSimpleExpressionImports; /** * Retrieves the metadata information for a given JSON Pointer string. * * @param jsonPtr - The JSON Pointer string that identifies the template node. * @returns The `MetaInfo` object corresponding to the provided JSON Pointer. * @throws If the JSON Pointer does not exist in the `templateMeta`. */ getMetaInfo(jsonPtr: JsonPointerString): MetaInfo; } //# sourceMappingURL=TemplateProcessor.d.ts.map