@bimetal/devtools 0.15.3 → 0.16.1
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/.tsbuildinfo +1 -1
- package/dist/causation-forest.d.ts +60 -0
- package/dist/causation-forest.d.ts.map +1 -0
- package/dist/causation-forest.js +191 -0
- package/dist/causation-forest.js.map +1 -0
- package/dist/index.d.ts +50 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +36 -7
- package/dist/index.js.map +1 -1
- package/dist/inspectable-json.d.ts +71 -0
- package/dist/inspectable-json.d.ts.map +1 -0
- package/dist/inspectable-json.js +225 -0
- package/dist/inspectable-json.js.map +1 -0
- package/dist/observed-bridge.d.ts +71 -0
- package/dist/observed-bridge.d.ts.map +1 -0
- package/dist/observed-bridge.js +165 -0
- package/dist/observed-bridge.js.map +1 -0
- package/dist/observed-helpers.d.ts +88 -0
- package/dist/observed-helpers.d.ts.map +1 -0
- package/dist/observed-helpers.js +76 -0
- package/dist/observed-helpers.js.map +1 -0
- package/dist/registry.d.ts +48 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +120 -0
- package/dist/registry.js.map +1 -1
- package/dist/replay-engine.d.ts +128 -0
- package/dist/replay-engine.d.ts.map +1 -0
- package/dist/replay-engine.js +199 -0
- package/dist/replay-engine.js.map +1 -0
- package/dist/snapshot-export.d.ts +73 -0
- package/dist/snapshot-export.d.ts.map +1 -0
- package/dist/snapshot-export.js +119 -0
- package/dist/snapshot-export.js.map +1 -0
- package/dist/time-travel.d.ts +62 -0
- package/dist/time-travel.d.ts.map +1 -0
- package/dist/time-travel.js +140 -0
- package/dist/time-travel.js.map +1 -0
- package/dist/types.d.ts +156 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AACvD,YAAY,EACV,gBAAgB,EAChB,uBAAuB,GACxB,MAAM,eAAe,CAAC;AAEvB,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAElE,YAAY,EACV,iBAAiB,EACjB,eAAe,EACf,iBAAiB,EACjB,aAAa,EACb,gBAAgB,EAChB,cAAc,EACd,oBAAoB,EACpB,aAAa,EACb,mBAAmB,EACnB,uBAAuB,EACvB,kBAAkB,EAClB,sBAAsB,EACtB,0BAA0B,EAC1B,YAAY,EACZ,UAAU,EACV,cAAc,EACd,YAAY,EACZ,0BAA0B,EAC1B,wBAAwB,EACxB,uBAAuB,EACvB,qBAAqB,EACrB,gBAAgB,EAChB,oBAAoB,EACpB,iBAAiB,EACjB,WAAW,EACX,WAAW,GACZ,MAAM,YAAY,CAAC;AAEpB,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,UAAU,EACV,qBAAqB,GACtB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACpE,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAEzE,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAC7D,YAAY,EACV,aAAa,EACb,SAAS,EACT,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AACrF,YAAY,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAElE,OAAO,EACL,wBAAwB,EACxB,2BAA2B,EAC3B,0BAA0B,GAC3B,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACV,yBAAyB,EACzB,wBAAwB,EACxB,4BAA4B,EAC5B,2BAA2B,EAC3B,2BAA2B,EAC3B,0BAA0B,GAC3B,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,aAAa,EACb,gBAAgB,GACjB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,gBAAgB,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAEpF;;;;;GAKG;AACH,YAAY,EACV,oBAAoB,EACpB,cAAc,EACd,gBAAgB,GACjB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,YAAY,EACZ,oBAAoB,EACpB,kBAAkB,EAClB,aAAa,EACb,cAAc,EACd,iBAAiB,EACjB,YAAY,GACb,MAAM,oBAAoB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,16 +1,45 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module @bimetal/devtools
|
|
3
3
|
*
|
|
4
|
-
* Event-Debugger primitives
|
|
4
|
+
* Event-Debugger primitives. Framework-agnostic registry +
|
|
5
5
|
* diagnostics-source for auditing event-sourced systems.
|
|
6
6
|
*
|
|
7
|
-
* **Architektur-Leitplanken** (
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* -
|
|
11
|
-
*
|
|
12
|
-
*
|
|
7
|
+
* **Architektur-Leitplanken** (verriegelt durch
|
|
8
|
+
* `__tests__/architecture.test.ts`):
|
|
9
|
+
*
|
|
10
|
+
* - **Beobachtungs-Core ist passiv.** Registry + Diagnostics-Source +
|
|
11
|
+
* ReplayEngine + TimeTravelController + Causation-Forest mutieren
|
|
12
|
+
* nichts, lesen nur. Sub-Komponenten verriegeln das mit Boundary-Tests.
|
|
13
|
+
*
|
|
14
|
+
* - **Zwei dokumentierte Convenience-Allowlists** ab v0.16.4/v0.16.5:
|
|
15
|
+
* 1. Der Bridge-Wrapper in `observed-bridge.ts` implementiert eine
|
|
16
|
+
* eigene Forward-Semantik statt die rohe Utility zu umhüllen.
|
|
17
|
+
* Read-only-Architecture-Test hat einen explizit dokumentierten
|
|
18
|
+
* Allowlist-Eintrag mit Doc-Verweis
|
|
19
|
+
* (`16-bridges-inspector-architecture.md` §Leitplanke 1).
|
|
20
|
+
* 2. `observed-helpers.ts` liefert Wrapper für die Erzeugung
|
|
21
|
+
* beobachteter Stores (Default-Adapter aus `@bimetal/event-sourcing`).
|
|
22
|
+
* Convenience-Wrapper für Konstruktor-APIs, kein Mutations-API auf
|
|
23
|
+
* einem fremden Subject. Eigene Verriegelung durch dedizierte
|
|
24
|
+
* Runtime-Tests in `observed-helpers.test.ts`.
|
|
25
|
+
*
|
|
26
|
+
* - **Kein hartes `@bimetal/broker`-/`@bimetal/calendar-data`-Dependency.**
|
|
27
|
+
* Domain-Andock bleibt strukturell.
|
|
28
|
+
*
|
|
29
|
+
* - **Explizite Registry, keine globalen Singletons, keine
|
|
30
|
+
* Auto-Registrierung.** Konsument erzeugt `createDevtoolsRegistry()`
|
|
31
|
+
* und reicht die Instanz an Helper/UI durch. Memory-Pattern aus
|
|
32
|
+
* v0.14.0 weitergeführt.
|
|
33
|
+
*
|
|
34
|
+
* - **Production-Build-Strip via `import.meta.env.DEV`-Branch im
|
|
35
|
+
* Konsumenten.**
|
|
13
36
|
*/
|
|
14
37
|
export { createDevtoolsRegistry } from './registry.js';
|
|
15
38
|
export { createDiagnosticsSource } from './diagnostics-source.js';
|
|
39
|
+
export { createReplayEngine, applyEventToSnapshot, foldEvents, createInitialSnapshot, } from './replay-engine.js';
|
|
40
|
+
export { toInspectableJson, jsonDiff } from './inspectable-json.js';
|
|
41
|
+
export { buildCausationForest } from './causation-forest.js';
|
|
42
|
+
export { createObservedBridge, createBridgeDiagnostics } from './observed-bridge.js';
|
|
43
|
+
export { createObservedEventStore, createObservedSyncTransport, createObservedReplayTarget, } from './observed-helpers.js';
|
|
44
|
+
export { exportSnapshot, serializeSnapshot, parseSnapshot, SNAPSHOT_VERSION, } from './snapshot-export.js';
|
|
16
45
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAMvD,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AA+BlE,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,UAAU,EACV,qBAAqB,GACtB,MAAM,oBAAoB,CAAC;AAE5B,OAAO,EAAE,iBAAiB,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAGpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAQ7D,OAAO,EAAE,oBAAoB,EAAE,uBAAuB,EAAE,MAAM,sBAAsB,CAAC;AAGrF,OAAO,EACL,wBAAwB,EACxB,2BAA2B,EAC3B,0BAA0B,GAC3B,MAAM,uBAAuB,CAAC;AAU/B,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,aAAa,EACb,gBAAgB,GACjB,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `toInspectableJson` + `jsonDiff` — pure Normalisierung + Diff für die
|
|
3
|
+
* Devtools-Inspector-Sicht (v0.16.2 Sub 2).
|
|
4
|
+
*
|
|
5
|
+
* **Warum normalisieren.** Aggregate-State enthält oft `Map`, `Set`,
|
|
6
|
+
* `Date`, `BigInt` — die kein gültiges JSON sind. Render und Diff brauchen
|
|
7
|
+
* eine einheitliche, vergleichbare Form. Die Normalisierung läuft **einmal
|
|
8
|
+
* vor jedem Render oder Diff**, danach operiert alles auf reinen JS-Werten
|
|
9
|
+
* (`string | number | boolean | null | undefined | object | array`).
|
|
10
|
+
*
|
|
11
|
+
* **Renderer-agnostisch.** Die Funktion weiß nichts über React, kein
|
|
12
|
+
* Stil-Konzept, kein UI-Format. Sie returnt rein normalisierte Werte;
|
|
13
|
+
* der UI-Konsument formt sie zum Display.
|
|
14
|
+
*
|
|
15
|
+
* **Map-Awareness an genau einer Stelle.** Wenn Map → Object hier zentral
|
|
16
|
+
* passiert, müssen Diff-Engine und JSON-Renderer keine eigene Special-Case-
|
|
17
|
+
* Logik haben. Vermeidet Drift zwischen „was sieht der User" und
|
|
18
|
+
* „was wird diffed".
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Normalisiert beliebige Werte für JSON-Render und Diff.
|
|
22
|
+
*
|
|
23
|
+
* Verriegelt:
|
|
24
|
+
* - Primitives (`string`, `number`, `boolean`, `null`, `undefined`) durchgereicht
|
|
25
|
+
* - `bigint` → string (`"123n"`-Form vermieden — direkte Stringification)
|
|
26
|
+
* - `function`, `symbol` → `null` (nicht inspectable, Inspector zeigt sie nicht)
|
|
27
|
+
* - `Date` → ISO-String via `.toISOString()`
|
|
28
|
+
* - `Error` → `{ name, message }` (Stack bewusst weggelassen — Cycles + Noise)
|
|
29
|
+
* - `Map` → Object (string-Keys; Nicht-String-Keys via `String(key)`)
|
|
30
|
+
* - `Set` → Array (Werte rekursiv normalisiert)
|
|
31
|
+
* - `Array` → Array (Elemente rekursiv normalisiert)
|
|
32
|
+
* - Object → Object (Keys rekursiv normalisiert)
|
|
33
|
+
* - Zyklen → `'[circular]'`-Sentinel (über `WeakSet`-Tracking)
|
|
34
|
+
*/
|
|
35
|
+
export declare function toInspectableJson(value: unknown): unknown;
|
|
36
|
+
export type JsonDiffKind = 'added' | 'removed' | 'changed';
|
|
37
|
+
export interface JsonDiffEntry {
|
|
38
|
+
/** JSON-Pointer-ähnlicher Pfad: `''` für Wurzel, `a.b.c` für nested, `xs[0]` für Array-Index. */
|
|
39
|
+
readonly path: string;
|
|
40
|
+
/**
|
|
41
|
+
* Richtung der Änderung:
|
|
42
|
+
* - `'added'`: Property/Element/Wert war vorher nicht da, jetzt da.
|
|
43
|
+
* - `'removed'`: war vorher da, jetzt nicht mehr.
|
|
44
|
+
* - `'changed'`: war vorher da, ist jetzt da, mit anderem Wert.
|
|
45
|
+
*
|
|
46
|
+
* Macht den Unterschied zwischen Presence-Wechseln (gleicher
|
|
47
|
+
* `undefined`-Wert auf beiden Seiten) und Value-Wechseln explizit —
|
|
48
|
+
* UI kann die Richtung farblich oder als Label rendern.
|
|
49
|
+
*/
|
|
50
|
+
readonly kind: JsonDiffKind;
|
|
51
|
+
readonly before: unknown;
|
|
52
|
+
readonly after: unknown;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Diff zwischen zwei beliebigen Werten. Beide werden ZUERST via
|
|
56
|
+
* `toInspectableJson` normalisiert; der eigentliche Vergleich operiert
|
|
57
|
+
* auf reinem JSON-Form (kein Map/Set/Date-Special-Case mehr).
|
|
58
|
+
*
|
|
59
|
+
* Output: flat array von `JsonDiffEntry`. Renderer-agnostisch — UI
|
|
60
|
+
* entscheidet, wie sie darstellt (Tree, Liste, Side-by-Side).
|
|
61
|
+
*
|
|
62
|
+
* **Was als Änderung zählt:**
|
|
63
|
+
* - Identische Werte (`===` oder deep-equal nach Normalisierung) → kein Eintrag.
|
|
64
|
+
* - Primitive Änderung → ein Eintrag mit Pfad.
|
|
65
|
+
* - Hinzugefügter Key → `before: undefined, after: value`.
|
|
66
|
+
* - Entfernter Key → `before: value, after: undefined`.
|
|
67
|
+
* - Container-Typ-Wechsel (Array → Object) → ein Eintrag auf Wurzel-Pfad, keine Detail-Rekursion.
|
|
68
|
+
* - Array-Länge ändert sich → Diff-Einträge pro betroffenem Index.
|
|
69
|
+
*/
|
|
70
|
+
export declare function jsonDiff(before: unknown, after: unknown): JsonDiffEntry[];
|
|
71
|
+
//# sourceMappingURL=inspectable-json.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inspectable-json.d.ts","sourceRoot":"","sources":["../src/inspectable-json.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAIH;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAEzD;AA0CD,MAAM,MAAM,YAAY,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;AAE3D,MAAM,WAAW,aAAa;IAC5B,iGAAiG;IACjG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB;;;;;;;;;OASG;IACH,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,GAAG,aAAa,EAAE,CAMzE"}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `toInspectableJson` + `jsonDiff` — pure Normalisierung + Diff für die
|
|
3
|
+
* Devtools-Inspector-Sicht (v0.16.2 Sub 2).
|
|
4
|
+
*
|
|
5
|
+
* **Warum normalisieren.** Aggregate-State enthält oft `Map`, `Set`,
|
|
6
|
+
* `Date`, `BigInt` — die kein gültiges JSON sind. Render und Diff brauchen
|
|
7
|
+
* eine einheitliche, vergleichbare Form. Die Normalisierung läuft **einmal
|
|
8
|
+
* vor jedem Render oder Diff**, danach operiert alles auf reinen JS-Werten
|
|
9
|
+
* (`string | number | boolean | null | undefined | object | array`).
|
|
10
|
+
*
|
|
11
|
+
* **Renderer-agnostisch.** Die Funktion weiß nichts über React, kein
|
|
12
|
+
* Stil-Konzept, kein UI-Format. Sie returnt rein normalisierte Werte;
|
|
13
|
+
* der UI-Konsument formt sie zum Display.
|
|
14
|
+
*
|
|
15
|
+
* **Map-Awareness an genau einer Stelle.** Wenn Map → Object hier zentral
|
|
16
|
+
* passiert, müssen Diff-Engine und JSON-Renderer keine eigene Special-Case-
|
|
17
|
+
* Logik haben. Vermeidet Drift zwischen „was sieht der User" und
|
|
18
|
+
* „was wird diffed".
|
|
19
|
+
*/
|
|
20
|
+
// ── toInspectableJson ────────────────────────────────────────────
|
|
21
|
+
/**
|
|
22
|
+
* Normalisiert beliebige Werte für JSON-Render und Diff.
|
|
23
|
+
*
|
|
24
|
+
* Verriegelt:
|
|
25
|
+
* - Primitives (`string`, `number`, `boolean`, `null`, `undefined`) durchgereicht
|
|
26
|
+
* - `bigint` → string (`"123n"`-Form vermieden — direkte Stringification)
|
|
27
|
+
* - `function`, `symbol` → `null` (nicht inspectable, Inspector zeigt sie nicht)
|
|
28
|
+
* - `Date` → ISO-String via `.toISOString()`
|
|
29
|
+
* - `Error` → `{ name, message }` (Stack bewusst weggelassen — Cycles + Noise)
|
|
30
|
+
* - `Map` → Object (string-Keys; Nicht-String-Keys via `String(key)`)
|
|
31
|
+
* - `Set` → Array (Werte rekursiv normalisiert)
|
|
32
|
+
* - `Array` → Array (Elemente rekursiv normalisiert)
|
|
33
|
+
* - Object → Object (Keys rekursiv normalisiert)
|
|
34
|
+
* - Zyklen → `'[circular]'`-Sentinel (über `WeakSet`-Tracking)
|
|
35
|
+
*/
|
|
36
|
+
export function toInspectableJson(value) {
|
|
37
|
+
return toInspectable(value, new WeakSet());
|
|
38
|
+
}
|
|
39
|
+
function toInspectable(value, seen) {
|
|
40
|
+
if (value === null || value === undefined)
|
|
41
|
+
return value;
|
|
42
|
+
const t = typeof value;
|
|
43
|
+
if (t === 'string' || t === 'number' || t === 'boolean')
|
|
44
|
+
return value;
|
|
45
|
+
if (t === 'bigint')
|
|
46
|
+
return value.toString();
|
|
47
|
+
if (t === 'function' || t === 'symbol')
|
|
48
|
+
return null;
|
|
49
|
+
if (value instanceof Date)
|
|
50
|
+
return value.toISOString();
|
|
51
|
+
if (value instanceof Error) {
|
|
52
|
+
return { name: value.name, message: value.message };
|
|
53
|
+
}
|
|
54
|
+
if (t !== 'object')
|
|
55
|
+
return value;
|
|
56
|
+
// Ab hier sind alle Werte object-typed → Cycle-Tracking via WeakSet
|
|
57
|
+
if (seen.has(value))
|
|
58
|
+
return '[circular]';
|
|
59
|
+
seen.add(value);
|
|
60
|
+
if (value instanceof Map) {
|
|
61
|
+
const out = {};
|
|
62
|
+
for (const [k, v] of value) {
|
|
63
|
+
const key = typeof k === 'string' ? k : String(k);
|
|
64
|
+
out[key] = toInspectable(v, seen);
|
|
65
|
+
}
|
|
66
|
+
return out;
|
|
67
|
+
}
|
|
68
|
+
if (value instanceof Set) {
|
|
69
|
+
return Array.from(value, (v) => toInspectable(v, seen));
|
|
70
|
+
}
|
|
71
|
+
if (Array.isArray(value)) {
|
|
72
|
+
return value.map((v) => toInspectable(v, seen));
|
|
73
|
+
}
|
|
74
|
+
// Plain object: nur enumerable own-keys, keine Prototypen-Felder
|
|
75
|
+
const out = {};
|
|
76
|
+
for (const k of Object.keys(value)) {
|
|
77
|
+
out[k] = toInspectable(value[k], seen);
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Diff zwischen zwei beliebigen Werten. Beide werden ZUERST via
|
|
83
|
+
* `toInspectableJson` normalisiert; der eigentliche Vergleich operiert
|
|
84
|
+
* auf reinem JSON-Form (kein Map/Set/Date-Special-Case mehr).
|
|
85
|
+
*
|
|
86
|
+
* Output: flat array von `JsonDiffEntry`. Renderer-agnostisch — UI
|
|
87
|
+
* entscheidet, wie sie darstellt (Tree, Liste, Side-by-Side).
|
|
88
|
+
*
|
|
89
|
+
* **Was als Änderung zählt:**
|
|
90
|
+
* - Identische Werte (`===` oder deep-equal nach Normalisierung) → kein Eintrag.
|
|
91
|
+
* - Primitive Änderung → ein Eintrag mit Pfad.
|
|
92
|
+
* - Hinzugefügter Key → `before: undefined, after: value`.
|
|
93
|
+
* - Entfernter Key → `before: value, after: undefined`.
|
|
94
|
+
* - Container-Typ-Wechsel (Array → Object) → ein Eintrag auf Wurzel-Pfad, keine Detail-Rekursion.
|
|
95
|
+
* - Array-Länge ändert sich → Diff-Einträge pro betroffenem Index.
|
|
96
|
+
*/
|
|
97
|
+
export function jsonDiff(before, after) {
|
|
98
|
+
const normBefore = toInspectableJson(before);
|
|
99
|
+
const normAfter = toInspectableJson(after);
|
|
100
|
+
const result = [];
|
|
101
|
+
diffRec(normBefore, normAfter, '', result);
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Klassifiziert eine primitive-Änderung anhand der Werte. Wird nur im
|
|
106
|
+
* primitive-Branch (kein Object/Array-Container) gerufen. Object-Presence-
|
|
107
|
+
* und Array-out-of-bounds-Cases setzen ihren `kind` explizit.
|
|
108
|
+
*/
|
|
109
|
+
function classifyPrimitive(before, after) {
|
|
110
|
+
if (before === undefined)
|
|
111
|
+
return 'added';
|
|
112
|
+
if (after === undefined)
|
|
113
|
+
return 'removed';
|
|
114
|
+
return 'changed';
|
|
115
|
+
}
|
|
116
|
+
function diffRec(before, after, path, result) {
|
|
117
|
+
if (isSame(before, after))
|
|
118
|
+
return;
|
|
119
|
+
const beforeIsObj = before !== null && typeof before === 'object';
|
|
120
|
+
const afterIsObj = after !== null && typeof after === 'object';
|
|
121
|
+
// Ein Wert ist primitive / null → Wechsel als ein Eintrag
|
|
122
|
+
if (!beforeIsObj || !afterIsObj) {
|
|
123
|
+
result.push({ path, kind: classifyPrimitive(before, after), before, after });
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const beforeIsArray = Array.isArray(before);
|
|
127
|
+
const afterIsArray = Array.isArray(after);
|
|
128
|
+
// Container-Typ-Mismatch → ein Wurzel-Eintrag, keine Rekursion. Beide
|
|
129
|
+
// sind hier non-null objects, also kind='changed'.
|
|
130
|
+
if (beforeIsArray !== afterIsArray) {
|
|
131
|
+
result.push({ path, kind: 'changed', before, after });
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (beforeIsArray) {
|
|
135
|
+
const a = before;
|
|
136
|
+
const b = after;
|
|
137
|
+
const max = Math.max(a.length, b.length);
|
|
138
|
+
for (let i = 0; i < max; i++) {
|
|
139
|
+
const childPath = `${path}[${i}]`;
|
|
140
|
+
if (i < a.length && i < b.length) {
|
|
141
|
+
// Beide Indexe vorhanden → primitive-Recurse, kind via classify
|
|
142
|
+
diffRec(a[i], b[i], childPath, result);
|
|
143
|
+
}
|
|
144
|
+
else if (i < a.length) {
|
|
145
|
+
// Array verkürzt — Element entfernt (auch wenn value undefined)
|
|
146
|
+
result.push({ path: childPath, kind: 'removed', before: a[i], after: undefined });
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
// Array verlängert — Element hinzugefügt
|
|
150
|
+
result.push({ path: childPath, kind: 'added', before: undefined, after: b[i] });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
// Beide Objects — Property-Presence über hasOwnProperty getrennt vom
|
|
156
|
+
// Value-Vergleich, damit `{x: undefined}` vs `{}` sichtbar wird
|
|
157
|
+
// (Pilot-Finding: Calendar-DomainStore setzt modified-instance-Fields
|
|
158
|
+
// explizit auf `recurrence: undefined`, exceptions: undefined`).
|
|
159
|
+
const beforeObj = before;
|
|
160
|
+
const afterObj = after;
|
|
161
|
+
const keys = new Set([...Object.keys(beforeObj), ...Object.keys(afterObj)]);
|
|
162
|
+
const sortedKeys = [...keys].sort();
|
|
163
|
+
for (const k of sortedKeys) {
|
|
164
|
+
const childPath = path === '' ? k : `${path}.${k}`;
|
|
165
|
+
const beforeHas = Object.prototype.hasOwnProperty.call(beforeObj, k);
|
|
166
|
+
const afterHas = Object.prototype.hasOwnProperty.call(afterObj, k);
|
|
167
|
+
if (beforeHas && afterHas) {
|
|
168
|
+
diffRec(beforeObj[k], afterObj[k], childPath, result);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
// Property präsenz-asymmetrisch — kind explizit, auch wenn der Wert
|
|
172
|
+
// undefined ist (sonst wäre der Presence-Wechsel unsichtbar).
|
|
173
|
+
if (beforeHas) {
|
|
174
|
+
result.push({ path: childPath, kind: 'removed', before: beforeObj[k], after: undefined });
|
|
175
|
+
}
|
|
176
|
+
else if (afterHas) {
|
|
177
|
+
result.push({ path: childPath, kind: 'added', before: undefined, after: afterObj[k] });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Deep-equal auf bereits via `toInspectableJson` normalisierten Werten.
|
|
183
|
+
* Reine JS-Werte — kein Map/Set/Date-Handling mehr nötig.
|
|
184
|
+
*/
|
|
185
|
+
function isSame(a, b) {
|
|
186
|
+
if (a === b)
|
|
187
|
+
return true;
|
|
188
|
+
if (a === null || b === null)
|
|
189
|
+
return false;
|
|
190
|
+
if (a === undefined || b === undefined)
|
|
191
|
+
return false;
|
|
192
|
+
if (typeof a !== typeof b)
|
|
193
|
+
return false;
|
|
194
|
+
if (typeof a !== 'object')
|
|
195
|
+
return false;
|
|
196
|
+
const aIsArr = Array.isArray(a);
|
|
197
|
+
const bIsArr = Array.isArray(b);
|
|
198
|
+
if (aIsArr !== bIsArr)
|
|
199
|
+
return false;
|
|
200
|
+
if (aIsArr) {
|
|
201
|
+
const aa = a;
|
|
202
|
+
const bb = b;
|
|
203
|
+
if (aa.length !== bb.length)
|
|
204
|
+
return false;
|
|
205
|
+
for (let i = 0; i < aa.length; i++) {
|
|
206
|
+
if (!isSame(aa[i], bb[i]))
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
const aObj = a;
|
|
212
|
+
const bObj = b;
|
|
213
|
+
const aKeys = Object.keys(aObj);
|
|
214
|
+
const bKeys = Object.keys(bObj);
|
|
215
|
+
if (aKeys.length !== bKeys.length)
|
|
216
|
+
return false;
|
|
217
|
+
for (const k of aKeys) {
|
|
218
|
+
if (!Object.prototype.hasOwnProperty.call(bObj, k))
|
|
219
|
+
return false;
|
|
220
|
+
if (!isSame(aObj[k], bObj[k]))
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
//# sourceMappingURL=inspectable-json.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inspectable-json.js","sourceRoot":"","sources":["../src/inspectable-json.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `createObservedBridge` — Devtools-Wrapper-Helper für die EventStore→
|
|
3
|
+
* Broker-Forward-Semantik (v0.16.4 Sub 1).
|
|
4
|
+
*
|
|
5
|
+
* Verriegelt aus `design/calendar/16-bridges-inspector-architecture.md`:
|
|
6
|
+
*
|
|
7
|
+
* **Ehrlichkeits-Vertrag — eigene Forward-Implementation:**
|
|
8
|
+
* Der Helper ruft die rohe EventStore→Broker-Bridge-Utility aus
|
|
9
|
+
* `@bimetal/event-sourcing` NICHT auf (die exakten Identifier sind im
|
|
10
|
+
* Architecture-Test verriegelt — diese Quelle vermeidet sie auch in
|
|
11
|
+
* Kommentaren, damit der Token-Scan über die ganze Datei einfach
|
|
12
|
+
* bleibt). Die rohe Bridge hat aktuell keinen beobachtbaren Hook —
|
|
13
|
+
* ein Wrapper, der sie nutzte, müsste entweder eine zweite Forward-
|
|
14
|
+
* Schiene erzeugen (jedes Event doppelt publiziert) oder die User-
|
|
15
|
+
* Subscribe-Reihenfolge raten. Stattdessen implementiert dieser Helper
|
|
16
|
+
* dieselbe Forward-Semantik (`subscribe` → `toTopic` → `publish` +
|
|
17
|
+
* Safe-Wrap-Error-Handler) selbst und hängt seinen Diagnostics-
|
|
18
|
+
* Subscriber direkt an die eigene Code-Stelle. Vertragsäquivalenz zur
|
|
19
|
+
* rohen Utility wird durch Tests verriegelt.
|
|
20
|
+
*
|
|
21
|
+
* **Keine neue Semantik:**
|
|
22
|
+
* - Kein Retry. `publish()`-Rejection wird via `onPublishError`-Hook
|
|
23
|
+
* propagiert, nicht wiederholt.
|
|
24
|
+
* - Kein Dedup.
|
|
25
|
+
* - Keine Backpressure. `publish()` läuft fire-and-forget (`.catch()`).
|
|
26
|
+
* - Keine Fehlerunterdrückung. Werfender User-Handler wird im
|
|
27
|
+
* Safe-Wrap gefangen (genau wie in der rohen Bridge), nicht
|
|
28
|
+
* stillschweigend verschluckt.
|
|
29
|
+
*
|
|
30
|
+
* **publish-error-Reihenfolge:**
|
|
31
|
+
* Der Record landet IMMER im rolling-window — VOR dem User-Handler-
|
|
32
|
+
* Aufruf. Sonst sähe der Inspector nur Erfolgspfade, wenn der User
|
|
33
|
+
* den Fehler verschluckt.
|
|
34
|
+
*/
|
|
35
|
+
import type { BridgeDiagnosticsSink, BridgeDiagnosticsSource, DomainEvent, EventStoreLike, PublishTargetLike, Unsubscribe } from './types.js';
|
|
36
|
+
import type { DevtoolsRegistry } from './registry.js';
|
|
37
|
+
export interface ObservedBridgeOptions {
|
|
38
|
+
readonly id: string;
|
|
39
|
+
readonly name: string;
|
|
40
|
+
readonly eventStore: EventStoreLike;
|
|
41
|
+
readonly target: PublishTargetLike;
|
|
42
|
+
/**
|
|
43
|
+
* Optional Label für die Source-Seite. Fallback-Kette:
|
|
44
|
+
* `sourceLabel ?? sourceStoreId ?? 'event-store'`.
|
|
45
|
+
*/
|
|
46
|
+
readonly sourceLabel?: string;
|
|
47
|
+
/** Pflicht — der Inspector zeigt source → target. */
|
|
48
|
+
readonly targetLabel: string;
|
|
49
|
+
/**
|
|
50
|
+
* Optional: wenn der EventStore in der Registry registriert ist,
|
|
51
|
+
* dessen `id`. Macht den Source-Endpoint im Inspector klickbar-
|
|
52
|
+
* verlinkbar (Future-Slice).
|
|
53
|
+
*/
|
|
54
|
+
readonly sourceStoreId?: string;
|
|
55
|
+
/** Wenn gesetzt, nur Events dieses Streams forwarden. */
|
|
56
|
+
readonly streamId?: string;
|
|
57
|
+
/** Topic-Ableitung pro Event. Default: `event.type`. */
|
|
58
|
+
readonly toTopic?: (event: DomainEvent) => string;
|
|
59
|
+
/**
|
|
60
|
+
* Aufgerufen NACH dem `bridge-publish-error`-Record (Pilot-
|
|
61
|
+
* Verriegelung). Default: konsolen-Log analog rohe Bridge.
|
|
62
|
+
*/
|
|
63
|
+
readonly onPublishError?: (error: unknown, event: DomainEvent, topic: string) => void;
|
|
64
|
+
}
|
|
65
|
+
export declare function createObservedBridge(registry: DevtoolsRegistry, options: ObservedBridgeOptions): Unsubscribe;
|
|
66
|
+
/**
|
|
67
|
+
* Bridge-Diagnostics-Subject: Listener-Pool wie `createDiagnosticsSource`,
|
|
68
|
+
* aber typed auf `BridgeRecord`. Throw-Isolation pro Listener.
|
|
69
|
+
*/
|
|
70
|
+
export declare function createBridgeDiagnostics(): BridgeDiagnosticsSource & BridgeDiagnosticsSink;
|
|
71
|
+
//# sourceMappingURL=observed-bridge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observed-bridge.d.ts","sourceRoot":"","sources":["../src/observed-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,KAAK,EACV,qBAAqB,EACrB,uBAAuB,EAIvB,WAAW,EACX,cAAc,EACd,iBAAiB,EACjB,WAAW,EACZ,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAEtD,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,UAAU,EAAE,cAAc,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IACnC;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,qDAAqD;IACrD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,yDAAyD;IACzD,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,wDAAwD;IACxD,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,MAAM,CAAC;IAClD;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,CACxB,KAAK,EAAE,OAAO,EACd,KAAK,EAAE,WAAW,EAClB,KAAK,EAAE,MAAM,KACV,IAAI,CAAC;CACX;AAED,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,qBAAqB,GAC7B,WAAW,CAgGb;AA2BD;;;GAGG;AACH,wBAAgB,uBAAuB,IAAI,uBAAuB,GAAG,qBAAqB,CAoBzF"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `createObservedBridge` — Devtools-Wrapper-Helper für die EventStore→
|
|
3
|
+
* Broker-Forward-Semantik (v0.16.4 Sub 1).
|
|
4
|
+
*
|
|
5
|
+
* Verriegelt aus `design/calendar/16-bridges-inspector-architecture.md`:
|
|
6
|
+
*
|
|
7
|
+
* **Ehrlichkeits-Vertrag — eigene Forward-Implementation:**
|
|
8
|
+
* Der Helper ruft die rohe EventStore→Broker-Bridge-Utility aus
|
|
9
|
+
* `@bimetal/event-sourcing` NICHT auf (die exakten Identifier sind im
|
|
10
|
+
* Architecture-Test verriegelt — diese Quelle vermeidet sie auch in
|
|
11
|
+
* Kommentaren, damit der Token-Scan über die ganze Datei einfach
|
|
12
|
+
* bleibt). Die rohe Bridge hat aktuell keinen beobachtbaren Hook —
|
|
13
|
+
* ein Wrapper, der sie nutzte, müsste entweder eine zweite Forward-
|
|
14
|
+
* Schiene erzeugen (jedes Event doppelt publiziert) oder die User-
|
|
15
|
+
* Subscribe-Reihenfolge raten. Stattdessen implementiert dieser Helper
|
|
16
|
+
* dieselbe Forward-Semantik (`subscribe` → `toTopic` → `publish` +
|
|
17
|
+
* Safe-Wrap-Error-Handler) selbst und hängt seinen Diagnostics-
|
|
18
|
+
* Subscriber direkt an die eigene Code-Stelle. Vertragsäquivalenz zur
|
|
19
|
+
* rohen Utility wird durch Tests verriegelt.
|
|
20
|
+
*
|
|
21
|
+
* **Keine neue Semantik:**
|
|
22
|
+
* - Kein Retry. `publish()`-Rejection wird via `onPublishError`-Hook
|
|
23
|
+
* propagiert, nicht wiederholt.
|
|
24
|
+
* - Kein Dedup.
|
|
25
|
+
* - Keine Backpressure. `publish()` läuft fire-and-forget (`.catch()`).
|
|
26
|
+
* - Keine Fehlerunterdrückung. Werfender User-Handler wird im
|
|
27
|
+
* Safe-Wrap gefangen (genau wie in der rohen Bridge), nicht
|
|
28
|
+
* stillschweigend verschluckt.
|
|
29
|
+
*
|
|
30
|
+
* **publish-error-Reihenfolge:**
|
|
31
|
+
* Der Record landet IMMER im rolling-window — VOR dem User-Handler-
|
|
32
|
+
* Aufruf. Sonst sähe der Inspector nur Erfolgspfade, wenn der User
|
|
33
|
+
* den Fehler verschluckt.
|
|
34
|
+
*/
|
|
35
|
+
export function createObservedBridge(registry, options) {
|
|
36
|
+
const toTopic = options.toTopic ?? defaultToTopic;
|
|
37
|
+
const userOnError = options.onPublishError ?? defaultPublishErrorHandler;
|
|
38
|
+
// Eigener Diagnostics-Subject pro Bridge — typisiert auf BridgeRecord.
|
|
39
|
+
const diagnostics = createBridgeDiagnostics();
|
|
40
|
+
// Source-Label-Fallback-Kette (Sub-1-Pilot-Schärfung):
|
|
41
|
+
// 1. explizites sourceLabel (Konsument hat etwas Konkretes im Kopf)
|
|
42
|
+
// 2. sourceStoreId (Identitäts-Label des registrierten Stores)
|
|
43
|
+
// 3. 'event-store' (generische Konstante als letzter Ausweg)
|
|
44
|
+
const source = {
|
|
45
|
+
label: options.sourceLabel ?? options.sourceStoreId ?? 'event-store',
|
|
46
|
+
...(options.sourceStoreId !== undefined ? { storeId: options.sourceStoreId } : {}),
|
|
47
|
+
};
|
|
48
|
+
const target = {
|
|
49
|
+
label: options.targetLabel,
|
|
50
|
+
};
|
|
51
|
+
const handle = {
|
|
52
|
+
id: options.id,
|
|
53
|
+
name: options.name,
|
|
54
|
+
kind: 'event-store-to-broker',
|
|
55
|
+
source,
|
|
56
|
+
target,
|
|
57
|
+
diagnostics,
|
|
58
|
+
};
|
|
59
|
+
// Registry-Registrierung VOR Subscribe — sonst landen die ersten
|
|
60
|
+
// Events in der Pipe, bevor der Inspector das Handle sieht.
|
|
61
|
+
const offRegister = registry.registerBridge(handle);
|
|
62
|
+
// EIGENE Forward-Implementation. KEIN Aufruf der rohen
|
|
63
|
+
// EventStore→Broker-Bridge-Utility aus event-sourcing — siehe
|
|
64
|
+
// Datei-Header zur Begründung (Token im Architecture-Test verriegelt).
|
|
65
|
+
//
|
|
66
|
+
// try/catch: wenn `subscribe()` synchron wirft, würde sonst die
|
|
67
|
+
// Bridge im Registry hängen bleiben (Handle registriert, kein
|
|
68
|
+
// Subscriber aktiv). Pilot-Finding Sub 1.
|
|
69
|
+
let offSubscribe;
|
|
70
|
+
try {
|
|
71
|
+
offSubscribe = options.eventStore.subscribe((event) => {
|
|
72
|
+
const topic = toTopic(event);
|
|
73
|
+
// 1. „forwarded"-Record emittieren VOR der publish-Call-Site —
|
|
74
|
+
// fire-and-forget heißt: wir markieren als forwarded, sobald
|
|
75
|
+
// wir den Wurf weggeschickt haben. Kein await, kein tookMs.
|
|
76
|
+
diagnostics.emit({
|
|
77
|
+
kind: 'bridge-event-forwarded',
|
|
78
|
+
topic,
|
|
79
|
+
event,
|
|
80
|
+
});
|
|
81
|
+
// 2. publish() fire-and-forget mit .catch() — wie in der rohen
|
|
82
|
+
// Bridge. Plus try/catch um den Aufruf selbst: strukturell ist
|
|
83
|
+
// publish() zwar Promise<void>, aber ein Adapter oder Test-
|
|
84
|
+
// Double kann synchron werfen. Ohne try/catch würde der Wurf
|
|
85
|
+
// aus dem EventStore-Subscriber rausfliegen und Record +
|
|
86
|
+
// User-Handler überspringen. Pilot-Finding Sub 1.
|
|
87
|
+
try {
|
|
88
|
+
Promise.resolve(options.target.publish(topic, event)).catch((error) => {
|
|
89
|
+
safeOnError(error, event, topic);
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
catch (syncError) {
|
|
93
|
+
safeOnError(syncError, event, topic);
|
|
94
|
+
}
|
|
95
|
+
}, options.streamId);
|
|
96
|
+
}
|
|
97
|
+
catch (subscribeError) {
|
|
98
|
+
offRegister();
|
|
99
|
+
throw subscribeError;
|
|
100
|
+
}
|
|
101
|
+
function safeOnError(error, event, topic) {
|
|
102
|
+
// Pilot-Verriegelung: Record ZUERST. Auch wenn der User-Handler
|
|
103
|
+
// wirft oder den Fehler verschluckt, ist er im Inspector sichtbar.
|
|
104
|
+
diagnostics.emit({
|
|
105
|
+
kind: 'bridge-publish-error',
|
|
106
|
+
topic,
|
|
107
|
+
event,
|
|
108
|
+
error: extractError(error),
|
|
109
|
+
});
|
|
110
|
+
// DANN User-Handler, in Safe-Wrap.
|
|
111
|
+
try {
|
|
112
|
+
userOnError(error, event, topic);
|
|
113
|
+
}
|
|
114
|
+
catch (innerError) {
|
|
115
|
+
// eslint-disable-next-line no-console
|
|
116
|
+
console.error('[@bimetal/devtools/observed-bridge] onPublishError handler itself threw — original error follows:', innerError, error);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return () => {
|
|
120
|
+
offSubscribe();
|
|
121
|
+
offRegister();
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function defaultToTopic(event) {
|
|
125
|
+
return event.type;
|
|
126
|
+
}
|
|
127
|
+
function defaultPublishErrorHandler(error, event, topic) {
|
|
128
|
+
// eslint-disable-next-line no-console
|
|
129
|
+
console.error(`[@bimetal/devtools/observed-bridge] publish failed for topic "${topic}" event ${event.envelope.id}:`, error);
|
|
130
|
+
}
|
|
131
|
+
function extractError(error) {
|
|
132
|
+
if (error instanceof Error) {
|
|
133
|
+
return error.stack !== undefined
|
|
134
|
+
? { message: error.message, stack: error.stack }
|
|
135
|
+
: { message: error.message };
|
|
136
|
+
}
|
|
137
|
+
return { message: String(error) };
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Bridge-Diagnostics-Subject: Listener-Pool wie `createDiagnosticsSource`,
|
|
141
|
+
* aber typed auf `BridgeRecord`. Throw-Isolation pro Listener.
|
|
142
|
+
*/
|
|
143
|
+
export function createBridgeDiagnostics() {
|
|
144
|
+
const listeners = new Set();
|
|
145
|
+
return {
|
|
146
|
+
emit(record) {
|
|
147
|
+
for (const listener of listeners) {
|
|
148
|
+
try {
|
|
149
|
+
listener(record);
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
// eslint-disable-next-line no-console
|
|
153
|
+
console.error('[@bimetal/devtools/observed-bridge] listener threw:', err);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
},
|
|
157
|
+
subscribe(listener) {
|
|
158
|
+
listeners.add(listener);
|
|
159
|
+
return () => {
|
|
160
|
+
listeners.delete(listener);
|
|
161
|
+
};
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=observed-bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observed-bridge.js","sourceRoot":"","sources":["../src/observed-bridge.ts"],"names":[],"mappings":"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"}
|