informa 4.0.0 → 5.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.
- package/README.md +27 -40
- package/dist/EnumerableWeakSet.d.ts +1 -0
- package/dist/EnumerableWeakSet.d.ts.map +1 -1
- package/dist/EnumerableWeakSet.js +8 -5
- package/dist/EnumerableWeakSet.js.map +1 -1
- package/dist/high.d.ts +2 -2
- package/dist/high.d.ts.map +1 -1
- package/dist/high.js +1 -1
- package/dist/high.js.map +1 -1
- package/dist/low.d.ts +0 -15
- package/dist/low.d.ts.map +1 -1
- package/dist/low.js +25 -48
- package/dist/low.js.map +1 -1
- package/dist/quirks/basestatified.d.ts +3 -23
- package/dist/quirks/basestatified.d.ts.map +1 -1
- package/dist/quirks/basestatified.js +107 -229
- package/dist/quirks/basestatified.js.map +1 -1
- package/dist/test.js +13 -7
- package/dist/test.js.map +1 -1
- package/package.json +2 -1
- package/src/EnumerableWeakSet.ts +8 -5
- package/src/high.ts +2 -2
- package/src/low.ts +32 -58
- package/src/quirks/basestatified.ts +130 -259
- package/src/test.ts +21 -12
- package/src/quirks/basestatified.test.ts +0 -375
package/src/low.ts
CHANGED
|
@@ -5,6 +5,7 @@ import { isListInGrid } from "./utils.js";
|
|
|
5
5
|
import { EnumerableWeakMap } from "./EnumerableWeakMap.js";
|
|
6
6
|
import { StatifiedMap } from "./quirks/map.js";
|
|
7
7
|
import { StatifiedArray } from "./quirks/array.js";
|
|
8
|
+
import { clearPendingAssemblies } from "./quirks/basestatified.js";
|
|
8
9
|
|
|
9
10
|
type ProxyEventEmitterEvents = {
|
|
10
11
|
"set": [any],
|
|
@@ -157,30 +158,15 @@ export type Statified<T extends StatifiableProp> = T extends object
|
|
|
157
158
|
: Statify<{ [K in keyof T]: Statified<T[K]> }>
|
|
158
159
|
: T;
|
|
159
160
|
|
|
160
|
-
/**
|
|
161
|
-
* Emit the appropriate structural events when a field transitions from `old` to
|
|
162
|
-
* `next`. Called after unhook/hook so the event graph is already up-to-date.
|
|
163
|
-
*
|
|
164
|
-
* Rules:
|
|
165
|
-
* Set → Set : symmetric diff (deleteItem for removed, addItem for added)
|
|
166
|
-
* Set → other : wipe old set (deleteItem every item)
|
|
167
|
-
* other→ Set : introduce new set (addItem every item)
|
|
168
|
-
* Array→ other : wipe old array (spliceOutElement every item)
|
|
169
|
-
* other→ Array : introduce new array (spliceInElement every item)
|
|
170
|
-
* Map → Map : diff entries (deleteEntry, replaceEntry, addEntry)
|
|
171
|
-
* Map → other : wipe old map (deleteEntry every entry)
|
|
172
|
-
* other→ Map : introduce new map (addEntry every entry)
|
|
173
|
-
*/
|
|
174
161
|
export function emitCollectionTransition(old: unknown, next: unknown): void {
|
|
175
|
-
const oldIsSet
|
|
176
|
-
const nextIsSet
|
|
177
|
-
const oldIsArray
|
|
178
|
-
const nextIsArray= next != null && typeof next === "object" && (next as any)[isStatifiedArrayKey];
|
|
179
|
-
const oldIsMap
|
|
180
|
-
const nextIsMap
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if (oldIsSet && nextIsSet) {
|
|
162
|
+
const oldIsSet = old != null && typeof old === "object" && (old as any)[isStatifiedSetKey];
|
|
163
|
+
const nextIsSet = next != null && typeof next === "object" && (next as any)[isStatifiedSetKey];
|
|
164
|
+
const oldIsArray = old != null && typeof old === "object" && (old as any)[isStatifiedArrayKey];
|
|
165
|
+
const nextIsArray = next != null && typeof next === "object" && (next as any)[isStatifiedArrayKey];
|
|
166
|
+
const oldIsMap = old != null && typeof old === "object" && (old as any)[isStatifiedMapKey];
|
|
167
|
+
const nextIsMap = next != null && typeof next === "object" && (next as any)[isStatifiedMapKey];
|
|
168
|
+
|
|
169
|
+
if (oldIsSet && nextIsSet && old !== next) {
|
|
184
170
|
const oldSet = old as StatifiedSet<unknown>;
|
|
185
171
|
const nextSet = next as StatifiedSet<unknown>;
|
|
186
172
|
const oldMeta = getMetadataOf(oldSet as Statify<{}>);
|
|
@@ -193,15 +179,13 @@ export function emitCollectionTransition(old: unknown, next: unknown): void {
|
|
|
193
179
|
}
|
|
194
180
|
if (changed) oldMeta.emit("cardChanged");
|
|
195
181
|
return;
|
|
196
|
-
}
|
|
197
|
-
if (oldIsSet) {
|
|
182
|
+
} else if (oldIsSet) {
|
|
198
183
|
const oldSet = old as StatifiedSet<unknown>;
|
|
199
184
|
const oldMeta = getMetadataOf(oldSet as Statify<{}>);
|
|
200
185
|
let changed = false;
|
|
201
186
|
for (const item of oldSet) { oldMeta.emit("deleteItem", item); changed = true; }
|
|
202
187
|
if (changed) oldMeta.emit("cardChanged");
|
|
203
|
-
}
|
|
204
|
-
if (nextIsSet) {
|
|
188
|
+
} else if (nextIsSet) {
|
|
205
189
|
const nextSet = next as StatifiedSet<unknown>;
|
|
206
190
|
const nextMeta = getMetadataOf(nextSet as Statify<{}>);
|
|
207
191
|
let changed = false;
|
|
@@ -209,30 +193,30 @@ export function emitCollectionTransition(old: unknown, next: unknown): void {
|
|
|
209
193
|
if (changed) nextMeta.emit("cardChanged");
|
|
210
194
|
}
|
|
211
195
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
196
|
+
if (old !== next) {
|
|
197
|
+
if (oldIsArray) {
|
|
198
|
+
const oldArr = old as StatifiedArray<unknown>;
|
|
199
|
+
const oldMeta = getMetadataOf(oldArr as Statify<{}>);
|
|
200
|
+
if (oldArr.length > 0) {
|
|
201
|
+
for (let i = oldArr.length - 1; i >= 0; i--) {
|
|
202
|
+
oldMeta.emit("spliceOutElement", oldArr[i], i);
|
|
203
|
+
}
|
|
204
|
+
oldMeta.emit("lengthChanged");
|
|
219
205
|
}
|
|
220
|
-
oldMeta.emit("lengthChanged");
|
|
221
206
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
207
|
+
if (nextIsArray) {
|
|
208
|
+
const nextArr = next as StatifiedArray<unknown>;
|
|
209
|
+
const nextMeta = getMetadataOf(nextArr as Statify<{}>);
|
|
210
|
+
if (nextArr.length > 0) {
|
|
211
|
+
for (let i = 0; i < nextArr.length; i++) {
|
|
212
|
+
nextMeta.emit("spliceInElement", nextArr[i], i);
|
|
213
|
+
}
|
|
214
|
+
nextMeta.emit("lengthChanged");
|
|
229
215
|
}
|
|
230
|
-
nextMeta.emit("lengthChanged");
|
|
231
216
|
}
|
|
232
217
|
}
|
|
233
218
|
|
|
234
|
-
|
|
235
|
-
if (oldIsMap && nextIsMap) {
|
|
219
|
+
if (oldIsMap && nextIsMap && old !== next) {
|
|
236
220
|
const oldMap = old as StatifiedMap<unknown, unknown>;
|
|
237
221
|
const nextMap = next as StatifiedMap<unknown, unknown>;
|
|
238
222
|
const oldMeta = getMetadataOf(oldMap as Statify<{}>);
|
|
@@ -246,15 +230,13 @@ export function emitCollectionTransition(old: unknown, next: unknown): void {
|
|
|
246
230
|
}
|
|
247
231
|
if (changed) oldMeta.emit("sizeChanged");
|
|
248
232
|
return;
|
|
249
|
-
}
|
|
250
|
-
if (oldIsMap) {
|
|
233
|
+
} else if (oldIsMap) {
|
|
251
234
|
const oldMap = old as StatifiedMap<unknown, unknown>;
|
|
252
235
|
const oldMeta = getMetadataOf(oldMap as Statify<{}>);
|
|
253
236
|
let changed = false;
|
|
254
237
|
for (const [k, v] of oldMap) { oldMeta.emit("deleteEntry", k, v); changed = true; }
|
|
255
238
|
if (changed) oldMeta.emit("sizeChanged");
|
|
256
|
-
}
|
|
257
|
-
if (nextIsMap) {
|
|
239
|
+
} else if (nextIsMap) {
|
|
258
240
|
const nextMap = next as StatifiedMap<unknown, unknown>;
|
|
259
241
|
const nextMeta = getMetadataOf(nextMap as Statify<{}>);
|
|
260
242
|
let changed = false;
|
|
@@ -440,16 +422,8 @@ export function extract(
|
|
|
440
422
|
});
|
|
441
423
|
}
|
|
442
424
|
|
|
443
|
-
const preExtractionHooks = new Set<() => void>();
|
|
444
|
-
export function registerPreExtractionHook(fn: () => void): () => void {
|
|
445
|
-
preExtractionHooks.add(fn);
|
|
446
|
-
return () => preExtractionHooks.delete(fn);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
425
|
export function selectorToRootAndPath(selector: () => Statify<StatifiableObj>) {
|
|
450
|
-
|
|
451
|
-
// This ensures field accessors are installed so path extraction resolves correctly.
|
|
452
|
-
for (const hook of preExtractionHooks) hook();
|
|
426
|
+
clearPendingAssemblies();
|
|
453
427
|
|
|
454
428
|
setGlobalStateMode("extract-proxy-path");
|
|
455
429
|
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
import { EnumerableWeakMap } from "../EnumerableWeakMap.js";
|
|
2
1
|
import { EnumerableWeakSet } from "../EnumerableWeakSet.js";
|
|
3
2
|
import {
|
|
4
3
|
exitProxySymbol,
|
|
5
4
|
getMetadataOf,
|
|
6
5
|
getGlobalStateMode,
|
|
7
|
-
isStatified,
|
|
8
|
-
metadataMap,
|
|
9
6
|
setMetadataOf,
|
|
10
7
|
statifySealKey,
|
|
11
8
|
type Statify,
|
|
@@ -15,7 +12,6 @@ import {
|
|
|
15
12
|
emitCollectionTransition,
|
|
16
13
|
extract,
|
|
17
14
|
hook,
|
|
18
|
-
registerPreExtractionHook,
|
|
19
15
|
StateMetadata,
|
|
20
16
|
unhook,
|
|
21
17
|
type StatifiableObj,
|
|
@@ -23,14 +19,8 @@ import {
|
|
|
23
19
|
|
|
24
20
|
type ClassType<T extends any[] = any[], U = object> = new (...args: T) => U;
|
|
25
21
|
|
|
26
|
-
// Classes whose instances have been constructed but not yet field-instrumented.
|
|
27
22
|
const pendingAssemblies = new EnumerableWeakSet<object>();
|
|
28
23
|
|
|
29
|
-
// Map from a still-pending child instance → list of (parentMetadata, prop) pairs
|
|
30
|
-
// that need hook() called once the child is reconciled.
|
|
31
|
-
const deferredHooks = new EnumerableWeakMap<object, { metadata: StateMetadata; prop: string | symbol }[]>();
|
|
32
|
-
|
|
33
|
-
// One shim proxy per outermost constructor (new.target).
|
|
34
24
|
const shimCache = new WeakMap<Function, object>();
|
|
35
25
|
|
|
36
26
|
function getPrototypeChainDescriptor(
|
|
@@ -46,295 +36,176 @@ function getPrototypeChainDescriptor(
|
|
|
46
36
|
return undefined;
|
|
47
37
|
}
|
|
48
38
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (prop === statifySealKey) return true;
|
|
39
|
+
function clearAssembly(assembly: object) {
|
|
40
|
+
for (const key of Reflect.ownKeys(assembly)) {
|
|
41
|
+
const value = Reflect.get(assembly, key);
|
|
53
42
|
|
|
54
|
-
|
|
55
|
-
// Root-level exit: this instance IS the stateRoot.
|
|
56
|
-
if (prop === exitProxySymbol) {
|
|
57
|
-
return { path: [], stateRoot: recv as Statify<StatifiableObj> };
|
|
58
|
-
}
|
|
43
|
+
Reflect.deleteProperty(assembly, key);
|
|
59
44
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
reconcileInstance(recv as object);
|
|
63
|
-
}
|
|
45
|
+
(assembly as Record<string | symbol, unknown>)[key] = value;
|
|
46
|
+
}
|
|
64
47
|
|
|
65
|
-
|
|
66
|
-
// Own accessor properties on recv are already handling extract-proxy-path
|
|
67
|
-
// themselves; the shim is only reached for prototype-level props.
|
|
68
|
-
return extract(
|
|
69
|
-
Reflect.get(target, prop, recv),
|
|
70
|
-
recv as Statify<StatifiableObj>,
|
|
71
|
-
[prop],
|
|
72
|
-
);
|
|
73
|
-
}
|
|
48
|
+
console.log(Reflect.ownKeys(assembly));
|
|
74
49
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
set(target, prop, newVal, recv) {
|
|
79
|
-
// This trap fires only when `prop` is NOT an own property of `recv`.
|
|
80
|
-
// That covers:
|
|
81
|
-
// (a) Pre-reconciliation field initializer writes → create own data prop.
|
|
82
|
-
// (b) Prototype accessor writes (e.g. `get state()` / `set state()`).
|
|
83
|
-
// (c) Genuinely new dynamic property additions.
|
|
84
|
-
//
|
|
85
|
-
// Own accessor properties installed by reconcileInstance are handled
|
|
86
|
-
// by their own setters and never reach here.
|
|
87
|
-
|
|
88
|
-
// Check for a prototype-level accessor setter BEFORE calling Reflect.set.
|
|
89
|
-
const protoDesc = getPrototypeChainDescriptor(target, prop);
|
|
90
|
-
|
|
91
|
-
// Delegate the actual assignment.
|
|
92
|
-
const result = Reflect.set(target, prop, newVal, recv);
|
|
93
|
-
|
|
94
|
-
// If this was a prototype accessor write and the instance has metadata,
|
|
95
|
-
// emit replacement events so subscribers fire.
|
|
96
|
-
if (result && protoDesc?.set && metadataMap.has(recv as Statify<StatifiableObj>)) {
|
|
97
|
-
const stateMetadata = getMetadataOf(recv as Statify<StatifiableObj>);
|
|
98
|
-
stateMetadata.emit("replaceProp", newVal, prop);
|
|
99
|
-
const ee = stateMetadata.eventEmitterAtPathMaybe([prop]);
|
|
100
|
-
if (ee) {
|
|
101
|
-
ee.emit("replace", newVal);
|
|
102
|
-
emitDescendantPathEvents(stateMetadata, [prop], newVal, true);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
50
|
+
pendingAssemblies.delete(assembly);
|
|
51
|
+
}
|
|
105
52
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
if (prop === statifySealKey || prop === exitProxySymbol) return true;
|
|
111
|
-
return Reflect.has(target, prop);
|
|
112
|
-
},
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
function getOrCreateShim(ctor: Function): object {
|
|
116
|
-
const cached = shimCache.get(ctor);
|
|
117
|
-
if (cached) return cached;
|
|
118
|
-
|
|
119
|
-
// shimTarget's [[Prototype]] = ctor.prototype, so the full chain is:
|
|
120
|
-
// instance → shim → shimTarget → ctor.prototype → … → Superclass.prototype
|
|
121
|
-
// This preserves instanceof for all classes in the chain.
|
|
122
|
-
const shimTarget = Object.create((ctor as ClassType).prototype) as object;
|
|
123
|
-
const shim = new Proxy(shimTarget, shimHandler);
|
|
124
|
-
shimCache.set(ctor, shim);
|
|
125
|
-
return shim;
|
|
53
|
+
export function clearPendingAssemblies() {
|
|
54
|
+
for (const assembly of pendingAssemblies) {
|
|
55
|
+
clearAssembly(assembly);
|
|
56
|
+
}
|
|
126
57
|
}
|
|
127
58
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
pendingAssemblies.delete(instance);
|
|
137
|
-
|
|
138
|
-
// Drain any deferred hooks where this instance was the pending child.
|
|
139
|
-
const deferred = deferredHooks.get(instance);
|
|
140
|
-
if (deferred) {
|
|
141
|
-
deferredHooks.delete(instance);
|
|
142
|
-
const childMetadata = getMetadataOf(instance as Statify<StatifiableObj>);
|
|
143
|
-
for (const { metadata: parentMetadata, prop } of deferred) {
|
|
144
|
-
hook(parentMetadata, prop, childMetadata);
|
|
145
|
-
}
|
|
59
|
+
export function makeStatified<
|
|
60
|
+
StateLayer extends object,
|
|
61
|
+
>(
|
|
62
|
+
Superclass: ClassType<any[], object>,
|
|
63
|
+
): typeof Superclass & ClassType<any[], Statify<StateLayer>> {
|
|
64
|
+
|
|
65
|
+
if (shimCache.has(Superclass)) {
|
|
66
|
+
return shimCache.get(Superclass) as typeof Superclass & ClassType<any[], Statify<StateLayer>>;
|
|
146
67
|
}
|
|
147
68
|
|
|
148
|
-
|
|
149
|
-
|
|
69
|
+
const dataLayers = new WeakMap<object, object>();
|
|
70
|
+
|
|
71
|
+
const ExtractionShimBase = function (...args: typeof Superclass extends ClassType<infer Args, object> ? Args : never) {
|
|
72
|
+
const inst = Reflect.construct(Superclass, args, new.target);
|
|
73
|
+
|
|
74
|
+
if (!pendingAssemblies.has(inst)) {
|
|
75
|
+
setMetadataOf(
|
|
76
|
+
inst as unknown as Statify<StatifiableObj>,
|
|
77
|
+
new StateMetadata(),
|
|
78
|
+
);
|
|
79
|
+
pendingAssemblies.add(inst);
|
|
80
|
+
}
|
|
150
81
|
|
|
151
|
-
|
|
82
|
+
queueMicrotask(() => {
|
|
83
|
+
clearPendingAssemblies();
|
|
84
|
+
});
|
|
152
85
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
];
|
|
86
|
+
const proto = new Proxy({}, {
|
|
87
|
+
get(target, prop, receiver) {
|
|
88
|
+
const result = Reflect.get(target, prop, receiver);
|
|
157
89
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
90
|
+
if (getGlobalStateMode() === "extract-proxy-path") {
|
|
91
|
+
if (prop === exitProxySymbol) {
|
|
92
|
+
return { path: [], stateRoot: receiver };
|
|
93
|
+
}
|
|
161
94
|
|
|
162
|
-
|
|
163
|
-
|
|
95
|
+
return extract(result, receiver, [prop]);
|
|
96
|
+
}
|
|
164
97
|
|
|
165
|
-
|
|
98
|
+
return result;
|
|
99
|
+
},
|
|
100
|
+
});
|
|
166
101
|
|
|
167
|
-
|
|
168
|
-
// Note: these properties retain their value but won't participate in the
|
|
169
|
-
// observable graph. This is intentional and tested.
|
|
170
|
-
if (!desc.configurable) continue;
|
|
102
|
+
Reflect.setPrototypeOf(proto, Reflect.getPrototypeOf(inst))
|
|
171
103
|
|
|
172
|
-
|
|
173
|
-
if ("get" in desc || "set" in desc) continue;
|
|
104
|
+
Reflect.setPrototypeOf(inst, proto);
|
|
174
105
|
|
|
175
|
-
|
|
176
|
-
let value: unknown = desc.value;
|
|
106
|
+
dataLayers.set(inst, {});
|
|
177
107
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
108
|
+
return inst;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
ExtractionShimBase.prototype = new Proxy({}, {
|
|
112
|
+
get(target, prop, recv) {
|
|
113
|
+
return Reflect.has(dataLayers.get(recv)!, prop)
|
|
114
|
+
? Reflect.get(dataLayers.get(recv)!, prop)
|
|
115
|
+
: Reflect.get(target, prop, recv);
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
set(target, prop, newVal, recv) {
|
|
119
|
+
const stateMetadata = getMetadataOf(recv);
|
|
120
|
+
|
|
121
|
+
const had = Reflect.has(recv, prop);
|
|
122
|
+
const oldVal = Reflect.get(recv, prop) as unknown;
|
|
123
|
+
|
|
124
|
+
let result;
|
|
125
|
+
const desc = getPrototypeChainDescriptor(target, prop);
|
|
126
|
+
if (desc?.set || desc?.get) {
|
|
127
|
+
result = Reflect.set(target, prop, newVal, recv);
|
|
128
|
+
} else if (typeof prop === "string" && prop.startsWith("#")) {
|
|
129
|
+
result = Reflect.set(target, prop, newVal, recv);
|
|
189
130
|
} else {
|
|
190
|
-
|
|
131
|
+
result = Reflect.set(dataLayers.get(recv)!, prop, newVal);
|
|
191
132
|
}
|
|
192
|
-
}
|
|
193
133
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
134
|
+
if (result) {
|
|
135
|
+
if (
|
|
136
|
+
oldVal !== newVal
|
|
137
|
+
&& typeof newVal === "object"
|
|
138
|
+
&& newVal != null
|
|
139
|
+
&& newVal[statifySealKey]
|
|
140
|
+
) {
|
|
141
|
+
const newValMetadata = getMetadataOf(newVal as Statify<StatifiableObj>);
|
|
142
|
+
|
|
143
|
+
hook(stateMetadata, prop, newValMetadata);
|
|
198
144
|
}
|
|
199
|
-
return value;
|
|
200
|
-
},
|
|
201
145
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
146
|
+
if (had) {
|
|
147
|
+
if (
|
|
148
|
+
oldVal !== newVal
|
|
149
|
+
&& typeof oldVal === "object"
|
|
150
|
+
&& oldVal != null
|
|
151
|
+
&& (oldVal as any)[statifySealKey]
|
|
152
|
+
) {
|
|
153
|
+
const oldValMetadata = getMetadataOf(oldVal as Statify<StatifiableObj>);
|
|
154
|
+
|
|
155
|
+
unhook(stateMetadata, prop, oldValMetadata);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
emitCollectionTransition(oldVal, newVal);
|
|
205
159
|
|
|
206
|
-
if (old !== next) {
|
|
207
160
|
if (
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
161
|
+
typeof prop !== "symbol" &&
|
|
162
|
+
Number.isInteger(Number(prop)) &&
|
|
163
|
+
Array.isArray(target)
|
|
211
164
|
) {
|
|
212
|
-
|
|
165
|
+
stateMetadata.emit('replaceElement', newVal, Number(prop));
|
|
213
166
|
}
|
|
214
167
|
|
|
168
|
+
stateMetadata.emit('replaceProp', newVal, prop);
|
|
169
|
+
} else {
|
|
215
170
|
if (
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
171
|
+
typeof prop !== "symbol" &&
|
|
172
|
+
Number.isInteger(Number(prop)) &&
|
|
173
|
+
Array.isArray(target)
|
|
219
174
|
) {
|
|
220
|
-
|
|
175
|
+
stateMetadata.emit('spliceInElement', newVal, Number(prop));
|
|
221
176
|
}
|
|
222
177
|
|
|
223
|
-
|
|
178
|
+
stateMetadata.emit('addProp', newVal, prop);
|
|
224
179
|
}
|
|
225
180
|
|
|
226
|
-
|
|
227
|
-
// set trap in low.ts. "listen → replace → fire" is preserved because this
|
|
228
|
-
// accessor is installed before any external mutation can reach the field.
|
|
229
|
-
metadata.emit("replaceProp", next, prop);
|
|
181
|
+
stateMetadata.emit('setProp', newVal, prop);
|
|
230
182
|
|
|
231
|
-
const
|
|
232
|
-
if (
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
},
|
|
183
|
+
const maybeEventEmitterAtVal = stateMetadata.eventEmitterAtPathMaybe([prop]);
|
|
184
|
+
if (maybeEventEmitterAtVal) {
|
|
185
|
+
if (had) {
|
|
186
|
+
maybeEventEmitterAtVal.emit('replace', newVal);
|
|
187
|
+
}
|
|
237
188
|
|
|
238
|
-
|
|
239
|
-
configurable: true,
|
|
240
|
-
});
|
|
241
|
-
}
|
|
189
|
+
maybeEventEmitterAtVal.emit('set', newVal);
|
|
242
190
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}
|
|
191
|
+
emitDescendantPathEvents(
|
|
192
|
+
stateMetadata,
|
|
193
|
+
[prop],
|
|
194
|
+
newVal,
|
|
195
|
+
had,
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
252
199
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
200
|
+
return result;
|
|
201
|
+
},
|
|
202
|
+
});
|
|
256
203
|
|
|
257
|
-
|
|
258
|
-
// Snapshot: reconcileInstance removes from pendingAssemblies, which mutates
|
|
259
|
-
// the set during iteration — take a copy first.
|
|
260
|
-
const snapshot = [...pendingAssemblies];
|
|
261
|
-
for (const instance of snapshot) {
|
|
262
|
-
reconcileInstance(instance);
|
|
263
|
-
}
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
* Wraps a user-defined class factory in the Informa observable lifecycle.
|
|
268
|
-
*
|
|
269
|
-
* Usage:
|
|
270
|
-
* ```ts
|
|
271
|
-
* const MyClass = statifyClass(
|
|
272
|
-
* (Base) => class MyClass extends Base {
|
|
273
|
-
* name = "default";
|
|
274
|
-
* constructor(name: string) { super(); this.name = name; }
|
|
275
|
-
* },
|
|
276
|
-
* Object,
|
|
277
|
-
* );
|
|
278
|
-
* ```
|
|
279
|
-
*
|
|
280
|
-
* Construction lifecycle:
|
|
281
|
-
* 1. ExtractionShimBase constructor runs → metadata installed, shim set as prototype.
|
|
282
|
-
* 2. UserSubclass field initializers run → own data properties created on instance.
|
|
283
|
-
* 3. UserSubclass constructor body runs.
|
|
284
|
-
* 4. Instance is in pendingAssemblies (field instrumentation deferred).
|
|
285
|
-
* 5. On first `on()`/`off()` call → registerPreExtractionHook fires → reconcileInstance
|
|
286
|
-
* converts own data properties to reactive accessor-backed properties.
|
|
287
|
-
*/
|
|
288
|
-
export function statifyClass<
|
|
289
|
-
SubclassType extends ClassType<ArgsSub, OutSub>,
|
|
290
|
-
ArgsSub extends any[],
|
|
291
|
-
OutSub extends object,
|
|
292
|
-
SuperclassType extends ClassType<ArgsSuper, OutSuper>,
|
|
293
|
-
ArgsSuper extends any[],
|
|
294
|
-
OutSuper extends object,
|
|
295
|
-
>(
|
|
296
|
-
makeSubclass: (SuperclassIn: SuperclassType) => SubclassType,
|
|
297
|
-
Superclass: SuperclassType,
|
|
298
|
-
): SubclassType & ClassType<ArgsSub, Statify<OutSub>> {
|
|
299
|
-
|
|
300
|
-
// ExtractionShimBase sits between Superclass and the user's class.
|
|
301
|
-
// Its constructor:
|
|
302
|
-
// - Installs StateMetadata on the instance eagerly (so subclasses like
|
|
303
|
-
// StatifiedArray can call getMetadataOf() from their constructor bodies).
|
|
304
|
-
// - Marks the instance as pending for lazy field instrumentation.
|
|
305
|
-
// - Inserts the shim proxy as the instance's [[Prototype]], enabling
|
|
306
|
-
// statifySealKey / exitProxySymbol / extract-proxy-path handling.
|
|
307
|
-
//
|
|
308
|
-
// Guard: in nested statifyClass chains (e.g. statifyClass(..., A) where A
|
|
309
|
-
// was itself produced by statifyClass), multiple ExtractionShimBase
|
|
310
|
-
// constructors run. The guard ensures setup happens exactly once — at the
|
|
311
|
-
// innermost (deepest) ExtractionShimBase call.
|
|
312
|
-
class ExtractionShimBase extends (Superclass as unknown as typeof Object) {
|
|
313
|
-
constructor(...args: any[]) {
|
|
314
|
-
super(...(args as []));
|
|
315
|
-
|
|
316
|
-
if (!pendingAssemblies.has(this)) {
|
|
317
|
-
setMetadataOf(
|
|
318
|
-
this as unknown as Statify<StatifiableObj>,
|
|
319
|
-
new StateMetadata(),
|
|
320
|
-
);
|
|
321
|
-
pendingAssemblies.add(this);
|
|
322
|
-
}
|
|
204
|
+
Reflect.setPrototypeOf(ExtractionShimBase.prototype, Superclass.prototype);
|
|
323
205
|
|
|
324
|
-
|
|
325
|
-
// In nested chains the innermost ExtractionShimBase sets the shim first;
|
|
326
|
-
// subsequent (shallower) ones overwrite it with the same value since
|
|
327
|
-
// new.target propagates as the outermost class throughout the chain.
|
|
328
|
-
const ctor = (new.target ?? ExtractionShimBase) as Function;
|
|
329
|
-
Reflect.setPrototypeOf(this, getOrCreateShim(ctor));
|
|
330
|
-
}
|
|
331
|
-
}
|
|
206
|
+
shimCache.set(Superclass, ExtractionShimBase);
|
|
332
207
|
|
|
333
|
-
|
|
334
|
-
// No outer Statified wrapper is needed: field instrumentation is lazy,
|
|
335
|
-
// triggered by registerPreExtractionHook before any selectorToRootAndPath call.
|
|
336
|
-
return makeSubclass(
|
|
337
|
-
ExtractionShimBase as unknown as SuperclassType,
|
|
338
|
-
) as unknown as SubclassType & ClassType<ArgsSub, Statify<OutSub>>;
|
|
208
|
+
return ExtractionShimBase as unknown as typeof Superclass & ClassType<any[], Statify<StateLayer>>;
|
|
339
209
|
}
|
|
340
210
|
|
|
211
|
+
export const makeBaseStatified = <T extends object>() => makeStatified<T>(Object);
|
package/src/test.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import $ from "./high.js";
|
|
2
|
+
import { makeStatified } from "./quirks/basestatified.js";
|
|
2
3
|
import { StatifiedSet } from "./quirks/set.js";
|
|
3
4
|
|
|
4
5
|
const stateful = $.state<{ a: ({ d: number })[], b?: { c?: { d?: Set<number> } } }>({ a: [] });
|
|
@@ -26,19 +27,21 @@ const asdf2 = $.state({ c: asdf });
|
|
|
26
27
|
// const aassddff = $.state(new Set());
|
|
27
28
|
stateful.b = asdf2;
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
interface WaylandState {
|
|
31
|
+
state2: number;
|
|
32
|
+
}
|
|
33
|
+
class Wayland extends makeStatified<WaylandState>(Object) {
|
|
34
|
+
displays = new StatifiedSet();
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
state = 10;
|
|
37
|
+
|
|
38
|
+
get state2() {
|
|
39
|
+
return super.state2;
|
|
40
|
+
}
|
|
41
|
+
set state2(v) {
|
|
42
|
+
super.state2 = v;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
42
45
|
|
|
43
46
|
const w = new Wayland();
|
|
44
47
|
$.onSet(() => w.state, (v) => console.log("asdf awawa!!", v));
|
|
@@ -47,6 +50,12 @@ $.onSet(() => w.state, (v) => console.log("asdf awawa!!", v));
|
|
|
47
50
|
w.state = 6;
|
|
48
51
|
w.state = 7;
|
|
49
52
|
|
|
53
|
+
$.onSet(() => w.state2, (v) => console.log("asdf awawa!!", v));
|
|
54
|
+
|
|
55
|
+
// w.state2 = 5;
|
|
56
|
+
w.state2 = 1234;
|
|
57
|
+
w.state2 = 12345;
|
|
58
|
+
|
|
50
59
|
const map = $.state<Map<number, string>>(new Map());
|
|
51
60
|
$.onSetEntry(() => map, (k, v) => console.log("set", k, v));
|
|
52
61
|
$.onReplaceEntry(() => map, (k, v) => console.log("replace", k, v));
|