proxy-facades 1.0.0

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.
Files changed (136) hide show
  1. package/.idea/.name +1 -0
  2. package/.idea/modules.xml +8 -0
  3. package/.idea/proxy-facades.iml +8 -0
  4. package/.idea/vcs.xml +6 -0
  5. package/DEVELOPMENT.md +15 -0
  6. package/LICENSE +21 -0
  7. package/RecordedReadOnProxiedObjectExt.d.ts +22 -0
  8. package/RecordedReadOnProxiedObjectExt.d.ts.map +1 -0
  9. package/RecordedReadOnProxiedObjectExt.js +41 -0
  10. package/RecordedReadOnProxiedObjectExt.js.map +1 -0
  11. package/RecordedReadOnProxiedObjectExt.ts +41 -0
  12. package/Util.d.ts +85 -0
  13. package/Util.d.ts.map +1 -0
  14. package/Util.js +239 -0
  15. package/Util.js.map +1 -0
  16. package/Util.ts +254 -0
  17. package/class-trackers/Array.d.ts +93 -0
  18. package/class-trackers/Array.d.ts.map +1 -0
  19. package/class-trackers/Array.js +193 -0
  20. package/class-trackers/Array.js.map +1 -0
  21. package/class-trackers/Array.ts +245 -0
  22. package/class-trackers/Iterator.d.ts +38 -0
  23. package/class-trackers/Iterator.d.ts.map +1 -0
  24. package/class-trackers/Iterator.js +69 -0
  25. package/class-trackers/Iterator.js.map +1 -0
  26. package/class-trackers/Iterator.ts +73 -0
  27. package/class-trackers/Map.d.ts +128 -0
  28. package/class-trackers/Map.d.ts.map +1 -0
  29. package/class-trackers/Map.js +310 -0
  30. package/class-trackers/Map.js.map +1 -0
  31. package/class-trackers/Map.ts +403 -0
  32. package/class-trackers/Set.d.ts +85 -0
  33. package/class-trackers/Set.d.ts.map +1 -0
  34. package/class-trackers/Set.js +197 -0
  35. package/class-trackers/Set.js.map +1 -0
  36. package/class-trackers/Set.ts +245 -0
  37. package/class-trackers/index.d.ts +7 -0
  38. package/class-trackers/index.d.ts.map +1 -0
  39. package/class-trackers/index.js +36 -0
  40. package/class-trackers/index.js.map +1 -0
  41. package/class-trackers/index.ts +38 -0
  42. package/class-trackers/readme.md +2 -0
  43. package/common.d.ts +235 -0
  44. package/common.d.ts.map +1 -0
  45. package/common.js +378 -0
  46. package/common.js.map +1 -0
  47. package/common.ts +501 -0
  48. package/dev_generateEsRuntimeBehaviourCheckerCode.d.ts +10 -0
  49. package/dev_generateEsRuntimeBehaviourCheckerCode.d.ts.map +1 -0
  50. package/dev_generateEsRuntimeBehaviourCheckerCode.js +76 -0
  51. package/dev_generateEsRuntimeBehaviourCheckerCode.js.map +1 -0
  52. package/dev_generateEsRuntimeBehaviourCheckerCode.ts +85 -0
  53. package/dist/mjs/RecordedReadOnProxiedObjectExt.d.ts +22 -0
  54. package/dist/mjs/RecordedReadOnProxiedObjectExt.d.ts.map +1 -0
  55. package/dist/mjs/RecordedReadOnProxiedObjectExt.js +37 -0
  56. package/dist/mjs/RecordedReadOnProxiedObjectExt.js.map +1 -0
  57. package/dist/mjs/Util.d.ts +85 -0
  58. package/dist/mjs/Util.d.ts.map +1 -0
  59. package/dist/mjs/Util.js +223 -0
  60. package/dist/mjs/Util.js.map +1 -0
  61. package/dist/mjs/class-trackers/Array.d.ts +93 -0
  62. package/dist/mjs/class-trackers/Array.d.ts.map +1 -0
  63. package/dist/mjs/class-trackers/Array.js +186 -0
  64. package/dist/mjs/class-trackers/Array.js.map +1 -0
  65. package/dist/mjs/class-trackers/Iterator.d.ts +38 -0
  66. package/dist/mjs/class-trackers/Iterator.d.ts.map +1 -0
  67. package/dist/mjs/class-trackers/Iterator.js +65 -0
  68. package/dist/mjs/class-trackers/Iterator.js.map +1 -0
  69. package/dist/mjs/class-trackers/Map.d.ts +128 -0
  70. package/dist/mjs/class-trackers/Map.d.ts.map +1 -0
  71. package/dist/mjs/class-trackers/Map.js +299 -0
  72. package/dist/mjs/class-trackers/Map.js.map +1 -0
  73. package/dist/mjs/class-trackers/Set.d.ts +85 -0
  74. package/dist/mjs/class-trackers/Set.d.ts.map +1 -0
  75. package/dist/mjs/class-trackers/Set.js +189 -0
  76. package/dist/mjs/class-trackers/Set.js.map +1 -0
  77. package/dist/mjs/class-trackers/index.d.ts +7 -0
  78. package/dist/mjs/class-trackers/index.d.ts.map +1 -0
  79. package/dist/mjs/class-trackers/index.js +32 -0
  80. package/dist/mjs/class-trackers/index.js.map +1 -0
  81. package/dist/mjs/common.d.ts +235 -0
  82. package/dist/mjs/common.d.ts.map +1 -0
  83. package/dist/mjs/common.js +361 -0
  84. package/dist/mjs/common.js.map +1 -0
  85. package/dist/mjs/dev_generateEsRuntimeBehaviourCheckerCode.d.ts +10 -0
  86. package/dist/mjs/dev_generateEsRuntimeBehaviourCheckerCode.d.ts.map +1 -0
  87. package/dist/mjs/dev_generateEsRuntimeBehaviourCheckerCode.js +76 -0
  88. package/dist/mjs/dev_generateEsRuntimeBehaviourCheckerCode.js.map +1 -0
  89. package/dist/mjs/index.d.ts +8 -0
  90. package/dist/mjs/index.d.ts.map +1 -0
  91. package/dist/mjs/index.js +8 -0
  92. package/dist/mjs/index.js.map +1 -0
  93. package/dist/mjs/objectChangeTracking.d.ts +43 -0
  94. package/dist/mjs/objectChangeTracking.d.ts.map +1 -0
  95. package/dist/mjs/objectChangeTracking.js +209 -0
  96. package/dist/mjs/objectChangeTracking.js.map +1 -0
  97. package/dist/mjs/origChangeTracking.d.ts +14 -0
  98. package/dist/mjs/origChangeTracking.d.ts.map +1 -0
  99. package/dist/mjs/origChangeTracking.js +58 -0
  100. package/dist/mjs/origChangeTracking.js.map +1 -0
  101. package/dist/mjs/proxyFacade.d.ts +45 -0
  102. package/dist/mjs/proxyFacade.d.ts.map +1 -0
  103. package/dist/mjs/proxyFacade.js +179 -0
  104. package/dist/mjs/proxyFacade.js.map +1 -0
  105. package/dist/mjs/watchedProxyFacade.d.ts +84 -0
  106. package/dist/mjs/watchedProxyFacade.d.ts.map +1 -0
  107. package/dist/mjs/watchedProxyFacade.js +300 -0
  108. package/dist/mjs/watchedProxyFacade.js.map +1 -0
  109. package/index.d.ts +8 -0
  110. package/index.d.ts.map +1 -0
  111. package/index.js +36 -0
  112. package/index.js.map +1 -0
  113. package/index.ts +7 -0
  114. package/index_esm.mjs +44 -0
  115. package/objectChangeTracking.d.ts +43 -0
  116. package/objectChangeTracking.d.ts.map +1 -0
  117. package/objectChangeTracking.js +214 -0
  118. package/objectChangeTracking.js.map +1 -0
  119. package/objectChangeTracking.ts +251 -0
  120. package/origChangeTracking.d.ts +14 -0
  121. package/origChangeTracking.d.ts.map +1 -0
  122. package/origChangeTracking.js +63 -0
  123. package/origChangeTracking.js.map +1 -0
  124. package/origChangeTracking.ts +72 -0
  125. package/package.json +52 -0
  126. package/proxyFacade.d.ts +45 -0
  127. package/proxyFacade.d.ts.map +1 -0
  128. package/proxyFacade.js +187 -0
  129. package/proxyFacade.js.map +1 -0
  130. package/proxyFacade.ts +222 -0
  131. package/readme.md +111 -0
  132. package/watchedProxyFacade.d.ts +84 -0
  133. package/watchedProxyFacade.d.ts.map +1 -0
  134. package/watchedProxyFacade.js +312 -0
  135. package/watchedProxyFacade.js.map +1 -0
  136. package/watchedProxyFacade.ts +369 -0
