summaryrefslogtreecommitdiff
path: root/node_modules/@11ty/eleventy/src/Util/Objects/ProxyWrap.js
blob: 38730fd18e4862036091d59962073c30d4821c74 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import types from "node:util/types";
import debugUtil from "debug";
import { isPlainObject } from "@11ty/eleventy-utils";

const debug = debugUtil("Dev:Eleventy:Proxy");

function wrapObject(target, fallback) {
	if (Object.isFrozen(target)) {
		return target;
	}

	return new Proxy(target, {
		getOwnPropertyDescriptor(target, prop) {
			let ret;

			if (Reflect.has(target, prop)) {
				ret = Reflect.getOwnPropertyDescriptor(target, prop);
			} else if (Reflect.has(fallback, prop)) {
				ret = Reflect.getOwnPropertyDescriptor(fallback, prop);
			}

			return ret;
		},
		has(target, prop) {
			if (Reflect.has(target, prop)) {
				return true;
			}

			return Reflect.has(fallback, prop);
		},
		ownKeys(target) {
			let s = new Set();
			// The fallback keys need to come first to preserve proper key order
			// https://github.com/11ty/eleventy/issues/3849
			if (isPlainObject(fallback)) {
				for (let k of Reflect.ownKeys(fallback)) {
					s.add(k);
				}
			}
			for (let k of Reflect.ownKeys(target)) {
				if (!s.has(k)) {
					s.add(k);
				}
			}
			return Array.from(s);
		},
		get(target, prop) {
			debug("handler:get", prop);

			let value = Reflect.get(target, prop);

			if (Reflect.has(target, prop)) {
				// Already proxied
				if (types.isProxy(value)) {
					return value;
				}

				if (isPlainObject(value) && Reflect.has(fallback, prop)) {
					if (Object.isFrozen(value)) {
						return value;
					}

					let ret = wrapObject(value, Reflect.get(fallback, prop));
					debug("handler:get (primary, object)", prop);
					return ret;
				}

				debug("handler:get (primary)", prop);
				return value;
			}

			// Does not exist in primary
			if (
				(typeof fallback === "object" || typeof fallback === "function") &&
				Reflect.has(fallback, prop)
			) {
				// fallback has prop
				let fallbackValue = Reflect.get(fallback, prop);

				if (isPlainObject(fallbackValue)) {
					if (Object.isFrozen(fallbackValue)) {
						return fallbackValue;
					}

					debug("handler:get (fallback, object)", prop);
					// set empty object on primary
					let emptyObject = {};
					Reflect.set(target, prop, emptyObject);

					return wrapObject(emptyObject, fallbackValue);
				}

				debug("handler:get (fallback)", prop);
				return fallbackValue;
			}

			// primary *and* fallback do _not_ have prop
			debug("handler:get (not on primary or fallback)", prop);

			return value;
		},
		set(target, prop, value) {
			debug("handler:set", prop);

			return Reflect.set(target, prop, value);
		},
	});
}

function ProxyWrap(target, fallback) {
	if (!isPlainObject(target) || !isPlainObject(fallback)) {
		throw new Error("ProxyWrap expects objects for both the target and fallback");
	}

	return wrapObject(target, fallback);
}

export { ProxyWrap };