@flighthq/entity 0.5.1-next.731.0ae0aa4 → 0.5.1-next.799.62ef0c7
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 +2 -2
- 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 +2 -1
- package/dist/enableEntityRuntimeGuards.d.ts.map +1 -1
- package/dist/enableEntityRuntimeGuards.js +9 -21
- package/dist/enableEntityRuntimeGuards.js.map +1 -1
- package/dist/explainEntityRuntimeWrite.d.ts +3 -0
- package/dist/explainEntityRuntimeWrite.d.ts.map +1 -0
- package/dist/explainEntityRuntimeWrite.js +19 -0
- package/dist/explainEntityRuntimeWrite.js.map +1 -0
- package/package.json +2 -3
- package/src/enableEntityRuntimeGuards.test.ts +25 -48
- package/src/explainEntityRuntimeWrite.test.ts +21 -0
package/README.md
CHANGED
|
@@ -13,5 +13,5 @@ Import the supported application-facing API from `@flighthq/entity`. The `@fligh
|
|
|
13
13
|
This package is part of the locked-version Flight SDK graph. Applications may instead install and import `@flighthq/sdk` when package-level tree shaking is sufficient.
|
|
14
14
|
|
|
15
15
|
- [Flight project](https://github.com/flighthq/flight)
|
|
16
|
-
- [Source for @flighthq/entity@0.5.1-next.
|
|
17
|
-
- [License](https://github.com/flighthq/flight/blob/
|
|
16
|
+
- [Source for @flighthq/entity@0.5.1-next.799.62ef0c7](https://github.com/flighthq/flight/tree/62ef0c74f09f24177aac3695b2487b2d743d4fe6/packages/entity)
|
|
17
|
+
- [License](https://github.com/flighthq/flight/blob/62ef0c74f09f24177aac3695b2487b2d743d4fe6/LICENSE.md)
|
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,6BAA6B,CAAC;AAC5C,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC;AACvB,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,aAAa,CAAC;AAC5B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC;AACvB,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,6BAA6B,CAAC;AAC5C,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC;AACvB,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,aAAa,CAAC;AAC5B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,UAAU,CAAC;AACzB,cAAc,QAAQ,CAAC;AACvB,cAAc,WAAW,CAAC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { EntityRuntimeWriteGuard } from '@flighthq/types/contract';
|
|
1
2
|
export declare function disableEntityRuntimeGuards(): void;
|
|
2
|
-
export declare function enableEntityRuntimeGuards(): void;
|
|
3
|
+
export declare function enableEntityRuntimeGuards(report: EntityRuntimeWriteGuard): void;
|
|
3
4
|
//# sourceMappingURL=enableEntityRuntimeGuards.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"enableEntityRuntimeGuards.d.ts","sourceRoot":"","sources":["../src/enableEntityRuntimeGuards.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"enableEntityRuntimeGuards.d.ts","sourceRoot":"","sources":["../src/enableEntityRuntimeGuards.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AAKxE,wBAAgB,0BAA0B,IAAI,IAAI,CAGjD;AAYD,wBAAgB,yBAAyB,CAAC,MAAM,EAAE,uBAAuB,GAAG,IAAI,CAG/E"}
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { logOnce } from '@flighthq/log/contract';
|
|
2
|
-
import { LogLevel } from '@flighthq/types/contract';
|
|
3
1
|
import { setEntityRuntimeGuardMode, setEntityRuntimeWriteGuard } from './guards';
|
|
4
2
|
// Uninstalls the guard installed by enableEntityRuntimeGuards, restoring unguarded entities.
|
|
5
3
|
export function disableEntityRuntimeGuards() {
|
|
@@ -7,27 +5,17 @@ export function disableEntityRuntimeGuards() {
|
|
|
7
5
|
setEntityRuntimeWriteGuard(null);
|
|
8
6
|
}
|
|
9
7
|
// 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 attachEntityBinding is reported
|
|
11
|
-
//
|
|
8
|
+
// EntityRuntime's binding slot — that bypasses attachEntityBinding is reported to `report`, making
|
|
9
|
+
// "the write landed on the wrong entity" and raw-slot-poke bugs visible early. The write is still
|
|
12
10
|
// allowed: the guard observes, it does not block.
|
|
13
11
|
//
|
|
14
|
-
// @flighthq/entity is a
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
|
|
12
|
+
// The reporter is the CALLER's. @flighthq/entity is a core package and owns no sink: it does not reach
|
|
13
|
+
// for a logger, a console, or any other ambient singleton, so nothing about where diagnostics go is
|
|
14
|
+
// decided here. Pass explainEntityRuntimeWrite's result to whatever the app already logs through.
|
|
15
|
+
// Turning the proxies on and installing the reporter is one act, which is why this exists rather than
|
|
16
|
+
// leaving callers to pair setEntityRuntimeGuardMode with setEntityRuntimeWriteGuard. Idempotent.
|
|
17
|
+
export function enableEntityRuntimeGuards(report) {
|
|
19
18
|
setEntityRuntimeGuardMode(true);
|
|
20
|
-
setEntityRuntimeWriteGuard(
|
|
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 attachEntityBinding, which allocates the slot for you; the write was allowed, but bypassing it is how a runtime ends up on the wrong entity.",
|
|
31
|
-
}, 'entity');
|
|
19
|
+
setEntityRuntimeWriteGuard(report);
|
|
32
20
|
}
|
|
33
21
|
//# sourceMappingURL=enableEntityRuntimeGuards.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"enableEntityRuntimeGuards.js","sourceRoot":"","sources":["../src/enableEntityRuntimeGuards.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"enableEntityRuntimeGuards.js","sourceRoot":"","sources":["../src/enableEntityRuntimeGuards.ts"],"names":[],"mappings":"AAEA,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,mGAAmG;AACnG,kGAAkG;AAClG,kDAAkD;AAClD,EAAE;AACF,uGAAuG;AACvG,oGAAoG;AACpG,kGAAkG;AAClG,sGAAsG;AACtG,iGAAiG;AACjG,MAAM,UAAU,yBAAyB,CAAC,MAA+B;IACvE,yBAAyB,CAAC,IAAI,CAAC,CAAC;IAChC,0BAA0B,CAAC,MAAM,CAAC,CAAC;AACrC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explainEntityRuntimeWrite.d.ts","sourceRoot":"","sources":["../src/explainEntityRuntimeWrite.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,6BAA6B,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAMtG,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,sBAAsB,GAAG,6BAA6B,CAerG"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Describes what a guarded direct write means, as plain data. The guard seam reports only which slot was
|
|
2
|
+
// written; this turns that into the remedy, so a caller's reporter can phrase a warning without core
|
|
3
|
+
// owning a message format or a logger. Separately importable, so a build that never diagnoses these
|
|
4
|
+
// writes never carries the text.
|
|
5
|
+
export function explainEntityRuntimeWrite(slot) {
|
|
6
|
+
if (slot === 'binding-slot') {
|
|
7
|
+
return {
|
|
8
|
+
message: 'EntityRuntime.binding was written directly. The write was allowed but is not tracked, so the binding and the runtime can disagree.',
|
|
9
|
+
slot,
|
|
10
|
+
useInstead: ['attachEntityBinding', 'detachEntityBinding'],
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
return {
|
|
14
|
+
message: "An entity's runtime slot was written directly. The write was allowed, but bypassing the helper is how a runtime ends up on the wrong entity.",
|
|
15
|
+
slot,
|
|
16
|
+
useInstead: ['attachEntityBinding'],
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=explainEntityRuntimeWrite.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explainEntityRuntimeWrite.js","sourceRoot":"","sources":["../src/explainEntityRuntimeWrite.ts"],"names":[],"mappings":"AAEA,yGAAyG;AACzG,qGAAqG;AACrG,oGAAoG;AACpG,iCAAiC;AACjC,MAAM,UAAU,yBAAyB,CAAC,IAA4B;IACpE,IAAI,IAAI,KAAK,cAAc,EAAE,CAAC;QAC5B,OAAO;YACL,OAAO,EACL,oIAAoI;YACtI,IAAI;YACJ,UAAU,EAAE,CAAC,qBAAqB,EAAE,qBAAqB,CAAC;SAC3D,CAAC;IACJ,CAAC;IACD,OAAO;QACL,OAAO,EACL,8IAA8I;QAChJ,IAAI;QACJ,UAAU,EAAE,CAAC,qBAAqB,CAAC;KACpC,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flighthq/entity",
|
|
3
|
-
"version": "0.5.1-next.
|
|
3
|
+
"version": "0.5.1-next.799.62ef0c7",
|
|
4
4
|
"author": "Joshua Granick and other contributors",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -38,8 +38,7 @@
|
|
|
38
38
|
"clean:dist": "tsx ../../scripts/clean-package-dist.ts"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@flighthq/
|
|
42
|
-
"@flighthq/types": "0.5.1-next.731.0ae0aa4"
|
|
41
|
+
"@flighthq/types": "0.5.1-next.799.62ef0c7"
|
|
43
42
|
},
|
|
44
43
|
"devDependencies": {
|
|
45
44
|
"typescript": "^5.3.0"
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { LogEntry } from '@flighthq/types/contract';
|
|
1
|
+
import type { EntityRuntimeWriteSlot } from '@flighthq/types/contract';
|
|
3
2
|
import { EntityRuntimeKey } from '@flighthq/types/contract';
|
|
4
3
|
|
|
5
4
|
import { disableEntityRuntimeGuards, enableEntityRuntimeGuards } from './enableEntityRuntimeGuards';
|
|
@@ -7,69 +6,47 @@ import { createEntity } from './entity';
|
|
|
7
6
|
import { areEntityRuntimeGuardsEnabled, createGuardedEntity, createGuardedEntityRuntime } from './guards';
|
|
8
7
|
import { createEntityRuntime } from './runtime';
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
// Every case runs the write under a reporter that records the slot, so what is asserted is what the
|
|
10
|
+
// caller's reporter actually received — not that some sink somewhere was touched.
|
|
11
|
+
function recordSlots(write: () => void): readonly EntityRuntimeWriteSlot[] {
|
|
12
|
+
const slots: EntityRuntimeWriteSlot[] = [];
|
|
13
|
+
enableEntityRuntimeGuards((slot) => slots.push(slot));
|
|
13
14
|
try {
|
|
14
|
-
|
|
15
|
-
return getMemoryLogSinkEntries(sink);
|
|
15
|
+
write();
|
|
16
16
|
} finally {
|
|
17
|
-
|
|
17
|
+
disableEntityRuntimeGuards();
|
|
18
18
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
function messageOf(entry: Readonly<LogEntry>): string {
|
|
22
|
-
const data = entry.data;
|
|
23
|
-
return typeof data === 'string' ? data : String(data.message);
|
|
19
|
+
return slots;
|
|
24
20
|
}
|
|
25
21
|
|
|
26
22
|
describe('disableEntityRuntimeGuards', () => {
|
|
27
23
|
it('uninstalls both the proxies and the reporter', () => {
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
expect(entries.length).toBe(0);
|
|
24
|
+
const slots: EntityRuntimeWriteSlot[] = [];
|
|
25
|
+
enableEntityRuntimeGuards((slot) => slots.push(slot));
|
|
26
|
+
disableEntityRuntimeGuards();
|
|
27
|
+
createGuardedEntity(createEntity())[EntityRuntimeKey] = undefined;
|
|
28
|
+
expect(slots).toEqual([]);
|
|
34
29
|
expect(areEntityRuntimeGuardsEnabled()).toBe(false);
|
|
35
30
|
});
|
|
36
31
|
});
|
|
37
32
|
|
|
38
33
|
describe('enableEntityRuntimeGuards', () => {
|
|
39
|
-
it('
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
createGuardedEntity(createEntity())[EntityRuntimeKey] = undefined;
|
|
44
|
-
} finally {
|
|
45
|
-
disableEntityRuntimeGuards();
|
|
46
|
-
}
|
|
47
|
-
});
|
|
48
|
-
expect(entries.length).toBe(1);
|
|
49
|
-
// Names a function that actually exists: the point of the warning is that the caller can act on it.
|
|
50
|
-
expect(messageOf(entries[0])).toContain('attachEntityBinding');
|
|
34
|
+
it('REPORTS a direct runtime-slot write to the caller-supplied reporter', () => {
|
|
35
|
+
expect(recordSlots(() => (createGuardedEntity(createEntity())[EntityRuntimeKey] = undefined))).toEqual([
|
|
36
|
+
'runtime-slot',
|
|
37
|
+
]);
|
|
51
38
|
});
|
|
52
39
|
|
|
53
|
-
it('
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
createGuardedEntityRuntime(createEntityRuntime()).binding = null;
|
|
58
|
-
} finally {
|
|
59
|
-
disableEntityRuntimeGuards();
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
|
-
expect(entries.length).toBe(1);
|
|
63
|
-
expect(messageOf(entries[0])).toContain('attachEntityBinding');
|
|
64
|
-
expect(messageOf(entries[0])).toContain('detachEntityBinding');
|
|
40
|
+
it('REPORTS a direct binding-slot write separately', () => {
|
|
41
|
+
expect(recordSlots(() => (createGuardedEntityRuntime(createEntityRuntime()).binding = null))).toEqual([
|
|
42
|
+
'binding-slot',
|
|
43
|
+
]);
|
|
65
44
|
});
|
|
66
45
|
|
|
67
46
|
it('stays SILENT without the guard — the production default', () => {
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
});
|
|
72
|
-
expect(entries.length).toBe(0);
|
|
47
|
+
const slots: EntityRuntimeWriteSlot[] = [];
|
|
48
|
+
createGuardedEntity(createEntity())[EntityRuntimeKey] = undefined;
|
|
49
|
+
expect(slots).toEqual([]);
|
|
73
50
|
expect(areEntityRuntimeGuardsEnabled()).toBe(false);
|
|
74
51
|
});
|
|
75
52
|
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { explainEntityRuntimeWrite } from './explainEntityRuntimeWrite';
|
|
2
|
+
|
|
3
|
+
describe('explainEntityRuntimeWrite', () => {
|
|
4
|
+
it('names both binding helpers for a binding-slot write', () => {
|
|
5
|
+
const explanation = explainEntityRuntimeWrite('binding-slot');
|
|
6
|
+
expect(explanation.slot).toBe('binding-slot');
|
|
7
|
+
expect(explanation.useInstead).toEqual(['attachEntityBinding', 'detachEntityBinding']);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('names the allocating helper for a runtime-slot write', () => {
|
|
11
|
+
const explanation = explainEntityRuntimeWrite('runtime-slot');
|
|
12
|
+
expect(explanation.slot).toBe('runtime-slot');
|
|
13
|
+
expect(explanation.useInstead).toEqual(['attachEntityBinding']);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('distinguishes the two slots by message, not only by name', () => {
|
|
17
|
+
expect(explainEntityRuntimeWrite('binding-slot').message).not.toBe(
|
|
18
|
+
explainEntityRuntimeWrite('runtime-slot').message,
|
|
19
|
+
);
|
|
20
|
+
});
|
|
21
|
+
});
|