@microsoft/windows-admin-center-sdk
Version:
Microsoft - Windows Admin Center Shell
256 lines (254 loc) • 12 kB
JavaScript
import { EMPTY, merge, of, throwError } from 'rxjs';
import { catchError, expand, filter, map, switchMap, take, tap } from 'rxjs/operators';
import { PersistentWorkItemApplyState } from './persistent-work-item-apply-state';
import { PersistentWorkItemState } from './persistent-work-item-state';
/**
* The runner of workflow.
*/
export class PersistentWorkflowRunner {
persistentStore;
builder;
/**
* Workflow always starts from startingId which is "1".
*/
static startingId = 1;
/**
* The collection of workflows. It allows multiple instances of workflows to track.
*/
workflowCollection = {};
/**
* Initializes a new instance of the PersistentWorkflowRunner class.
*
* @param persistentStore The persistent store instance.
* @param builder The builder of the workflow.
*/
constructor(persistentStore, builder) {
this.persistentStore = persistentStore;
this.builder = builder;
}
/**
* Start a new single workflow.
*
* @param transitData the transit data for initialization for the first workflow.
*/
start(transitData) {
const workflow = this.builder.build();
const firstWorkItem = workflow.collection.find(item => item.id === PersistentWorkflowRunner.startingId);
if (!firstWorkItem || firstWorkItem.state !== PersistentWorkItemState.NotStarted) {
return throwError(() => new Error('Couldn\'t find first work item.'));
}
const context = {
transitData,
persistentData: null,
applyState: PersistentWorkItemApplyState.Required,
store: {
save: this.save.bind(this)
},
logs: [],
workflow
};
return this.checkpoint(firstWorkItem, context, PersistentWorkItemState.NotStarted)
.pipe(switchMap(instanceId => {
this.workflowCollection[instanceId] = workflow;
return this.process(firstWorkItem, context);
}));
}
/**
* Start workflows from existing snapshots data from the store.
*/
startFromStore() {
return this.persistentStore.restore()
.pipe(switchMap(list => {
if (list.length === 0) {
return EMPTY;
}
const observables = [];
for (const snapshot of list) {
const workflow = this.builder.build();
const firstWorkItem = workflow.collection.find(item => item.id === snapshot.workItemId);
if (!firstWorkItem) {
return throwError(() => new Error('Couldn\'t locate the work item to restart.'));
}
const context = {
transitData: null,
persistentData: snapshot.workItemData,
applyState: PersistentWorkItemApplyState.Required,
instanceId: snapshot.instanceId,
store: {
save: this.save.bind(this)
},
logs: [],
workflow,
restoreVersion: snapshot.version
};
firstWorkItem.state = snapshot.workItemState;
if (firstWorkItem.state === PersistentWorkItemState.Applying) {
// at default apply() call will be omitted.
context.applyState = PersistentWorkItemApplyState.Skip;
firstWorkItem.state = PersistentWorkItemState.PreValidateByRestore;
}
this.workflowCollection[snapshot.instanceId] = workflow;
observables.push(this.process(firstWorkItem, context));
}
return merge(...observables);
}));
}
process(workItem, context) {
return this.processNext(workItem, context)
.pipe(catchError((error) => this.processResult(workItem, context, error)), switchMap(innerContext => {
const loopContext = {
completed: !!context.error,
context: innerContext,
workItem
};
return this.processResult(loopContext.workItem, loopContext.context)
.pipe(map(() => loopContext));
}), expand((loopContext) => {
if (loopContext.completed) {
return EMPTY;
}
return this.processNext(loopContext.workItem, loopContext.context)
.pipe(tap(() => {
if (loopContext.workItem.state === PersistentWorkItemState.Completed) {
loopContext.workItem = this.processStep(loopContext.workItem, loopContext.context);
if (!loopContext.workItem) {
loopContext.completed = true;
}
}
}), catchError((error) => this.processResult(loopContext.workItem, loopContext.context, error)), switchMap(inner2Context => {
loopContext.completed = loopContext.completed || !!inner2Context.error;
loopContext.context = inner2Context;
return this.processResult(loopContext.workItem, loopContext.context)
.pipe(map(() => loopContext));
}));
}), filter(loopContext => loopContext.completed), take(1), map(innerContext => {
const result = {
logs: innerContext.context.logs,
result: innerContext.context.transitData,
instanceId: innerContext.context.instanceId,
clear: () => this.persistentStore.clear(innerContext.context.instanceId),
error: innerContext.context.error
};
return result;
}));
}
processResult(workItem, context, error) {
if (!workItem) {
return of(context);
}
if (context.error) {
return of(context);
}
let checkpoint = () => of(null);
if (!error) {
switch (workItem.state) {
case PersistentWorkItemState.PreValidatingByRestore:
checkpoint = () => this.checkpoint(workItem, context, PersistentWorkItemState.PreValidateByRestoreSuccess);
break;
case PersistentWorkItemState.PreValidating:
checkpoint = () => this.checkpoint(workItem, context, PersistentWorkItemState.PreValidateSuccess);
break;
case PersistentWorkItemState.Applying:
checkpoint = () => this.checkpoint(workItem, context, PersistentWorkItemState.ApplySuccess);
break;
case PersistentWorkItemState.PostValidating:
checkpoint = () => this.checkpoint(workItem, context, PersistentWorkItemState.PostValidateSuccess);
break;
case PersistentWorkItemState.NotStarted:
case PersistentWorkItemState.Completed:
break;
default:
checkpoint = () => throwError(() => new Error('Unexpected success handling state.'));
}
return of(null)
.pipe(switchMap(checkpoint), map(() => context));
}
context.error = error;
switch (workItem.state) {
case PersistentWorkItemState.PreValidatingByRestore:
checkpoint = () => this.checkpoint(workItem, context, PersistentWorkItemState.PreValidateByRestoreError);
break;
case PersistentWorkItemState.PreValidating:
checkpoint = () => this.checkpoint(workItem, context, PersistentWorkItemState.PreValidateError);
break;
case PersistentWorkItemState.Applying:
checkpoint = () => this.checkpoint(workItem, context, PersistentWorkItemState.ApplyError);
break;
case PersistentWorkItemState.PostValidating:
checkpoint = () => this.checkpoint(workItem, context, PersistentWorkItemState.PostValidateError);
break;
default:
return throwError(() => new Error('Unexpected error handling state.'));
}
return of(null)
.pipe(switchMap(checkpoint), map(() => context));
}
processNext(workItem, context) {
let checkpointFunction;
let observableFunction = null;
switch (workItem.state) {
case PersistentWorkItemState.PreValidateByRestore:
case PersistentWorkItemState.PreValidatingByRestore:
checkpointFunction = () => this.checkpoint(workItem, context, PersistentWorkItemState.PreValidatingByRestore);
observableFunction = () => workItem.preValidate(context);
break;
case PersistentWorkItemState.NotStarted:
case PersistentWorkItemState.PreValidating:
workItem.init(context);
checkpointFunction = () => this.checkpoint(workItem, context, PersistentWorkItemState.PreValidating);
observableFunction = () => workItem.preValidate(context);
break;
case PersistentWorkItemState.PreValidateByRestoreSuccess:
case PersistentWorkItemState.PreValidateSuccess:
if (context.applyState === PersistentWorkItemApplyState.Skip) {
checkpointFunction = () => this.checkpoint(workItem, context, PersistentWorkItemState.PostValidating);
observableFunction = () => workItem.postValidate(context);
}
else {
checkpointFunction = () => this.checkpoint(workItem, context, PersistentWorkItemState.Applying);
observableFunction = () => workItem.apply(context);
}
break;
case PersistentWorkItemState.PostValidating:
case PersistentWorkItemState.ApplySuccess:
checkpointFunction = () => this.checkpoint(workItem, context, PersistentWorkItemState.PostValidating);
observableFunction = () => workItem.postValidate(context);
break;
case PersistentWorkItemState.PostValidateSuccess:
case PersistentWorkItemState.Completed:
workItem.finalize(context);
context.applyState = PersistentWorkItemApplyState.Required;
checkpointFunction = () => this.checkpoint(workItem, context, PersistentWorkItemState.Completed);
break;
default:
return throwError(() => new Error('Unexpected process next state.'));
}
if (!observableFunction) {
return of(null)
.pipe(switchMap(checkpointFunction), map(() => context));
}
return of(null)
.pipe(switchMap(checkpointFunction), switchMap(observableFunction));
}
processStep(workItem, context) {
if (workItem.state === PersistentWorkItemState.ApplyError || workItem.state === PersistentWorkItemState.PostValidateError) {
return null;
}
if (workItem.state === PersistentWorkItemState.Completed && workItem.nextId < 0) {
return null;
}
return this.workflowCollection[context.instanceId].collection.find(item => item.id === workItem.nextId);
}
checkpoint(workItem, context, state) {
return this.persistentStore.save(context.workflow.version, workItem.id, state, context.persistentData, context.instanceId)
.pipe(map(snapshot => {
workItem.state = state;
context.logs.push({ time: Date.now(), name: workItem.name, state: PersistentWorkItemState[state] });
return context.instanceId = snapshot.instanceId;
}));
}
save(workItem, context) {
return this.checkpoint(workItem, context, workItem.state);
}
}
//# sourceMappingURL=persistent-workflow-runner.js.map