@@ -92,16 +92,20 @@ export class CycleTracker<K> {
9292 constructor ( ) {
9393 this . partial = new Set < K > ( ) ;
9494 }
95- enter ( k : K ) : boolean {
95+ include ( k : K , context ?: unknown ) : Disposable | null {
9696 if ( this . partial . has ( k ) ) {
97- console . error ( "Cycle Detected!" ) ;
98- return false ;
97+ console . error (
98+ "Cycle Detected!" ,
99+ context == null ? JSON . stringify ( k ) : JSON . stringify ( context ) ,
100+ ) ;
101+ return null ;
99102 }
100103 this . partial . add ( k ) ;
101- return true ;
102- }
103- exit ( k : K ) {
104- this . partial . delete ( k ) ;
104+ return {
105+ [ Symbol . dispose ] : ( ) => {
106+ this . partial . delete ( k ) ;
107+ } ,
108+ } ;
105109 }
106110}
107111
@@ -204,22 +208,17 @@ export abstract class BaseObjectTraverser<K, S> {
204208 if ( isPrimitive ( doc . value ) ) {
205209 return doc . value ;
206210 } else if ( Array . isArray ( doc . value ) ) {
207- if ( tracker . enter ( doc . value ) ) {
208- try {
209- return doc . value . map ( ( item , index ) =>
210- this . traverseDAG (
211- { ...doc , path : [ ...doc . path , index . toString ( ) ] , value : item } ,
212- tracker ,
213- schemaTracker ,
214- )
215- ) as Immutable < JSONValue > [ ] ;
216- } finally {
217- tracker . exit ( doc . value ) ;
218- }
219- } else {
220- console . log ( "Cycle detected" , JSON . stringify ( doc ) ) ;
211+ using t = tracker . include ( doc . value , doc ) ;
212+ if ( t === null ) {
221213 return null ;
222214 }
215+ return doc . value . map ( ( item , index ) =>
216+ this . traverseDAG (
217+ { ...doc , path : [ ...doc . path , index . toString ( ) ] , value : item } ,
218+ tracker ,
219+ schemaTracker ,
220+ )
221+ ) as Immutable < JSONValue > [ ] ;
223222 } else if ( isObject ( doc . value ) ) {
224223 // First, see if we need special handling
225224 if ( isAnyCellLink ( doc . value ) ) {
@@ -236,31 +235,26 @@ export abstract class BaseObjectTraverser<K, S> {
236235 }
237236 return this . traverseDAG ( newDoc , tracker , schemaTracker ) ;
238237 } else {
239- if ( tracker . enter ( doc . value ) ) {
240- try {
241- return Object . fromEntries (
242- Object . entries ( doc . value ) . map ( (
243- [ k , value ] ,
244- ) => [
245- k ,
246- this . traverseDAG (
247- {
248- ...doc ,
249- path : [ ...doc . path , k ] ,
250- value : value ,
251- } ,
252- tracker ,
253- schemaTracker ,
254- ) ,
255- ] ) ,
256- ) as Immutable < JSONValue > ;
257- } finally {
258- tracker . exit ( doc . value ) ;
259- }
260- } else {
261- console . log ( "Cycle detected" , JSON . stringify ( doc ) ) ;
238+ using t = tracker . include ( doc . value , doc ) ;
239+ if ( t === null ) {
262240 return null ;
263241 }
242+ return Object . fromEntries (
243+ Object . entries ( doc . value ) . map ( (
244+ [ k , value ] ,
245+ ) => [
246+ k ,
247+ this . traverseDAG (
248+ {
249+ ...doc ,
250+ path : [ ...doc . path , k ] ,
251+ value : value ,
252+ } ,
253+ tracker ,
254+ schemaTracker ,
255+ ) ,
256+ ] ) ,
257+ ) as Immutable < JSONValue > ;
264258 }
265259 } else {
266260 console . error ( "Encountered unexpected object: " , doc . value ) ;
@@ -360,75 +354,69 @@ function followPointer<K, S>(
360354 schemaTracker ?: MapSet < string , SchemaPathSelector > ,
361355 selector ?: SchemaPathSelector ,
362356) : [ ValueAtPath < K > , SchemaPathSelector | undefined ] {
363- if ( ! tracker . enter ( doc . value ! ) ) {
364- console . log ( "Cycle detected" , JSON . stringify ( doc ) ) ;
357+ using t = tracker . include ( doc . value ! , doc ) ;
358+ if ( t === null ) {
365359 return [ { ...doc , path : [ ] , value : undefined } , selector ] ;
366360 }
367- try {
368- const link = parseLink ( doc . value ) ! ;
369- const target = ( link . id !== undefined )
370- ? manager . getTarget ( link . id )
371- : doc . doc ;
372- let [ targetDoc , targetDocRoot ] = [ doc . doc , doc . docRoot ] ;
373- if ( selector !== undefined ) {
374- // We'll need to re-root the selector for the target doc
375- // Remove the portions of doc.path from selector.path, limiting schema if needed
376- // Also insert the portions of cellTarget.path, so selector is relative to new target doc
377- // We do this even if the target doc is the same doc, since we want the
378- // selector path to match.
379- selector = narrowSchema ( doc . path , selector , link . path as string [ ] ) ;
361+ const link = parseLink ( doc . value ) ! ;
362+ const target = ( link . id !== undefined ) ? manager . getTarget ( link . id ) : doc . doc ;
363+ let [ targetDoc , targetDocRoot ] = [ doc . doc , doc . docRoot ] ;
364+ if ( selector !== undefined ) {
365+ // We'll need to re-root the selector for the target doc
366+ // Remove the portions of doc.path from selector.path, limiting schema if needed
367+ // Also insert the portions of cellTarget.path, so selector is relative to new target doc
368+ // We do this even if the target doc is the same doc, since we want the
369+ // selector path to match.
370+ selector = narrowSchema ( doc . path , selector , link . path as string [ ] ) ;
371+ }
372+ if ( link . id !== undefined ) {
373+ // We have a reference to a different cell, so track the dependency
374+ // and update our targetDoc and targetDocRoot
375+ const valueEntry = manager . load ( target ) ;
376+ if ( valueEntry === null ) {
377+ return [ { ...doc , path : [ ] , value : undefined } , selector ] ;
380378 }
381- if ( link . id !== undefined ) {
382- // We have a reference to a different cell, so track the dependency
383- // and update our targetDoc and targetDocRoot
384- const valueEntry = manager . load ( target ) ;
385- if ( valueEntry === null ) {
386- return [ { ...doc , path : [ ] , value : undefined } , selector ] ;
387- }
388- if ( schemaTracker !== undefined && selector !== undefined ) {
389- schemaTracker . add ( manager . toKey ( target ) , selector ) ;
390- }
391- // If the object we're pointing to is a retracted fact, just return undefined.
392- // We can't do a better match, but we do want to include the result so we watch this doc
393- if ( valueEntry . value === undefined ) {
394- return [
395- { doc : target , docRoot : undefined , path : [ ] , value : undefined } ,
396- selector ,
397- ] ;
398- }
399- // Otherwise, we can continue with the target.
400- // an assertion fact.is will be an object with a value property, and
401- // that's what our schema is relative to.
402- targetDoc = target ;
403- const targetObj = valueEntry . value as Immutable < JSONObject > ;
404- targetDocRoot = targetObj [ "value" ] ;
405- // Load any sources (recursively) if they exist and any linked recipes
406- loadSource (
407- manager ,
408- valueEntry ,
409- new Set < string > ( ) ,
410- schemaTracker ,
411- ) ;
379+ if ( schemaTracker !== undefined && selector !== undefined ) {
380+ schemaTracker . add ( manager . toKey ( target ) , selector ) ;
412381 }
413-
414- // We've loaded the linked doc, so walk the path to get to the right part of that doc (or whatever doc that path leads to),
415- // then the provided path from the arguments.
416- return getAtPath (
382+ // If the object we're pointing to is a retracted fact, just return undefined.
383+ // We can't do a better match, but we do want to include the result so we watch this doc
384+ if ( valueEntry . value === undefined ) {
385+ return [
386+ { doc : target , docRoot : undefined , path : [ ] , value : undefined } ,
387+ selector ,
388+ ] ;
389+ }
390+ // Otherwise, we can continue with the target.
391+ // an assertion fact.is will be an object with a value property, and
392+ // that's what our schema is relative to.
393+ targetDoc = target ;
394+ const targetObj = valueEntry . value as Immutable < JSONObject > ;
395+ targetDocRoot = targetObj [ "value" ] ;
396+ // Load any sources (recursively) if they exist and any linked recipes
397+ loadSource (
417398 manager ,
418- {
419- doc : targetDoc ,
420- docRoot : targetDocRoot ,
421- path : [ ] ,
422- value : targetDocRoot ,
423- } ,
424- [ ...link . path , ...path ] as string [ ] ,
425- tracker ,
399+ valueEntry ,
400+ new Set < string > ( ) ,
426401 schemaTracker ,
427- selector ,
428402 ) ;
429- } finally {
430- tracker . exit ( doc . value ! ) ;
431403 }
404+
405+ // We've loaded the linked doc, so walk the path to get to the right part of that doc (or whatever doc that path leads to),
406+ // then the provided path from the arguments.
407+ return getAtPath (
408+ manager ,
409+ {
410+ doc : targetDoc ,
411+ docRoot : targetDocRoot ,
412+ path : [ ] ,
413+ value : targetDocRoot ,
414+ } ,
415+ [ ...link . path , ...path ] as string [ ] ,
416+ tracker ,
417+ schemaTracker ,
418+ selector ,
419+ ) ;
432420}
433421
434422// Recursively load the source from the doc ()
@@ -677,19 +665,14 @@ export class SchemaObjectTraverser<K, S> extends BaseObjectTraverser<K, S> {
677665 return this . isValidType ( schemaObj , "number" ) ? doc . value : undefined ;
678666 } else if ( Array . isArray ( doc . value ) ) {
679667 if ( this . isValidType ( schemaObj , "array" ) ) {
680- if ( this . tracker . enter ( doc . value ) ) {
681- try {
682- return this . traverseArrayWithSchema ( doc , {
683- schema : schemaObj ,
684- rootSchema : schemaContext . rootSchema ,
685- } ) ;
686- } finally {
687- this . tracker . exit ( doc . value ) ;
688- }
689- } else {
690- console . log ( "Cycle detected" , JSON . stringify ( doc ) ) ;
668+ using t = this . tracker . include ( doc . value , doc ) ;
669+ if ( t === null ) {
691670 return null ;
692671 }
672+ return this . traverseArrayWithSchema ( doc , {
673+ schema : schemaObj ,
674+ rootSchema : schemaContext . rootSchema ,
675+ } ) ;
693676 }
694677 return undefined ;
695678 } else if ( isObject ( doc . value ) ) {
@@ -701,19 +684,14 @@ export class SchemaObjectTraverser<K, S> extends BaseObjectTraverser<K, S> {
701684 // TODO(@ubik2): it might be technically ok to follow the same pointer more than once, since we might have
702685 // a different schema the second time, which could prevent an infinite cycle, but for now, just reject these.
703686 } else if ( this . isValidType ( schemaObj , "object" ) ) {
704- if ( this . tracker . enter ( doc . value ) ) {
705- try {
706- return this . traverseObjectWithSchema ( doc , {
707- schema : schemaObj ,
708- rootSchema : schemaContext . rootSchema ,
709- } ) ;
710- } finally {
711- this . tracker . exit ( doc . value ) ;
712- }
713- } else {
714- console . log ( "Cycle detected" , JSON . stringify ( doc ) ) ;
687+ using t = this . tracker . include ( doc . value , doc ) ;
688+ if ( t === null ) {
715689 return null ;
716690 }
691+ return this . traverseObjectWithSchema ( doc , {
692+ schema : schemaObj ,
693+ rootSchema : schemaContext . rootSchema ,
694+ } ) ;
717695 }
718696 }
719697 }
@@ -837,16 +815,11 @@ export class SchemaObjectTraverser<K, S> extends BaseObjectTraverser<K, S> {
837815 // but we may have a pointer cycle of docs, and we've finished resolving
838816 // the pointer now. To avoid descending into a cycle, track entry to the
839817 // doc we were called with (not the one we resolved, which may be a pointer).
840- if ( this . tracker . enter ( doc . value ! ) ) {
841- try {
842- return this . traverseWithSelector ( newDoc , newSelector ! ) ;
843- } finally {
844- this . tracker . exit ( doc . value ! ) ;
845- }
846- } else {
847- console . log ( "Cycle detected" , JSON . stringify ( doc ) ) ;
818+ using t = this . tracker . include ( doc . value ! , doc ) ;
819+ if ( t === null ) {
848820 return null ;
849821 }
822+ return this . traverseWithSelector ( newDoc , newSelector ! ) ;
850823 }
851824}
852825
0 commit comments