@@ -6,6 +6,14 @@ import { type ReactivityLog } from "./scheduler.ts";
66import { diffAndUpdate , setNestedValue } from "./data-updating.ts" ;
77import { resolveLinkToValue } from "./link-resolution.ts" ;
88
9+ // Maximum recursion depth to prevent infinite loops
10+ const MAX_RECURSION_DEPTH = 100 ;
11+
12+ // Cache of target objects to their proxies, scoped by ReactivityLog
13+ const proxyCacheByLog = new WeakMap < ReactivityLog , WeakMap < object , any > > ( ) ;
14+ // Use this to index cache if there is no log provided.
15+ const fallbackLog : ReactivityLog = { reads : [ ] , writes : [ ] } ;
16+
917// Array.prototype's entries, and whether they modify the array
1018enum ArrayMethodType {
1119 ReadOnly ,
@@ -51,7 +59,15 @@ export function createQueryResultProxy<T>(
5159 valueCell : DocImpl < T > ,
5260 valuePath : PropertyKey [ ] ,
5361 log ?: ReactivityLog ,
62+ depth : number = 0 ,
5463) : T {
64+ // Check recursion depth
65+ if ( depth > MAX_RECURSION_DEPTH ) {
66+ throw new Error (
67+ `Maximum recursion depth of ${ MAX_RECURSION_DEPTH } exceeded` ,
68+ ) ;
69+ }
70+
5571 // Resolve path and follow links to actual value.
5672 ( { cell : valueCell , path : valuePath } = resolveLinkToValue (
5773 valueCell ,
@@ -64,7 +80,19 @@ export function createQueryResultProxy<T>(
6480
6581 if ( ! isRecord ( target ) ) return target ;
6682
67- return new Proxy ( target as object , {
83+ // Get the appropriate cache index by log
84+ const cacheIndex = log ?? fallbackLog ;
85+ let logCache = proxyCacheByLog . get ( cacheIndex ) ;
86+ if ( ! logCache ) {
87+ logCache = new WeakMap < object , any > ( ) ;
88+ proxyCacheByLog . set ( cacheIndex , logCache ) ;
89+ }
90+
91+ // Check if we already have a proxy for this target in the cache
92+ const existingProxy = logCache ?. get ( target ) ;
93+ if ( existingProxy ) return existingProxy ;
94+
95+ const proxy = new Proxy ( target as object , {
6896 get : ( target , prop , receiver ) => {
6997 if ( typeof prop === "symbol" ) {
7098 if ( prop === getCellLink ) {
@@ -88,7 +116,12 @@ export function createQueryResultProxy<T>(
88116 // methods implicitly read all elements. TODO: Deal with
89117 // exceptions like at().
90118 const copy = target . map ( ( _ , index ) =>
91- createQueryResultProxy ( valueCell , [ ...valuePath , index ] , log )
119+ createQueryResultProxy (
120+ valueCell ,
121+ [ ...valuePath , index ] ,
122+ log ,
123+ depth + 1 ,
124+ )
92125 ) ;
93126
94127 return method . apply ( copy , args ) ;
@@ -179,7 +212,12 @@ export function createQueryResultProxy<T>(
179212 } ;
180213 }
181214
182- return createQueryResultProxy ( valueCell , [ ...valuePath , prop ] , log ) ;
215+ return createQueryResultProxy (
216+ valueCell ,
217+ [ ...valuePath , prop ] ,
218+ log ,
219+ depth + 1 ,
220+ ) ;
183221 } ,
184222 set : ( target , prop , value ) => {
185223 if ( isQueryResult ( value ) ) value = value [ getCellLink ] ;
@@ -221,6 +259,10 @@ export function createQueryResultProxy<T>(
221259 return true ;
222260 } ,
223261 } ) as T ;
262+
263+ // Cache the proxy in the appropriate cache before returning
264+ logCache . set ( target , proxy ) ;
265+ return proxy ;
224266}
225267
226268// Wraps a value on an array so that it can be read as literal or object,
0 commit comments