@@ -2,22 +2,62 @@ import { ActionContext } from 'vuex';
2
2
import { NuxtHTTPInstance } from '@nuxt/http' ;
3
3
import { Context } from '@nuxt/types' ;
4
4
5
+ export interface OengusStateCacheable {
6
+ _expires : number ;
7
+ }
8
+ export type OengusStateValue < V > = V & OengusStateCacheable | OengusStateCacheable | undefined ;
9
+ export interface OengusStateValuesById < V > {
10
+ [ id : string ] : OengusStateValue < V > ;
11
+ }
5
12
export interface OengusState {
6
- [ key : string ] : any ;
13
+ [ key : string ] : OengusStateValuesById < any > | OengusStateValue < any > ;
7
14
}
8
15
9
- export interface GetterArgs < U , V > {
16
+ /**
17
+ * Used to create a specific API endpoint
18
+ * U refers to the type retruned by the API
19
+ * V refers to the type that ends up in the state and defaults to U
20
+ */
21
+ export interface GetterArgs < U , V = U > {
22
+ /**
23
+ * Final portion of the API path.
24
+ * Follows the id, if provided, which in turn follows the basePath.
25
+ * e.g. basePath/id/path or basePath/path
26
+ */
10
27
path ?: string ;
28
+ /**
29
+ * Key these responses are stored in in the state object
30
+ * e.g. state[key]
31
+ */
11
32
key : string ;
12
- transform ?( value : U , id ?: number | string ) : V ;
33
+ /**
34
+ * Name of the mutation function to store the responses
35
+ * If not provided, uses addKey, e.g. if key is 'users' it picks 'addUsers'
36
+ */
13
37
mutation ?: string ;
38
+ /**
39
+ * Used to alter the raw API response to whatever is stored in the state
40
+ * This can be strip, change, or add new values or even change into a full class
41
+ */
42
+ transform ?( value : U , id ?: number | string ) : V & OengusStateCacheable ;
43
+ /**
44
+ * How long should responses be cached for
45
+ * Can be ignored with forceFetch
46
+ * Defaults to 5 minutes
47
+ */
48
+ cacheDuration ?: number ;
14
49
}
15
50
16
51
export interface ExtendedFetch {
17
52
id ?: number | string ;
18
53
forceFetch ?: boolean ;
19
54
}
20
55
56
+ /**
57
+ * The actual function returned by a specific API for the action
58
+ * T refers to the state
59
+ * V refers to the type that ends up in the state
60
+ */
21
61
export type GetterFunc < T , V > = ( context : ActionContext < T , T > , id ?: number | string | ExtendedFetch ) => Promise < V | undefined > ;
22
62
23
63
export class OengusAPI < T extends OengusState > {
@@ -30,50 +70,67 @@ export class OengusAPI<T extends OengusState> {
30
70
* U is the type returned by the API
31
71
* V is the type that will get stored. V is equal to U except when transform is given
32
72
*/
33
- public get < U , V = U > ( { path, key, transform, mutation } : GetterArgs < U , V > ) : GetterFunc < T , V > {
73
+ public get < U , V = U > (
74
+ { path, key, transform, mutation : _mutation , cacheDuration : _cacheDuration } : GetterArgs < U , V > ,
75
+ ) : GetterFunc < T , V > {
76
+ const mutation = _mutation ?? `add${ key [ 0 ] . toUpperCase ( ) } ${ key . slice ( 1 ) } ` ;
77
+ // Five minutes unless overridden
78
+ const cacheDuration = _cacheDuration ?? 300_000 ;
34
79
return async ( { commit, state } , id ) => {
35
80
let forceFetch = false ;
36
81
if ( typeof id === 'object' ) {
37
82
forceFetch = id . forceFetch ?? forceFetch ;
38
83
id = id . id ;
39
84
}
40
85
41
- // Cache check (needs improvements)
42
- let response : U | V = id ? state [ key ] [ id ] : state [ key ] ;
43
- if ( response !== undefined && ! forceFetch ) {
86
+ // Cache check
87
+ let response : OengusStateValue < U | V > = id ? state [ key ] [ id ] : state [ key ] ;
88
+ const cachedTime = response ?. _expires ?? 0 ;
89
+ if ( cachedTime + cacheDuration > Date . now ( ) && ! forceFetch ) {
44
90
return response as V ;
45
91
}
46
- const updating = response !== undefined ;
47
- const resolvedMutation = mutation ?? `add${ key [ 0 ] . toUpperCase ( ) } ${ key . slice ( 1 ) } ` ;
48
- if ( updating ) {
92
+
93
+ if ( cachedTime ) {
49
94
// Mark the entry as updating by changing the cache expired marker
95
+ // This allows existing stuff to continue to use the cached value,
96
+ commit ( mutation , { id, value : { ...response , _expires : Date . now ( ) } } ) ;
50
97
} else {
51
- // Mark the entry as "being fetched" by marking it `null` (only `undefined` is empty)
52
- commit ( resolvedMutation , { id, value : null } ) ;
98
+ // Mark the entry as "being fetched" by giving it a promise
99
+ // Anything that wants to read the value is welcome to await it or use Vuex's observers
100
+ commit ( mutation , { id, value : { _expires : Date . now ( ) } } ) ;
53
101
}
54
102
55
103
// Fetch and store into cache
56
104
const route = `${ this . basePath } ${ id ? `/${ id } ` : '' } ${ path ? `/${ path } ` : '' } ` ;
105
+ let apiResponse : U & OengusStateCacheable ;
57
106
try {
58
- response = await OengusAPI . http . $get ( route ) ;
107
+ apiResponse = await OengusAPI . http . $get ( route ) ;
59
108
} catch {
60
109
// This isn't intrinsically bad, just catch the error, mark as not fetching, and return nothing
61
- if ( updating ) {
62
- // What should we do here? Right now, nothing, leave the old value.
110
+ if ( cachedTime ) {
111
+ // Reset the old cache timer, maybe the API is just down (should we worry about loops?)
112
+ commit ( mutation , { id, value : response } ) ;
63
113
} else {
64
- commit ( resolvedMutation , { id, value : undefined } ) ;
114
+ commit ( mutation , { id, value : undefined } ) ;
65
115
}
66
116
return ;
67
117
}
118
+ response = apiResponse ;
119
+
68
120
if ( transform ) {
69
121
response = transform ( response as U , id ) ;
70
122
}
71
- commit ( resolvedMutation , { id, value : response as V } ) ;
123
+ response . _expires = Date . now ( ) ;
124
+ commit ( mutation , { id, value : response as OengusStateValue < V > } ) ;
72
125
return response as V ;
73
126
} ;
74
127
}
75
128
}
76
129
130
+ /**
131
+ * Makes sure anything that OengusAPI needs is created
132
+ * All values this write to must be static, so they can be shared among instances
133
+ */
77
134
export default function setupOengusAPI ( { $http } : Context ) {
78
135
OengusAPI . http = $http ;
79
136
}
0 commit comments