@@ -201,6 +201,35 @@ const makeUseFn = (): ExtensionUseFn => {
201201 * `'store:baz'` / `'controller:qux'` / `'driver:fred'` — returns a lazy
202202 * proxy to the registered instance (thrown on use-before-init).
203203 */
204+ /**
205+ * Wrap a resolved layer instance so that pulling a method off the import
206+ * comes out *bound* to the instance. Extensions routinely grab a method as a
207+ * bare reference — `const { write } = extension.import('service').fs` or
208+ * `const w = svc.fs.write` — then call it detached; without binding, `this`
209+ * is `undefined` and the method's private-field access throws on the first
210+ * line. Getters keep the real instance as their receiver, so private-field
211+ * reads inside accessors still resolve. Only `get` is trapped; writes, `in`,
212+ * and descriptor reads fall through to the instance unchanged.
213+ *
214+ * Trade-off: each method access returns a fresh bound function, so reference
215+ * identity is not stable (`svc.fs.write !== svc.fs.write`). That's acceptable
216+ * for the import surface, where instances are grabbed once and methods called.
217+ */
218+ const bindLayerMethods = < T > ( instance : T ) : T => {
219+ if ( instance === null || typeof instance !== 'object' ) {
220+ return instance ;
221+ }
222+ return new Proxy ( instance as object , {
223+ get ( target , prop ) {
224+ const value = Reflect . get ( target , prop , target ) ;
225+ return typeof value === 'function'
226+ ? // eslint-disable-next-line @typescript-eslint/no-explicit-any
227+ ( value as ( ...a : any [ ] ) => unknown ) . bind ( target )
228+ : value ;
229+ } ,
230+ } ) as T ;
231+ } ;
232+
204233export const extension = {
205234 // -- Config access -----------------------------------------------
206235 //
@@ -324,7 +353,7 @@ export const extension = {
324353 } ;
325354 return new Proxy ( { } , proxyProxyHandler ) as object ;
326355 }
327- return proxiedObj ;
356+ return bindLayerMethods ( proxiedObj ) ;
328357 } ,
329358 } ;
330359 // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -352,7 +381,7 @@ export const extension = {
352381 } ;
353382 return new Proxy ( { } , proxyProxyHandler ) as object ;
354383 }
355- return proxiedObj ;
384+ return bindLayerMethods ( proxiedObj ) ;
356385 } ,
357386 } ;
358387 // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -380,7 +409,7 @@ export const extension = {
380409 } ;
381410 return new Proxy ( { } , proxyProxyHandler ) as object ;
382411 }
383- return proxiedObj ;
412+ return bindLayerMethods ( proxiedObj ) ;
384413 } ,
385414 } ;
386415 // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -408,7 +437,7 @@ export const extension = {
408437 } ;
409438 return new Proxy ( { } , proxyProxyHandler ) as object ;
410439 }
411- return proxiedObj ;
440+ return bindLayerMethods ( proxiedObj ) ;
412441 } ,
413442 } ;
414443 // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -436,7 +465,7 @@ export const extension = {
436465 } ;
437466 return new Proxy ( { } , proxyProxyHandler ) as object ;
438467 }
439- return proxiedObj ;
468+ return bindLayerMethods ( proxiedObj ) ;
440469 } ,
441470 } ;
442471 // eslint-disable-next-line @typescript-eslint/no-explicit-any
0 commit comments