UNPKG

@microsoft/windows-admin-center-sdk

Version:

Microsoft - Windows Admin Center Shell

256 lines (254 loc) 12 kB
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