@@ -38,7 +38,6 @@ type NormalisedEntity<E extends FullEntity = FullEntity> = E extends any
38
38
? Omit < E , 'parent' > & {
39
39
parent : Link | null ;
40
40
children : Link [ ] ;
41
- digest : string ;
42
41
}
43
42
: never ;
44
43
@@ -50,6 +49,7 @@ export class NodeManager {
50
49
private createNodeId : NodePluginArgs [ 'createNodeId' ] ;
51
50
private createContentDigest : NodePluginArgs [ 'createContentDigest' ] ;
52
51
private cache : NodePluginArgs [ 'cache' ] ;
52
+ private getNode : NodePluginArgs [ 'getNode' ] ;
53
53
private reporter : NodePluginArgs [ 'reporter' ] ;
54
54
55
55
/**
@@ -63,6 +63,7 @@ export class NodeManager {
63
63
cache,
64
64
createContentDigest,
65
65
createNodeId,
66
+ getNode,
66
67
reporter,
67
68
} = args ;
68
69
/* eslint-enable */
@@ -73,6 +74,7 @@ export class NodeManager {
73
74
this . touchNode = touchNode ;
74
75
this . createNodeId = createNodeId ;
75
76
this . createContentDigest = createContentDigest ;
77
+ this . getNode = getNode ;
76
78
this . reporter = reporter ;
77
79
}
78
80
@@ -82,29 +84,28 @@ export class NodeManager {
82
84
*/
83
85
public async update ( entities : FullEntity [ ] ) : Promise < void > {
84
86
// get entries with relationship build-in
85
- const oldMap = new Map < string , NormalisedEntity > (
86
- ( await this . cache . get ( 'entityMap ' ) ) ?? [ ] ,
87
+ const old = new Map < string , NodeInput > (
88
+ ( await this . cache . get ( 'nodeGraph ' ) ) ?? [ ] ,
87
89
) ;
88
- const newMap = computeEntityMap ( entities , this . createContentDigest ) ;
90
+ const current = this . computeNodeGraph ( entities ) ;
91
+ const { added, updated, removed, unchanged } = computeChanges ( old , current ) ;
89
92
90
93
// for the usage of createNode
91
94
// see https://www.gatsbyjs.com/docs/reference/config-files/actions/#createNode
92
- await this . addNodes ( this . findNewEntities ( oldMap , newMap ) ) ;
93
- this . updateNodes ( this . findUpdatedEntities ( oldMap , newMap ) ) ;
94
- this . removeNodes ( this . findRemovedEntities ( oldMap , newMap ) ) ;
95
- this . touchNodes ( [ ... newMap . values ( ) ] ) ;
95
+ await this . addNodes ( added ) ;
96
+ await this . updateNodes ( updated ) ;
97
+ this . removeNodes ( removed ) ;
98
+ this . touchNodes ( unchanged ) ;
96
99
97
- await this . cache . set ( 'entityMap ' , [ ...newMap . entries ( ) ] ) ;
100
+ await this . cache . set ( 'nodeGraph ' , [ ...current . entries ( ) ] ) ;
98
101
}
99
102
100
103
/**
101
104
* add new nodes
102
105
* @param added new nodes to be added
103
106
*/
104
- private async addNodes ( added : NormalisedEntity [ ] ) : Promise < void > {
105
- for ( const entity of added ) {
106
- const node = this . nodifyEntity ( entity ) ;
107
-
107
+ private async addNodes ( added : NodeInput [ ] ) : Promise < void > {
108
+ for ( const node of added ) {
108
109
// DEBT: disable a false alarm from eslint as currently Gatsby is exporting an incorrect type
109
110
// this should be removed when https://github.com/gatsbyjs/gatsby/pull/32522 is merged
110
111
/* eslint-disable @typescript-eslint/await-thenable */
@@ -123,9 +124,14 @@ export class NodeManager {
123
124
* update existing nodes
124
125
* @param updated updated nodes
125
126
*/
126
- private updateNodes ( updated : NormalisedEntity [ ] ) : void {
127
- for ( const entity of updated ) {
128
- this . createNode ( this . nodifyEntity ( entity ) ) ;
127
+ private async updateNodes ( updated : NodeInput [ ] ) : Promise < void > {
128
+ for ( const node of updated ) {
129
+ // DEBT: disable a false alarm from eslint as currently Gatsby is exporting an incorrect type
130
+ // this should be removed when https://github.com/gatsbyjs/gatsby/pull/32522 is merged
131
+ /* eslint-disable @typescript-eslint/await-thenable */
132
+ // update the node
133
+ await this . createNode ( node ) ;
134
+ /* eslint-enable */
129
135
}
130
136
131
137
// don't be noisy if there's nothing new happen
@@ -138,9 +144,9 @@ export class NodeManager {
138
144
* remove old nodes
139
145
* @param removed nodes to be removed
140
146
*/
141
- private removeNodes ( removed : NormalisedEntity [ ] ) : void {
142
- for ( const entity of removed ) {
143
- this . deleteNode ( this . nodifyEntity ( entity ) ) ;
147
+ private removeNodes ( removed : NodeInput [ ] ) : void {
148
+ for ( const node of removed ) {
149
+ this . deleteNode ( node ) ;
144
150
}
145
151
146
152
// don't be noisy if there's nothing new happen
@@ -150,22 +156,47 @@ export class NodeManager {
150
156
}
151
157
152
158
/**
153
- * keep all current notion nodes alive
154
- * @param entities list of current notion entities
159
+ * keep unchanged notion nodes alive
160
+ * @param untouched list of current notion entities
155
161
*/
156
- private touchNodes ( entities : NormalisedEntity [ ] ) : void {
157
- for ( const entity of entities ) {
158
- const node = this . nodifyEntity ( entity ) ;
159
- this . touchNode ( {
160
- id : node . id ,
161
- internal : {
162
- type : node . internal . type ,
163
- contentDigest : node . internal . contentDigest ,
164
- } ,
165
- } ) ;
162
+ private touchNodes ( untouched : NodeInput [ ] ) : void {
163
+ for ( const node of untouched ) {
164
+ // DEBT: disable a false alarm from eslint as currently Gatsby is exporting an incorrect type
165
+ // this should be removed when https://github.com/gatsbyjs/gatsby/pull/32522 is merged
166
+ /* eslint-disable-next-line @typescript-eslint/no-unnecessary-condition */
167
+ if ( this . getNode ( node . id ) ) {
168
+ // just make a light-touched operation if the node is still alive
169
+ this . touchNode ( {
170
+ id : node . id ,
171
+ internal : {
172
+ type : node . internal . type ,
173
+ contentDigest : node . internal . contentDigest ,
174
+ } ,
175
+ } ) ;
176
+ } else {
177
+ // recreate it again if somehow it's missing
178
+ this . createNode ( node ) ;
179
+ }
166
180
}
167
181
168
- this . reporter . info ( `[${ name } ] processed ${ entities . length } nodes` ) ;
182
+ this . reporter . info ( `[${ name } ] keeping ${ untouched . length } nodes` ) ;
183
+ }
184
+
185
+ /**
186
+ * convert entities into gatsby node with full parent-child relationship
187
+ * @param entities all sort of entities including database and page
188
+ * @returns a map of gatsby nodes with parent and children linked
189
+ */
190
+ private computeNodeGraph ( entities : FullEntity [ ] ) : Map < string , NodeInput > {
191
+ // first compute the graph with entities before converting to nodes
192
+ const entityMap = computeEntityMap ( entities ) ;
193
+
194
+ return new Map < string , NodeInput > (
195
+ [ ...entityMap . entries ( ) ] . map ( ( [ id , entity ] ) => [
196
+ id ,
197
+ this . nodifyEntity ( entity ) ,
198
+ ] ) ,
199
+ ) ;
169
200
}
170
201
171
202
/**
@@ -204,7 +235,7 @@ export class NodeManager {
204
235
entity : NormalisedEntity ,
205
236
internal : Omit < NodeInput [ 'internal' ] , 'contentDigest' > & { type : T } ,
206
237
) : ContentNode < T > {
207
- return {
238
+ const basis = {
208
239
id : this . createNodeId ( `${ entity . object } :${ entity . id } ` ) ,
209
240
ref : entity . id ,
210
241
createdTime : entity . created_time ,
@@ -217,77 +248,20 @@ export class NodeManager {
217
248
children : entity . children . map ( ( { object, id } ) =>
218
249
this . createNodeId ( `${ object } :${ id } ` ) ,
219
250
) ,
251
+ } ;
252
+
253
+ const excludedKeys = [ 'parent' , 'children' , 'internal' ] ;
254
+ const contentDigest = this . createContentDigest ( omit ( basis , excludedKeys ) ) ;
255
+
256
+ return {
257
+ ...basis ,
220
258
internal : {
221
- contentDigest : entity . digest ,
259
+ contentDigest,
222
260
...internal ,
223
261
} ,
224
262
} ;
225
263
}
226
264
227
- /**
228
- * find new entities
229
- * @param oldMap the old entity map generated from earlier data
230
- * @param newMap the new entity map computed from up-to-date data from Notion
231
- * @returns a list of new entities
232
- */
233
- private findNewEntities (
234
- oldMap : Map < string , NormalisedEntity > ,
235
- newMap : Map < string , NormalisedEntity > ,
236
- ) : NormalisedEntity [ ] {
237
- const added : NormalisedEntity [ ] = [ ] ;
238
- for ( const [ id , newEntity ] of newMap . entries ( ) ) {
239
- const oldEntity = oldMap . get ( id ) ;
240
- if ( ! oldEntity ) {
241
- added . push ( newEntity ) ;
242
- }
243
- }
244
-
245
- return added ;
246
- }
247
-
248
- /**
249
- * find removed entities
250
- * @param oldMap the old entity map generated from earlier data
251
- * @param newMap the new entity map computed from up-to-date data from Notion
252
- * @returns a list of removed entities
253
- */
254
- private findRemovedEntities (
255
- oldMap : Map < string , NormalisedEntity > ,
256
- newMap : Map < string , NormalisedEntity > ,
257
- ) : NormalisedEntity [ ] {
258
- const removed : NormalisedEntity [ ] = [ ] ;
259
-
260
- for ( const [ id , oldEntity ] of oldMap . entries ( ) ) {
261
- if ( ! newMap . has ( id ) ) {
262
- removed . push ( oldEntity ) ;
263
- }
264
- }
265
-
266
- return removed ;
267
- }
268
-
269
- /**
270
- * find updated entities
271
- * @param oldMap the old entity map generated from earlier data
272
- * @param newMap the new entity map computed from up-to-date data from Notion
273
- * @returns a list of updated entities
274
- */
275
- private findUpdatedEntities (
276
- oldMap : Map < string , NormalisedEntity > ,
277
- newMap : Map < string , NormalisedEntity > ,
278
- ) : NormalisedEntity [ ] {
279
- const updated : NormalisedEntity [ ] = [ ] ;
280
-
281
- for ( const [ id , newEntity ] of newMap . entries ( ) ) {
282
- const oldEntity = oldMap . get ( id ) ;
283
- if ( oldEntity && oldEntity . digest !== newEntity . digest ) {
284
- updated . push ( newEntity ) ;
285
- }
286
- }
287
-
288
- return updated ;
289
- }
290
-
291
265
/**
292
266
* convert an entity to a NodeInput
293
267
* @param entity the entity to be converted
@@ -306,15 +280,44 @@ export class NodeManager {
306
280
}
307
281
}
308
282
283
+ /**
284
+ * compute changes between two node graphs
285
+ * @param old the old graph
286
+ * @param current the latest graph
287
+ * @returns a map of nodes in different states
288
+ */
289
+ export function computeChanges (
290
+ old : Map < string , NodeInput > ,
291
+ current : Map < string , NodeInput > ,
292
+ ) : Record < 'added' | 'updated' | 'removed' | 'unchanged' , NodeInput [ ] > {
293
+ const added = [ ...current . entries ( ) ] . filter ( ( [ id ] ) => ! old . has ( id ) ) ;
294
+ const removed = [ ...old . entries ( ) ] . filter ( ( [ id ] ) => ! current . has ( id ) ) ;
295
+
296
+ const bothExists = [ ...current . entries ( ) ] . filter ( ( [ id ] ) => old . has ( id ) ) ;
297
+ const updated = bothExists . filter (
298
+ ( [ id , node ] ) =>
299
+ old . get ( id ) ! . internal . contentDigest !== node . internal . contentDigest ,
300
+ ) ;
301
+ const unchanged = bothExists . filter (
302
+ ( [ id , node ] ) =>
303
+ old . get ( id ) ! . internal . contentDigest === node . internal . contentDigest ,
304
+ ) ;
305
+
306
+ return {
307
+ added : added . map ( ( [ , node ] ) => node ) ,
308
+ updated : updated . map ( ( [ , node ] ) => node ) ,
309
+ removed : removed . map ( ( [ , node ] ) => node ) ,
310
+ unchanged : unchanged . map ( ( [ , node ] ) => node ) ,
311
+ } ;
312
+ }
313
+
309
314
/**
310
315
* attach parent-child relationship to gatsby node
311
316
* @param entities all sort of entities including database and page
312
- * @param hashFn a hash function for generating the content digest
313
317
* @returns a map of entities with parent and children linked
314
318
*/
315
319
export function computeEntityMap (
316
320
entities : FullEntity [ ] ,
317
- hashFn : ( content : string | FullEntity ) => string ,
318
321
) : Map < string , NormalisedEntity > {
319
322
// create a new working set
320
323
const map = new Map < string , NormalisedEntity > ( ) ;
@@ -323,7 +326,6 @@ export function computeEntityMap(
323
326
...entity ,
324
327
parent : normaliseParent ( entity . parent ) ,
325
328
children : [ ] ,
326
- digest : hashFn ( entity ) ,
327
329
} ) ;
328
330
}
329
331
@@ -366,3 +368,18 @@ export function normaliseParent(parent: FullEntity['parent']): Link | null {
366
368
throw new TypeError ( `unknown parent` ) ;
367
369
}
368
370
}
371
+
372
+ /**
373
+ * return an object with the specified keys omitted
374
+ * @param record the record to be converted
375
+ * @param keys a list of keys to be omitted
376
+ * @returns an object with the specified keys omitted
377
+ */
378
+ function omit (
379
+ record : Record < string , unknown > ,
380
+ keys : string [ ] ,
381
+ ) : Record < string , unknown > {
382
+ return Object . fromEntries (
383
+ Object . entries ( record ) . filter ( ( [ key ] ) => ! keys . includes ( key ) ) ,
384
+ ) ;
385
+ }
0 commit comments