@@ -18,7 +18,9 @@ import {
1818 NestedPartial ,
1919 Options ,
2020 ResolveActions ,
21- ResolveMockActions ,
21+ DefaultMode ,
22+ TestMode ,
23+ SSRMode ,
2224 ResolveState ,
2325} from './internalTypes'
2426import { proxifyEffects } from './proxyfyEffects'
@@ -31,6 +33,11 @@ import {
3133 IState ,
3234 IOnInitialize ,
3335} from './types'
36+ import {
37+ deepCopy ,
38+ MockedEventEmitter ,
39+ makeStringifySafeMutations ,
40+ } from './utils'
3441
3542export * from './types'
3643
@@ -52,6 +59,11 @@ const IS_PRODUCTION = process.env.NODE_ENV === 'production'
5259const IS_DEVELOPMENT =
5360 ! process . env . NODE_ENV || process . env . NODE_ENV === 'development'
5461const IS_OPERATOR = Symbol ( 'operator' )
62+
63+ const MODE_DEFAULT = Symbol ( 'MODE_DEFAULT' )
64+ const MODE_TEST = Symbol ( 'MODE_TEST' )
65+ const MODE_SSR = Symbol ( 'MODE_SSR' )
66+
5567let hasWarnedDeprecatedValue = false
5668
5769function warnValueDeprecation ( ) {
@@ -63,31 +75,33 @@ function warnValueDeprecation() {
6375 }
6476}
6577
66- export const makeStringifySafeMutations = ( mutations : IMutation [ ] ) => {
67- return mutations . map ( ( mutation ) => ( {
68- ...mutation ,
69- args : safeValues ( mutation . args ) ,
70- } ) )
78+ export interface OvermindSSR < Config extends IConfiguration >
79+ extends Overmind < Config > {
80+ hydrate ( ) : IMutation [ ]
7181}
7282
73- function deepCopy ( obj ) {
74- if ( isPlainObject ( obj ) ) {
75- return Object . keys ( obj ) . reduce ( ( aggr , key ) => {
76- aggr [ key ] = deepCopy ( obj [ key ] )
83+ export function createOvermindSSR < Config extends IConfiguration > (
84+ config : Config
85+ ) : OvermindSSR < Config > {
86+ const ssr = new Overmind (
87+ config ,
88+ {
89+ devtools : false ,
90+ } ,
91+ {
92+ mode : MODE_SSR ,
93+ } as SSRMode
94+ ) as OvermindSSR < Config >
7795
78- return aggr
79- } , { } )
80- } else if ( Array . isArray ( obj ) ) {
81- return obj . map ( ( item ) => deepCopy ( item ) )
96+ ssr . state = ( ssr as any ) . proxyStateTree . getMutationTree ( ) . state
97+ ssr . hydrate = ( ) => {
98+ return ( ssr as any ) . proxyStateTree . mutationTree . flush ( ) . mutations
8299 }
83-
84- return obj
100+ return ssr
85101}
86102
87- export interface OvermindMock < Config extends IConfiguration > {
88- actions : ResolveMockActions < Config [ 'actions' ] >
89- effects : Config [ 'effects' ]
90- state : ResolveState < Config [ 'state' ] >
103+ export interface OvermindMock < Config extends IConfiguration >
104+ extends Overmind < Config > {
91105 onInitialize : ( ) => Promise < IMutation [ ] >
92106}
93107
@@ -101,7 +115,10 @@ export function createOvermindMock<Config extends IConfiguration>(
101115 } ) ,
102116 {
103117 devtools : false ,
104- testMode : {
118+ } ,
119+ {
120+ mode : MODE_TEST ,
121+ options : {
105122 effectsCallback : ( effect ) => {
106123 const mockedEffect = ( effect . name
107124 ? effect . name . split ( '.' )
@@ -124,33 +141,50 @@ export function createOvermindMock<Config extends IConfiguration>(
124141 return execution . flush ( ) . mutations
125142 } ,
126143 } ,
127- }
128- )
144+ } as TestMode
145+ ) as OvermindMock < Config >
146+
147+ const action = ( mock as any ) . createAction ( 'onInitialize' , config . onInitialize )
148+
149+ mock . onInitialize = ( ) => action ( mock )
129150
130151 return mock as any
131152}
132153
154+ export function createOvermind < Config extends IConfiguration > (
155+ config : Config ,
156+ options ?: Options
157+ ) : Overmind < Config > {
158+ return new Overmind ( config , options , { mode : MODE_DEFAULT } )
159+ }
160+
133161const hotReloadingCache = { }
134162
135163// We do not use IConfig<Config> directly to type the class in order to avoid
136164// the 'import(...)' function to be used in exported types.
137-
138165export class Overmind < ThisConfig extends IConfiguration >
139166 implements IConfiguration {
140167 private proxyStateTree : ProxyStateTree < object >
141168 private actionReferences : Function [ ] = [ ]
142169 private nextExecutionId : number = 0
143170 private options : Options
171+ private mode : DefaultMode | TestMode | SSRMode
144172 initialized : Promise < any >
145173 eventHub : EventEmitter < Events >
146174 devtools : Devtools
147175 actions : ResolveActions < ThisConfig [ 'actions' ] >
148176 state : ResolveState < ThisConfig [ 'state' ] >
149177 effects : ThisConfig [ 'effects' ] & { }
150- constructor ( configuration : ThisConfig , options : Options = { } ) {
151- const name = options . name || 'MyConfig'
152-
153- if ( IS_DEVELOPMENT && ! options . testMode ) {
178+ constructor (
179+ configuration : ThisConfig ,
180+ options : Options = { } ,
181+ mode : DefaultMode | TestMode | SSRMode = {
182+ mode : MODE_DEFAULT ,
183+ } as DefaultMode
184+ ) {
185+ const name = options . name || 'OvermindApp'
186+
187+ if ( IS_DEVELOPMENT && mode . mode === MODE_DEFAULT ) {
154188 if ( hotReloadingCache [ name ] ) {
155189 return hotReloadingCache [ name ]
156190 } else {
@@ -161,7 +195,10 @@ export class Overmind<ThisConfig extends IConfiguration>
161195 /*
162196 Set up an eventHub to trigger information from derived, computed and reactions
163197 */
164- const eventHub = new EventEmitter < Events > ( )
198+ const eventHub =
199+ mode . mode === MODE_SSR
200+ ? new MockedEventEmitter ( )
201+ : new EventEmitter < Events > ( )
165202
166203 /*
167204 Create the proxy state tree instance with the state and a wrapper to expose
@@ -170,27 +207,34 @@ export class Overmind<ThisConfig extends IConfiguration>
170207 const proxyStateTree = new ProxyStateTree (
171208 this . getState ( configuration ) as any ,
172209 {
173- devmode : ! IS_PRODUCTION ,
210+ devmode : IS_DEVELOPMENT ,
174211 dynamicWrapper : ( _ , path , func ) => func ( eventHub , proxyStateTree , path ) ,
175- onGetter : ( path , value ) => {
176- // We need to let any initial values be set first
177- setTimeout ( ( ) => {
178- this . eventHub . emit ( EventType . GETTER , {
179- path,
180- value : safeValue ( value ) ,
181- } )
182- } )
183- } ,
212+ onGetter : IS_DEVELOPMENT
213+ ? ( path , value ) => {
214+ // We need to let any initial values be set first
215+ setTimeout ( ( ) => {
216+ this . eventHub . emit ( EventType . GETTER , {
217+ path,
218+ value : safeValue ( value ) ,
219+ } )
220+ } )
221+ }
222+ : undefined ,
184223 }
185224 )
186225
187226 this . state = proxyStateTree . state
188227 this . effects = configuration . effects || { }
189228 this . proxyStateTree = proxyStateTree
190- this . eventHub = eventHub
229+ this . eventHub = eventHub as EventEmitter < Event >
191230 this . options = options
231+ this . mode = mode
192232
193- if ( ! IS_PRODUCTION && typeof window !== 'undefined' ) {
233+ if (
234+ IS_DEVELOPMENT &&
235+ mode . mode === MODE_DEFAULT &&
236+ typeof window !== 'undefined'
237+ ) {
194238 let warning = 'OVERMIND: You are running in DEVELOPMENT mode.'
195239 if ( options . logProxies !== true ) {
196240 const originalConsoleLog = console . log
@@ -246,7 +290,7 @@ export class Overmind<ThisConfig extends IConfiguration>
246290 nextTick && clearTimeout ( nextTick )
247291 nextTick = setTimeout ( flushTree , 0 )
248292 } )
249- } else if ( ! options . testMode ) {
293+ } else if ( mode . mode === MODE_DEFAULT ) {
250294 eventHub . on ( EventType . OPERATOR_ASYNC , ( execution ) => {
251295 const flushData = execution . flush ( )
252296 if ( this . devtools && flushData . mutations . length ) {
@@ -279,15 +323,7 @@ export class Overmind<ThisConfig extends IConfiguration>
279323 */
280324 this . actions = this . getActions ( configuration )
281325
282- if ( options . testMode ) {
283- const action = this . createAction (
284- 'onInitialize' ,
285- configuration . onInitialize
286- )
287-
288- const overmind = this as any
289- overmind . onInitialize = ( ) => action ( this )
290- } else if ( configuration . onInitialize ) {
326+ if ( mode . mode === MODE_DEFAULT && configuration . onInitialize ) {
291327 const onInitialize = this . createAction (
292328 'onInitialize' ,
293329 configuration . onInitialize
@@ -390,7 +426,12 @@ export class Overmind<ThisConfig extends IConfiguration>
390426 operatorId : finalContext . execution . operatorId - 1 ,
391427 } )
392428 if ( err ) reject ( err )
393- else resolve ( this . options . testMode && finalContext . execution )
429+ else
430+ resolve (
431+ this . mode . mode === MODE_TEST
432+ ? finalContext . execution
433+ : undefined
434+ )
394435 }
395436 )
396437 : resolve (
@@ -454,12 +495,13 @@ export class Overmind<ThisConfig extends IConfiguration>
454495 await result
455496 }
456497
457- return this . options . testMode && execution
498+ return this . mode . mode === MODE_TEST ? execution : undefined
458499 }
459500 }
460501
461- if ( this . options . testMode ) {
462- const actionCallback = this . options . testMode . actionCallback
502+ if ( this . mode . mode === MODE_TEST ) {
503+ const mode = this . mode as TestMode
504+ const actionCallback = mode . options . actionCallback
463505
464506 return async ( value ?) => {
465507 const result = await actionFunc ( value )
@@ -478,9 +520,12 @@ export class Overmind<ThisConfig extends IConfiguration>
478520 return proxifyEffects ( this . effects , ( effect ) => {
479521 let result
480522 try {
481- result = this . options . testMode
482- ? this . options . testMode . effectsCallback ( effect )
483- : effect . func . apply ( this , effect . args )
523+ if ( this . mode . mode === MODE_TEST ) {
524+ const mode = this . mode as TestMode
525+ result = mode . options . effectsCallback ( effect )
526+ } else {
527+ result = effect . func . apply ( this , effect . args )
528+ }
484529 } catch ( error ) {
485530 // eslint-disable-next-line standard/no-callback-literal
486531 this . eventHub . emit ( EventType . EFFECT , {
@@ -657,6 +702,21 @@ export class Overmind<ThisConfig extends IConfiguration>
657702 addFlushListener = ( cb : IFlushCallback ) => {
658703 return this . proxyStateTree . onFlush ( cb )
659704 }
705+ rehydrate ( state : object , mutations : IMutation [ ] ) {
706+ mutations . forEach ( ( mutation ) => {
707+ const pathArray = mutation . path . split ( '.' )
708+ const key = pathArray . pop ( )
709+ const target = pathArray . reduce ( ( aggr , key ) => aggr [ key ] , state )
710+
711+ if ( mutation . method === 'set' ) {
712+ target [ key ] = mutation . args [ 0 ]
713+ } else if ( mutation . method === 'unset' ) {
714+ delete target [ key ]
715+ } else {
716+ target [ key ] [ mutation . method ] ( ...mutation . args )
717+ }
718+ } )
719+ }
660720}
661721
662722/*
0 commit comments