@flighthq/entity 0.3.0-next.906.07cea63 → 0.3.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/dist/contract.d.ts +1 -0
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +1 -0
- package/dist/contract.js.map +1 -1
- package/dist/enableEntityRuntimeGuards.d.ts +3 -0
- package/dist/enableEntityRuntimeGuards.d.ts.map +1 -0
- package/dist/enableEntityRuntimeGuards.js +33 -0
- package/dist/enableEntityRuntimeGuards.js.map +1 -0
- package/dist/guards.d.ts +3 -2
- package/dist/guards.d.ts.map +1 -1
- package/dist/guards.js +17 -18
- package/dist/guards.js.map +1 -1
- package/package.json +5 -2
- package/src/enableEntityRuntimeGuards.test.ts +73 -0
- package/src/guards.test.ts +47 -30
package/dist/contract.d.ts
CHANGED
package/dist/contract.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,6BAA6B,CAAC;AAC5C,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC"}
|
package/dist/contract.js
CHANGED
package/dist/contract.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"contract.js","sourceRoot":"","sources":["../src/contract.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC;AACzB,cAAc,6BAA6B,CAAC;AAC5C,cAAc,UAAU,CAAC;AACzB,cAAc,WAAW,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enableEntityRuntimeGuards.d.ts","sourceRoot":"","sources":["../src/enableEntityRuntimeGuards.ts"],"names":[],"mappings":"AAOA,wBAAgB,0BAA0B,IAAI,IAAI,CAGjD;AAWD,wBAAgB,yBAAyB,IAAI,IAAI,CAGhD"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { logOnce } from '@flighthq/log/contract';
|
|
2
|
+
import { LogLevel } from '@flighthq/types/contract';
|
|
3
|
+
import { setEntityRuntimeGuardMode, setEntityRuntimeWriteGuard } from './guards';
|
|
4
|
+
// Uninstalls the guard installed by enableEntityRuntimeGuards, restoring unguarded entities.
|
|
5
|
+
export function disableEntityRuntimeGuards() {
|
|
6
|
+
setEntityRuntimeGuardMode(false);
|
|
7
|
+
setEntityRuntimeWriteGuard(null);
|
|
8
|
+
}
|
|
9
|
+
// Opt-in development guard mode. When enabled, a direct write to an entity's runtime slot — or to an
|
|
10
|
+
// EntityRuntime's binding slot — that bypasses ensureEntityRuntime / attachEntityBinding is reported,
|
|
11
|
+
// making "the write landed on the wrong entity" and raw-slot-poke bugs visible early. The write is still
|
|
12
|
+
// allowed: the guard observes, it does not block.
|
|
13
|
+
//
|
|
14
|
+
// @flighthq/entity is a CORE package, and core may otherwise depend only on types/core. This module is the
|
|
15
|
+
// sanctioned exception: it is separately importable and shakeable, so a build that never imports it never
|
|
16
|
+
// pulls @flighthq/log, and the layer rule's real concern — feature weight in core's always-loaded graph —
|
|
17
|
+
// does not arise. `packages:check` enforces that the import appears in guard modules only. Idempotent.
|
|
18
|
+
export function enableEntityRuntimeGuards() {
|
|
19
|
+
setEntityRuntimeGuardMode(true);
|
|
20
|
+
setEntityRuntimeWriteGuard(warnOnDirectWrite);
|
|
21
|
+
}
|
|
22
|
+
function warnOnDirectWrite(slot) {
|
|
23
|
+
if (slot === 'binding-slot') {
|
|
24
|
+
logOnce('entity:direct-binding-write', LogLevel.Warn, {
|
|
25
|
+
message: 'EntityRuntime.binding was written directly. Use attachEntityBinding or detachEntityBinding, which keep the binding and the runtime consistent; the write was allowed but is not tracked.',
|
|
26
|
+
}, 'entity');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
logOnce('entity:direct-runtime-write', LogLevel.Warn, {
|
|
30
|
+
message: "An entity's runtime slot was written directly. Use ensureEntityRuntime or attachEntityBinding; the write was allowed, but bypassing them is how a runtime ends up on the wrong entity.",
|
|
31
|
+
}, 'entity');
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=enableEntityRuntimeGuards.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enableEntityRuntimeGuards.js","sourceRoot":"","sources":["../src/enableEntityRuntimeGuards.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAEjD,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEpD,OAAO,EAAE,yBAAyB,EAAE,0BAA0B,EAAE,MAAM,UAAU,CAAC;AAEjF,6FAA6F;AAC7F,MAAM,UAAU,0BAA0B;IACxC,yBAAyB,CAAC,KAAK,CAAC,CAAC;IACjC,0BAA0B,CAAC,IAAI,CAAC,CAAC;AACnC,CAAC;AAED,qGAAqG;AACrG,sGAAsG;AACtG,yGAAyG;AACzG,kDAAkD;AAClD,EAAE;AACF,2GAA2G;AAC3G,0GAA0G;AAC1G,0GAA0G;AAC1G,uGAAuG;AACvG,MAAM,UAAU,yBAAyB;IACvC,yBAAyB,CAAC,IAAI,CAAC,CAAC;IAChC,0BAA0B,CAAC,iBAAiB,CAAC,CAAC;AAChD,CAAC;AAED,SAAS,iBAAiB,CAAC,IAA4B;IACrD,IAAI,IAAI,KAAK,cAAc,EAAE,CAAC;QAC5B,OAAO,CACL,6BAA6B,EAC7B,QAAQ,CAAC,IAAI,EACb;YACE,OAAO,EACL,0LAA0L;SAC7L,EACD,QAAQ,CACT,CAAC;QACF,OAAO;IACT,CAAC;IACD,OAAO,CACL,6BAA6B,EAC7B,QAAQ,CAAC,IAAI,EACb;QACE,OAAO,EACL,wLAAwL;KAC3L,EACD,QAAQ,CACT,CAAC;AACJ,CAAC"}
|
package/dist/guards.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { Entity, EntityRuntime } from '@flighthq/types/contract';
|
|
1
|
+
import type { Entity, EntityRuntime, EntityRuntimeWriteGuard } from '@flighthq/types/contract';
|
|
2
2
|
export declare function areEntityRuntimeGuardsEnabled(): boolean;
|
|
3
3
|
export declare function createGuardedEntity<Type extends object>(entity: Type & Entity): Type & Entity;
|
|
4
4
|
export declare function createGuardedEntityRuntime(runtime: EntityRuntime): EntityRuntime;
|
|
5
|
-
export declare function
|
|
5
|
+
export declare function setEntityRuntimeGuardMode(enabled: boolean): void;
|
|
6
|
+
export declare function setEntityRuntimeWriteGuard(guard: EntityRuntimeWriteGuard | null): void;
|
|
6
7
|
//# sourceMappingURL=guards.d.ts.map
|
package/dist/guards.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guards.d.ts","sourceRoot":"","sources":["../src/guards.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"guards.d.ts","sourceRoot":"","sources":["../src/guards.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AAI/F,wBAAgB,6BAA6B,IAAI,OAAO,CAEvD;AAKD,wBAAgB,mBAAmB,CAAC,IAAI,SAAS,MAAM,EAAE,MAAM,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,GAAG,MAAM,CAe7F;AAKD,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,aAAa,GAAG,aAAa,CAWhF;AAID,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAGhE;AAID,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,uBAAuB,GAAG,IAAI,GAAG,IAAI,CAEtF"}
|
package/dist/guards.js
CHANGED
|
@@ -12,12 +12,11 @@ export function createGuardedEntity(entity) {
|
|
|
12
12
|
return new Proxy(entity, {
|
|
13
13
|
set(target, prop, value) {
|
|
14
14
|
if (prop === EntityRuntimeKey && _guardsEnabled) {
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
console.warn('[entity] Direct write to EntityRuntimeKey detected. Use ensureEntityRuntime or attachEntityBinding instead.', entity);
|
|
15
|
+
// The write is reported and then ALLOWED: the stack cannot be inspected reliably across
|
|
16
|
+
// environments, so trusted callers (ensureEntityRuntime, attachEntityBinding) cannot be told apart
|
|
17
|
+
// from a raw poke. Reporting goes through the seam rather than the console so it uses the standard
|
|
18
|
+
// sink — see enableEntityRuntimeGuards.
|
|
19
|
+
_writeGuard?.('runtime-slot');
|
|
21
20
|
}
|
|
22
21
|
target[prop] = value;
|
|
23
22
|
return true;
|
|
@@ -33,25 +32,25 @@ export function createGuardedEntityRuntime(runtime) {
|
|
|
33
32
|
return new Proxy(runtime, {
|
|
34
33
|
set(target, prop, value) {
|
|
35
34
|
if (prop === 'binding' && _guardsEnabled) {
|
|
36
|
-
|
|
37
|
-
console.warn('[entity] Direct write to EntityRuntime.binding detected. Use attachEntityBinding or detachEntityBinding instead.', runtime);
|
|
35
|
+
_writeGuard?.('binding-slot');
|
|
38
36
|
}
|
|
39
37
|
target[prop] = value;
|
|
40
38
|
return true;
|
|
41
39
|
},
|
|
42
40
|
});
|
|
43
41
|
}
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
// This function is a no-op in production if the calling module is never imported — it is
|
|
49
|
-
// fully tree-shakable, never called at module top level, and has no effect on
|
|
50
|
-
// "sideEffects": false.
|
|
51
|
-
export function enableEntityRuntimeGuards() {
|
|
52
|
-
if (typeof Proxy === 'undefined')
|
|
42
|
+
// Turns the guarding PROXIES on or off. This is the machinery seam, not the caller-facing entry point —
|
|
43
|
+
// use enableEntityRuntimeGuards, which switches this on and installs the reporter together.
|
|
44
|
+
export function setEntityRuntimeGuardMode(enabled) {
|
|
45
|
+
if (enabled && typeof Proxy === 'undefined')
|
|
53
46
|
return;
|
|
54
|
-
_guardsEnabled =
|
|
47
|
+
_guardsEnabled = enabled;
|
|
48
|
+
}
|
|
49
|
+
// The diagnostics seam for a direct slot write. Null uninstalls it, and null is what a build that never
|
|
50
|
+
// imports the guard module sees — which is why this file needs no logger and stays inside the core layer.
|
|
51
|
+
export function setEntityRuntimeWriteGuard(guard) {
|
|
52
|
+
_writeGuard = guard;
|
|
55
53
|
}
|
|
56
54
|
let _guardsEnabled = false;
|
|
55
|
+
let _writeGuard = null;
|
|
57
56
|
//# sourceMappingURL=guards.js.map
|
package/dist/guards.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guards.js","sourceRoot":"","sources":["../src/guards.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAE5D,yFAAyF;AACzF,MAAM,UAAU,6BAA6B;IAC3C,OAAO,cAAc,CAAC;AACxB,CAAC;AAED,uFAAuF;AACvF,uFAAuF;AACvF,yCAAyC;AACzC,MAAM,UAAU,mBAAmB,CAAsB,MAAqB;IAC5E,IAAI,CAAC,cAAc,IAAI,OAAO,KAAK,KAAK,WAAW;QAAE,OAAO,MAAM,CAAC;IACnE,OAAO,IAAI,KAAK,CAAC,MAAM,EAAE;QACvB,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK;YACrB,IAAI,IAAI,KAAK,gBAAgB,IAAI,cAAc,EAAE,CAAC;gBAChD,
|
|
1
|
+
{"version":3,"file":"guards.js","sourceRoot":"","sources":["../src/guards.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAE5D,yFAAyF;AACzF,MAAM,UAAU,6BAA6B;IAC3C,OAAO,cAAc,CAAC;AACxB,CAAC;AAED,uFAAuF;AACvF,uFAAuF;AACvF,yCAAyC;AACzC,MAAM,UAAU,mBAAmB,CAAsB,MAAqB;IAC5E,IAAI,CAAC,cAAc,IAAI,OAAO,KAAK,KAAK,WAAW;QAAE,OAAO,MAAM,CAAC;IACnE,OAAO,IAAI,KAAK,CAAC,MAAM,EAAE;QACvB,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK;YACrB,IAAI,IAAI,KAAK,gBAAgB,IAAI,cAAc,EAAE,CAAC;gBAChD,wFAAwF;gBACxF,mGAAmG;gBACnG,mGAAmG;gBACnG,wCAAwC;gBACxC,WAAW,EAAE,CAAC,cAAc,CAAC,CAAC;YAChC,CAAC;YACA,MAAkD,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;YAClE,OAAO,IAAI,CAAC;QACd,CAAC;KACF,CAAC,CAAC;AACL,CAAC;AAED,0FAA0F;AAC1F,2FAA2F;AAC3F,eAAe;AACf,MAAM,UAAU,0BAA0B,CAAC,OAAsB;IAC/D,IAAI,CAAC,cAAc,IAAI,OAAO,KAAK,KAAK,WAAW;QAAE,OAAO,OAAO,CAAC;IACpE,OAAO,IAAI,KAAK,CAAC,OAAO,EAAE;QACxB,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK;YACrB,IAAI,IAAI,KAAK,SAAS,IAAI,cAAc,EAAE,CAAC;gBACzC,WAAW,EAAE,CAAC,cAAc,CAAC,CAAC;YAChC,CAAC;YACA,MAAkD,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;YAClE,OAAO,IAAI,CAAC;QACd,CAAC;KACF,CAAC,CAAC;AACL,CAAC;AAED,wGAAwG;AACxG,4FAA4F;AAC5F,MAAM,UAAU,yBAAyB,CAAC,OAAgB;IACxD,IAAI,OAAO,IAAI,OAAO,KAAK,KAAK,WAAW;QAAE,OAAO;IACpD,cAAc,GAAG,OAAO,CAAC;AAC3B,CAAC;AAED,wGAAwG;AACxG,0GAA0G;AAC1G,MAAM,UAAU,0BAA0B,CAAC,KAAqC;IAC9E,WAAW,GAAG,KAAK,CAAC;AACtB,CAAC;AAED,IAAI,cAAc,GAAG,KAAK,CAAC;AAC3B,IAAI,WAAW,GAAmC,IAAI,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flighthq/entity",
|
|
3
|
-
"version": "0.3.0
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"author": "Joshua Granick and other contributors",
|
|
5
|
+
"license": "MIT",
|
|
4
6
|
"repository": {
|
|
5
7
|
"type": "git",
|
|
6
8
|
"url": "https://github.com/flighthq/flight.git",
|
|
@@ -36,7 +38,8 @@
|
|
|
36
38
|
"clean:dist": "tsx ../../scripts/clean-package-dist.ts"
|
|
37
39
|
},
|
|
38
40
|
"dependencies": {
|
|
39
|
-
"@flighthq/
|
|
41
|
+
"@flighthq/log": "0.3.0",
|
|
42
|
+
"@flighthq/types": "0.3.0"
|
|
40
43
|
},
|
|
41
44
|
"devDependencies": {
|
|
42
45
|
"typescript": "^5.3.0"
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { addLogSink, createMemoryLogSink, getMemoryLogSinkEntries, removeLogSink } from '@flighthq/log/contract';
|
|
2
|
+
import type { LogEntry } from '@flighthq/types/contract';
|
|
3
|
+
import { EntityRuntimeKey } from '@flighthq/types/contract';
|
|
4
|
+
|
|
5
|
+
import { disableEntityRuntimeGuards, enableEntityRuntimeGuards } from './enableEntityRuntimeGuards';
|
|
6
|
+
import { createEntity } from './entity';
|
|
7
|
+
import { areEntityRuntimeGuardsEnabled, createGuardedEntity, createGuardedEntityRuntime } from './guards';
|
|
8
|
+
import { createEntityRuntime } from './runtime';
|
|
9
|
+
|
|
10
|
+
function captureLog(run: () => void): readonly LogEntry[] {
|
|
11
|
+
const sink = createMemoryLogSink(8);
|
|
12
|
+
addLogSink(sink.sink);
|
|
13
|
+
try {
|
|
14
|
+
run();
|
|
15
|
+
return getMemoryLogSinkEntries(sink);
|
|
16
|
+
} finally {
|
|
17
|
+
removeLogSink(sink.sink);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function messageOf(entry: Readonly<LogEntry>): string {
|
|
22
|
+
const data = entry.data;
|
|
23
|
+
return typeof data === 'string' ? data : String(data.message);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe('disableEntityRuntimeGuards', () => {
|
|
27
|
+
it('uninstalls both the proxies and the reporter', () => {
|
|
28
|
+
const entries = captureLog(() => {
|
|
29
|
+
enableEntityRuntimeGuards();
|
|
30
|
+
disableEntityRuntimeGuards();
|
|
31
|
+
createGuardedEntity(createEntity())[EntityRuntimeKey] = undefined;
|
|
32
|
+
});
|
|
33
|
+
expect(entries.length).toBe(0);
|
|
34
|
+
expect(areEntityRuntimeGuardsEnabled()).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('enableEntityRuntimeGuards', () => {
|
|
39
|
+
it('WARNS through the log sink on a direct runtime-slot write', () => {
|
|
40
|
+
const entries = captureLog(() => {
|
|
41
|
+
enableEntityRuntimeGuards();
|
|
42
|
+
try {
|
|
43
|
+
createGuardedEntity(createEntity())[EntityRuntimeKey] = undefined;
|
|
44
|
+
} finally {
|
|
45
|
+
disableEntityRuntimeGuards();
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
expect(entries.length).toBe(1);
|
|
49
|
+
expect(messageOf(entries[0])).toContain('ensureEntityRuntime');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('WARNS separately for a direct binding-slot write', () => {
|
|
53
|
+
const entries = captureLog(() => {
|
|
54
|
+
enableEntityRuntimeGuards();
|
|
55
|
+
try {
|
|
56
|
+
createGuardedEntityRuntime(createEntityRuntime()).binding = null;
|
|
57
|
+
} finally {
|
|
58
|
+
disableEntityRuntimeGuards();
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
expect(entries.length).toBe(1);
|
|
62
|
+
expect(messageOf(entries[0])).toContain('attachEntityBinding');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('stays SILENT without the guard — the production default', () => {
|
|
66
|
+
const entries = captureLog(() => {
|
|
67
|
+
// Unguarded entities are returned as-is, so there is nothing to intercept.
|
|
68
|
+
createGuardedEntity(createEntity())[EntityRuntimeKey] = undefined;
|
|
69
|
+
});
|
|
70
|
+
expect(entries.length).toBe(0);
|
|
71
|
+
expect(areEntityRuntimeGuardsEnabled()).toBe(false);
|
|
72
|
+
});
|
|
73
|
+
});
|
package/src/guards.test.ts
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
import { EntityRuntimeKey } from '@flighthq/types/contract';
|
|
2
|
+
import type { EntityRuntimeWriteSlot } from '@flighthq/types/contract';
|
|
2
3
|
|
|
3
4
|
import { createEntity } from './entity';
|
|
4
5
|
import {
|
|
5
6
|
areEntityRuntimeGuardsEnabled,
|
|
6
7
|
createGuardedEntity,
|
|
7
8
|
createGuardedEntityRuntime,
|
|
8
|
-
|
|
9
|
+
setEntityRuntimeGuardMode,
|
|
10
|
+
setEntityRuntimeWriteGuard,
|
|
9
11
|
} from './guards';
|
|
10
12
|
import { createEntityRuntime } from './runtime';
|
|
11
13
|
|
|
12
14
|
describe('areEntityRuntimeGuardsEnabled', () => {
|
|
13
|
-
it('returns false before
|
|
15
|
+
it('returns false before guard mode is switched on', () => {
|
|
14
16
|
// Vitest isolates modules per file, so this starts false in a fresh module import.
|
|
15
17
|
expect(areEntityRuntimeGuardsEnabled()).toBe(false);
|
|
16
18
|
});
|
|
@@ -19,46 +21,61 @@ describe('areEntityRuntimeGuardsEnabled', () => {
|
|
|
19
21
|
describe('createGuardedEntity', () => {
|
|
20
22
|
it('returns the entity unchanged when guards are not enabled', () => {
|
|
21
23
|
const entity = createEntity({ x: 1 });
|
|
22
|
-
|
|
23
|
-
expect(guarded).toBe(entity);
|
|
24
|
+
expect(createGuardedEntity(entity)).toBe(entity);
|
|
24
25
|
});
|
|
25
26
|
});
|
|
26
27
|
|
|
27
28
|
describe('createGuardedEntityRuntime', () => {
|
|
28
29
|
it('returns the runtime unchanged when guards are not enabled', () => {
|
|
29
30
|
const runtime = createEntityRuntime();
|
|
30
|
-
|
|
31
|
-
expect(guarded).toBe(runtime);
|
|
31
|
+
expect(createGuardedEntityRuntime(runtime)).toBe(runtime);
|
|
32
32
|
});
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
-
describe('
|
|
36
|
-
it('
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
35
|
+
describe('setEntityRuntimeGuardMode', () => {
|
|
36
|
+
it('switches the proxies on and back off', () => {
|
|
37
|
+
setEntityRuntimeGuardMode(true);
|
|
38
|
+
try {
|
|
39
|
+
expect(areEntityRuntimeGuardsEnabled()).toBe(true);
|
|
40
|
+
const entity = createEntity({ x: 1 });
|
|
41
|
+
expect(createGuardedEntity(entity).x).toBe(1);
|
|
42
|
+
expect(createGuardedEntityRuntime(createEntityRuntime()).binding).toBeNull();
|
|
43
|
+
} finally {
|
|
44
|
+
setEntityRuntimeGuardMode(false);
|
|
45
|
+
}
|
|
46
|
+
expect(areEntityRuntimeGuardsEnabled()).toBe(false);
|
|
46
47
|
});
|
|
48
|
+
});
|
|
47
49
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
describe('setEntityRuntimeWriteGuard', () => {
|
|
51
|
+
it('reports which slot a direct write landed on, and still ALLOWS the write', () => {
|
|
52
|
+
const slots: EntityRuntimeWriteSlot[] = [];
|
|
53
|
+
setEntityRuntimeGuardMode(true);
|
|
54
|
+
setEntityRuntimeWriteGuard((slot) => slots.push(slot));
|
|
55
|
+
try {
|
|
56
|
+
const guardedEntity = createGuardedEntity(createEntity());
|
|
57
|
+
guardedEntity[EntityRuntimeKey] = undefined;
|
|
58
|
+
const guardedRuntime = createGuardedEntityRuntime(createEntityRuntime());
|
|
59
|
+
guardedRuntime.binding = null;
|
|
60
|
+
expect(slots).toEqual(['runtime-slot', 'binding-slot']);
|
|
61
|
+
// The guard observes; it does not block. The write must land.
|
|
62
|
+
expect(EntityRuntimeKey in guardedEntity).toBe(true);
|
|
63
|
+
} finally {
|
|
64
|
+
setEntityRuntimeWriteGuard(null);
|
|
65
|
+
setEntityRuntimeGuardMode(false);
|
|
66
|
+
}
|
|
53
67
|
});
|
|
54
68
|
|
|
55
|
-
it('
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
69
|
+
it('is silent once uninstalled', () => {
|
|
70
|
+
const slots: EntityRuntimeWriteSlot[] = [];
|
|
71
|
+
setEntityRuntimeGuardMode(true);
|
|
72
|
+
setEntityRuntimeWriteGuard((slot) => slots.push(slot));
|
|
73
|
+
setEntityRuntimeWriteGuard(null);
|
|
74
|
+
try {
|
|
75
|
+
createGuardedEntity(createEntity())[EntityRuntimeKey] = undefined;
|
|
76
|
+
expect(slots).toEqual([]);
|
|
77
|
+
} finally {
|
|
78
|
+
setEntityRuntimeGuardMode(false);
|
|
79
|
+
}
|
|
63
80
|
});
|
|
64
81
|
});
|