diff --git a/package.json b/package.json index 58b06fb7..981683ab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "stated-js", - "version": "0.1.4", + "version": "0.1.5", "license": "Apache-2.0", "description": "JSONata embedded in JSON", "main": "./dist/src/TemplateProcessor.js", diff --git a/src/TemplateProcessor.ts b/src/TemplateProcessor.ts index 0af11b2f..67c7dafc 100644 --- a/src/TemplateProcessor.ts +++ b/src/TemplateProcessor.ts @@ -964,15 +964,15 @@ export default class TemplateProcessor { } } - private async executePlan(plan:JsonPointerString[], data:any = TemplateProcessor.NOOP, op:"set"|"setDeferred"|"delete"="set"):Promise { + private async executePlan(changedJsonPointers:JsonPointerString[], data:any = TemplateProcessor.NOOP, op:"set"|"setDeferred"|"delete"="set"):Promise { const resp = []; - let dependencies = plan; + let dependencies = changedJsonPointers; if (data !== TemplateProcessor.NOOP) { //this plan begins with setting data - await this.applyMutationToFirstJsonPointerOfPlan(plan, data, op); - dependencies = plan.slice(1); //we have processes=d the entry point which can receive a mutation (data), so now just need to process remaining dependencies + await this.applyMutationToFirstJsonPointerOfPlan(changedJsonPointers, data, op); + dependencies = changedJsonPointers.slice(1); //we have processes=d the entry point which can receive a mutation (data), so now just need to process remaining dependencies } await this.executeDependentExpressions(dependencies); - await this.executeDataChangeCallbacks(plan); + await this.executeDataChangeCallbacks(changedJsonPointers, op); } private async executeDependentExpressions(dependencies: JsonPointerString[]) { @@ -989,18 +989,20 @@ export default class TemplateProcessor { } } - private async executeDataChangeCallbacks(plan: JsonPointerString[]) { + private async executeDataChangeCallbacks(changedJsonPointers: JsonPointerString[], op:"set"|"setDeferred"|"delete"="set") { let anyUpdates = false; - const thoseThatUpdated = plan.filter(jptr => { + const thoseThatUpdated = changedJsonPointers.filter(jptr => { const meta = jp.get(this.templateMeta, jptr); anyUpdates ||= meta.didUpdate__; return meta.didUpdate__ }); if (anyUpdates) { + // current callback APIs are not interested in deferred updates, so we reduce op to boolean "removed" + const removed = op==="delete"; //admittedly this structure of this common callback is disgusting. Essentially if you are using the //common callback you don't want to get passed any data that changed because you are saying in essence //"I don't care what changed". - await this.callDataChangeCallbacks(this.output, plan); + await this.callDataChangeCallbacks(this.output, changedJsonPointers, removed); } } @@ -1117,7 +1119,12 @@ export default class TemplateProcessor { return didSet; //true means that the data was new/fresh/changed and that subsequent updates must be propagated } - private async setUntrackedLocation(output, jsonPtr, data) { + private async setUntrackedLocation(output, jsonPtr, data, op:"set" |"setDeferred"| "delete"="set") { + if(op==="delete"){ + if(!jp.has(this.output, jsonPtr)){ + return; // we are being asked to remove something that isn't here + } + } jp.set(output, jsonPtr, data); //this is just the weird case of setting something into the template that has no effect on any expressions jp.set(this.templateMeta, jsonPtr, { "materialized__": true, @@ -1193,6 +1200,7 @@ export default class TemplateProcessor { if(op === 'delete'){ if(jp.has(output, jsonPtr)) { jp.remove(output, jsonPtr); + this.callDataChangeCallbacks(data, jsonPtr, true); return true; } return false; @@ -1204,7 +1212,6 @@ export default class TemplateProcessor { if (!isEqual(existingData, data)) { jp.set(output, jsonPtr, data); this.callDataChangeCallbacks(data, jsonPtr, false); - //this.commonCallback && this.commonCallback(data, jsonPtr); //called if callback set on "/" return true; } else { this.logger.verbose(`data to be set at ${jsonPtr} did not change, ignored. `); diff --git a/src/test/TemplateProcessor.test.js b/src/test/TemplateProcessor.test.js index 15a37f82..501b76de 100644 --- a/src/test/TemplateProcessor.test.js +++ b/src/test/TemplateProcessor.test.js @@ -23,6 +23,7 @@ import DependencyFinder from "../../dist/src/DependencyFinder.js"; import jsonata from "jsonata"; import { default as jp } from "../../dist/src/JsonPointer.js"; import StatedREPL from "../../dist/src/StatedREPL.js"; +import {expect} from "@jest/globals"; test("test 1", async () => { @@ -2089,14 +2090,17 @@ test("data change on array append (/foo/-)", async () => { expect(cbCount1).toBe(1); }); - - - - - - - - - - - +test("dataChangeCallback on delete op", async () => { + const tp = new TemplateProcessor({"foo": "bar"}); + let done; + let latch = new Promise(resolve => done = resolve); + tp.setDataChangeCallback('/foo', (data, jsonPtr, removed)=>{ + if(removed){ + done(); + } + }); + await tp.initialize(); + tp.setData("/foo", undefined, "delete"); + await latch; + expect(tp.output.foo).toBeUndefined(); +}) \ No newline at end of file