@@ -38,7 +38,6 @@ type NormalisedEntity<E extends FullEntity = FullEntity> = E extends any
3838 ? Omit < E , 'parent' > & {
3939 parent : Link | null ;
4040 children : Link [ ] ;
41- digest : string ;
4241 }
4342 : never ;
4443
@@ -50,6 +49,7 @@ export class NodeManager {
5049 private createNodeId : NodePluginArgs [ 'createNodeId' ] ;
5150 private createContentDigest : NodePluginArgs [ 'createContentDigest' ] ;
5251 private cache : NodePluginArgs [ 'cache' ] ;
52+ private getNode : NodePluginArgs [ 'getNode' ] ;
5353 private reporter : NodePluginArgs [ 'reporter' ] ;
5454
5555 /**
@@ -63,6 +63,7 @@ export class NodeManager {
6363 cache,
6464 createContentDigest,
6565 createNodeId,
66+ getNode,
6667 reporter,
6768 } = args ;
6869 /* eslint-enable */
@@ -73,6 +74,7 @@ export class NodeManager {
7374 this . touchNode = touchNode ;
7475 this . createNodeId = createNodeId ;
7576 this . createContentDigest = createContentDigest ;
77+ this . getNode = getNode ;
7678 this . reporter = reporter ;
7779 }
7880
@@ -82,29 +84,28 @@ export class NodeManager {
8284 */
8385 public async update ( entities : FullEntity [ ] ) : Promise < void > {
8486 // 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 ' ) ) ?? [ ] ,
8789 ) ;
88- const newMap = computeEntityMap ( entities , this . createContentDigest ) ;
90+ const current = this . computeNodeGraph ( entities ) ;
91+ const { added, updated, removed, unchanged } = computeChanges ( old , current ) ;
8992
9093 // for the usage of createNode
9194 // 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 ) ;
9699
97- await this . cache . set ( 'entityMap ' , [ ...newMap . entries ( ) ] ) ;
100+ await this . cache . set ( 'nodeGraph ' , [ ...current . entries ( ) ] ) ;
98101 }
99102
100103 /**
101104 * add new nodes
102105 * @param added new nodes to be added
103106 */
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 ) {
108109 // DEBT: disable a false alarm from eslint as currently Gatsby is exporting an incorrect type
109110 // this should be removed when https://github.com/gatsbyjs/gatsby/pull/32522 is merged
110111 /* eslint-disable @typescript-eslint/await-thenable */
@@ -123,9 +124,14 @@ export class NodeManager {
123124 * update existing nodes
124125 * @param updated updated nodes
125126 */
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 */
129135 }
130136
131137 // don't be noisy if there's nothing new happen
@@ -138,9 +144,9 @@ export class NodeManager {
138144 * remove old nodes
139145 * @param removed nodes to be removed
140146 */
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 ) ;
144150 }
145151
146152 // don't be noisy if there's nothing new happen
@@ -150,22 +156,47 @@ export class NodeManager {
150156 }
151157
152158 /**
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
155161 */
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+ }
166180 }
167181
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+ ) ;
169200 }
170201
171202 /**
@@ -204,7 +235,7 @@ export class NodeManager {
204235 entity : NormalisedEntity ,
205236 internal : Omit < NodeInput [ 'internal' ] , 'contentDigest' > & { type : T } ,
206237 ) : ContentNode < T > {
207- return {
238+ const basis = {
208239 id : this . createNodeId ( `${ entity . object } :${ entity . id } ` ) ,
209240 ref : entity . id ,
210241 createdTime : entity . created_time ,
@@ -217,77 +248,20 @@ export class NodeManager {
217248 children : entity . children . map ( ( { object, id } ) =>
218249 this . createNodeId ( `${ object } :${ id } ` ) ,
219250 ) ,
251+ } ;
252+
253+ const excludedKeys = [ 'parent' , 'children' , 'internal' ] ;
254+ const contentDigest = this . createContentDigest ( omit ( basis , excludedKeys ) ) ;
255+
256+ return {
257+ ...basis ,
220258 internal : {
221- contentDigest : entity . digest ,
259+ contentDigest,
222260 ...internal ,
223261 } ,
224262 } ;
225263 }
226264
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-
291265 /**
292266 * convert an entity to a NodeInput
293267 * @param entity the entity to be converted
@@ -306,15 +280,44 @@ export class NodeManager {
306280 }
307281}
308282
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+
309314/**
310315 * attach parent-child relationship to gatsby node
311316 * @param entities all sort of entities including database and page
312- * @param hashFn a hash function for generating the content digest
313317 * @returns a map of entities with parent and children linked
314318 */
315319export function computeEntityMap (
316320 entities : FullEntity [ ] ,
317- hashFn : ( content : string | FullEntity ) => string ,
318321) : Map < string , NormalisedEntity > {
319322 // create a new working set
320323 const map = new Map < string , NormalisedEntity > ( ) ;
@@ -323,7 +326,6 @@ export function computeEntityMap(
323326 ...entity ,
324327 parent : normaliseParent ( entity . parent ) ,
325328 children : [ ] ,
326- digest : hashFn ( entity ) ,
327329 } ) ;
328330 }
329331
@@ -366,3 +368,18 @@ export function normaliseParent(parent: FullEntity['parent']): Link | null {
366368 throw new TypeError ( `unknown parent` ) ;
367369 }
368370}
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