@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.
Files changed (40) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/causation-forest.d.ts +60 -0
  3. package/dist/causation-forest.d.ts.map +1 -0
  4. package/dist/causation-forest.js +191 -0
  5. package/dist/causation-forest.js.map +1 -0
  6. package/dist/index.d.ts +50 -8
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +36 -7
  9. package/dist/index.js.map +1 -1
  10. package/dist/inspectable-json.d.ts +71 -0
  11. package/dist/inspectable-json.d.ts.map +1 -0
  12. package/dist/inspectable-json.js +225 -0
  13. package/dist/inspectable-json.js.map +1 -0
  14. package/dist/observed-bridge.d.ts +71 -0
  15. package/dist/observed-bridge.d.ts.map +1 -0
  16. package/dist/observed-bridge.js +165 -0
  17. package/dist/observed-bridge.js.map +1 -0
  18. package/dist/observed-helpers.d.ts +88 -0
  19. package/dist/observed-helpers.d.ts.map +1 -0
  20. package/dist/observed-helpers.js +76 -0
  21. package/dist/observed-helpers.js.map +1 -0
  22. package/dist/registry.d.ts +48 -1
  23. package/dist/registry.d.ts.map +1 -1
  24. package/dist/registry.js +120 -0
  25. package/dist/registry.js.map +1 -1
  26. package/dist/replay-engine.d.ts +128 -0
  27. package/dist/replay-engine.d.ts.map +1 -0
  28. package/dist/replay-engine.js +199 -0
  29. package/dist/replay-engine.js.map +1 -0
  30. package/dist/snapshot-export.d.ts +73 -0
  31. package/dist/snapshot-export.d.ts.map +1 -0
  32. package/dist/snapshot-export.js +119 -0
  33. package/dist/snapshot-export.js.map +1 -0
  34. package/dist/time-travel.d.ts +62 -0
  35. package/dist/time-travel.d.ts.map +1 -0
  36. package/dist/time-travel.js +140 -0
  37. package/dist/time-travel.js.map +1 -0
  38. package/dist/types.d.ts +156 -2
  39. package/dist/types.d.ts.map +1 -1
  40. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;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,WAAW,EACX,WAAW,GACZ,MAAM,YAAY,CAAC"}
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 (v0.14). Framework-agnostic registry +
4
+ * Event-Debugger primitives. Framework-agnostic registry +
5
5
  * diagnostics-source for auditing event-sourced systems.
6
6
  *
7
- * **Architektur-Leitplanken** (siehe `design/calendar/14-event-debugger-architecture.md`):
8
- * - Strukturelle Anbindung an EventStore — kein `EventStore`-Runtime-Import.
9
- * - Kein hartes `@bimetal/broker`-/`@bimetal/calendar-data`-Dependency.
10
- * - Devtools mutieren nichts: kein `append`, kein `dispatch`, kein `publish`.
11
- * - Explizite Registry, keine globalen Singletons, keine Auto-Registrierung.
12
- * - Production-Build-Strip via `import.meta.env.DEV`-Branch im Konsumenten.
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;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAMvD,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC"}
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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"}