Skip to content

Commit

Permalink
Restore (#51)
Browse files Browse the repository at this point in the history
* snapshot recover

* CliCore extend command + test

* add recoverSnapshot example

* add restore command, and document basic snapshot usage

---------

Co-authored-by: Sergey Sergeev <[email protected]>
  • Loading branch information
zhirafovod and Sergey Sergeev authored Feb 29, 2024
1 parent c15a33f commit 270d3bc
Show file tree
Hide file tree
Showing 8 changed files with 187 additions and 97 deletions.
64 changes: 45 additions & 19 deletions README.md

Large diffs are not rendered by default.

18 changes: 18 additions & 0 deletions example/restoreSnapshot.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"template": {
"count": 0,
"counter": "${ $setInterval(function(){$set('/count', count+1)}, 10) }",
"stop": "${ count=10?($clearInterval($$.counter);'done'):'not done' }"
},
"output": {
"count": 3,
"counter": "--interval/timeout--",
"stop": "not done"
},
"options": {
"snapshot": {
"seconds": 1
},
"importPath": "/Users/sesergee/projects/sandbox/workflows/stated-workflow"
}
}
34 changes: 30 additions & 4 deletions src/CliCore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,28 @@ export default class CliCore {
return path.join(process.cwd(), filepath);
}

