@yiin/reactive-proxy-state 1.0.12 → 1.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,237 +0,0 @@
1
- import { deepEqual, getPathConcat, setPathConcat, wrapperCache } from './utils';
2
- import { reactive } from './reactive';
3
- import { wrapMap } from './wrap-map';
4
- import { wrapSet } from './wrap-set';
5
- import { track, trigger } from './watch-effect';
6
- // avoid repeated typeof checks
7
- function isObject(v) {
8
- return v && typeof v === 'object';
9
- }
10
- export function wrapArray(arr, emit, path) {
11
- // reuse existing proxy if available for performance
12
- const cachedProxy = wrapperCache.get(arr);
13
- if (cachedProxy)
14
- return cachedProxy;
15
- // cache for wrapped methods to avoid re-creating them on each call
16
- const methodCache = {};
17
- const proxy = new Proxy(arr, {
18
- get(target, prop, receiver) {
19
- track(target, prop);
20
- if (methodCache[prop]) {
21
- return methodCache[prop];
22
- }
23
- // handle specific array mutation methods that require custom logic and event emission
24
- switch (prop) {
25
- case 'push':
26
- track(target, 'length');
27
- methodCache[prop] = function (...items) {
28
- const oldLength = target.length;
29
- const result = target.push(...items);
30
- const newLength = target.length;
31
- if (items.length > 0) {
32
- const event = {
33
- action: 'array-push',
34
- path: path,
35
- key: oldLength, // start index was the old length
36
- items: items
37
- };
38
- emit(event);
39
- trigger(target, Symbol.iterator);
40
- if (oldLength !== newLength) {
41
- trigger(target, 'length');
42
- }
43
- }
44
- return result;
45
- };
46
- return methodCache[prop];
47
- case 'pop':
48
- track(target, 'length');
49
- methodCache[prop] = function () {
50
- if (target.length === 0)
51
- return undefined;
52
- const oldLength = target.length;
53
- const poppedIndex = oldLength - 1;
54
- const oldValue = target[poppedIndex];
55
- const result = target.pop();
56
- const newLength = target.length;
57
- const event = {
58
- action: 'array-pop',
59
- path: path,
60
- key: poppedIndex,
61
- oldValue: oldValue
62
- };
63
- emit(event);
64
- trigger(target, Symbol.iterator);
65
- if (oldLength !== newLength) {
66
- trigger(target, 'length');
67
- }
68
- return result;
69
- };
70
- return methodCache[prop];
71
- case 'shift':
72
- track(target, 'length');
73
- methodCache[prop] = function () {
74
- if (target.length === 0)
75
- return undefined;
76
- const oldLength = target.length;
77
- const oldValue = target[0];
78
- const result = target.shift();
79
- const newLength = target.length;
80
- const event = {
81
- action: 'array-shift',
82
- path: path,
83
- key: 0,
84
- oldValue: oldValue
85
- };
86
- emit(event);
87
- trigger(target, Symbol.iterator);
88
- if (oldLength !== newLength) {
89
- trigger(target, 'length');
90
- }
91
- return result;
92
- };
93
- return methodCache[prop];
94
- case 'unshift':
95
- track(target, 'length');
96
- methodCache[prop] = function (...items) {
97
- const oldLength = target.length;
98
- const result = target.unshift(...items);
99
- const newLength = target.length;
100
- if (items.length > 0) {
101
- const event = {
102
- action: 'array-unshift',
103
- path: path,
104
- key: 0,
105
- items: items
106
- };
107
- emit(event);
108
- trigger(target, Symbol.iterator);
109
- if (oldLength !== newLength) {
110
- trigger(target, 'length');
111
- }
112
- }
113
- return result;
114
- };
115
- return methodCache[prop];
116
- case 'splice':
117
- track(target, 'length');
118
- methodCache[prop] = function (start, deleteCount, ...items) {
119
- const oldLength = target.length;
120
- const actualStart = start < 0 ? Math.max(target.length + start, 0) : Math.min(start, target.length);
121
- const deleteCountNum = deleteCount === undefined ? target.length - actualStart : Number(deleteCount);
122
- const actualDeleteCount = Math.min(deleteCountNum, target.length - actualStart);
123
- const deletedItems = target.slice(actualStart, actualStart + actualDeleteCount);
124
- const result = target.splice(start, deleteCountNum, ...items);
125
- const newLength = target.length;
126
- if (actualDeleteCount > 0 || items.length > 0) {
127
- const event = {
128
- action: 'array-splice',
129
- path: path,
130
- key: actualStart,
131
- deleteCount: actualDeleteCount,
132
- items: items.length > 0 ? items : undefined,
133
- oldValues: deletedItems.length > 0 ? deletedItems : undefined
134
- };
135
- emit(event);
136
- trigger(target, Symbol.iterator);
137
- if (oldLength !== newLength) {
138
- trigger(target, 'length');
139
- }
140
- }
141
- return result;
142
- };
143
- return methodCache[prop];
144
- // handle methods that rely on iteration state
145
- case Symbol.iterator:
146
- case 'values':
147
- case 'keys':
148
- case 'entries':
149
- case 'forEach':
150
- case 'map':
151
- case 'filter':
152
- case 'reduce':
153
- case 'reduceRight':
154
- case 'find':
155
- case 'findIndex':
156
- case 'every':
157
- case 'some':
158
- case 'join':
159
- track(target, Symbol.iterator);
160
- // fall through to default behavior (usually binding)
161
- break;
162
- case 'length':
163
- track(target, 'length');
164
- return Reflect.get(target, prop, receiver);
165
- }
166
- const value = Reflect.get(target, prop, receiver);
167
- // determine if the property access is numeric array index access
168
- const isNumericIndex = typeof prop === 'number' || (typeof prop === 'string' && !isNaN(parseInt(prop, 10)));
169
- if (isNumericIndex) {
170
- track(target, String(prop));
171
- if (!isObject(value))
172
- return value;
173
- // reuse existing proxy for nested object/array if available
174
- const cachedValueProxy = wrapperCache.get(value);
175
- if (cachedValueProxy)
176
- return cachedValueProxy;
177
- // calculate the nested path for the element, optimizing with caching
178
- const propKey = String(prop);
179
- const pathKey = path.length > 0 ? `${path.join('.')}.${propKey}` : propKey;
180
- let newPath = getPathConcat(pathKey);
181
- if (newPath === undefined) {
182
- newPath = path.concat(propKey);
183
- setPathConcat(pathKey, newPath);
184
- }
185
- // recursively wrap nested structures
186
- if (Array.isArray(value))
187
- return wrapArray(value, emit, newPath);
188
- if (value instanceof Map)
189
- return wrapMap(value, emit, newPath);
190
- if (value instanceof Set)
191
- return wrapSet(value, emit, newPath);
192
- if (value instanceof Date)
193
- return new Date(value.getTime()); // dates are not proxied, return a copy
194
- return reactive(value, emit, newPath);
195
- }
196
- // ensure functions accessed directly are bound to the original target
197
- if (typeof value === 'function') {
198
- return value.bind(target);
199
- }
200
- return value;
201
- },
202
- set(target, prop, value, receiver) {
203
- const oldValue = target[prop];
204
- // avoid unnecessary triggers if value hasn't changed
205
- if (oldValue === value)
206
- return true;
207
- if (isObject(oldValue) && isObject(value) && deepEqual(oldValue, value, new WeakMap()))
208
- return true;
209
- const descriptor = Reflect.getOwnPropertyDescriptor(target, prop);
210
- const result = Reflect.set(target, prop, value, receiver);
211
- const isNumericIndex = typeof prop === 'number' || (typeof prop === 'string' && !isNaN(parseInt(String(prop))));
212
- // emit event and trigger effects only if the set was successful and wasn't intercepted by a setter
213
- // (unless it's a direct numeric index set, which doesn't have a descriptor.set)
214
- if (result && (!descriptor || !descriptor.set || isNumericIndex)) {
215
- const propKey = String(prop);
216
- const pathKey = path.length > 0 ? `${path.join('.')}.${propKey}` : propKey;
217
- let newPath = getPathConcat(pathKey);
218
- if (newPath === undefined) {
219
- newPath = path.concat(propKey);
220
- setPathConcat(pathKey, newPath);
221
- }
222
- const event = {
223
- action: 'set',
224
- path: newPath,
225
- oldValue,
226
- newValue: value
227
- };
228
- emit(event);
229
- trigger(target, prop);
230
- }
231
- return result;
232
- }
233
- });
234
- // cache the newly created proxy before returning
235
- wrapperCache.set(arr, proxy);
236
- return proxy;
237
- }
package/dist/wrap-map.js DELETED
@@ -1,252 +0,0 @@
1
- import { deepEqual, getPathConcat, setPathConcat, wrapperCache } from './utils';
2
- import { reactive } from './reactive';
3
- import { wrapArray } from './wrap-array';
4
- import { wrapSet } from './wrap-set';
5
- import { track, trigger } from './watch-effect';
6
- export function wrapMap(map, emit, path) {
7
- // reuse existing proxy if available for performance
8
- const cachedProxy = wrapperCache.get(map);
9
- if (cachedProxy)
10
- return cachedProxy;
11
- // cache for wrapped methods to avoid re-creating them on each call
12
- const methodCache = {};
13
- const proxy = new Proxy(map, {
14
- get(target, prop, receiver) {
15
- track(target, prop);
16
- // iteration methods need to track the iterator symbol on every access
17
- // to re-establish dependency after effect cleanup, even when cached
18
- if (prop === Symbol.iterator || prop === 'entries' || prop === 'values' || prop === 'keys' || prop === 'forEach') {
19
- track(target, Symbol.iterator);
20
- }
21
- if (methodCache[prop]) {
22
- return methodCache[prop];
23
- }
24
- if (prop === 'set') {
25
- methodCache[prop] = function (key, value) {
26
- const existed = target.has(key);
27
- const oldValue = target.get(key);
28
- const oldSize = target.size;
29
- // avoid unnecessary work if value hasn't changed
30
- if (oldValue === value)
31
- return receiver;
32
- if (oldValue && typeof oldValue === 'object' && value && typeof value === 'object' && deepEqual(oldValue, value, new WeakMap()))
33
- return receiver;
34
- target.set(key, value);
35
- const newSize = target.size;
36
- // optimize path calculation by caching concatenated paths
37
- const pathKey = path.join('.');
38
- let cachedPath = getPathConcat(pathKey);
39
- if (cachedPath === undefined) {
40
- cachedPath = path;
41
- setPathConcat(pathKey, cachedPath);
42
- }
43
- const event = {
44
- action: 'map-set',
45
- path: cachedPath,
46
- key,
47
- oldValue,
48
- newValue: value
49
- };
50
- emit(event);
51
- // trigger effects based on whether it was an add or update
52
- if (!existed) {
53
- trigger(target, Symbol.iterator);
54
- if (oldSize !== newSize) {
55
- trigger(target, 'size');
56
- }
57
- }
58
- else {
59
- trigger(target, String(key));
60
- }
61
- return receiver;
62
- };
63
- return methodCache[prop];
64
- }
65
- if (prop === 'delete') {
66
- methodCache[prop] = function (key) {
67
- const existed = target.has(key);
68
- if (!existed)
69
- return false;
70
- const oldValue = target.get(key);
71
- const oldSize = target.size;
72
- const result = target.delete(key);
73
- const newSize = target.size;
74
- if (result) { // only emit and trigger if delete was successful
75
- const pathKey = path.join('.');
76
- let cachedPath = getPathConcat(pathKey);
77
- if (cachedPath === undefined) {
78
- cachedPath = path;
79
- setPathConcat(pathKey, cachedPath);
80
- }
81
- const event = {
82
- action: 'map-delete',
83
- path: cachedPath,
84
- key,
85
- oldValue
86
- };
87
- emit(event);
88
- trigger(target, Symbol.iterator);
89
- if (oldSize !== newSize) {
90
- trigger(target, 'size');
91
- }
92
- trigger(target, String(key));
93
- }
94
- return result;
95
- };
96
- return methodCache[prop];
97
- }
98
- if (prop === 'clear') {
99
- methodCache[prop] = function () {
100
- const oldSize = target.size;
101
- if (oldSize === 0)
102
- return;
103
- target.clear();
104
- const newSize = target.size;
105
- const event = {
106
- action: 'map-clear',
107
- path: path,
108
- key: null
109
- };
110
- emit(event);
111
- trigger(target, Symbol.iterator);
112
- if (oldSize !== newSize) {
113
- trigger(target, 'size');
114
- }
115
- };
116
- return methodCache[prop];
117
- }
118
- if (prop === 'get') {
119
- // return a function that tracks the specific key only when called
120
- methodCache[prop] = function (key) {
121
- track(target, String(key));
122
- const value = target.get(key);
123
- if (!value || typeof value !== 'object')
124
- return value;
125
- const cachedValueProxy = wrapperCache.get(value);
126
- if (cachedValueProxy)
127
- return cachedValueProxy;
128
- const keyString = String(key);
129
- const pathKey = path.length > 0 ? `${path.join('.')}.${keyString}` : keyString;
130
- let newPath = getPathConcat(pathKey);
131
- if (newPath === undefined) {
132
- newPath = path.concat(keyString);
133
- setPathConcat(pathKey, newPath);
134
- }
135
- // recursively wrap nested structures
136
- if (value instanceof Map)
137
- return wrapMap(value, emit, newPath);
138
- if (value instanceof Set)
139
- return wrapSet(value, emit, newPath);
140
- if (Array.isArray(value))
141
- return wrapArray(value, emit, newPath);
142
- if (value instanceof Date)
143
- return new Date(value.getTime()); // dates are not proxied, return a copy
144
- return reactive(value, emit, newPath);
145
- };
146
- return methodCache[prop];
147
- }
148
- if (prop === 'has') {
149
- track(target, Symbol.iterator);
150
- methodCache[prop] = function (key) {
151
- // track the specific key only when 'has' is called
152
- track(target, String(key));
153
- return target.has(key);
154
- }.bind(target); // bind is still okay here, doesn't interfere with caching
155
- return methodCache[prop];
156
- }
157
- // handle iteration methods
158
- if (prop === Symbol.iterator || prop === 'entries' || prop === 'values' || prop === 'keys' || prop === 'forEach') {
159
- track(target, Symbol.iterator);
160
- const originalMethod = Reflect.get(target, prop, receiver);
161
- // return custom iterators/foreach that wrap values during iteration
162
- if (prop === 'forEach') {
163
- methodCache[prop] = (callbackfn, thisArg) => {
164
- // use the proxied .entries() to ensure values passed to callback are wrapped and tracked
165
- const entriesIterator = proxy.entries();
166
- for (const [key, value] of entriesIterator) {
167
- callbackfn.call(thisArg, value, key, proxy);
168
- }
169
- };
170
- return methodCache[prop];
171
- }
172
- // handle symbol.iterator, entries, values, keys by creating generator functions
173
- methodCache[prop] = function* (...args) {
174
- const iterator = originalMethod.apply(target, args);
175
- for (const entry of iterator) {
176
- let keyToWrap = entry;
177
- let valueToWrap = entry;
178
- let isEntry = false;
179
- if (prop === 'entries' || prop === Symbol.iterator) {
180
- keyToWrap = entry[0];
181
- valueToWrap = entry[1];
182
- isEntry = true;
183
- }
184
- // wrap key if it's an object
185
- // note: reactivity on map keys can be complex/unexpected
186
- let wrappedKey = keyToWrap;
187
- if (isEntry && keyToWrap && typeof keyToWrap === 'object') {
188
- const pathKey = path.length > 0 ? `${path.join('.')}.${String(keyToWrap)}` : String(keyToWrap);
189
- let keyPath = getPathConcat(pathKey);
190
- if (keyPath === undefined) {
191
- keyPath = path.concat(String(keyToWrap));
192
- setPathConcat(pathKey, keyPath);
193
- }
194
- // todo: decide if map keys should be deeply reactive
195
- wrappedKey = reactive(keyToWrap, emit, keyPath);
196
- }
197
- // wrap value if it's an object
198
- let wrappedValue = valueToWrap;
199
- if (valueToWrap && typeof valueToWrap === 'object') {
200
- const cachedValueProxy = wrapperCache.get(valueToWrap);
201
- if (cachedValueProxy) {
202
- wrappedValue = cachedValueProxy;
203
- }
204
- else {
205
- const keyString = String(keyToWrap); // use original key for path
206
- const pathKey = path.length > 0 ? `${path.join('.')}.${keyString}` : keyString;
207
- let newPath = getPathConcat(pathKey);
208
- if (newPath === undefined) {
209
- newPath = path.concat(keyString);
210
- setPathConcat(pathKey, newPath);
211
- }
212
- if (valueToWrap instanceof Map)
213
- wrappedValue = wrapMap(valueToWrap, emit, newPath);
214
- else if (valueToWrap instanceof Set)
215
- wrappedValue = wrapSet(valueToWrap, emit, newPath);
216
- else if (Array.isArray(valueToWrap))
217
- wrappedValue = wrapArray(valueToWrap, emit, newPath);
218
- else if (valueToWrap instanceof Date)
219
- wrappedValue = new Date(valueToWrap.getTime());
220
- else
221
- wrappedValue = reactive(valueToWrap, emit, newPath);
222
- }
223
- }
224
- if (prop === 'entries' || prop === Symbol.iterator) {
225
- yield [wrappedKey, wrappedValue];
226
- }
227
- else if (prop === 'values') {
228
- yield wrappedValue;
229
- }
230
- else { // keys
231
- yield wrappedKey;
232
- }
233
- }
234
- };
235
- return methodCache[prop];
236
- }
237
- if (prop === 'size') {
238
- track(target, 'size');
239
- return target.size;
240
- }
241
- const value = Reflect.get(target, prop, receiver);
242
- // Bind plain functions accessed directly (e.g., toString)
243
- if (typeof value === 'function') {
244
- return value.bind(target);
245
- }
246
- return value;
247
- }
248
- });
249
- // cache the newly created proxy before returning
250
- wrapperCache.set(map, proxy);
251
- return proxy;
252
- }
package/dist/wrap-set.js DELETED
@@ -1,182 +0,0 @@
1
- import { getPathConcat, setPathConcat, wrapperCache } from './utils';
2
- import { reactive } from './reactive';
3
- import { wrapArray } from './wrap-array';
4
- import { wrapMap } from './wrap-map';
5
- import { track, trigger } from './watch-effect';
6
- export function wrapSet(set, emit, path) {
7
- // reuse existing proxy if available for performance
8
- const cachedProxy = wrapperCache.get(set);
9
- if (cachedProxy)
10
- return cachedProxy;
11
- // cache for wrapped methods to avoid re-creating them on each call
12
- const methodCache = {};
13
- const proxy = new Proxy(set, {
14
- get(target, prop, receiver) {
15
- track(target, prop);
16
- if (methodCache[prop]) {
17
- return methodCache[prop];
18
- }
19
- if (prop === 'add') {
20
- methodCache[prop] = function (value) {
21
- const existed = target.has(value);
22
- const oldSize = target.size;
23
- // only add and trigger if the value doesn't already exist
24
- if (!existed) {
25
- target.add(value);
26
- const newSize = target.size;
27
- const event = {
28
- action: 'set-add',
29
- path: path,
30
- value: value
31
- };
32
- emit(event);
33
- trigger(target, Symbol.iterator);
34
- if (oldSize !== newSize) {
35
- trigger(target, 'size');
36
- }
37
- }
38
- return receiver; // return the proxy itself for chaining
39
- };
40
- return methodCache[prop];
41
- }
42
- if (prop === 'delete') {
43
- methodCache[prop] = function (value) {
44
- const existed = target.has(value);
45
- const oldSize = target.size;
46
- if (existed) {
47
- const oldValue = value;
48
- const result = target.delete(value);
49
- const newSize = target.size;
50
- if (result) { // only emit and trigger if delete was successful
51
- const event = {
52
- action: 'set-delete',
53
- path: path,
54
- value: value,
55
- oldValue: oldValue
56
- };
57
- emit(event);
58
- trigger(target, Symbol.iterator);
59
- if (oldSize !== newSize) {
60
- trigger(target, 'size');
61
- }
62
- }
63
- return result;
64
- }
65
- return false;
66
- };
67
- return methodCache[prop];
68
- }
69
- if (prop === 'clear') {
70
- methodCache[prop] = function () {
71
- const oldSize = target.size;
72
- if (oldSize === 0)
73
- return;
74
- target.clear();
75
- const newSize = target.size;
76
- const event = {
77
- action: 'set-clear',
78
- path: path,
79
- value: null
80
- };
81
- emit(event);
82
- trigger(target, Symbol.iterator);
83
- if (oldSize !== newSize) {
84
- trigger(target, 'size');
85
- }
86
- };
87
- return methodCache[prop];
88
- }
89
- if (prop === 'has') {
90
- track(target, Symbol.iterator);
91
- methodCache[prop] = function (value) {
92
- // track specific primitive value when 'has' is called
93
- // tracking object values for existence is complex and less common, handled by iteration track
94
- if (typeof value === 'string' || typeof value === 'number' || typeof value === 'symbol') {
95
- track(target, String(value));
96
- }
97
- return target.has(value);
98
- }.bind(target); // bind is still okay here, doesn't interfere with caching
99
- return methodCache[prop];
100
- }
101
- // handle iteration methods
102
- if (prop === 'values' || prop === Symbol.iterator || prop === 'entries' || prop === 'keys' || prop === 'forEach') {
103
- track(target, Symbol.iterator);
104
- const originalMethod = Reflect.get(target, prop, receiver);
105
- // return custom iterators/foreach that wrap values during iteration
106
- if (prop === 'forEach') {
107
- methodCache[prop] = (callbackfn, thisArg) => {
108
- // use the proxied values() to ensure values passed to callback are wrapped and tracked
109
- const valuesIterator = proxy.values();
110
- for (const value of valuesIterator) {
111
- callbackfn.call(thisArg, value, value, proxy);
112
- }
113
- };
114
- return methodCache[prop];
115
- }
116
- // handle symbol.iterator, values, keys, entries by creating generator functions
117
- methodCache[prop] = function* (...args) {
118
- let index = 0; // use index for path generation if value is not primitive
119
- const iterator = originalMethod.apply(target, args);
120
- for (const entry of iterator) {
121
- let valueToWrap = entry;
122
- let mapKey = undefined; // key for entries() which yields [value, value]
123
- if (prop === 'entries') {
124
- mapKey = entry[0]; // for Set.entries(), key and value are the same
125
- valueToWrap = entry[1];
126
- }
127
- track(target, String(index));
128
- let wrappedValue = valueToWrap;
129
- if (valueToWrap && typeof valueToWrap === 'object') {
130
- const cachedValueProxy = wrapperCache.get(valueToWrap);
131
- if (cachedValueProxy) {
132
- wrappedValue = cachedValueProxy;
133
- }
134
- else {
135
- // calculate path using index as key, as set values don't have inherent keys
136
- const keyForPath = String(index);
137
- const pathKey = path.length > 0 ? `${path.join('.')}.${keyForPath}` : keyForPath;
138
- let newPath = getPathConcat(pathKey);
139
- if (newPath === undefined) {
140
- newPath = path.concat(keyForPath);
141
- setPathConcat(pathKey, newPath);
142
- }
143
- // recursively wrap nested structures
144
- if (valueToWrap instanceof Map)
145
- wrappedValue = wrapMap(valueToWrap, emit, newPath);
146
- else if (valueToWrap instanceof Set)
147
- wrappedValue = wrapSet(valueToWrap, emit, newPath);
148
- else if (Array.isArray(valueToWrap))
149
- wrappedValue = wrapArray(valueToWrap, emit, newPath);
150
- else if (valueToWrap instanceof Date)
151
- wrappedValue = new Date(valueToWrap.getTime()); // dates are not proxied, return copy
152
- else
153
- wrappedValue = reactive(valueToWrap, emit, newPath);
154
- }
155
- }
156
- if (prop === 'entries') {
157
- yield [wrappedValue, wrappedValue]; // set entries yield [value, value]
158
- }
159
- else {
160
- yield wrappedValue;
161
- }
162
- index++;
163
- }
164
- };
165
- return methodCache[prop];
166
- }
167
- if (prop === 'size') {
168
- track(target, 'size');
169
- return target.size;
170
- }
171
- const value = Reflect.get(target, prop, receiver);
172
- // Bind plain functions accessed directly (e.g., toString)
173
- if (typeof value === 'function') {
174
- return value.bind(target);
175
- }
176
- return value;
177
- }
178
- });
179
- // cache the newly created proxy before returning
180
- wrapperCache.set(set, proxy);
181
- return proxy;
182
- }
@@ -1,2 +0,0 @@
1
- import { EmitFunction, Path } from './types';
2
- export declare function wrapArray<T extends any[]>(arr: T, emit: EmitFunction, path: Path): T;