1
+ "use strict" ;
2
+
3
+ Object . defineProperty ( exports , "__esModule" , {
4
+ value : true
5
+ } ) ;
6
+ exports . createCachingMethods = void 0 ;
7
+ exports . getNestedValue = getNestedValue ;
8
+ exports . isValidObjectIdString = exports . idToString = void 0 ;
9
+ exports . prepFields = prepFields ;
10
+ exports . stringToId = void 0 ;
11
+ var _dataloader = _interopRequireDefault ( require ( "dataloader" ) ) ;
12
+ var _mongodb = require ( "mongodb" ) ;
13
+ var _bson = require ( "bson" ) ;
14
+ var _helpers = require ( "./helpers" ) ;
15
+ function _interopRequireDefault ( obj ) { return obj && obj . __esModule ? obj : { default : obj } ; }
16
+ const idToString = id => {
17
+ if ( id instanceof _mongodb . ObjectId ) {
18
+ return id . toHexString ( ) ;
19
+ } else {
20
+ return id && id . toString ? id . toString ( ) : id ;
21
+ }
22
+ } ;
23
+
24
+ // https://www.geeksforgeeks.org/how-to-check-if-a-string-is-valid-mongodb-objectid-in-nodejs/
25
+ exports . idToString = idToString ;
26
+ const isValidObjectIdString = string => _mongodb . ObjectId . isValid ( string ) && String ( new _mongodb . ObjectId ( string ) ) === string ;
27
+ exports . isValidObjectIdString = isValidObjectIdString ;
28
+ const stringToId = string => {
29
+ if ( string instanceof _mongodb . ObjectId ) {
30
+ return string ;
31
+ }
32
+ if ( isValidObjectIdString ( string ) ) {
33
+ return new _mongodb . ObjectId ( string ) ;
34
+ }
35
+ return string ;
36
+ } ;
37
+ exports . stringToId = stringToId ;
38
+ function prepFields ( fields ) {
39
+ const cleanedFields = { } ;
40
+ Object . keys ( fields ) . sort ( ) . forEach ( key => {
41
+ if ( typeof key !== 'undefined' ) {
42
+ cleanedFields [ key ] = Array . isArray ( fields [ key ] ) ? fields [ key ] : [ fields [ key ] ] ;
43
+ }
44
+ } ) ;
45
+ return {
46
+ loaderKey : _bson . EJSON . stringify ( cleanedFields ) ,
47
+ cleanedFields
48
+ } ;
49
+ }
50
+
51
+ // getNestedValue({ nested: { foo: 'bar' } }, 'nested.foo')
52
+ // => 'bar'
53
+ function getNestedValue ( object , string ) {
54
+ string = string . replace ( / \[ ( \w + ) \] / g, '.$1' ) ; // convert indexes to properties
55
+ string = string . replace ( / ^ \. / , '' ) ; // strip a leading dot
56
+ var a = string . split ( '.' ) ;
57
+ for ( var i = 0 , n = a . length ; i < n ; ++ i ) {
58
+ var k = a [ i ] ;
59
+ if ( k in object ) {
60
+ object = object [ k ] ;
61
+ } else {
62
+ return ;
63
+ }
64
+ }
65
+ return object ;
66
+ }
67
+
68
+ // https://github.com/graphql/dataloader#batch-function
69
+ // "The Array of values must be the same length as the Array of keys."
70
+ // "Each index in the Array of values must correspond to the same index in the Array of keys."
71
+ const orderDocs = ( fieldsArray , docs ) => fieldsArray . map ( fields => docs . filter ( doc => {
72
+ for ( let fieldName of Object . keys ( fields ) ) {
73
+ const fieldValue = getNestedValue ( fields , fieldName ) ;
74
+ if ( typeof fieldValue === 'undefined' ) continue ;
75
+ const filterValuesArr = Array . isArray ( fieldValue ) ? fieldValue . map ( val => idToString ( val ) ) : [ idToString ( fieldValue ) ] ;
76
+ const docValue = doc [ fieldName ] ;
77
+ const docValuesArr = Array . isArray ( docValue ) ? docValue . map ( val => idToString ( val ) ) : [ idToString ( docValue ) ] ;
78
+ let isMatch = false ;
79
+ for ( const filterVal of filterValuesArr ) {
80
+ if ( docValuesArr . includes ( filterVal ) ) {
81
+ isMatch = true ;
82
+ }
83
+ }
84
+ if ( ! isMatch ) return false ;
85
+ }
86
+ return true ;
87
+ } ) ) ;
88
+ const createCachingMethods = ( {
89
+ collection,
90
+ model,
91
+ cache
92
+ } ) => {
93
+ const loader = new _dataloader . default ( async ejsonArray => {
94
+ const fieldsArray = ejsonArray . map ( _bson . EJSON . parse ) ;
95
+ ( 0 , _helpers . log ) ( 'fieldsArray' , fieldsArray ) ;
96
+ const filterArray = fieldsArray . reduce ( ( filterArray , fields ) => {
97
+ const existingFieldsFilter = filterArray . find ( filter => [ ...Object . keys ( filter ) ] . sort ( ) . join ( ) === [ ...Object . keys ( fields ) ] . sort ( ) . join ( ) ) ;
98
+ const filter = existingFieldsFilter || { } ;
99
+ for ( const fieldName in fields ) {
100
+ if ( typeof fields [ fieldName ] === 'undefined' ) continue ;
101
+ if ( ! filter [ fieldName ] ) filter [ fieldName ] = {
102
+ $in : [ ]
103
+ } ;
104
+ let newVals = Array . isArray ( fields [ fieldName ] ) ? fields [ fieldName ] : [ fields [ fieldName ] ] ;
105
+ filter [ fieldName ] . $in = [ ...filter [ fieldName ] . $in , ...newVals . map ( stringToId ) . filter ( val => ! filter [ fieldName ] . $in . includes ( val ) ) ] ;
106
+ }
107
+ if ( existingFieldsFilter ) return filterArray ;
108
+ return [ ...filterArray , filter ] ;
109
+ } , [ ] ) ;
110
+ ( 0 , _helpers . log ) ( 'filterArray: ' , filterArray ) ;
111
+ const filter = filterArray . length === 1 ? filterArray [ 0 ] : {
112
+ $or : filterArray
113
+ } ;
114
+ ( 0 , _helpers . log ) ( 'filter: ' , filter ) ;
115
+ const findPromise = model ? model . find ( filter ) . lean ( {
116
+ defaults : true
117
+ } ) . exec ( ) : collection . find ( filter ) . toArray ( ) ;
118
+ const results = await findPromise ;
119
+ ( 0 , _helpers . log ) ( 'results: ' , results ) ;
120
+ const orderedDocs = orderDocs ( fieldsArray , results ) ;
121
+ ( 0 , _helpers . log ) ( 'orderedDocs: ' , orderedDocs ) ;
122
+ return orderedDocs ;
123
+ } ) ;
124
+ const cachePrefix = `mongo-${ ( 0 , _helpers . getCollection ) ( collection ) . collectionName } -` ;
125
+ const methods = {
126
+ findOneById : async ( _id , {
127
+ ttl
128
+ } = { } ) => {
129
+ const cacheKey = cachePrefix + idToString ( _id ) ;
130
+ const cacheDoc = await cache . get ( cacheKey ) ;
131
+ ( 0 , _helpers . log ) ( 'findOneById found in cache:' , cacheDoc ) ;
132
+ if ( cacheDoc ) {
133
+ return _bson . EJSON . parse ( cacheDoc ) ;
134
+ }
135
+ ( 0 , _helpers . log ) ( `Dataloader.load: ${ _bson . EJSON . stringify ( {
136
+ _id
137
+ } ) } `) ;
138
+ const docs = await loader . load ( _bson . EJSON . stringify ( {
139
+ _id
140
+ } ) ) ;
141
+ ( 0 , _helpers . log ) ( 'Dataloader.load returned: ' , docs ) ;
142
+ if ( Number . isInteger ( ttl ) ) {
143
+ // https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-caching#apollo-server-caching
144
+ cache . set ( cacheKey , _bson . EJSON . stringify ( docs [ 0 ] ) , {
145
+ ttl
146
+ } ) ;
147
+ }
148
+ return docs [ 0 ] ;
149
+ } ,
150
+ findManyByIds : ( ids , {
151
+ ttl
152
+ } = { } ) => {
153
+ return Promise . all ( ids . map ( id => methods . findOneById ( id , {
154
+ ttl
155
+ } ) ) ) ;
156
+ } ,
157
+ findByFields : async ( fields , {
158
+ ttl
159
+ } = { } ) => {
160
+ const {
161
+ cleanedFields,
162
+ loaderKey
163
+ } = prepFields ( fields ) ;
164
+ const cacheKey = cachePrefix + loaderKey ;
165
+ const cacheDoc = await cache . get ( cacheKey ) ;
166
+ if ( cacheDoc ) {
167
+ return _bson . EJSON . parse ( cacheDoc ) ;
168
+ }
169
+ const fieldNames = Object . keys ( cleanedFields ) ;
170
+ let docs ;
171
+ if ( fieldNames . length === 1 ) {
172
+ const field = cleanedFields [ fieldNames [ 0 ] ] ;
173
+ const fieldArray = Array . isArray ( field ) ? field : [ field ] ;
174
+ const docsArray = await Promise . all ( fieldArray . map ( value => {
175
+ const filter = { } ;
176
+ filter [ fieldNames [ 0 ] ] = value ;
177
+ return loader . load ( _bson . EJSON . stringify ( filter ) ) ;
178
+ } ) ) ;
179
+ docs = [ ] . concat ( ...docsArray ) ;
180
+ } else {
181
+ docs = await loader . load ( loaderKey ) ;
182
+ }
183
+ if ( Number . isInteger ( ttl ) ) {
184
+ // https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-caching#apollo-server-caching
185
+ cache . set ( cacheKey , _bson . EJSON . stringify ( docs ) , {
186
+ ttl
187
+ } ) ;
188
+ }
189
+ return docs ;
190
+ } ,
191
+ deleteFromCacheById : async _id => {
192
+ loader . clear ( _bson . EJSON . stringify ( {
193
+ _id
194
+ } ) ) ;
195
+ const cacheKey = cachePrefix + idToString ( _id ) ;
196
+ ( 0 , _helpers . log ) ( 'Deleting cache key: ' , cacheKey ) ;
197
+ await cache . delete ( cacheKey ) ;
198
+ } ,
199
+ deleteFromCacheByFields : async fields => {
200
+ const {
201
+ loaderKey
202
+ } = prepFields ( fields ) ;
203
+ const cacheKey = cachePrefix + loaderKey ;
204
+ loader . clear ( loaderKey ) ;
205
+ await cache . delete ( cacheKey ) ;
206
+ }
207
+ } ;
208
+ return methods ;
209
+ } ;
210
+ exports . createCachingMethods = createCachingMethods ;
0 commit comments