-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Expand file tree
/
Copy path-proxy.ts
More file actions
147 lines (117 loc) · 3.87 KB
/
-proxy.ts
File metadata and controls
147 lines (117 loc) · 3.87 KB
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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/**
@module ember
*/
import { meta } from '@ember/-internals/meta';
import Mixin from '@ember/object/mixin';
import {
get,
set,
defineProperty,
tagForObject,
computed,
tagForProperty,
} from '@ember/-internals/metal';
import { setProxy, setupMandatorySetter, isObject, isProxy } from '@ember/-internals/utils';
import { assert } from '@ember/debug';
import { setCustomTagFor } from '@glimmer/manager';
import type { UpdatableTag, Tag } from '@glimmer/validator';
import { combine, updateTag, tagFor, tagMetaFor } from '@glimmer/validator';
export function contentFor<T>(proxy: ProxyMixin<T>): T | null {
let content = get(proxy, 'content');
// SAFETY: Ideally we'd assert instead of casting, but @glimmer/validator doesn't give us
// sufficient public types for this. Previously this code was .js and worked correctly so
// hopefully this is sufficiently reliable.
updateTag(tagForObject(proxy) as UpdatableTag, tagForObject(content));
return content;
}
function customTagForProxy(proxy: object, key: string, addMandatorySetter?: boolean): Tag {
assert('Expected a proxy', isProxy(proxy));
let meta = tagMetaFor(proxy);
let tag = tagFor(proxy, key, meta);
if (import.meta.env?.DEV) {
// TODO: Replace this with something more first class for tracking tags in import.meta.env?.DEV
// SAFETY: This is not an officially supported property but setting shouldn't cause issues.
(tag as any)._propertyKey = key;
}
if (key in proxy) {
if (import.meta.env?.DEV && addMandatorySetter) {
assert('[BUG] setupMandatorySetter should be set when debugging', setupMandatorySetter !== undefined);
setupMandatorySetter(tag, proxy, key);
}
return tag;
} else {
let tags: Tag[] = [tag, tagFor(proxy, 'content', meta)];
let content = contentFor(proxy);
if (isObject(content)) {
tags.push(tagForProperty(content, key, addMandatorySetter));
}
return combine(tags);
}
}
/**
`ProxyMixin` forwards all properties not defined by the proxy itself
to a proxied `content` object. See ObjectProxy for more details.
@class ProxyMixin
@namespace Ember
@private
*/
interface ProxyMixin<T = unknown> {
/**
The object whose properties will be forwarded.
@property content
@type {unknown}
@default null
@public
*/
content: T | null;
willDestroy(): void;
isTruthy: boolean;
unknownProperty<K extends keyof T>(key: K): T[K] | undefined;
unknownProperty(key: string): unknown;
setUnknownProperty<K extends keyof T>(key: K, value: T[K]): T[K];
setUnknownProperty<V>(key: string, value: V): V;
}
const ProxyMixin = Mixin.create({
/**
The object whose properties will be forwarded.
@property content
@type {unknown}
@default null
@public
*/
content: null,
init() {
this._super(...arguments);
setProxy(this);
tagForObject(this);
setCustomTagFor(this, customTagForProxy);
},
willDestroy() {
this.set('content', null);
this._super(...arguments);
},
isTruthy: computed('content', function () {
return Boolean(get(this, 'content'));
}),
unknownProperty(key: string) {
let content = contentFor(this);
return content ? get(content, key) : undefined;
},
setUnknownProperty(key: string, value: unknown) {
let m = meta(this);
if (m.isInitializing() || m.isPrototypeMeta(this)) {
// if marked as prototype or object is initializing then just
// defineProperty rather than delegate
defineProperty(this, key, null, value);
return value;
}
let content = contentFor(this);
assert(
`Cannot delegate set('${key}', ${value}) to the 'content' property of object proxy ${this}: its 'content' is undefined.`,
content
);
// SAFETY: We don't actually guarantee that this is an object, so this isn't necessarily safe :(
return set(content as object, key, value);
},
});
export default ProxyMixin;