'use strict'; const { target: tv, unwrap, bound, unbound } = require('proxy-target/array'); const { create: createGCHook } = require('gc-hook'); const { ARRAY, OBJECT, FUNCTION, NUMBER, STRING, SYMBOL } = require('proxy-target/types'); const { TypedArray, augment, asEntry, symbol, transform } = require('./utils.js'); const { APPLY, CONSTRUCT, DEFINE_PROPERTY, DELETE_PROPERTY, GET, GET_OWN_PROPERTY_DESCRIPTOR, GET_PROTOTYPE_OF, HAS, IS_EXTENSIBLE, OWN_KEYS, PREVENT_EXTENSION, SET, SET_PROTOTYPE_OF, DELETE } = require('./traps.js'); module.exports = name => { let id = 0; const ids = new Map; const values = new Map; const __proxy__ = Symbol(); return function (main, MAIN, THREAD) { const $ = this?.transform || transform; const { [MAIN]: __main__ } = main; const proxies = new Map; const onGarbageCollected = value => { proxies.delete(value); __main__(DELETE, argument(value)); }; const argument = asEntry( (type, value) => { if (__proxy__ in value) return unbound(value[__proxy__]); if (type === FUNCTION) { value = $(value); if (!values.has(value)) { let sid; // a bit apocalyptic scenario but if this thread runs forever // and the id does a whole int32 roundtrip we might have still // some reference dangling around while (values.has(sid = String(id++))); ids.set(value, sid); values.set(sid, value); } return tv(type, ids.get(value)); } if (!(value instanceof TypedArray)) { value = $(value); for(const key in value) value[key] = argument(value[key]); } return tv(type, value); } ); const register = (entry, type, value) => { const retained = proxies.get(value)?.deref(); if (retained) return retained; const target = type === FUNCTION ? bound(entry) : entry; const proxy = new Proxy(target, proxyHandler); proxies.set(value, new WeakRef(proxy)); return createGCHook(value, onGarbageCollected, { return: proxy, token: false, }); }; const fromEntry = entry => unwrap(entry, (type, value) => { switch (type) { case OBJECT: if (value === null) return globalThis; case ARRAY: return typeof value === NUMBER ? register(entry, type, value) : value; case FUNCTION: return typeof value === STRING ? values.get(value) : register(entry, type, value); case SYMBOL: return symbol(value); } return value; }); const result = (TRAP, target, ...args) => fromEntry(__main__(TRAP, unbound(target), ...args)); const proxyHandler = { [APPLY]: (target, thisArg, args) => result(APPLY, target, argument(thisArg), args.map(argument)), [CONSTRUCT]: (target, args) => result(CONSTRUCT, target, args.map(argument)), [DEFINE_PROPERTY]: (target, name, descriptor) => { const { get, set, value } = descriptor; if (typeof get === FUNCTION) descriptor.get = argument(get); if (typeof set === FUNCTION) descriptor.set = argument(set); if (typeof value === FUNCTION) descriptor.value = argument(value); return result(DEFINE_PROPERTY, target, argument(name), descriptor); }, [DELETE_PROPERTY]: (target, name) => result(DELETE_PROPERTY, target, argument(name)), [GET_PROTOTYPE_OF]: target => result(GET_PROTOTYPE_OF, target), [GET]: (target, name) => name === __proxy__ ? target : result(GET, target, argument(name)), [GET_OWN_PROPERTY_DESCRIPTOR]: (target, name) => { const descriptor = result(GET_OWN_PROPERTY_DESCRIPTOR, target, argument(name)); return descriptor && augment(descriptor, fromEntry); }, [HAS]: (target, name) => name === __proxy__ || result(HAS, target, argument(name)), [IS_EXTENSIBLE]: target => result(IS_EXTENSIBLE, target), [OWN_KEYS]: target => result(OWN_KEYS, target).map(fromEntry), [PREVENT_EXTENSION]: target => result(PREVENT_EXTENSION, target), [SET]: (target, name, value) => result(SET, target, argument(name), argument(value)), [SET_PROTOTYPE_OF]: (target, proto) => result(SET_PROTOTYPE_OF, target, argument(proto)), }; main[THREAD] = (trap, entry, ctx, args) => { switch (trap) { case APPLY: return fromEntry(entry).apply(fromEntry(ctx), args.map(fromEntry)); case DELETE: { const id = fromEntry(entry); ids.delete(values.get(id)); values.delete(id); } } }; const global = new Proxy(tv(OBJECT, null), proxyHandler); return { [name.toLowerCase()]: global, [`is${name}Proxy`]: value => typeof value === OBJECT && !!value && __proxy__ in value, proxy: main }; }; };