@@ -0,0 +1,369 @@
1
+ import {FacadeProxyHandler, ProxyFacade} from "./proxyFacade";
2
+ import {throwError} from "./Util";
3
+ import {
4
+ AfterChangeOwnKeysListener,
5
+ AfterReadListener,
6
+ ChangeListener,
7
+ checkEsRuntimeBehaviour, EventHook,
8
+ getPropertyDescriptor,
9
+ IWatchedProxyHandler_common, objectMembershipInGraphs,
10
+ ObjKey,
11
+ RecordedRead,
12
+ RecordedReadOnProxiedObject,
13
+ runChangeOperation, UnspecificObjectChange,
14
+ } from "./common";
15
+ import {getChangeHooksForObject, changeHooksForObject} from "./objectChangeTracking";
16
+ import _ from "underscore"
17
+ import {getTrackingConfigFor} from "./class-trackers/index";
18
+ import {RecordedReadOnProxiedObjectExt} from "./RecordedReadOnProxiedObjectExt";
19
+
20
+
21
+ /**
22
+ * Access a single value (=variable or return value from a function)
23
+ * This read is can only be constructed manually (not through a WatchedProxyFacade / WatchedProxyHandler
24
+ */
25
+ export class RecordedValueRead extends RecordedRead{
26
+ value: unknown;
27
+
28
+ constructor(value: unknown) {
29
+ super();
30
+ this.value = value;
31
+ }
32
+
33
+ get isChanged(): boolean {
34
+ throw new Error("Cannot check if simple value (not on object) has changed.");
35
+ }
36
+
37
+ onAfterChange(listener: () => void, trackOriginal = false) {
38
+ throw new Error("Cannot listen for changes on simple value (not on object)");
39
+ }
40
+
41
+ offAfterChange(listener: () => void) {
42
+ }
43
+
44
+ equals(other: RecordedRead) {
45
+ if(! (other instanceof RecordedValueRead)) {
46
+ return false;
47
+ }
48
+
49
+ return this.value === other.value;
50
+ }
51
+ }
52
+
53
+ export class RecordedPropertyRead extends RecordedReadOnProxiedObjectExt {
54
+ key!: ObjKey;
55
+ value!: unknown;
56
+
57
+
58
+ constructor(key: ObjKey, value: unknown) {
59
+ super();
60
+ this.key = key;
61
+ this.value = value;
62
+ }
63
+
64
+ get isChanged() {
65
+ //@ts-ignore
66
+ return this.obj[this.key] !== this.value;
67
+ }
68
+
69
+ getAffectingChangeHooks(target: this["obj"]) {
70
+ const result = [
71
+ getChangeHooksForObject(target).changeSpecificProperty.get(this.key)
72
+ ]
73
+ if(Array.isArray(this.obj)) {
74
+ result.push(getChangeHooksForObject(target).unspecificChange);
75
+ }
76
+ return result;
77
+ }
78
+
79
+ equals(other: RecordedRead) {
80
+ if(! (other instanceof RecordedPropertyRead)) {
81
+ return false;
82
+ }
83
+
84
+ return this.proxyHandler === other.proxyHandler && this.obj === other.obj && this.key === other.key && this.value === other.value;
85
+ }
86
+ }
87
+
88
+ export class RecordedOwnKeysRead extends RecordedReadOnProxiedObjectExt{
89
+ value!: ArrayLike<string | symbol>;
90
+
91
+ constructor(value: RecordedOwnKeysRead["value"]) {
92
+ super();
93
+ this.value = value;
94
+ }
95
+
96
+ get isChanged() {
97
+ return !_.isEqual(Reflect.ownKeys(this.obj), this.value);
98
+ }
99
+
100
+ getAffectingChangeHooks(target: this["obj"]) {
101
+ const result = [
102
+ getChangeHooksForObject(target).changeOwnKeys
103
+ ]
104
+ if(Array.isArray(this.obj)) {
105
+ result.push(getChangeHooksForObject(target).unspecificChange);
106
+ }
107
+ return result;
108
+ }
109
+
110
+ equals(other: RecordedRead) {
111
+ if(! (other instanceof RecordedOwnKeysRead)) {
112
+ return false;
113
+ }
114
+
115
+ return this.proxyHandler === other.proxyHandler && this.obj === other.obj && _.isEqual(this.value, other.value);
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Fired when a method was called that is not implemented in the supervisor. May be from a future js version
121
+ */
122
+ export class RecordedUnspecificRead extends RecordedReadOnProxiedObjectExt{
123
+ get isChanged() {
124
+ return true;
125
+ }
126
+
127
+ getAffectingChangeHooks(target: this["obj"]) {
128
+ return [
129
+ getChangeHooksForObject(target).anyChange
130
+ ]
131
+ }
132
+
133
+ equals(other: RecordedRead) {
134
+ return false;
135
+ }
136
+ }
137
+
138
+
139
+ /**
140
+ * Use cases:
141
+ * - record read + watch recorded for modifications. For re-render trigger
142
+ * - record read and make several snapshots (when load is called) and compare exactly those reads
143
+ */
144
+ export class WatchedProxyFacade extends ProxyFacade<WatchedProxyHandler> {
145
+ // ** Configuration**
146
+
147
+ trackReadsOnPrototype = false;
148
+
149
+ // *** State: ****
150
+
151
+ /**
152
+ * Called after a read has been made to any object inside this facade
153
+ * @protected
154
+ */
155
+ _afterReadListeners = new Set<AfterReadListener>()
156
+
157
+
158
+ onAfterRead(listener: AfterReadListener) {
159
+ this._afterReadListeners.add(listener);
160
+ }
161
+
162
+ offAfterRead(listener: AfterReadListener) {
163
+ this._afterReadListeners.delete(listener);
164
+ }
165
+
166
+ constructor() {
167
+ super();
168
+ checkEsRuntimeBehaviour();
169
+ }
170
+
171
+ /**
172
+ * Watches for writes on a specified property
173
+ * @param obj
174
+ * @param key Not restricted here (for the tests), but it must not be number !
175
+ * @param listener
176
+ */
177
+ onAfterWriteOnProperty<O extends object, K extends keyof O>(obj: O, key: K, listener: ChangeListener) {
178
+ getChangeHooksForObject(obj).changeSpecificProperty.get(key as ObjKey).afterListeners.add(listener);
179
+ }
180
+
181
+ /**
182
+ * Watches for writes on a specified property
183
+ * @param obj
184
+ * @param key Not restricted here (for the tests), but it must not be number !
185
+ * @param listener
186
+ */
187
+ offAfterWriteOnProperty<O extends object, K extends keyof O>(obj: O, key: K, listener: ChangeListener) {
188
+ changeHooksForObject.get(obj)?.changeSpecificProperty.get(key as ObjKey).afterListeners.delete(listener);
189
+ }
190
+
191
+ protected crateHandler(target: object, facade: any): WatchedProxyHandler {
192
+ return new WatchedProxyHandler(target, facade);
193
+ }
194
+ }
195
+
196
+ export class WatchedProxyHandler extends FacadeProxyHandler<WatchedProxyFacade> implements IWatchedProxyHandler_common{
197
+
198
+ constructor(target: object, facade: WatchedProxyFacade) {
199
+ super(target, facade);
200
+ }
201
+
202
+ get trackingConfig() {
203
+ return getTrackingConfigFor(this.target); // TODO: cache (performance)
204
+ }
205
+
206
+ fireAfterRead(read: RecordedReadOnProxiedObject) {
207
+ read.proxyHandler = this;
208
+ read.obj = this.target;
209
+
210
+ this.facade._afterReadListeners.forEach(l => l(read)); // Inform listeners
211
+ }
212
+
213
+ getFacade() {
214
+ return this.facade;
215
+ }
216
+
217
+ get (fake_target:object, key:string | symbol, receiver:any) {
218
+ const target = this.target;
219
+ const proxy = this.proxy;
220
+ const thisHandler = this;
221
+ const receiverMustBeNonProxied = this.trackingConfig?.receiverMustBeNonProxied === true;
222
+
223
+ if(key === "_watchedProxyHandler") { // TODO: use symbol for that (performance)
224
+ return this;
225
+ }
226
+ if(key === "_target") { // TODO: use symbol for that (performance)
227
+ return this.target;
228
+ }
229
+
230
+ // Check for and use supervisor class:
231
+ if(this.trackingConfig !== undefined) {
232
+ for(const TrackerClass of this.trackingConfig.getTrackerClasses()) {
233
+ let propOnSupervisor = Object.getOwnPropertyDescriptor(TrackerClass.prototype, key);
234
+ if(propOnSupervisor !== undefined) { // Supervisor class is responsible for the property (or method) ?
235
+ //@ts-ignore
236
+ if(propOnSupervisor.get) { // Prop is a getter?
237
+ return this.facade.getProxyFor(propOnSupervisor.get.apply(this.proxy));
238
+ }
239
+ if(propOnSupervisor.set) { // Prop is a setter ?
240
+ throw new Error("setters not yet implemented")
241
+ }
242
+ else {
243
+ typeof propOnSupervisor.value === "function" || throwError(`Accessing supervisor's plain property: ${String(key)}`); // validity check
244
+ const supervisorMethod = propOnSupervisor.value;
245
+ return supervisorMethod;
246
+ }
247
+ }
248
+ }
249
+ // When arriving here, the field is not **directly** in one of the tracker classes
250
+ //@ts-ignore
251
+ origMethod = this.target[key];
252
+ if(this.trackingConfig.knownHighLevelMethods.has(key)) {
253
+ return trapHighLevelReaderWriterMethod
254
+ }
255
+
256
+ if(typeof origMethod === "function" && !(key as any in Object.prototype)) { // Read+write method that was not handled directly by supervisor class?
257
+ if(this.trackingConfig.readOnlyMethods.has(key)) {
258
+ return trapForGenericReaderMethod
259
+ }
260
+ else {
261
+ return trapForGenericReaderWriterMethod // Assume the worst, that it is a writer method
262
+ }
263
+ }
264
+ }
265
+
266
+ return super.get(fake_target, key, receiver);
267
+
268
+
269
+ var origMethod: ((this:unknown, ...args:unknown[]) => unknown) | undefined = undefined;
270
+ /**
271
+ * Fires a RecordedUnspecificRead
272
+ */
273
+ function trapForGenericReaderMethod(this:object, ...args: unknown[]) {
274
+ const callResult = origMethod!.apply(receiverMustBeNonProxied?target:this, args); // call original method:
275
+ if(thisHandler.trackingConfig?.trackTreads !== false) { // not explicitly disabled ?
276
+ thisHandler.fireAfterRead(new RecordedUnspecificRead());
277
+ }
278
+ return thisHandler.trackingConfig?.proxyUnhandledMethodResults?thisHandler.facade.getProxyFor(callResult):callResult;
279
+ }
280
+ /**
281
+ * Fires a RecordedUnspecificRead and calls the unspecificChange listeners
282
+ * @param args
283
+ */
284
+ function trapForGenericReaderWriterMethod(this:object, ...args: unknown[]) {
285
+ return runChangeOperation(proxy, new UnspecificObjectChange(proxy),[getChangeHooksForObject(proxy).unspecificChange],() => {
286
+ const callResult = origMethod!.apply(receiverMustBeNonProxied?target:this, args); // call original method:
287
+ if(thisHandler.trackingConfig?.trackTreads !== false) { // not explicitly disabled ?
288
+ thisHandler.fireAfterRead(new RecordedUnspecificRead());
289
+ }
290
+ return thisHandler.trackingConfig?.proxyUnhandledMethodResults?thisHandler.facade.getProxyFor(callResult):callResult;
291
+ });
292
+ }
293
+ /**
294
+ * Wraps it in runAndCallListenersOnce_after
295
+ * @param args
296
+ */
297
+ function trapHighLevelReaderWriterMethod(this:object, ...args: unknown[]) {
298
+ return runChangeOperation(proxy, undefined,[],() => {
299
+ return origMethod!.apply(this, args); // call original method
300
+ });
301
+ }
302
+ }
303
+
304
+ rawRead(key: ObjKey) {
305
+ const result = super.rawRead(key);
306
+ if(!this.facade.trackReadsOnPrototype) {
307
+ if(Object.getOwnPropertyDescriptor(this.target, key) === undefined && getPropertyDescriptor(this.target,key ) !== undefined) { // Property is on prototype only ?
308
+ return result;
309
+ }
310
+ }
311
+ if(this.trackingConfig?.trackTreads !== false) { // not explicitly disabled ?
312
+ this.fireAfterRead(new RecordedPropertyRead(key, result)); // Inform listeners
313
+ }
314
+ return result;
315
+ }
316
+
317
+ protected rawChange(key: string | symbol, newUnproxiedValue: any) {
318
+ const changeHooksForThisObject = getChangeHooksForObject(this.proxy);
319
+ const hooksToServe: EventHook[] = [];
320
+
321
+ if (this.isForArray()) {
322
+ hooksToServe.push(changeHooksForThisObject.unspecificChange);
323
+ }
324
+
325
+ hooksToServe.push(changeHooksForThisObject.changeSpecificProperty.get(key));
326
+ hooksToServe.push(changeHooksForThisObject.changeAnyProperty);
327
+
328
+ const isNewProperty = getPropertyDescriptor(this.target, key) === undefined;
329
+ if (isNewProperty) {
330
+ hooksToServe.push(changeHooksForThisObject.changeOwnKeys);
331
+ }
332
+
333
+ runChangeOperation(this.proxy, new UnspecificObjectChange(this.proxy),hooksToServe,() => {
334
+ super.rawChange(key, newUnproxiedValue);
335
+ });
336
+ }
337
+
338
+ deleteProperty(target: object, key: string | symbol): boolean {
339
+ const doesExists = Object.getOwnPropertyDescriptor(this.target, key) !== undefined;
340
+ if (!doesExists) {
341
+ return true;
342
+ }
343
+
344
+ return runChangeOperation(this.proxy, new UnspecificObjectChange(this.proxy),[getChangeHooksForObject(this.proxy).changeOwnKeys],() => {
345
+ this.set(target, key, undefined, this.proxy); // Set to undefined first, so property change listeners will get informed
346
+ return super.deleteProperty(target, key);
347
+ });
348
+ }
349
+
350
+ ownKeys(target: object): ArrayLike<string | symbol> {
351
+ const result = Reflect.ownKeys(this.target);
352
+ if(this.trackingConfig?.trackTreads !== false) { // not explicitly disabled ?
353
+ this.fireAfterRead(new RecordedOwnKeysRead(result))
354
+ }
355
+ return result;
356
+ }
357
+
358
+ isForArray() {
359
+ return Array.isArray(this.target)
360
+ }
361
+
362
+ isForSet() {
363
+ return this.target instanceof Set;
364
+ }
365
+
366
+ isForMap() {
367
+ return this.target instanceof Map;
368
+ }
369
+ }