|
'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; |
|
|
|
|
|
|
|
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 |
|
}; |
|
}; |
|
}; |
|
|