//replCmdInoutStr like: -f "example/ex23.json" --tags=["PEACE"] --xf=example/myEnv.json
async init(replCmdInputStr) {
//replCmdInoutStr like: -f "defaultSnapshot.json"

/**
* replCmdInoutStr example: -f "example/restoreSnapshot.json" --tags=["PEACE"] --xf=example/myEnv.json
* @param replCmdInputStr - the command line string that will be parsed into arguments
*/
async restore(replCmdInputStr: string) {
return this.init(replCmdInputStr, true);

}

/**
* This Cli core command may be invoked directly from the REPL init command or from restore command
*
* - fromSnapshot=false, replCmdInoutStr example: -f "example/ex23.json" --tags=["PEACE"] --xf=example/myEnv.json
* - fromSnapshot=true, replCmdInoutStr example: -f "example/restoreSnapshot.json" --tags=["PEACE"] --xf=example/myEnv.json
*
* @param replCmdInputStr
* @param fromSnapshot - when set to true, template processor will treat input as a snapshot of a previous
* templateProcessor state
*/
async init(replCmdInputStr, fromSnapshot: boolean=false) {
if(this.templateProcessor){
this.templateProcessor.close();
}
Expand All @@ -136,8 +156,10 @@ export default class CliCore {
const contextData = contextFilePath ? await this.readFileAndParse(contextFilePath, importPath) : {};
options.importPath = importPath; //path is where local imports will be sourced from. We sneak path in with the options
// if we initialize for the first time, we need to create a new instance of TemplateProcessor
if (!this.templateProcessor) {
if (!this.templateProcessor && !fromSnapshot) {
this.templateProcessor = new TemplateProcessor(input, contextData, options);
} else if (!this.templateProcessor && fromSnapshot) {
this.templateProcessor = new TemplateProcessor(input.template, contextData, input.options);
} else { // if we are re-initializing, we need to reset the tagSet and options, if provided
this.templateProcessor.tagSet = new Set();
this.templateProcessor.options = options;
Expand All @@ -160,7 +182,11 @@ export default class CliCore {
if(tail !== undefined){
tailPromise = this.tail(tail);
}
await this.templateProcessor.initialize(input);
if (fromSnapshot) {
await this.templateProcessor.initialize(input.template,"/", input.output);
} else {
await this.templateProcessor.initialize(input);
}
if(tail !== undefined){
return tailPromise;
}
Expand Down
42 changes: 23 additions & 19 deletions src/StatedREPL.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,28 @@ export default class StatedREPL {
this.cliCore = new CliCore(temaplateProcessor);
}

// a list of commands that are available in the CLI
// this list can be extended if CLICORE is extended as well
static CLICORE_COMMANDS: string[][] = [
["open", 'interactive command to open select and open a template'],
["cd", "change directory for example 'cd ..' "],
["init", '-f <fname> to Initialize the template'],
["set", 'Set data to a JSON pointer path and show the executed output'],
["in", 'Show the input template'],
["out", '[jsonPointer] Show the executed output'],
["state", 'Show the current state of the templateMeta'],
["from", 'Show the dependents of a given JSON pointer'],
["to", 'Show the dependencies of a given JSON pointer'],
["plan", 'Show the evaluation plan'],
["note", "returns ═══ ... for creating documentation"],
["log", "set the log level [debug, info, warn, error]"],
["debug", "set debug commands (WIP)"],
["errors", "return an error report"],
["tail", 'tail "/ until count=100" will tail the template root until its count field is 100'],
["svg", 'serve SVG of depedency graph off http://localhost:3000'],
["restore", 'restore the template from a snapshot'],
];

async initialize() {
this.isColorized = false;
const cmdLineArgsStr = process.argv.slice(2).join(" ");
Expand All @@ -52,25 +74,7 @@ export default class StatedREPL {
}

registerCommands() {
[ //these are CLICore commands
["open", 'interactive command to open select and open a template'],
["cd", "change directory for example 'cd ..' "],
["init", '-f <fname> to Initialize the template'],
["set", 'Set data to a JSON pointer path and show the executed output'],
["in", 'Show the input template'],
["out", '[jsonPointer] Show the executed output'],
["state", 'Show the current state of the templateMeta'],
["from", 'Show the dependents of a given JSON pointer'],
["to", 'Show the dependencies of a given JSON pointer'],
["plan", 'Show the evaluation plan'],
["note", "returns ═══ ... for creating documentation"],
["log", "set the log level [debug, info, warn, error]"],
["debug", "set debug commands (WIP)"],
["errors", "return an error report"],
["tail", 'tail "/ until count=100" will tail the template root until its count field is 100'],
["svg", 'serve SVG of depedency graph off http://localhost:3000'],

].map(c=>{
StatedREPL.CLICORE_COMMANDS.map(c=>{
const [cmdName, helpMsg] = c;
this.r.defineCommand(cmdName, {
help: helpMsg,
Expand Down
2 changes: 1 addition & 1 deletion src/TemplateProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ export default class TemplateProcessor {
* @param snapshottedOutput - if provided, output is set to this initial value
*
*/
public async initialize(importedSubtemplate: {} = undefined, jsonPtr = "/", snapshottedOutput?:any):Promise<void> {
public async initialize(importedSubtemplate: {} = undefined, jsonPtr: string = "/", snapshottedOutput: {} = undefined):Promise<void> {
if(jsonPtr === "/"){
this.timerManager.clearAll();
}
Expand Down
2 changes: 1 addition & 1 deletion src/TestUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ function runMarkdownTests(testData: CommandAndResponse[], cliCore:CliCore, print
const compiledExpr = jsonata(jsonataExpr);
const success = await compiledExpr.evaluate(responseNormalized);
if (success === false) {
throw new Error(`Markdown codeblock contained custom jsonata test expression that returned false: ${jsonataExpr} \n data was: ${responseNormalized}` );
throw new Error(`Markdown codeblock contained custom jsonata test expression that returned false: ${StatedREPL.stringify(jsonataExpr)} \n data was: ${StatedREPL.stringify(responseNormalized)}` );
}
} else {
let expected;
Expand Down
32 changes: 32 additions & 0 deletions src/test/StatedREPL.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,36 @@ test("TemplateProcessor keeps context on init", async () => {
);
});

// Validates restore command
test("Extend CliCore with restore command", async () => {

// ensure jest argv does not interfere with the test
const originalCmdLineArgsStr = process.argv;
process.argv = ["node", "dist/stated.js"]; // this is an argv when running stated.js repl.

// extend CliCore with restore command
const repl = new StatedREPL();

try {
await repl.initialize();

// we call restore on the repl, which will expect it to be defined in CliCore.
await repl.cli('restore', '-f example/restoreSnapshot.json');


console.log(StatedREPL.stringify(repl.cliCore.templateProcessor.output));
expect(repl.cliCore.templateProcessor.output).toBeDefined();
expect(repl.cliCore.templateProcessor.output.count).toBeGreaterThanOrEqual(3); // should be 3 or more right after restoring from the snapshot
expect(repl.cliCore.templateProcessor.output.count).toBeLessThan(10); // ... but less than 10


while (repl.cliCore.templateProcessor.output.count < 10) { // validates that templateProcessor picks up where it was left in the snapshot.
console.log("waiting for output.count to reach 10")
await new Promise(resolve => setTimeout(resolve, 50));
}
expect(repl.cliCore.templateProcessor.output.count).toBe(10);
} finally {
process.argv = originalCmdLineArgsStr;
if (repl !== undefined) repl.close();
}
});
Loading

0 comments on commit 270d3bc

Please sign in to comment.