Comparing version 0.7.1 to 0.8.0-alpha.0
@@ -1,3 +0,23 @@ | ||
### 0.7.0 | ||
## 0.8.0 | ||
Some refactoring of the Async Actions and adding of hooks for much finer grained control: | ||
`shortCicuitHook()`: Run checks to resolve the action with a response before it even sets out. | ||
`breakCacheHook()`: When an action's state is being returned from the cache, this hook allows you to run checks on the current cache and your stores to decide whether this action should be run again (essentially flushing / breaking the cache). | ||
`postActionHook()`: This hook allows you to run some things after the action has resolved, and most importantly allows code to run after each time we hit the cached result of this action as well. This is very useful for interface changes which need to change / update outside of the action code. | ||
`postActionHook()` is run with a context variable which tells you in which context it was run, one of: CACHE, SHORT_CIRCUIT, DIRECT_RUN | ||
These hooks should hopefully allow even more boilerplate code to be eliminated while working in asynchronous state scenarios. | ||
## 0.7.1 | ||
* Made the `isResolved()` function safe from causing infinite loops (Async Action resolves, but the state of the store still makes `isResolved()` return false which causes a re-trigger when re-rendering - most likely happens when not checking for error states in `isResolved()`) - instead posting an error message to the console informing about the loop which needs to be fixed. | ||
## 0.7.0 | ||
**:warning: Replaced with async action hooks above in 0.8.0** | ||
Added the options of setting an `isResolve()` synchronous checking function on Async Actions. This allows for early escape hatching (we don't need to run this async action based on the current state) and cache busting (even though we ran this Async Action before and we have a cached result, the current state indicates we need to run it again). | ||
@@ -38,3 +58,3 @@ | ||
### 0.6.0 | ||
## 0.6.0 | ||
@@ -41,0 +61,0 @@ * Added "reactions" to store state. Usable like so: |
import { IPullstateAllStores } from "./PullstateCore"; | ||
declare type TPullstateAsyncUpdateListener = () => void; | ||
export declare type TPullstateAsyncWatchResponse<R, T extends string> = [boolean, boolean, TAsyncActionResult<R, T>, boolean]; | ||
export declare type TPullstateAsyncResponseCacheFull<R, T extends string> = [boolean, boolean, TAsyncActionResult<R, T>, boolean, TAsyncActionResult<R, T> | true | null]; | ||
export declare type TPullstateAsyncBeckonResponse<R, T extends string> = [boolean, TAsyncActionResult<R, T>, boolean]; | ||
@@ -31,3 +32,10 @@ export declare type TPullstateAsyncRunResponse<R, T extends string> = Promise<TAsyncActionResult<R, T>>; | ||
export declare type TPullstateAsyncAction<A, R, T extends string, S extends IPullstateAllStores> = (args: A, stores: S) => Promise<TAsyncActionResult<R, T>>; | ||
export declare type TPullstateAsyncIsResolvedFunction<A, R, T extends string, S extends IPullstateAllStores> = (args: A, stores: S) => TAsyncActionResult<R, T> | false; | ||
export declare type TPullstateAsyncShortCircuitHook<A, R, T extends string, S extends IPullstateAllStores> = (args: A, stores: S) => TAsyncActionResult<R, T> | false; | ||
export declare type TPullstateAsyncCacheBreakHook<A, R, T extends string, S extends IPullstateAllStores> = (args: A, result: TAsyncActionResult<R, T>, stores: S) => boolean; | ||
export declare enum EPostActionContext { | ||
CACHE = "CACHE", | ||
SHORT_CIRCUIT = "SHORT_CIRCUIT", | ||
DIRECT_RUN = "DIRECT_RUN" | ||
} | ||
export declare type TPullstateAsyncPostActionHook<A, R, T extends string, S extends IPullstateAllStores> = (args: A, result: TAsyncActionResult<R, T>, stores: S, context: EPostActionContext) => TAsyncActionResult<R, T> | void; | ||
export interface IAsyncActionBeckonOptions { | ||
@@ -41,2 +49,3 @@ ssr?: boolean; | ||
treatAsUpdate?: boolean; | ||
ignoreShortCircuit?: boolean; | ||
} | ||
@@ -71,3 +80,9 @@ declare type TAsyncActionBeckon<A, R, T extends string> = (args?: A, options?: IAsyncActionBeckonOptions) => TPullstateAsyncBeckonResponse<R, T>; | ||
export declare function errorResult<R = any, T extends string = string>(tags?: (EAsyncEndTags | T)[], message?: string): IAsyncActionResultNegative<T>; | ||
export declare function createAsyncAction<A = any, R = any, T extends string = string, S extends IPullstateAllStores = IPullstateAllStores>(action: TPullstateAsyncAction<A, R, T, S>, isResolved?: TPullstateAsyncIsResolvedFunction<A, R, T, S>, clientStores?: S): IOCreateAsyncActionOutput<A, R, T>; | ||
export interface ICreateAsyncActionOptions<A, R, T extends string, S extends IPullstateAllStores> { | ||
clientStores?: S; | ||
shortCircuitHook?: TPullstateAsyncShortCircuitHook<A, R, T, S>; | ||
cacheBreakHook?: TPullstateAsyncCacheBreakHook<A, R, T, S>; | ||
postActionHook?: TPullstateAsyncPostActionHook<A, R, T, S>; | ||
} | ||
export declare function createAsyncAction<A = any, R = any, T extends string = string, S extends IPullstateAllStores = IPullstateAllStores>(action: TPullstateAsyncAction<A, R, T, S>, { clientStores, shortCircuitHook, cacheBreakHook, postActionHook, }?: ICreateAsyncActionOptions<A, R, T, S>): IOCreateAsyncActionOutput<A, R, T>; | ||
export {}; |
@@ -129,2 +129,8 @@ import React,{useState,useRef,useCallback,useEffect,useContext,useMemo}from'react';const shallowEqual = require("fbjs/lib/shallowEqual"); | ||
})(EAsyncEndTags || (EAsyncEndTags = {})); | ||
var EPostActionContext; | ||
(function (EPostActionContext) { | ||
EPostActionContext["CACHE"] = "CACHE"; | ||
EPostActionContext["SHORT_CIRCUIT"] = "SHORT_CIRCUIT"; | ||
EPostActionContext["DIRECT_RUN"] = "DIRECT_RUN"; | ||
})(EPostActionContext || (EPostActionContext = {})); | ||
const clientAsyncCache = { | ||
@@ -201,48 +207,62 @@ listeners: {}, | ||
} | ||
function createAsyncAction(action, isResolved, clientStores = {}) { | ||
function createAsyncAction(action, { clientStores = {}, shortCircuitHook, cacheBreakHook, postActionHook, } = {}) { | ||
const ordinal = asyncCreationOrdinal++; | ||
const onServer = typeof window === "undefined"; | ||
let isResolvedWatcher = {}; | ||
console.log(`Pullstate Core creating action with options`); | ||
console.log({ shortCircuitHook, cacheBreakHook, postActionHook, clientStores }); | ||
let cacheBreakWatcher = {}; | ||
let watchIdOrd = 0; | ||
const shouldUpdate = {}; | ||
function runPostActionHook(result, args, stores, context) { | ||
if (postActionHook !== undefined) { | ||
const potentialResponse = postActionHook(args, result, stores, context); | ||
return potentialResponse != null ? potentialResponse : result; | ||
} | ||
return result; | ||
} | ||
function checkKeyAndReturnResponse(key, cache, initiate, ssr, args, stores) { | ||
let checkedResolved = false; | ||
const isResolvedUndefined = isResolved === undefined; | ||
if (cache.results.hasOwnProperty(key)) { | ||
if (isResolvedUndefined || isResolved(args, stores) !== false) { | ||
if (!isResolvedUndefined) { | ||
isResolvedWatcher[key] = 0; | ||
const cacheBreakLoop = (cacheBreakWatcher.hasOwnProperty(key) && cacheBreakWatcher[key] > 2); | ||
if (cacheBreakHook !== undefined && | ||
cacheBreakHook(args, cache.results[key][2], stores) && | ||
!cacheBreakLoop) { | ||
if (cacheBreakWatcher.hasOwnProperty(key)) { | ||
cacheBreakWatcher[key]++; | ||
} | ||
return cache.results[key]; | ||
else { | ||
cacheBreakWatcher[key] = 1; | ||
} | ||
delete cache.results[key]; | ||
} | ||
else { | ||
delete cache.results[key]; | ||
checkedResolved = true; | ||
if (cacheBreakLoop) { | ||
console.error(`[${key}] Pullstate detected an infinite loop caused by cacheBreakHook() | ||
returning true too often (breaking cache as soon as your action is resolving - hence | ||
causing beckoned actions to run the action again) in one of your AsyncActions - prevented | ||
further looping. Fix in your cacheBreakHook() is needed.`); | ||
} | ||
else { | ||
cacheBreakWatcher[key] = 0; | ||
} | ||
return [ | ||
true, | ||
true, | ||
runPostActionHook(cache.results[key][2], args, stores, EPostActionContext.CACHE), | ||
false, | ||
]; | ||
} | ||
} | ||
if (!cache.actions.hasOwnProperty(key)) { | ||
if (!checkedResolved && !isResolvedUndefined) { | ||
const resolvedResponse = isResolved(args, stores); | ||
if (resolvedResponse !== false) { | ||
cache.results[key] = [true, true, resolvedResponse, false]; | ||
return cache.results[key]; | ||
if (shortCircuitHook !== undefined) { | ||
const shortCircuitResponse = shortCircuitHook(args, stores); | ||
if (shortCircuitResponse !== false) { | ||
cache.results[key] = [true, true, shortCircuitResponse, false]; | ||
return [ | ||
true, | ||
true, | ||
runPostActionHook(shortCircuitResponse, args, stores, EPostActionContext.SHORT_CIRCUIT), | ||
false, | ||
]; | ||
} | ||
} | ||
if (!isResolvedUndefined) { | ||
isResolvedWatcher[key] = isResolvedWatcher[key] !== undefined ? isResolvedWatcher[key] + 1 : 1; | ||
} | ||
if (!isResolvedUndefined && isResolvedWatcher[key] !== undefined && isResolvedWatcher[key] > 2) { | ||
console.error(`Pullstate [${key}]: an Async Action with isResolved() set may be causing an infinite loop. Not initiating the next run of this action. Check your method to make sure, or file an issue if this is not the case.`); | ||
return [ | ||
false, | ||
false, | ||
{ | ||
message: "", | ||
tags: [EAsyncEndTags.UNFINISHED], | ||
error: false, | ||
payload: null, | ||
}, | ||
false, | ||
]; | ||
} | ||
if (initiate) { | ||
@@ -283,3 +303,3 @@ if (ssr || !onServer) { | ||
tags: [EAsyncEndTags.UNFINISHED], | ||
error: false, | ||
error: true, | ||
payload: null, | ||
@@ -297,3 +317,3 @@ }, | ||
tags: [EAsyncEndTags.UNFINISHED], | ||
error: false, | ||
error: true, | ||
payload: null, | ||
@@ -359,3 +379,3 @@ }, | ||
}; | ||
const run = async (args = {}, { treatAsUpdate = false } = {}) => { | ||
const run = async (args = {}, { treatAsUpdate = false, ignoreShortCircuit = false } = {}) => { | ||
const key = createKey(ordinal, args); | ||
@@ -366,3 +386,3 @@ const [, prevFinished, prevResp] = clientAsyncCache.results[key] || [ | ||
{ | ||
error: false, | ||
error: true, | ||
message: "", | ||
@@ -379,2 +399,12 @@ payload: null, | ||
} | ||
console.log(`Running async function`); | ||
console.log({ shortCircuitHook }); | ||
if (shortCircuitHook !== undefined) { | ||
const shortCircuitResponse = shortCircuitHook(args, clientStores); | ||
if (shortCircuitResponse !== false) { | ||
clientAsyncCache.results[key] = [true, true, shortCircuitResponse, false]; | ||
notifyListeners(key); | ||
return runPostActionHook(shortCircuitResponse, args, clientStores, EPostActionContext.DIRECT_RUN); | ||
} | ||
} | ||
notifyListeners(key); | ||
@@ -388,3 +418,3 @@ let currentActionOrd = actionOrdUpdate(clientAsyncCache, key); | ||
} | ||
return result; | ||
return runPostActionHook(result, args, clientStores, EPostActionContext.DIRECT_RUN); | ||
} | ||
@@ -402,3 +432,3 @@ catch (e) { | ||
} | ||
return result; | ||
return runPostActionHook(result, args, clientStores, EPostActionContext.DIRECT_RUN); | ||
} | ||
@@ -459,3 +489,6 @@ }; | ||
} | ||
newStores[storeName]._setInternalOptions({ ssr, reactionCreators: this.originStores[storeName]._getReactionCreators() }); | ||
newStores[storeName]._setInternalOptions({ | ||
ssr, | ||
reactionCreators: this.originStores[storeName]._getReactionCreators(), | ||
}); | ||
} | ||
@@ -467,4 +500,9 @@ return new PullstateInstance(newStores); | ||
} | ||
createAsyncAction(action, isResolved) { | ||
return createAsyncAction(action, isResolved, this.originStores); | ||
createAsyncAction(action, options = {}) { | ||
return createAsyncAction(action, { | ||
clientStores: this.originStores, | ||
shortCircuitHook: options.shortCircuitHook, | ||
cacheBreakHook: options.cacheBreakHook, | ||
postActionHook: options.postActionHook, | ||
}); | ||
} | ||
@@ -471,0 +509,0 @@ } |
@@ -128,2 +128,8 @@ 'use strict';Object.defineProperty(exports,'__esModule',{value:true});function _interopDefault(e){return(e&&(typeof e==='object')&&'default'in e)?e['default']:e}var React=require('react'),React__default=_interopDefault(React);const shallowEqual = require("fbjs/lib/shallowEqual"); | ||
})(exports.EAsyncEndTags || (exports.EAsyncEndTags = {})); | ||
var EPostActionContext; | ||
(function (EPostActionContext) { | ||
EPostActionContext["CACHE"] = "CACHE"; | ||
EPostActionContext["SHORT_CIRCUIT"] = "SHORT_CIRCUIT"; | ||
EPostActionContext["DIRECT_RUN"] = "DIRECT_RUN"; | ||
})(EPostActionContext || (EPostActionContext = {})); | ||
const clientAsyncCache = { | ||
@@ -200,48 +206,62 @@ listeners: {}, | ||
} | ||
function createAsyncAction(action, isResolved, clientStores = {}) { | ||
function createAsyncAction(action, { clientStores = {}, shortCircuitHook, cacheBreakHook, postActionHook, } = {}) { | ||
const ordinal = asyncCreationOrdinal++; | ||
const onServer = typeof window === "undefined"; | ||
let isResolvedWatcher = {}; | ||
console.log(`Pullstate Core creating action with options`); | ||
console.log({ shortCircuitHook, cacheBreakHook, postActionHook, clientStores }); | ||
let cacheBreakWatcher = {}; | ||
let watchIdOrd = 0; | ||
const shouldUpdate = {}; | ||
function runPostActionHook(result, args, stores, context) { | ||
if (postActionHook !== undefined) { | ||
const potentialResponse = postActionHook(args, result, stores, context); | ||
return potentialResponse != null ? potentialResponse : result; | ||
} | ||
return result; | ||
} | ||
function checkKeyAndReturnResponse(key, cache, initiate, ssr, args, stores) { | ||
let checkedResolved = false; | ||
const isResolvedUndefined = isResolved === undefined; | ||
if (cache.results.hasOwnProperty(key)) { | ||
if (isResolvedUndefined || isResolved(args, stores) !== false) { | ||
if (!isResolvedUndefined) { | ||
isResolvedWatcher[key] = 0; | ||
const cacheBreakLoop = (cacheBreakWatcher.hasOwnProperty(key) && cacheBreakWatcher[key] > 2); | ||
if (cacheBreakHook !== undefined && | ||
cacheBreakHook(args, cache.results[key][2], stores) && | ||
!cacheBreakLoop) { | ||
if (cacheBreakWatcher.hasOwnProperty(key)) { | ||
cacheBreakWatcher[key]++; | ||
} | ||
return cache.results[key]; | ||
else { | ||
cacheBreakWatcher[key] = 1; | ||
} | ||
delete cache.results[key]; | ||
} | ||
else { | ||
delete cache.results[key]; | ||
checkedResolved = true; | ||
if (cacheBreakLoop) { | ||
console.error(`[${key}] Pullstate detected an infinite loop caused by cacheBreakHook() | ||
returning true too often (breaking cache as soon as your action is resolving - hence | ||
causing beckoned actions to run the action again) in one of your AsyncActions - prevented | ||
further looping. Fix in your cacheBreakHook() is needed.`); | ||
} | ||
else { | ||
cacheBreakWatcher[key] = 0; | ||
} | ||
return [ | ||
true, | ||
true, | ||
runPostActionHook(cache.results[key][2], args, stores, EPostActionContext.CACHE), | ||
false, | ||
]; | ||
} | ||
} | ||
if (!cache.actions.hasOwnProperty(key)) { | ||
if (!checkedResolved && !isResolvedUndefined) { | ||
const resolvedResponse = isResolved(args, stores); | ||
if (resolvedResponse !== false) { | ||
cache.results[key] = [true, true, resolvedResponse, false]; | ||
return cache.results[key]; | ||
if (shortCircuitHook !== undefined) { | ||
const shortCircuitResponse = shortCircuitHook(args, stores); | ||
if (shortCircuitResponse !== false) { | ||
cache.results[key] = [true, true, shortCircuitResponse, false]; | ||
return [ | ||
true, | ||
true, | ||
runPostActionHook(shortCircuitResponse, args, stores, EPostActionContext.SHORT_CIRCUIT), | ||
false, | ||
]; | ||
} | ||
} | ||
if (!isResolvedUndefined) { | ||
isResolvedWatcher[key] = isResolvedWatcher[key] !== undefined ? isResolvedWatcher[key] + 1 : 1; | ||
} | ||
if (!isResolvedUndefined && isResolvedWatcher[key] !== undefined && isResolvedWatcher[key] > 2) { | ||
console.error(`Pullstate [${key}]: an Async Action with isResolved() set may be causing an infinite loop. Not initiating the next run of this action. Check your method to make sure, or file an issue if this is not the case.`); | ||
return [ | ||
false, | ||
false, | ||
{ | ||
message: "", | ||
tags: [exports.EAsyncEndTags.UNFINISHED], | ||
error: false, | ||
payload: null, | ||
}, | ||
false, | ||
]; | ||
} | ||
if (initiate) { | ||
@@ -282,3 +302,3 @@ if (ssr || !onServer) { | ||
tags: [exports.EAsyncEndTags.UNFINISHED], | ||
error: false, | ||
error: true, | ||
payload: null, | ||
@@ -296,3 +316,3 @@ }, | ||
tags: [exports.EAsyncEndTags.UNFINISHED], | ||
error: false, | ||
error: true, | ||
payload: null, | ||
@@ -358,3 +378,3 @@ }, | ||
}; | ||
const run = async (args = {}, { treatAsUpdate = false } = {}) => { | ||
const run = async (args = {}, { treatAsUpdate = false, ignoreShortCircuit = false } = {}) => { | ||
const key = createKey(ordinal, args); | ||
@@ -365,3 +385,3 @@ const [, prevFinished, prevResp] = clientAsyncCache.results[key] || [ | ||
{ | ||
error: false, | ||
error: true, | ||
message: "", | ||
@@ -378,2 +398,12 @@ payload: null, | ||
} | ||
console.log(`Running async function`); | ||
console.log({ shortCircuitHook }); | ||
if (shortCircuitHook !== undefined) { | ||
const shortCircuitResponse = shortCircuitHook(args, clientStores); | ||
if (shortCircuitResponse !== false) { | ||
clientAsyncCache.results[key] = [true, true, shortCircuitResponse, false]; | ||
notifyListeners(key); | ||
return runPostActionHook(shortCircuitResponse, args, clientStores, EPostActionContext.DIRECT_RUN); | ||
} | ||
} | ||
notifyListeners(key); | ||
@@ -387,3 +417,3 @@ let currentActionOrd = actionOrdUpdate(clientAsyncCache, key); | ||
} | ||
return result; | ||
return runPostActionHook(result, args, clientStores, EPostActionContext.DIRECT_RUN); | ||
} | ||
@@ -401,3 +431,3 @@ catch (e) { | ||
} | ||
return result; | ||
return runPostActionHook(result, args, clientStores, EPostActionContext.DIRECT_RUN); | ||
} | ||
@@ -458,3 +488,6 @@ }; | ||
} | ||
newStores[storeName]._setInternalOptions({ ssr, reactionCreators: this.originStores[storeName]._getReactionCreators() }); | ||
newStores[storeName]._setInternalOptions({ | ||
ssr, | ||
reactionCreators: this.originStores[storeName]._getReactionCreators(), | ||
}); | ||
} | ||
@@ -466,4 +499,9 @@ return new PullstateInstance(newStores); | ||
} | ||
createAsyncAction(action, isResolved) { | ||
return createAsyncAction(action, isResolved, this.originStores); | ||
createAsyncAction(action, options = {}) { | ||
return createAsyncAction(action, { | ||
clientStores: this.originStores, | ||
shortCircuitHook: options.shortCircuitHook, | ||
cacheBreakHook: options.cacheBreakHook, | ||
postActionHook: options.postActionHook, | ||
}); | ||
} | ||
@@ -470,0 +508,0 @@ } |
import React from "react"; | ||
import { Store } from "./Store"; | ||
import { IOCreateAsyncActionOutput, IPullstateAsyncActionOrdState, IPullstateAsyncCache, IPullstateAsyncResultState, TPullstateAsyncAction, TPullstateAsyncIsResolvedFunction } from "./async"; | ||
import { ICreateAsyncActionOptions, IOCreateAsyncActionOutput, IPullstateAsyncActionOrdState, IPullstateAsyncCache, IPullstateAsyncResultState, TPullstateAsyncAction } from "./async"; | ||
declare type Omit<T, K> = Pick<T, Exclude<keyof T, K>>; | ||
export interface IPullstateAllStores { | ||
@@ -21,3 +22,3 @@ [storeName: string]: Store<any>; | ||
useStores(): S; | ||
createAsyncAction<A = any, R = any, T extends string = string>(action: TPullstateAsyncAction<A, R, T, S>, isResolved?: TPullstateAsyncIsResolvedFunction<A, R, T, S>): IOCreateAsyncActionOutput<A, R, T>; | ||
createAsyncAction<A = any, R = any, T extends string = string>(action: TPullstateAsyncAction<A, R, T, S>, options?: Omit<ICreateAsyncActionOptions<A, R, T, S>, "clientStores">): IOCreateAsyncActionOutput<A, R, T>; | ||
} | ||
@@ -24,0 +25,0 @@ interface IPullstateSnapshot { |
{ | ||
"name": "pullstate", | ||
"version": "0.7.1", | ||
"version": "0.8.0-alpha.0", | ||
"description": "Simple state stores using immer and React hooks", | ||
@@ -51,4 +51,2 @@ "main": "dist/index.js", | ||
"prettier": "^1.16.4", | ||
"react": "^16.8.4", | ||
"react-dom": "^16.8.4", | ||
"react-test-renderer": "^16.8.3", | ||
@@ -55,0 +53,0 @@ "rollup": "^1.2.2", |
@@ -0,1 +1,5 @@ | ||
<p align="center"> | ||
<img width="736" height="373" src="https://github.com/lostpebble/pullstate/raw/master/graphics/pullstate-logo.png" alt="Pullstate" /> | ||
</p> | ||
### pullstate | ||
@@ -2,0 +6,0 @@ |
83893
27
1319
506