diff --git a/ActionStrategy.md b/ActionStrategy.md index 1fdbe2c..e7cf086 100644 --- a/ActionStrategy.md +++ b/ActionStrategy.md @@ -118,3 +118,36 @@ The same is true when accessing the payload from a reducer, method, or principle SomethingFactory> ``` This was a purposeful design choice, if you find yourself doing such. Known this system is already complicated enough. + +## Helper Functions for Standard Method Creators +*You still need to create a function of type MethodCreator to use these Helpers. :MethodCreator = () => methodCreator* +```typescript +export const createMethod = + (method: (action: Action) => Action): [Method, Subject] => {} +export const createMethodWithConcepts = + (method: (action: Action) => Action, concepts$: UnifiedSubject): [Method, Subject] => {} +export const createAsyncMethod = + (asyncMethod: (controller: ActionController, action: Action) => void): [Method, Subject] => {} +export const createAsyncMethodWithConcepts = + (asyncMethodWithConcepts: (controller: ActionController, action: Action, concepts: Concept[]) => void, concepts$: UnifiedSubject) + : [Method, Subject] => {} +export const createMethodDebounce = + (method: (action: Action) => Action, duration: number): [Method, Subject] => {} +export const createMethodDebounceWithConcepts = + (methodWithConcepts: (action: Action, concepts: Concept[]) => Action, concepts$: UnifiedSubject, duration: number) + : [Method, Subject] => {} +export const createAsyncMethodDebounce = + (asyncMethod: (controller: ActionController, action: Action) => void, duration: number): [Method, Subject] => {} +export const createAsyncMethodDebounceWithConcepts = + (asyncMethodWithConcepts: (controller: ActionController, action: Action, concepts: Concept[]) => + void, concepts$: UnifiedSubject, duration: number): [Method, Subject] => {} +``` +* createMethod - Your standard method, be sure to handle the action.strategy via one of the strategy decision functions, in addition to passing the action if there is no attached strategy. +* createMethodWithConcepts - This will allow your method to have the most recent concepts to be accessed via the asyncMethod function. +* createAsyncMethod - Handled differently than the rest, you will have to use the passed controller to fire your actions back into the action stream. +* createAsyncMethodWithConcepts - Will also have access to the most recent concepts. +*Note if you are implementing your own debounceAction how these methods work. They are handling a passed conclude from debounceAction within their map/switchMap* +* createMethodDebounce - After the first action, this will filter actions within the duration to be set to the conclude action. +* createMethodDebounceWithConcepts - Will filter actions within the duration while providing access to the most recent concepts. +* createAsyncMethodDebounce - Will not disengage the initial ActionController, but will allow debounced actions to pass through when filtered as conclude actions. And will fire the first action upon its own conditions are met asynchronously. +* createAsyncMethodDebounceWithConcepts - Filters and then first the first action once conditions are met, and provides access to the most recent concepts. \ No newline at end of file diff --git a/Axium.md b/Axium.md index de2e0a9..503312c 100644 --- a/Axium.md +++ b/Axium.md @@ -27,6 +27,7 @@ export type AxiumState { dialog: string; storeDialog: boolean; lastStrategy: string; + lastStrategyData: unknown; generation: number; modeIndex: number; defaultModeIndex: number; @@ -46,6 +47,7 @@ export type AxiumState { * dialog - Is the internal representation of the strategies that the axium has ran. * storeDialog - This is set to false by default to save on memory, but if true will store each dialog, and allows such to be subscribed to. * lastStrategy - Informs specifically the of the last ActionStrategy topic to have ran through the system. This is used via testing or the deployment of addition strategies upon completion. +* lastStrategyData - Paired with lastStrategy. Use to access thee last data of the previous strategy. * generation - This iterates each time the Set of Concepts is transformed. And if an action is received of the wrong generation. Will be primed at run time, if not found this will emit a badAction that if logging is set to True. Will be emitted to the console alongside the invalidated action as payload. * modeIndex - This determines which Mode is currently being ran from the lists of modes stored on the axium concept. * defaultModeIndex - This determines what mode will be set by setDefaultMode. Of importance for adding and removing strategies. @@ -92,6 +94,7 @@ Please avoid using these qualities, but are providing explanations to understand * clearBadActionTypeFromBadActionList - This is to allow for plans to take into account for expired actions and clear such. * clearBadStrategyTopicFromBadActionList - Allows plans to accounts for specific ActionStrategy topics that might find themselves in badActions and clear such. * clearBadPlanFromBadPlanList - This additionally allows for concepts to take into account potentially failed plans that are set by axium.stage(). Via their topic as payload and clears such. +* kick - This is a pure action that will just trigger the next function via the UnifiedSubject to prime subscribers or stages. Noting that the downside of STRX's halting quality, is you have to kick it into gear if it hasn't received an action recently for your staged Plans to operate as intended. ## Axium Strategies Concept Set Transformation ```typescript diff --git a/README.md b/README.md index b89e3f8..1115ef1 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ let finalRun = true; const axium = createAxium('ownershipTest', [ createOwnershipConcept(), createCounterConcept(), - createExperimentConcept(createExperimentActionQueState(), [checkInStrategyQuality], [experimentActionQuePrinciple]) + createExperimentConcept(createExperimentState(), [checkInStrategyQuality], [experimentActionQuePrinciple]) ], true, true); const plan = axium.stage( 'Testing Ownership Staging', [ diff --git a/package.json b/package.json index 02e488f..da09323 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@phuire/strx", - "version": "0.0.36", + "version": "0.0.38", "description": "Unified Turing Machine", "main": "dist/index.js", "module": "dist/index.mjs", diff --git a/src/concepts/axium/axium.concept.ts b/src/concepts/axium/axium.concept.ts index 4eb608d..2eb8cf2 100644 --- a/src/concepts/axium/axium.concept.ts +++ b/src/concepts/axium/axium.concept.ts @@ -26,6 +26,7 @@ import { clearBadActionTypeFromBadActionListQuality } from './qualities/clearBad import { clearBadStrategyTopicFromBadActionListQuality } from './qualities/clearBadStrategyTopicFromBadActionList.quality'; import { clearBadPlanFromBadPlanListQuality } from './qualities/clearBadPlanFromBadPlanList.quality'; import { registerStagePlannerQuality } from './qualities/registerStagePlanner.quality'; +import { kickQuality } from './qualities/kick.quality'; export type NamedSubscription = { name: string; @@ -40,6 +41,7 @@ export type AxiumState = { dialog: string; storeDialog: boolean; lastStrategy: string; + lastStrategyData: unknown; generation: number; cachedSemaphores: Map> modeIndex: number; @@ -67,6 +69,7 @@ const createAxiumState = (name: string, storeDialog?: boolean, logging?: boolean dialog: '', storeDialog: storeDialog ? storeDialog : false, lastStrategy: '', + lastStrategyData: '', generation: 0, cachedSemaphores: new Map>(), modeIndex: 0, @@ -90,6 +93,7 @@ export const createAxiumConcept = (name: string, storeDialog?: boolean, logging? axiumName, createAxiumState(name, storeDialog, logging), [ + kickQuality, openQuality, badActionQuality, closeQuality, diff --git a/src/concepts/axium/axium.selector.ts b/src/concepts/axium/axium.selector.ts index aacb08c..1423115 100644 --- a/src/concepts/axium/axium.selector.ts +++ b/src/concepts/axium/axium.selector.ts @@ -10,6 +10,11 @@ export const axiumSelectLastStrategy: KeyedSelector = { stateKeys: 'lastStrategy', }; +export const axiumSelectLastStrategyData: KeyedSelector = { + conceptName: 'axium', + stateKeys: 'lastStrategyData', +}; + export const axiumSelectBadPlans: KeyedSelector = { conceptName: 'axium', stateKeys: 'badPlans', diff --git a/src/concepts/axium/qualities/addConceptsFromQue.quality.ts b/src/concepts/axium/qualities/addConceptsFromQue.quality.ts index 5b4b9d3..f349b7d 100644 --- a/src/concepts/axium/qualities/addConceptsFromQue.quality.ts +++ b/src/concepts/axium/qualities/addConceptsFromQue.quality.ts @@ -14,7 +14,7 @@ function addConceptsFromQueReducer(state: AxiumState, _ : Action) { addConceptsQue.forEach(concept => { concept.qualities.forEach(quality => { if (quality.methodCreator) { - [quality.method, quality.subject] = quality.methodCreator(state.subConcepts$); + [quality.method, quality.subject] = quality.methodCreator(state.concepts$); quality.method.pipe( catchError((err: unknown, caught: Observable) => { if (state.logging) { diff --git a/src/concepts/axium/qualities/appendActionListToDialog.quality.ts b/src/concepts/axium/qualities/appendActionListToDialog.quality.ts index 359178f..beba967 100644 --- a/src/concepts/axium/qualities/appendActionListToDialog.quality.ts +++ b/src/concepts/axium/qualities/appendActionListToDialog.quality.ts @@ -8,12 +8,13 @@ import { selectPayload } from '../../../model/selector'; export type AppendActionListToDialogPayload = { actionList: Array; strategyTopic: string; + strategyData: unknown; } export const axiumAppendActionListToDialogType: ActionType = 'append Action List to Axium\'s Dialog'; export const axiumAppendActionListToDialog = prepareActionWithPayloadCreator(axiumAppendActionListToDialogType); -export function appendActionListToDialogReducer(state: AxiumState, action: Action) { +export function appendActionListToDialogReducer(state: AxiumState, action: Action): AxiumState { const payload = selectPayload(action); let newDialog = ''; if (state.storeDialog) { @@ -25,11 +26,13 @@ export function appendActionListToDialogReducer(state: AxiumState, action: Actio ...state, dialog: state.dialog + newDialog, lastStrategy: payload.strategyTopic, + lastStrategyData: payload.strategyData }; } return { ...state, - lastStrategy: payload.strategyTopic + lastStrategy: payload.strategyTopic, + lastStrategyData: payload.strategyData }; } diff --git a/src/concepts/axium/qualities/close.quality.ts b/src/concepts/axium/qualities/close.quality.ts index 976b8a6..d5b75f8 100644 --- a/src/concepts/axium/qualities/close.quality.ts +++ b/src/concepts/axium/qualities/close.quality.ts @@ -1,17 +1,28 @@ import { createQuality } from '../../../model/concept'; -import { Action, ActionType, prepareActionCreator } from '../../../model/action'; +import { Action, ActionType, prepareActionWithPayloadCreator } from '../../../model/action'; import { AxiumState } from '../axium.concept'; +import { selectPayload } from '../../../model/selector'; +/** + * @parm exit - If set to true, will exit the current process. + */ +export type ClosePayload = { + exit: boolean +}; export const axiumCloseType: ActionType = 'Close Axium'; -export const axiumClose = prepareActionCreator(axiumCloseType); +export const axiumClose = prepareActionWithPayloadCreator(axiumCloseType); export function closeReducer(state: AxiumState, _action: Action): AxiumState { + const {exit} = selectPayload(_action); state.generalSubscribers.forEach(named => named.subscription.unsubscribe()); state.methodSubscribers.forEach(named => named.subscription.unsubscribe()); state.stagePlanners.forEach(named => named.conclude()); state.action$.complete(); state.concepts$.complete(); state.subConcepts$.complete(); + if (exit) { + process.exit(); + } return { ...state, methodSubscribers: [], diff --git a/src/concepts/axium/qualities/kick.quality.ts b/src/concepts/axium/qualities/kick.quality.ts new file mode 100644 index 0000000..7096fe7 --- /dev/null +++ b/src/concepts/axium/qualities/kick.quality.ts @@ -0,0 +1,12 @@ +import { defaultMethodCreator, defaultReducer } from '../../../model/concept'; +import { ActionType, prepareActionCreator } from '../../../model/action'; +import { createQuality } from '../../../model/concept'; + +export const axiumKickType: ActionType = 'Kick Axium'; +export const axiumKick = prepareActionCreator(axiumKickType); + +export const kickQuality = createQuality( + axiumKickType, + defaultReducer, + defaultMethodCreator +); diff --git a/src/concepts/axium/qualities/log.quality.ts b/src/concepts/axium/qualities/log.quality.ts index 5ab13ac..6b42fb9 100644 --- a/src/concepts/axium/qualities/log.quality.ts +++ b/src/concepts/axium/qualities/log.quality.ts @@ -4,26 +4,19 @@ import { Action, ActionType, prepareActionCreator } from '../../../model/action' import { createQuality } from '../../../model/concept'; import { axiumConclude } from './conclude.quality'; import { strategySuccess } from '../../../model/actionStrategy'; +import { createMethod } from '../../../model/method'; export const axiumLogType: ActionType = 'logged a message passed to Axium'; export const axiumLog = prepareActionCreator(axiumLogType); -const createLogMethodCreator: MethodCreator = () => { - const logSubject = new Subject(); - const logMethod: Method = logSubject.pipe( - map((action: Action) => { - console.log('Logging: ', action); - if (action.strategy) { - return strategySuccess(action.strategy); - } - return axiumConclude(); - }) - ); - return [ - logMethod, - logSubject - ]; -}; +export const createLogMethodCreator: MethodCreator = () => createMethod((action) => { + console.log('Logging: ', action); + if (action.strategy) { + return strategySuccess(action.strategy); + } else { + return axiumConclude(); + } +}); export const logQuality = createQuality( axiumLogType, diff --git a/src/concepts/axium/qualities/setMode.quality.ts b/src/concepts/axium/qualities/setMode.quality.ts index 3a0345e..ddf8d99 100644 --- a/src/concepts/axium/qualities/setMode.quality.ts +++ b/src/concepts/axium/qualities/setMode.quality.ts @@ -2,9 +2,10 @@ import { AxiumState } from '../axium.concept'; import { Action, createAction, prepareActionWithPayloadCreator} from '../../../model/action'; import { createQuality, MethodCreator, Method } from '../../../model/concept'; import { Subject, map } from 'rxjs'; -import { axiumConcludeType } from './conclude.quality'; +import { axiumConclude, axiumConcludeType } from './conclude.quality'; import { strategySuccess } from '../../../model/actionStrategy'; import { selectPayload } from '../../../model/selector'; +import { createMethod } from '../../../model/method'; export type SetModePayload = { modeIndex: number; @@ -14,22 +15,16 @@ export type SetModePayload = { export const axiumSetModeType = 'set Axium Mode'; export const axiumSetMode = prepareActionWithPayloadCreator(axiumSetModeType); -export const createOwnershipMethodCreator: MethodCreator = () : [Method, Subject] => { - const defaultSubject = new Subject(); - const defaultMethod: Method = defaultSubject.pipe( - map((action: Action) => { - const payload = action.payload as SetModePayload; - if (action.strategy) { - action.strategy.currentNode.successNotes = { - denoter: `to ${payload.modeName}.` - }; - return strategySuccess(action.strategy); - } - return createAction(axiumConcludeType); - }), - ); - return [defaultMethod, defaultSubject]; -}; +export const axiumSetModeMethodCreator: MethodCreator = () => createMethod((action) => { + const payload = action.payload as SetModePayload; + if (action.strategy) { + action.strategy.currentNode.successNotes = { + denoter: `to ${payload.modeName}.` + }; + return strategySuccess(action.strategy); + } + return action; +}); export function setModeReducer(state: AxiumState, _action: Action) { const payload = selectPayload(_action); @@ -42,5 +37,5 @@ export function setModeReducer(state: AxiumState, _action: Action) { export const setModeQuality = createQuality( axiumSetModeType, setModeReducer, - createOwnershipMethodCreator + axiumSetModeMethodCreator ); diff --git a/src/concepts/counter/qualities/add.quality.ts b/src/concepts/counter/qualities/add.quality.ts index be146c1..7690d68 100644 --- a/src/concepts/counter/qualities/add.quality.ts +++ b/src/concepts/counter/qualities/add.quality.ts @@ -1,16 +1,20 @@ -import { map, Subject } from 'rxjs'; import { Action, ActionType, prepareActionCreator } from '../../../model/action'; -import { Method, MethodCreator } from '../../../model/concept'; -import { strategySuccess } from '../../../model/actionStrategy'; +import { defaultMethodCreator, Method, MethodCreator } from '../../../model/concept'; import { Counter } from '../counter.concept'; import { createQuality } from '../../../model/concept'; import { counterSelectCount } from '../counter.selector'; -import { axiumConclude } from '../../axium/qualities/conclude.quality'; +// import { createMethod } from '../../../model/method'; +// import { strategySuccess } from '../../../model/actionStrategy'; export const counterAddType: ActionType = 'Counter Add'; export const counterAdd = prepareActionCreator(counterAddType); - +// const createAddMethodCreator: MethodCreator = () => createMethod((action) => { +// if (action.strategy) { +// return strategySuccess(action.strategy); +// } +// return action; +// }); export function addReducer(state: Counter, _: Action) { return { ...state, @@ -18,25 +22,9 @@ export function addReducer(state: Counter, _: Action) { }; } -const addMethodCreator: MethodCreator = () => { - const addSubject = new Subject(); - const addMethod: Method = addSubject.pipe( - map((action: Action) => { - if (action.strategy) { - return strategySuccess(action.strategy); - } - return axiumConclude(); - }) - ); - return [ - addMethod, - addSubject - ]; -}; - export const addQuality = createQuality( counterAddType, addReducer, - addMethodCreator, + defaultMethodCreator, [counterSelectCount] ); \ No newline at end of file diff --git a/src/concepts/counter/qualities/subtract.quality.ts b/src/concepts/counter/qualities/subtract.quality.ts index c751d5c..ba9d16a 100644 --- a/src/concepts/counter/qualities/subtract.quality.ts +++ b/src/concepts/counter/qualities/subtract.quality.ts @@ -1,6 +1,6 @@ import { map, Subject } from 'rxjs'; import { strategySuccess } from '../../../model/actionStrategy'; -import { Method, MethodCreator } from '../../../model/concept'; +import { defaultMethodCreator, Method, MethodCreator } from '../../../model/concept'; import { Counter } from '../counter.concept'; import { Action, ActionType, createAction, prepareActionCreator } from '../../../model/action'; import { createQuality } from '../../../model/concept'; @@ -18,25 +18,9 @@ export function subtractReducer(state: Counter) { }; } -const subtractMethodCreator: MethodCreator = () => { - const subtractSubject = new Subject(); - const subtractMethod: Method = subtractSubject.pipe( - map((action: Action) => { - if (action.strategy) { - return strategySuccess(action.strategy); - } - return axiumConclude(); - }) - ); - return [ - subtractMethod, - subtractSubject - ]; -}; - export const subtractQuality = createQuality( counterSubtractType, subtractReducer, - subtractMethodCreator, + defaultMethodCreator, [counterSelectCount] ); diff --git a/src/concepts/experiment/experiment.concept.ts b/src/concepts/experiment/experiment.concept.ts index 4902f21..8dadcc2 100644 --- a/src/concepts/experiment/experiment.concept.ts +++ b/src/concepts/experiment/experiment.concept.ts @@ -2,15 +2,17 @@ import { Mode, Quality, createConcept } from '../../model/concept'; import { Action } from '../../model/action'; import { PrincipleFunction } from '../../model/principle'; -export type ExperimentActionQueState = { +export type ExperimentState = { actionQue: Action[], + mock: boolean, } export const experimentName = 'experiment'; -export const createExperimentActionQueState = (): ExperimentActionQueState => { +export const createExperimentState = (): ExperimentState => { return { actionQue: [], + mock: false }; }; diff --git a/src/concepts/experiment/experiment.principle.ts b/src/concepts/experiment/experiment.principle.ts index 64d3bce..46698f8 100644 --- a/src/concepts/experiment/experiment.principle.ts +++ b/src/concepts/experiment/experiment.principle.ts @@ -4,7 +4,7 @@ import { PrincipleFunction } from '../../model/principle'; import { Concept } from '../../model/concept'; import { UnifiedSubject } from '../../model/stagePlanner'; import { selectState } from '../../model/selector'; -import { ExperimentActionQueState, experimentName } from './experiment.concept'; +import { ExperimentState, experimentName } from './experiment.concept'; import { axiumRegisterStagePlanner } from '../axium/qualities/registerStagePlanner.quality'; import { axiumSelectOpen } from '../axium/axium.selector'; @@ -26,7 +26,7 @@ export const experimentActionQuePrinciple: PrincipleFunction = ( }, (cpts, _) => { const concepts = cpts; - const experimentState = selectState(concepts, experimentName); + const experimentState = selectState(concepts, experimentName); // console.log('Check que', experimentState.actionQue); if (experimentState.actionQue.length > 0) { if (!readyToGo) { diff --git a/src/concepts/experiment/qualities/checkInStrategy.quality.ts b/src/concepts/experiment/qualities/checkInStrategy.quality.ts index c2f45d1..878fd64 100644 --- a/src/concepts/experiment/qualities/checkInStrategy.quality.ts +++ b/src/concepts/experiment/qualities/checkInStrategy.quality.ts @@ -3,13 +3,13 @@ import { Action, prepareActionCreator } from '../../../model/action'; import { createQuality } from '../../../model/concept'; import { strategySuccess } from '../../../model/actionStrategy'; import { axiumConcludeType } from '../../axium/qualities/conclude.quality'; -import { ExperimentActionQueState } from '../experiment.concept'; +import { ExperimentState } from '../experiment.concept'; export const experimentCheckInStrategyType = 'Experiment Check in Action'; export const experimentCheckInStrategy = prepareActionCreator(experimentCheckInStrategyType); -export function checkInStrategyReducer(state: ExperimentActionQueState, action: Action): ExperimentActionQueState { +export function checkInStrategyReducer(state: ExperimentState, action: Action): ExperimentState { if (action.strategy) { // console.log('Check in reducer', action); const nextAction = strategySuccess(action.strategy); diff --git a/src/concepts/experiment/qualities/debounceAsyncNextActionNode.quality.ts b/src/concepts/experiment/qualities/debounceAsyncNextActionNode.quality.ts new file mode 100644 index 0000000..5c0f2c7 --- /dev/null +++ b/src/concepts/experiment/qualities/debounceAsyncNextActionNode.quality.ts @@ -0,0 +1,24 @@ +import { MethodCreator, defaultReducer } from '../../../model/concept'; +import { prepareActionCreator } from '../../../model/action'; +import { createQuality } from '../../../model/concept'; +import { createAsyncMethodDebounce } from '../../../model/method'; +import { strategySuccess } from '../../../model/actionStrategy'; + +export const experimentAsyncDebounceNextActionNodeType = 'Experiment will debounce incoming actions within set duration asynchronously'; +export const experimentAsyncDebounceNextActionNode = prepareActionCreator(experimentAsyncDebounceNextActionNodeType); + +export const experimentDebounceNextActionNodeMethodCreator: MethodCreator = () => createAsyncMethodDebounce((controller, action) => { + setTimeout(() => { + if (action.strategy) { + controller.fire(strategySuccess(action.strategy)); + } else { + controller.fire(action); + } + }, 50); +}, 500); + +export const asyncDebounceNextActionNodeQuality = createQuality( + experimentAsyncDebounceNextActionNodeType, + defaultReducer, + experimentDebounceNextActionNodeMethodCreator +); diff --git a/src/concepts/experiment/qualities/debounceNextActionNode.quality.ts b/src/concepts/experiment/qualities/debounceNextActionNode.quality.ts new file mode 100644 index 0000000..eac0e3b --- /dev/null +++ b/src/concepts/experiment/qualities/debounceNextActionNode.quality.ts @@ -0,0 +1,22 @@ +import { MethodCreator, defaultReducer } from '../../../model/concept'; +import { prepareActionCreator } from '../../../model/action'; +import { createQuality } from '../../../model/concept'; +import { createMethodDebounce } from '../../../model/method'; +import { strategySuccess } from '../../../model/actionStrategy'; + +export const experimentDebounceNextActionNodeType = 'Experiment will debounce incoming actions within set duration'; +export const experimentDebounceNextActionNode = prepareActionCreator(experimentDebounceNextActionNodeType); + +export const experimentDebounceNextActionNodeMethodCreator: MethodCreator = () => createMethodDebounce((action) => { + if (action.strategy) { + return strategySuccess(action.strategy); + } else { + return action; + } +}, 500); + +export const debounceNextActionNodeQuality = createQuality( + experimentDebounceNextActionNodeType, + defaultReducer, + experimentDebounceNextActionNodeMethodCreator +); diff --git a/src/concepts/experiment/qualities/mockTrue.quality.ts b/src/concepts/experiment/qualities/mockTrue.quality.ts new file mode 100644 index 0000000..5637efe --- /dev/null +++ b/src/concepts/experiment/qualities/mockTrue.quality.ts @@ -0,0 +1,21 @@ +import { defaultMethodCreator, defaultReducer } from '../../../model/concept'; +import { Action, prepareActionCreator } from '../../../model/action'; +import { createQuality } from '../../../model/concept'; +import { ExperimentState } from '../experiment.concept'; + +export const experimentMockTrueType = 'Experiment mock set to True'; + +export const experimentMockTrue = prepareActionCreator(experimentMockTrueType); + +export function checkInStrategyReducer(state: ExperimentState, action: Action): ExperimentState { + return { + ...state, + mock: true + }; +} + +export const mockToTrueQuality = createQuality( + experimentMockTrueType, + checkInStrategyReducer, + defaultMethodCreator +); diff --git a/src/concepts/experiment/qualities/timerEmitAction.quality.ts b/src/concepts/experiment/qualities/timerEmitAction.quality.ts new file mode 100644 index 0000000..d67d83f --- /dev/null +++ b/src/concepts/experiment/qualities/timerEmitAction.quality.ts @@ -0,0 +1,25 @@ +import { MethodCreator, defaultReducer } from '../../../model/concept'; +import { prepareActionCreator } from '../../../model/action'; +import { createQuality } from '../../../model/concept'; +import { createAsyncMethod } from '../../../model/method'; +import { strategySuccess } from '../../../model/actionStrategy'; +import { axiumConclude } from '../../axium/qualities/conclude.quality'; + +export const experimentTimerEmitActionType = 'Experiment create async method with timer, to return action'; +export const experimentTimerEmitAction = prepareActionCreator(experimentTimerEmitActionType); + +export const experimentTimerEmitActionMethodCreator: MethodCreator = () => createAsyncMethod((controller, action) => { + setTimeout(() => { + if (action.strategy) { + controller.fire(strategySuccess(action.strategy)); + } else { + controller.fire(axiumConclude()); + } + }, 500); +}); + +export const timerEmitActionQuality = createQuality( + experimentTimerEmitActionType, + defaultReducer, + experimentTimerEmitActionMethodCreator +); diff --git a/src/concepts/experiment/qualities/timerEmitActionWithConcepts.quality.ts b/src/concepts/experiment/qualities/timerEmitActionWithConcepts.quality.ts new file mode 100644 index 0000000..4edd17f --- /dev/null +++ b/src/concepts/experiment/qualities/timerEmitActionWithConcepts.quality.ts @@ -0,0 +1,32 @@ +import { MethodCreator, defaultReducer } from '../../../model/concept'; +import { prepareActionCreator } from '../../../model/action'; +import { createQuality } from '../../../model/concept'; +import { createAsyncMethodWithConcepts } from '../../../model/method'; +import { strategySuccess } from '../../../model/actionStrategy'; +import { axiumConclude } from '../../axium/qualities/conclude.quality'; +import { UnifiedSubject } from '../../../model/stagePlanner'; +import { strategyData_unifyData } from '../../../model/actionStrategyData'; +import { selectState } from '../../../model/selector'; +import { ExperimentState, experimentName } from '../experiment.concept'; + +export const experimentTimerEmitActionWithConceptsType = 'Experiment create async method with timer and concepts, to return action'; +export const experimentTimerEmitActionWithConcepts = prepareActionCreator(experimentTimerEmitActionWithConceptsType); + +export const experimentTimerEmitActionWithConceptsMethodCreator: MethodCreator = (concepts$?: UnifiedSubject) => + createAsyncMethodWithConcepts((controller, action, concepts) => { + setTimeout(() => { + if (action.strategy) { + const experimentState = selectState(concepts, experimentName); + const data = strategyData_unifyData(action.strategy, { mock: experimentState.mock }); + controller.fire(strategySuccess(action.strategy, data)); + } else { + controller.fire(axiumConclude()); + } + }, 500); + }, concepts$ as UnifiedSubject); + +export const timerEmitActionWithConceptsQuality = createQuality( + experimentTimerEmitActionWithConceptsType, + defaultReducer, + experimentTimerEmitActionWithConceptsMethodCreator +); diff --git a/src/concepts/experiment/strategies/asyncDebounceAddOne.strategy.ts b/src/concepts/experiment/strategies/asyncDebounceAddOne.strategy.ts new file mode 100644 index 0000000..8a56328 --- /dev/null +++ b/src/concepts/experiment/strategies/asyncDebounceAddOne.strategy.ts @@ -0,0 +1,33 @@ +import { createStrategy, ActionStrategy, ActionStrategyParameters, createActionNode } from '../../../model/actionStrategy'; +import { counterAdd } from '../../counter/qualities/add.quality'; +import { experimentAsyncDebounceNextActionNode } from '../qualities/debounceAsyncNextActionNode.quality'; +import { experimentDebounceNextActionNode } from '../qualities/debounceNextActionNode.quality'; + +export const experimentAsyncDebounceAddOneTopic = 'Async debounce add one'; +export function experimentAsyncDebounceAddOneStrategy(): ActionStrategy { + const stepTwo = createActionNode(counterAdd(), { + successNode: null, + successNotes: { + preposition: '', + denoter: 'One;', + }, + failureNode: null, + agreement: 1000, + }); + const stepOne = createActionNode(experimentAsyncDebounceNextActionNode(), { + successNode: stepTwo, + successNotes: { + preposition: '', + denoter: 'One;', + }, + failureNode: null, + agreement: 1000, + }); + + const params: ActionStrategyParameters = { + topic: experimentAsyncDebounceAddOneTopic, + initialNode: stepOne, + }; + + return createStrategy(params); +} \ No newline at end of file diff --git a/src/concepts/experiment/strategies/debounceAddOne.strategy.ts b/src/concepts/experiment/strategies/debounceAddOne.strategy.ts new file mode 100644 index 0000000..ff57eea --- /dev/null +++ b/src/concepts/experiment/strategies/debounceAddOne.strategy.ts @@ -0,0 +1,32 @@ +import { createStrategy, ActionStrategy, ActionStrategyParameters, createActionNode } from '../../../model/actionStrategy'; +import { counterAdd } from '../../counter/qualities/add.quality'; +import { experimentDebounceNextActionNode } from '../qualities/debounceNextActionNode.quality'; + +export const experimentDebounceAddOneTopic = 'Debounce add one'; +export function experimentDebounceAddOneStrategy(): ActionStrategy { + const stepTwo = createActionNode(counterAdd(), { + successNode: null, + successNotes: { + preposition: '', + denoter: 'One;', + }, + failureNode: null, + agreement: 1000, + }); + const stepOne = createActionNode(experimentDebounceNextActionNode(), { + successNode: stepTwo, + successNotes: { + preposition: '', + denoter: 'One;', + }, + failureNode: null, + agreement: 1000, + }); + + const params: ActionStrategyParameters = { + topic: experimentDebounceAddOneTopic, + initialNode: stepOne, + }; + + return createStrategy(params); +} \ No newline at end of file diff --git a/src/concepts/experiment/strategies/timedMockToTrue.strategy.ts b/src/concepts/experiment/strategies/timedMockToTrue.strategy.ts new file mode 100644 index 0000000..ac5a73c --- /dev/null +++ b/src/concepts/experiment/strategies/timedMockToTrue.strategy.ts @@ -0,0 +1,23 @@ +import { ActionStrategy, ActionStrategyParameters, createActionNode, createStrategy } from '../../../model/actionStrategy'; +import { counterSelectCount } from '../../counter/counter.selector'; +import { experimentMockTrue } from '../qualities/mockTrue.quality'; +import { experimentTimerEmitAction } from '../qualities/timerEmitAction.quality'; + +export const timedMockToTrueTopic = 'This will use a async method to eventually set mock to True'; +export function timedMockToTrue(): ActionStrategy { + const stepTwo = createActionNode(experimentMockTrue(), { + successNode: null, + failureNode: null, + }); + const stepOne = createActionNode(experimentTimerEmitAction(), { + successNode: stepTwo, + failureNode: null, + }); + + const params: ActionStrategyParameters = { + topic: timedMockToTrueTopic, + initialNode: stepOne, + }; + + return createStrategy(params); +} \ No newline at end of file diff --git a/src/concepts/experiment/strategies/timedMockToTrueWithConcepts.strategy.ts b/src/concepts/experiment/strategies/timedMockToTrueWithConcepts.strategy.ts new file mode 100644 index 0000000..75c3570 --- /dev/null +++ b/src/concepts/experiment/strategies/timedMockToTrueWithConcepts.strategy.ts @@ -0,0 +1,24 @@ +import { ActionStrategy, ActionStrategyParameters, createActionNode, createStrategy } from '../../../model/actionStrategy'; +import { counterSelectCount } from '../../counter/counter.selector'; +import { experimentMockTrue } from '../qualities/mockTrue.quality'; +import { experimentTimerEmitActionWithConcepts } from '../qualities/timerEmitActionWithConcepts.quality'; + +export const timedMockToTrueWithConceptsTopic = + 'This will use a async method to eventually set mock to TrueWithConcepts and append mock to strategy data.'; +export function timedMockToTrueWithConcepts(): ActionStrategy { + const stepTwo = createActionNode(experimentMockTrue(), { + successNode: null, + failureNode: null, + }); + const stepOne = createActionNode(experimentTimerEmitActionWithConcepts(), { + successNode: stepTwo, + failureNode: null, + }); + + const params: ActionStrategyParameters = { + topic: timedMockToTrueWithConceptsTopic, + initialNode: stepOne, + }; + + return createStrategy(params); +} \ No newline at end of file diff --git a/src/concepts/ownership/ownership.mode.ts b/src/concepts/ownership/ownership.mode.ts index 78c1e57..3497d6a 100644 --- a/src/concepts/ownership/ownership.mode.ts +++ b/src/concepts/ownership/ownership.mode.ts @@ -6,9 +6,7 @@ import { permissiveMode, blockingMode } from '../axium/axium.mode'; import { checkIn, clearStubs, ownershipShouldBlock, updateAddToPendingActions } from '../../model/ownership'; import { ActionStrategy, strategyFailed } from '../../model/actionStrategy'; import { UnifiedSubject } from '../../model/stagePlanner'; -import { AppendActionListToDialogPayload, axiumAppendActionListToDialogType } from '../axium/qualities/appendActionListToDialog.quality'; -import { selectState } from '../../model/selector'; -import { OwnershipState, ownershipName } from './ownership.concept'; +import { AppendActionListToDialogPayload, axiumAppendActionListToDialog, axiumAppendActionListToDialogType } from '../axium/qualities/appendActionListToDialog.quality'; import { AxiumState } from '../axium/axium.concept'; import { failureConditions, strategyData_appendFailure } from '../../model/actionStrategyData'; @@ -44,11 +42,11 @@ export const ownershipMode: Mode = ( // Logical Determination: axiumConcludeType if (nextAction.semaphore[3] === 3) { concepts = clearStubs(concepts, nextAction.strategy as ActionStrategy); - nextAction = createAction(axiumAppendActionListToDialogType); - nextAction.payload = { + nextAction = axiumAppendActionListToDialog({ actionList: action.strategy.actionList, - strategyTopic: action.strategy.topic - } as AppendActionListToDialogPayload; + strategyTopic: action.strategy.topic, + strategyData: action.strategy.data + }); } finalMode([nextAction, concepts, action$, concepts$]); } else { diff --git a/src/concepts/ownership/qualities/backTrack.quality.ts b/src/concepts/ownership/qualities/backTrack.quality.ts index 9a46086..0b05119 100644 --- a/src/concepts/ownership/qualities/backTrack.quality.ts +++ b/src/concepts/ownership/qualities/backTrack.quality.ts @@ -1,28 +1,19 @@ -import { Method, MethodCreator, createQuality, defaultMethodCreator, defaultReducer } from '../../../model/concept'; -import { Action, ActionType, prepareActionCreator } from '../../../model/action'; -import { Subject, map } from 'rxjs'; +import { MethodCreator, createQuality, defaultMethodCreator, defaultReducer } from '../../../model/concept'; +import { ActionType, prepareActionCreator } from '../../../model/action'; import { strategyBackTrack } from '../../../model/actionStrategy'; -import { axiumConclude } from '../../axium/qualities/conclude.quality'; +import { createMethod } from '../../../model/method'; export const ownershipBackTrackType: ActionType = 'backtracking to previous ActionNode'; export const ownershipBackTrack = prepareActionCreator(ownershipBackTrackType); -const createBackTrackMethodCreator: MethodCreator = () => { - const backTrackSubject = new Subject(); - const backTrackMethod: Method = backTrackSubject.pipe( - map((action: Action) => { - if (action.strategy) { - const newAction = strategyBackTrack(action.strategy); - return newAction; - } - return axiumConclude(); - }) - ); - return [ - backTrackMethod, - backTrackSubject - ]; -}; +const createBackTrackMethodCreator: MethodCreator = () => createMethod((action) => { + if (action.strategy) { + const newAction = strategyBackTrack(action.strategy); + return newAction; + } else { + return action; + } +}); export const backTrackQuality = createQuality( ownershipBackTrackType, diff --git a/src/concepts/ownership/qualities/clearStrategyStubsFromLedgerAndSelf.quality.ts b/src/concepts/ownership/qualities/clearStrategyStubsFromLedgerAndSelf.quality.ts index 4235cca..b7e4696 100644 --- a/src/concepts/ownership/qualities/clearStrategyStubsFromLedgerAndSelf.quality.ts +++ b/src/concepts/ownership/qualities/clearStrategyStubsFromLedgerAndSelf.quality.ts @@ -1,30 +1,20 @@ -import { Method, MethodCreator, createQuality, defaultMethodCreator } from '../../../model/concept'; -import { Action, ActionType, createAction, prepareActionCreator } from '../../../model/action'; +import { MethodCreator, createQuality } from '../../../model/concept'; +import { Action, ActionType, prepareActionCreator } from '../../../model/action'; import { OwnershipState } from '../ownership.concept'; import { OwnershipTicket } from '../../../model/ownership'; -import { Subject, map } from 'rxjs'; import { strategySuccess } from '../../../model/actionStrategy'; -import { axiumConclude } from '../../axium/qualities/conclude.quality'; +import { createMethod } from '../../../model/method'; export const ownershipClearStrategyStubsFromLedgerAndSelfType: ActionType = 'clear current Strategy Stubs from Ownership Ledger and Itself'; export const ownershipClearStrategyStubsFromLedgerAndSelf = prepareActionCreator(ownershipClearStrategyStubsFromLedgerAndSelfType); -const createClearStrategyStubsFromLedgerAndSelfMethodCreator: MethodCreator = () => { - const logSubject = new Subject(); - const logMethod: Method = logSubject.pipe( - map((action: Action) => { - if (action.strategy) { - action.strategy.stubs = undefined; - return strategySuccess(action.strategy); - } - return axiumConclude(); - }) - ); - return [ - logMethod, - logSubject - ]; -}; +const createClearStrategyStubsFromLedgerAndSelfMethodCreator: MethodCreator = () => createMethod((action) => { + if (action.strategy) { + action.strategy.stubs = undefined; + return strategySuccess(action.strategy); + } + return action; +}); export function clearStrategyStubsFromLedgerAndSelfReducer(state: OwnershipState, action: Action): OwnershipState { const stubs = action?.strategy?.stubs; diff --git a/src/index.ts b/src/index.ts index aba428a..fd4da28 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,6 +23,19 @@ export { strategyData_select, strategyData_unifyData } from './model/actionStrategyData'; +export { + createMethod, + createAsyncMethod, + createMethodWithConcepts, + createAsyncMethodWithConcepts, + createMethodDebounce, + createAsyncMethodDebounce, + createMethodDebounceWithConcepts, + createAsyncMethodDebounceWithConcepts +} from './model/method'; +export { + debounceAction +} from './model/debounceAction'; export type { Action, ActionType } from './model/action'; export { primeAction, createAction, getSemaphore, prepareActionCreator, prepareActionWithPayloadCreator } from './model/action'; export { createConcept, createQuality, defaultReducer, defaultMethodCreator } from './model/concept'; @@ -49,10 +62,11 @@ export { AxiumState, axiumName, createAxiumConcept } from './concepts/axium/axiu export { blockingMode, permissiveMode } from './concepts/axium/axium.mode'; export { axiumSelectOpen, axiumSelectLastStrategy, axiumSelectBadActions, axiumSelectBadPlans } from './concepts/axium/axium.selector'; // Qualities +export { axiumKick, axiumKickType } from './concepts/axium/qualities/kick.quality'; export { axiumConclude, axiumConcludeType } from './concepts/axium/qualities/conclude.quality'; export { axiumOpen, axiumOpenType } from './concepts/axium/qualities/open.quality'; export { axiumLog, axiumLogType } from './concepts/axium/qualities/log.quality'; -export { axiumClose, axiumCloseType } from './concepts/axium/qualities/close.quality'; +export { axiumClose, axiumCloseType, ClosePayload } from './concepts/axium/qualities/close.quality'; export { axiumBadAction, axiumBadActionType } from './concepts/axium/qualities/badAction.quality'; export { axiumSetMode, axiumSetModeType, SetModePayload } from './concepts/axium/qualities/setMode.quality'; export { @@ -135,8 +149,8 @@ export { // Experiment export { - ExperimentActionQueState, - createExperimentActionQueState, + ExperimentState, + createExperimentState, createExperimentConcept, experimentName } from './concepts/experiment/experiment.concept'; diff --git a/src/model/axium.ts b/src/model/axium.ts index 85a22d7..9b19189 100644 --- a/src/model/axium.ts +++ b/src/model/axium.ts @@ -14,7 +14,7 @@ import { initializationStrategy, } from '../concepts/axium/axium.concept'; import { axiumBadActionType } from '../concepts/axium/qualities/badAction.quality'; -import { axiumCloseType } from '../concepts/axium/qualities/close.quality'; +import { axiumClose, axiumCloseType } from '../concepts/axium/qualities/close.quality'; import { axiumAppendActionListToDialog, } from '../concepts/axium/qualities/appendActionListToDialog.quality'; @@ -28,7 +28,8 @@ export const blockingMethodSubscription = (action$: Subject, action: Act // Allows for reducer next in sequence const appendToDialog = axiumAppendActionListToDialog({ actionList: action.strategy.actionList, - strategyTopic: action.strategy.topic + strategyTopic: action.strategy.topic, + strategyData: action.strategy.data }); action$.next(appendToDialog); action$.next(action); @@ -49,7 +50,8 @@ export const defaultMethodSubscription = (action$: Subject, action: Acti // Allows for reducer next in sequence const appendToDialog = axiumAppendActionListToDialog({ actionList: action.strategy.actionList, - strategyTopic: action.strategy.topic + strategyTopic: action.strategy.topic, + strategyData: action.strategy.data }); setTimeout(() => { action$.next(appendToDialog); @@ -72,7 +74,7 @@ export function createAxium(name: string, initialConcepts: Concept[], logging?: concepts.forEach((concept, _index) => { concept.qualities.forEach(quality => { if (quality.methodCreator) { - const [method, subject] = quality.methodCreator(axiumState.subConcepts$); + const [method, subject] = quality.methodCreator(axiumState.concepts$); quality.method = method; quality.subject = subject; quality.method.pipe( @@ -132,8 +134,10 @@ export function createAxium(name: string, initialConcepts: Concept[], logging?: axiumState.action$.next( strategyBegin(initializationStrategy(concepts)), ); - const close = () => { - action$.next(createAction(axiumCloseType)); + const close = (exit?: boolean) => { + action$.next(axiumClose({ + exit: exit ? exit : false + })); }; return { subscribe: subConcepts$.subscribe.bind(subConcepts$), diff --git a/src/model/concept.ts b/src/model/concept.ts index 9b8b11b..1e641e1 100644 --- a/src/model/concept.ts +++ b/src/model/concept.ts @@ -1,11 +1,12 @@ -import { Observable, Subject } from 'rxjs'; -import { Action, ActionType, createAction } from './action'; +import { Observable, Subject, debounceTime, switchMap, withLatestFrom } from 'rxjs'; +import { Action, ActionType } from './action'; import { PrincipleFunction } from '../model/principle'; import { strategySuccess } from './actionStrategy'; import { map } from 'rxjs'; import { KeyedSelector } from './selector'; -import { axiumConclude } from '../concepts/axium/qualities/conclude.quality'; +import { axiumConclude, axiumConcludeType } from '../concepts/axium/qualities/conclude.quality'; import { UnifiedSubject } from './stagePlanner'; +import { ActionController, createActionController$ } from './actionController'; // eslint-disable-next-line @typescript-eslint/no-explicit-any export type Reducer = (state: any, action: Action) => any; @@ -127,7 +128,10 @@ export const defaultMethodCreator: MethodCreator = () : [Method, Subject if (action.strategy) { return strategySuccess(action.strategy); } - return axiumConclude(); + return { + ...action, + type: axiumConcludeType + }; }), ); return [defaultMethod, defaultSubject]; diff --git a/src/model/debounceAction.ts b/src/model/debounceAction.ts new file mode 100644 index 0000000..5ff146c --- /dev/null +++ b/src/model/debounceAction.ts @@ -0,0 +1,165 @@ +import { + MonoTypeOperatorFunction, + Observable, + OperatorFunction, + SchedulerAction, + SchedulerLike, + Subscriber, + Subscription, + asyncScheduler +} from 'rxjs'; +import { Action } from './action'; +import { axiumConclude } from '../concepts/axium/qualities/conclude.quality'; + +function hasLift(source: any): source is { lift: InstanceType['lift'] } { + return typeof source?.lift === 'function'; +} + +function operate( + init: (liftedSource: Observable, subscriber: Subscriber) => (() => void) | void +): OperatorFunction { + return (source: Observable) => { + if (hasLift(source)) { + // eslint-disable-next-line consistent-return + return source.lift(function (this: Subscriber, liftedSource: Observable) { + try { + return init(liftedSource, this); + } catch (err) { + this.error(err); + } + }); + } + throw new TypeError('Unable to lift unknown Observable type'); + }; +} + +class OperatorSubscriber extends Subscriber { + constructor( + destination: Subscriber, + onNext?: (value: T) => void, + onComplete?: () => void, + onError?: (err: any) => void, + private onFinalize?: () => void, + private shouldUnsubscribe?: () => boolean + ) { + super(destination); + this._next = onNext + ? function (this: OperatorSubscriber, value: T) { + try { + onNext(value); + } catch (err) { + destination.error(err); + } + } + : super._next; + this._error = onError + ? function (this: OperatorSubscriber, err: any) { + try { + onError(err); + } catch (error) { + // Send any errors that occur down stream. + destination.error(error); + } finally { + // Ensure finalization. + this.unsubscribe(); + } + } + : super._error; + this._complete = onComplete + ? function (this: OperatorSubscriber) { + try { + onComplete(); + } catch (err) { + // Send any errors that occur down stream. + destination.error(err); + } finally { + // Ensure finalization. + this.unsubscribe(); + } + } + : super._complete; + } + + unsubscribe() { + if (!this.shouldUnsubscribe || this.shouldUnsubscribe()) { + const { closed } = this; + super.unsubscribe(); + !closed && this.onFinalize?.(); + } + } +} + +function createOperatorSubscriber( + destination: Subscriber, + onNext?: (value: T) => void, + onComplete?: () => void, + onError?: (err: any) => void, + onFinalize?: () => void +): Subscriber { + return new OperatorSubscriber(destination, onNext, onComplete, onError, onFinalize); +} + +/** + * This will prevent all actions for the specified duration, but will still emit actions as axiumConclude + * Thus this needs to be taken into account in the Method using debounceAction if implemented directly. + * But will be handled automatically in actionControllers and associated debounce createMethods. + */ +export function debounceAction(dueTime: number, scheduler: SchedulerLike = asyncScheduler): MonoTypeOperatorFunction { + return operate((source, subscriber) => { + let activeTask: Subscription | null = null; + let lastValue: Action | null = null; + let lastTime: number | null = null; + + const emit = () => { + if (activeTask) { + activeTask.unsubscribe(); + activeTask = null; + const value = lastValue!; + lastValue = null; + subscriber.next(value); + } + }; + function emitWhenIdle(this: SchedulerAction) { + const targetTime = lastTime! + dueTime; + const now = scheduler.now(); + if (now < targetTime) { + activeTask = this.schedule(undefined, targetTime - now); + subscriber.add(activeTask); + return; + } + + emit(); + } + + source.subscribe( + createOperatorSubscriber( + subscriber, + (value: Action) => { + lastValue = value; + lastTime = scheduler.now(); + if (!activeTask) { + activeTask = scheduler.schedule(emitWhenIdle, dueTime); + subscriber.add(activeTask); + } else { + // All this code just to place this code block. + const conclude = { + ...value, + ...axiumConclude(), + }; + subscriber.next( + conclude + ); + } + }, + () => { + emit(); + subscriber.complete(); + }, + undefined, + () => { + lastValue = activeTask = null; + } + ) + ); + }); +} \ No newline at end of file diff --git a/src/model/method.ts b/src/model/method.ts new file mode 100644 index 0000000..e732745 --- /dev/null +++ b/src/model/method.ts @@ -0,0 +1,168 @@ +import { Observable, Subject, map, switchMap, withLatestFrom } from 'rxjs'; +import { Concept } from './concept'; +import { UnifiedSubject } from './stagePlanner'; +import { ActionController, createActionController$ } from './actionController'; +import { ActionStrategy } from './actionStrategy'; +import { KeyedSelector } from './selector'; +import { debounceAction } from './debounceAction'; + +export type ActionType = string; +const axiumConcludeType: ActionType = 'Conclude'; +type Action = { + type: ActionType; + semaphore: [number, number, number, number]; + payload?: unknown; + strategy?: ActionStrategy; + keyedSelectors?: KeyedSelector[]; + agreement?: number; + expiration: number; + axium?: string; +}; +type Method = Observable; + +export const createMethod = + (method: (action: Action) => Action): [Method, Subject] => { + const defaultSubject = new Subject(); + const defaultMethod: Method = defaultSubject.pipe( + map((action: Action) => { + const methodAction = method(action); + if (methodAction.strategy) { + return methodAction; + } + return { + ...action, + type: axiumConcludeType + }; + }), + ); + return [defaultMethod, defaultSubject]; + }; +export const createMethodWithConcepts = + (method: (action: Action) => Action, concepts$: UnifiedSubject): [Method, Subject] => { + const defaultSubject = new Subject(); + const defaultMethod: Method = defaultSubject.pipe( + withLatestFrom(concepts$ as UnifiedSubject), + map(([act, concepts] : [Action, Concept[]]) => { + const methodAction = method(act); + if (methodAction.strategy) { + return methodAction; + } + return { + ...act, + type: axiumConcludeType + }; + }), + ); + return [defaultMethod, defaultSubject]; + }; +export const createAsyncMethod = + (asyncMethod: (controller: ActionController, action: Action) => void): [Method, Subject] => { + const defaultSubject = new Subject(); + const defaultMethod: Method = defaultSubject.pipe( + switchMap(act => createActionController$(act, (controller: ActionController, action: Action) => { + asyncMethod(controller, action); + })), + ); + return [defaultMethod, defaultSubject]; + }; +export const createAsyncMethodWithConcepts = + (asyncMethodWithConcepts: (controller: ActionController, action: Action, concepts: Concept[]) => void, concepts$: UnifiedSubject) + : [Method, Subject] => { + const defaultSubject = new Subject(); + const defaultMethod: Method = defaultSubject.pipe( + withLatestFrom(concepts$ as UnifiedSubject), + switchMap(([act, concepts] : [Action, Concept[]]) => + createActionController$(act, (controller: ActionController, action: Action) => { + asyncMethodWithConcepts(controller, action, concepts); + })), + ); + return [defaultMethod, defaultSubject]; + }; +export const createMethodDebounce = + (method: (action: Action) => Action, duration: number): [Method, Subject] => { + const defaultSubject = new Subject(); + const defaultMethod: Method = defaultSubject.pipe( + debounceAction(duration), + map((action: Action) => { + // Logically Determined axiumConclude + if (action.semaphore[3] !== 3) { + const methodAction = method(action); + if (methodAction.strategy) { + return methodAction; + } + return { + ...action, + type: axiumConcludeType + }; + } else { + return action; + } + }), + ); + return [defaultMethod, defaultSubject]; + }; +export const createMethodDebounceWithConcepts = + (methodWithConcepts: (action: Action, concepts: Concept[]) => Action, concepts$: UnifiedSubject, duration: number) + : [Method, Subject] => { + const defaultSubject = new Subject(); + const defaultMethod: Method = defaultSubject.pipe( + debounceAction(duration), + withLatestFrom(concepts$ as UnifiedSubject), + map(([act, concepts] : [Action, Concept[]]) => { + // Logically Determined axiumConclude + if (act.semaphore[3] !== 3) { + const methodAction = methodWithConcepts(act, concepts); + if (methodAction.strategy) { + return methodAction; + } + return { + ...act, + type: axiumConcludeType + }; + } else { + return act; + } + }), + ); + return [defaultMethod, defaultSubject]; + }; +export const createAsyncMethodDebounce = + (asyncMethod: (controller: ActionController, action: Action) => void, duration: number): [Method, Subject] => { + const defaultSubject = new Subject(); + const defaultMethod: Method = defaultSubject.pipe( + debounceAction(duration), + switchMap((act) => { + if (act.semaphore[3] !== 3) { + return createActionController$(act, (controller: ActionController, action: Action) => { + asyncMethod(controller, action); + }); + } else { + return createActionController$(act, (controller: ActionController, _) => { + controller.fire(act); + }); + } + }), + ); + return [defaultMethod, defaultSubject]; + }; +export const createAsyncMethodDebounceWithConcepts = + (asyncMethodWithConcepts: (controller: ActionController, action: Action, concepts: Concept[]) => + void, concepts$: UnifiedSubject, duration: number): [Method, Subject] => { + const defaultSubject = new Subject(); + const defaultMethod: Method = defaultSubject.pipe( + debounceAction(duration), + withLatestFrom(concepts$ as UnifiedSubject), + switchMap(([act, concepts] : [Action, Concept[]]) => { + if (act.semaphore[3] !== 3) { + return createActionController$(act, (controller: ActionController, action: Action) => { + asyncMethodWithConcepts(controller, action, concepts); + }); + } else { + return createActionController$(act, (controller: ActionController, _) => { + controller.fire(act); + }); + } + }) + ); + return [defaultMethod, defaultSubject]; + }; \ No newline at end of file diff --git a/src/model/stagePlanner.ts b/src/model/stagePlanner.ts index 543f574..8590cf8 100644 --- a/src/model/stagePlanner.ts +++ b/src/model/stagePlanner.ts @@ -196,9 +196,9 @@ export class UnifiedSubject extends Subject { super(); } stage(title: string, stages: Staging[]): StagePlanner { - this.currentStages.set(this.planId, {title, stages, stage: 0, stageFailed: -1}); const planId = this.planId; this.planId++; + this.currentStages.set(planId, {title, stages, stage: 0, stageFailed: -1}); const conclude = () => { this.currentStages.delete(planId); }; @@ -221,6 +221,7 @@ export class UnifiedSubject extends Subject { let run = true; [stageDelimiter, goodAction] = handleStageDelimiter(plan, action, stageDelimiter, options); [stageDelimiter, run] = handleRun(value, stageDelimiter, plan, action, options); + // console.log('HIT', action, goodAction, run); this.stageDelimiters.set(key, stageDelimiter); if (goodAction && run) { const action$ = axiumState.action$ as Subject; diff --git a/src/test/actionController.test.ts b/src/test/actionController.test.ts index dd56552..80bd184 100644 --- a/src/test/actionController.test.ts +++ b/src/test/actionController.test.ts @@ -1,6 +1,20 @@ import { ActionController, createActionController$ } from '../model/actionController'; import { axiumBadActionType } from '../concepts/axium/qualities/badAction.quality'; import { axiumLog, axiumLogType } from '../concepts/axium/qualities/log.quality'; +import { ExperimentState, createExperimentConcept, createExperimentState, experimentName } from '../concepts/experiment/experiment.concept'; +import { timerEmitActionQuality } from '../concepts/experiment/qualities/timerEmitAction.quality'; +import { mockToTrueQuality } from '../concepts/experiment/qualities/mockTrue.quality'; +import { createAxium } from '../model/axium'; +import { timedMockToTrue } from '../concepts/experiment/strategies/timedMockToTrue.strategy'; +import { createActionNode, strategyBegin } from '../model/actionStrategy'; +import { selectSlice, selectState } from '../model/selector'; +import { axiumConcludeType } from '../concepts/axium/qualities/conclude.quality'; +import { timerEmitActionWithConceptsQuality } from '../concepts/experiment/qualities/timerEmitActionWithConcepts.quality'; +import { + timedMockToTrueWithConcepts, + timedMockToTrueWithConceptsTopic +} from '../concepts/experiment/strategies/timedMockToTrueWithConcepts.strategy'; +import { axiumSelectLastStrategy, axiumSelectLastStrategyData } from '../concepts/axium/axium.selector'; test('ActionController Expired Test', (done) => { const act = axiumLog(undefined, 200); @@ -30,4 +44,23 @@ test('ActionController createActionController$ Test', (done) => { expect(action.type).toBe(axiumLogType); done(); }); -}); \ No newline at end of file +}); + +test('ActionController createActionController$ Mock Strategy', (done) => { + const act = axiumLog(undefined, 200); + act.strategy = { + topic: 'Mock Strategy', + actionList: [''], + currentNode: createActionNode(act, { + successNode: null, + failureNode: null + }) + }; + const cont = createActionController$(act, (controller, action) => { + controller.fire(action); + }); + cont.subscribe(action => { + expect(action.type).toBe(axiumLogType); + done(); + }); +}); diff --git a/src/test/debounceMethods.test.ts b/src/test/debounceMethods.test.ts new file mode 100644 index 0000000..25b62c4 --- /dev/null +++ b/src/test/debounceMethods.test.ts @@ -0,0 +1,107 @@ +import { axiumKick } from '../concepts/axium/qualities/kick.quality'; +import { Counter, counterName, createCounterConcept } from '../concepts/counter/counter.concept'; +import { createExperimentConcept, createExperimentState } from '../concepts/experiment/experiment.concept'; +import { asyncDebounceNextActionNodeQuality } from '../concepts/experiment/qualities/debounceAsyncNextActionNode.quality'; +import { debounceNextActionNodeQuality } from '../concepts/experiment/qualities/debounceNextActionNode.quality'; +import { experimentAsyncDebounceAddOneStrategy } from '../concepts/experiment/strategies/asyncDebounceAddOne.strategy'; +import { experimentDebounceAddOneStrategy } from '../concepts/experiment/strategies/debounceAddOne.strategy'; +import { strategyBegin } from '../model/actionStrategy'; +import { createAxium } from '../model/axium'; +import { selectState } from '../model/selector'; + +test('Debounce method prevent excess count', (done) => { + const experiment = createExperimentConcept(createExperimentState(), [debounceNextActionNodeQuality]); + const axium = createAxium('Experiment async method creator with Concepts', [createCounterConcept(), experiment]); + const plan = axium.stage('timed mock to true', [ + (_, dispatch) => { + dispatch(strategyBegin(experimentDebounceAddOneStrategy()), { + iterateStage: true + }); + }, + (_, dispatch) => { + dispatch(strategyBegin(experimentDebounceAddOneStrategy()), { + iterateStage: true + }); + }, + (_, dispatch) => { + dispatch(strategyBegin(experimentDebounceAddOneStrategy()), { + iterateStage: true + }); + }, + (concepts, _) => { + const counterState = selectState(concepts, counterName); + console.log('Debounce HIT 4', counterState); + if (counterState.count === 1) { + console.log('Final Debounce HIT 4', counterState); + expect(counterState.count).toBe(1); + plan.conclude(); + done(); + } + } + ]); +}); + +test('Async debounce method prevent excess count', (done) => { + const experiment = createExperimentConcept(createExperimentState(), [asyncDebounceNextActionNodeQuality]); + const axium = createAxium('Experiment async method creator with Concepts', [createCounterConcept(), experiment]); + const plan = axium.stage('timed mock to true', [ + (_, dispatch) => { + dispatch(strategyBegin(experimentAsyncDebounceAddOneStrategy()), { + iterateStage: true + }); + }, + (_, dispatch) => { + dispatch(strategyBegin(experimentAsyncDebounceAddOneStrategy()), { + iterateStage: true + }); + }, + (_, dispatch) => { + dispatch(strategyBegin(experimentAsyncDebounceAddOneStrategy()), { + iterateStage: true + }); + }, + (concepts, _) => { + const counterState = selectState(concepts, counterName); + console.log('Async Debounce HIT 4', counterState); + if (counterState.count === 1) { + console.log('FINAL Async Debounce HIT 4', counterState); + expect(counterState.count).toBe(1); + plan.conclude(); + } + } + ]); + setTimeout(() => { + const secondPlan = axium.stage('second timed mock', [ + (_, dispatch) => { + dispatch(strategyBegin(experimentAsyncDebounceAddOneStrategy()), { + iterateStage: true + }); + }, + (_, dispatch) => { + dispatch(strategyBegin(experimentAsyncDebounceAddOneStrategy()), { + iterateStage: true + }); + }, + (_, dispatch) => { + dispatch(strategyBegin(experimentAsyncDebounceAddOneStrategy()), { + iterateStage: true + }); + }, + (concepts, _) => { + const counterState = selectState(concepts, counterName); + console.log('Async 2 Debounce HIT 4', counterState); + if (counterState.count === 2) { + console.log('FINAL Async 2 Debounce HIT 4', counterState); + expect(counterState.count).toBe(2); + secondPlan.conclude(); + setTimeout(() => { + done(); + }, 500); + } + } + ]); + // Axium must be primed, therefore we kick it back into gear. + // Downside of halting quality. + axium.dispatch(axiumKick()); + }, 1000); +}); \ No newline at end of file diff --git a/src/test/methodHelpers.test.ts b/src/test/methodHelpers.test.ts new file mode 100644 index 0000000..07b5caa --- /dev/null +++ b/src/test/methodHelpers.test.ts @@ -0,0 +1,58 @@ +import { axiumSelectLastStrategy, axiumSelectLastStrategyData } from '../concepts/axium/axium.selector'; +import { ExperimentState, createExperimentConcept, createExperimentState, experimentName } from '../concepts/experiment/experiment.concept'; +import { mockToTrueQuality } from '../concepts/experiment/qualities/mockTrue.quality'; +import { timerEmitActionQuality } from '../concepts/experiment/qualities/timerEmitAction.quality'; +import { timerEmitActionWithConceptsQuality } from '../concepts/experiment/qualities/timerEmitActionWithConcepts.quality'; +import { timedMockToTrue } from '../concepts/experiment/strategies/timedMockToTrue.strategy'; +import { + timedMockToTrueWithConcepts, + timedMockToTrueWithConceptsTopic +} from '../concepts/experiment/strategies/timedMockToTrueWithConcepts.strategy'; +import { strategyBegin } from '../model/actionStrategy'; +import { createAxium } from '../model/axium'; +import { selectSlice, selectState } from '../model/selector'; + +test('ActionController async Method Test', (done) => { + const experiment = createExperimentConcept(createExperimentState(), [timerEmitActionQuality, mockToTrueQuality]); + const axium = createAxium('Experiment async method creator', [experiment]); + const plan = axium.stage('timed mock to true', [ + (_, dispatch) => { + dispatch(strategyBegin(timedMockToTrue()), { + iterateStage: true + }); + }, + (concepts, _) => { + const experimentState = selectState(concepts, experimentName); + if (experimentState.mock) { + expect(experimentState.mock).toBe(true); + plan.conclude(); + done(); + } + } + ]); +}); + +test('ActionController async Method Test with Concepts', (done) => { + const experiment = createExperimentConcept(createExperimentState(), [timerEmitActionWithConceptsQuality, mockToTrueQuality]); + const axium = createAxium('Experiment async method creator with Concepts', [experiment]); + const plan = axium.stage('timed mock to true', [ + (_, dispatch) => { + dispatch(strategyBegin(timedMockToTrueWithConcepts()), { + iterateStage: true + }); + }, + (concepts, _) => { + const lastStrategy = selectSlice(concepts, axiumSelectLastStrategy); + const experimentState = selectState(concepts, experimentName); + if (lastStrategy === timedMockToTrueWithConceptsTopic) { + const data = selectSlice(concepts, axiumSelectLastStrategyData); + if (data) { + expect(data.mock).toBe(false); + expect(experimentState.mock).toBe(true); + plan.conclude(); + done(); + } + } + } + ]); +}); diff --git a/src/test/ownership.test.ts b/src/test/ownership.test.ts index 30c28e0..484d8e6 100644 --- a/src/test/ownership.test.ts +++ b/src/test/ownership.test.ts @@ -5,7 +5,7 @@ import { OwnershipState, createOwnershipConcept, ownershipName } from '../concep import { AxiumState } from '../concepts/axium/axium.concept'; import { setOwnerShipModeTopic } from '../concepts/ownership/strategies/setOwnerShipMode.strategy'; import { Counter, counterName, createCounterConcept } from '../concepts/counter/counter.concept'; -import { createExperimentActionQueState, createExperimentConcept } from '../concepts/experiment/experiment.concept'; +import { createExperimentState, createExperimentConcept } from '../concepts/experiment/experiment.concept'; import { puntCountingStrategy } from '../concepts/experiment/strategies/puntCounting.strategy'; import { strategyBegin } from '../model/actionStrategy'; import { @@ -24,7 +24,7 @@ test('Ownership Test', (done) => { const axium = createAxium('ownershipTest', [ createOwnershipConcept(), createCounterConcept(), - createExperimentConcept(createExperimentActionQueState(), [checkInStrategyQuality], [experimentActionQuePrinciple]) + createExperimentConcept(createExperimentState(), [checkInStrategyQuality], [experimentActionQuePrinciple]) ], true, true); const plan = axium.stage( 'Testing Ownership Staging', [