@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
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Observe-Helper für Konsumenten (v0.16.5 Sub 2).
|
|
3
|
+
*
|
|
4
|
+
* Bisher lebten diese Helper in `apps/demo/src/event-inspector/setup.ts`
|
|
5
|
+
* als Singleton-gebundene Funktionen. Mit der `@bimetal/devtools-react`-
|
|
6
|
+
* Extraktion ziehen sie hierher um — als reine TS-Wrapper, weil sie
|
|
7
|
+
* registry-agnostisch sind und keinen React-Bezug haben.
|
|
8
|
+
*
|
|
9
|
+
* **Vertrag:**
|
|
10
|
+
* - `registry` ist Pflicht-Argument (kein impliziter Singleton).
|
|
11
|
+
* - `id` ist Pflicht-Argument (kein versteckter Module-Level-Counter).
|
|
12
|
+
* - Pro Helper-Aufruf eine eigene `DiagnosticsSource` (sonst fan-outet
|
|
13
|
+
* ein gemeinsamer Source jedes Event an N Registry-Listener).
|
|
14
|
+
* - Returnt `unregister`-Unsubscribe; Konsument MUSS aufrufen im
|
|
15
|
+
* useEffect-Cleanup / Variant-Teardown.
|
|
16
|
+
*
|
|
17
|
+
* **Boundary:** Die Helper hängen an `@bimetal/event-sourcing`
|
|
18
|
+
* (`createInMemoryEventStore` als Default + `EventStore`-Type). Das ist
|
|
19
|
+
* konsistent mit der bestehenden devtools→event-sourcing-Abhängigkeit;
|
|
20
|
+
* Bridge-Wrapper aus v0.16.4 macht das genauso.
|
|
21
|
+
*/
|
|
22
|
+
import type { EventStore } from '@bimetal/event-sourcing';
|
|
23
|
+
import type { DevtoolsRegistry } from './registry.js';
|
|
24
|
+
import type { AggregateLike, ProjectionLike, ReplayTargetHandle } from './replay-engine.js';
|
|
25
|
+
import type { DiagnosticsSource, Unsubscribe } from './types.js';
|
|
26
|
+
export interface ObservedEventStoreOptions {
|
|
27
|
+
readonly id: string;
|
|
28
|
+
/** Default: gleicher Wert wie `id`. */
|
|
29
|
+
readonly name?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface ObservedEventStoreHandle {
|
|
32
|
+
readonly eventStore: EventStore;
|
|
33
|
+
/**
|
|
34
|
+
* Demontiert den Store aus dem Registry. Konsument MUSS aufrufen,
|
|
35
|
+
* sonst akkumulieren stale Store-Handles im Inspector über
|
|
36
|
+
* Variant-/Component-Wechsel hinweg.
|
|
37
|
+
*/
|
|
38
|
+
readonly unregister: Unsubscribe;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Drop-in-Wrapper für `createInMemoryEventStore()`, der den Store
|
|
42
|
+
* gleichzeitig im `DevtoolsRegistry` anmeldet.
|
|
43
|
+
*
|
|
44
|
+
* Pro Aufruf eine eigene `DiagnosticsSource`. Sub-Klasse-Effekt: Events
|
|
45
|
+
* landen 1:1 in `state.events`, keine N-fach-Duplikate.
|
|
46
|
+
*/
|
|
47
|
+
export declare function createObservedEventStore(registry: DevtoolsRegistry, options: ObservedEventStoreOptions): ObservedEventStoreHandle;
|
|
48
|
+
export interface ObservedSyncTransportOptions {
|
|
49
|
+
readonly id: string;
|
|
50
|
+
/** Default: gleicher Wert wie `id`. */
|
|
51
|
+
readonly name?: string;
|
|
52
|
+
}
|
|
53
|
+
export interface ObservedSyncTransportHandle {
|
|
54
|
+
/**
|
|
55
|
+
* `DiagnosticsSink`, den der Konsument an `createInMemorySync*` /
|
|
56
|
+
* `createWebSocketSync` als `{ diagnostics }` weitergibt. Pro Aufruf
|
|
57
|
+
* eine eigene Source — keine Cross-Transport-Lecks.
|
|
58
|
+
*/
|
|
59
|
+
readonly diagnostics: DiagnosticsSource;
|
|
60
|
+
/** Stabile transportId, die der Adapter via `{ transportId }` nutzt. */
|
|
61
|
+
readonly transportId: string;
|
|
62
|
+
readonly unregister: Unsubscribe;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Erzeugt eine `DiagnosticsSource` + meldet den Transport beim
|
|
66
|
+
* Registry an. Konsument bindet `diagnostics` + `transportId` an seinen
|
|
67
|
+
* Sync-Adapter; Records erscheinen dann im Sync-Tab des Inspectors.
|
|
68
|
+
*/
|
|
69
|
+
export declare function createObservedSyncTransport(registry: DevtoolsRegistry, options: ObservedSyncTransportOptions): ObservedSyncTransportHandle;
|
|
70
|
+
export interface ObservedReplayTargetOptions<S, R> {
|
|
71
|
+
readonly id: string;
|
|
72
|
+
/** Default: gleicher Wert wie `id`. */
|
|
73
|
+
readonly name?: string;
|
|
74
|
+
readonly eventStore: ReplayTargetHandle<S, R>['eventStore'];
|
|
75
|
+
readonly streamId: string;
|
|
76
|
+
readonly aggregate: AggregateLike<S>;
|
|
77
|
+
readonly projection: ProjectionLike<R>;
|
|
78
|
+
}
|
|
79
|
+
export interface ObservedReplayTargetHandle {
|
|
80
|
+
readonly unregister: Unsubscribe;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Registriert ein passives ReplayTarget am Registry. Engine baut den
|
|
84
|
+
* Stream-State live durch; im Inspector erscheint das Target als
|
|
85
|
+
* Eintrag im State-Tab.
|
|
86
|
+
*/
|
|
87
|
+
export declare function createObservedReplayTarget<S, R>(registry: DevtoolsRegistry, options: ObservedReplayTargetOptions<S, R>): ObservedReplayTargetHandle;
|
|
88
|
+
//# sourceMappingURL=observed-helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observed-helpers.d.ts","sourceRoot":"","sources":["../src/observed-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AAE1D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC5F,OAAO,KAAK,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAIjE,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,uCAAuC;IACvC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC;;;;OAIG;IACH,QAAQ,CAAC,UAAU,EAAE,WAAW,CAAC;CAClC;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,yBAAyB,GACjC,wBAAwB,CAU1B;AAID,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,uCAAuC;IACvC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,2BAA2B;IAC1C;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,iBAAiB,CAAC;IACxC,wEAAwE;IACxE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,WAAW,CAAC;CAClC;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CACzC,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,4BAA4B,GACpC,2BAA2B,CAY7B;AAID,MAAM,WAAW,2BAA2B,CAAC,CAAC,EAAE,CAAC;IAC/C,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,uCAAuC;IACvC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,UAAU,EAAE,kBAAkB,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;IAC5D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;IACrC,QAAQ,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;CACxC;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,UAAU,EAAE,WAAW,CAAC;CAClC;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,CAAC,EAAE,CAAC,EAC7C,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,2BAA2B,CAAC,CAAC,EAAE,CAAC,CAAC,GACzC,0BAA0B,CAU5B"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Observe-Helper für Konsumenten (v0.16.5 Sub 2).
|
|
3
|
+
*
|
|
4
|
+
* Bisher lebten diese Helper in `apps/demo/src/event-inspector/setup.ts`
|
|
5
|
+
* als Singleton-gebundene Funktionen. Mit der `@bimetal/devtools-react`-
|
|
6
|
+
* Extraktion ziehen sie hierher um — als reine TS-Wrapper, weil sie
|
|
7
|
+
* registry-agnostisch sind und keinen React-Bezug haben.
|
|
8
|
+
*
|
|
9
|
+
* **Vertrag:**
|
|
10
|
+
* - `registry` ist Pflicht-Argument (kein impliziter Singleton).
|
|
11
|
+
* - `id` ist Pflicht-Argument (kein versteckter Module-Level-Counter).
|
|
12
|
+
* - Pro Helper-Aufruf eine eigene `DiagnosticsSource` (sonst fan-outet
|
|
13
|
+
* ein gemeinsamer Source jedes Event an N Registry-Listener).
|
|
14
|
+
* - Returnt `unregister`-Unsubscribe; Konsument MUSS aufrufen im
|
|
15
|
+
* useEffect-Cleanup / Variant-Teardown.
|
|
16
|
+
*
|
|
17
|
+
* **Boundary:** Die Helper hängen an `@bimetal/event-sourcing`
|
|
18
|
+
* (`createInMemoryEventStore` als Default + `EventStore`-Type). Das ist
|
|
19
|
+
* konsistent mit der bestehenden devtools→event-sourcing-Abhängigkeit;
|
|
20
|
+
* Bridge-Wrapper aus v0.16.4 macht das genauso.
|
|
21
|
+
*/
|
|
22
|
+
import { createInMemoryEventStore } from '@bimetal/event-sourcing';
|
|
23
|
+
import { createDiagnosticsSource } from './diagnostics-source.js';
|
|
24
|
+
/**
|
|
25
|
+
* Drop-in-Wrapper für `createInMemoryEventStore()`, der den Store
|
|
26
|
+
* gleichzeitig im `DevtoolsRegistry` anmeldet.
|
|
27
|
+
*
|
|
28
|
+
* Pro Aufruf eine eigene `DiagnosticsSource`. Sub-Klasse-Effekt: Events
|
|
29
|
+
* landen 1:1 in `state.events`, keine N-fach-Duplikate.
|
|
30
|
+
*/
|
|
31
|
+
export function createObservedEventStore(registry, options) {
|
|
32
|
+
const source = createDiagnosticsSource();
|
|
33
|
+
const eventStore = createInMemoryEventStore({ diagnostics: source });
|
|
34
|
+
const unregister = registry.registerEventStore({
|
|
35
|
+
id: options.id,
|
|
36
|
+
name: options.name ?? options.id,
|
|
37
|
+
eventStore,
|
|
38
|
+
diagnostics: source,
|
|
39
|
+
});
|
|
40
|
+
return { eventStore, unregister };
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Erzeugt eine `DiagnosticsSource` + meldet den Transport beim
|
|
44
|
+
* Registry an. Konsument bindet `diagnostics` + `transportId` an seinen
|
|
45
|
+
* Sync-Adapter; Records erscheinen dann im Sync-Tab des Inspectors.
|
|
46
|
+
*/
|
|
47
|
+
export function createObservedSyncTransport(registry, options) {
|
|
48
|
+
const diagnostics = createDiagnosticsSource();
|
|
49
|
+
const unregister = registry.registerSyncTransport({
|
|
50
|
+
id: options.id,
|
|
51
|
+
name: options.name ?? options.id,
|
|
52
|
+
diagnostics,
|
|
53
|
+
});
|
|
54
|
+
return {
|
|
55
|
+
diagnostics,
|
|
56
|
+
transportId: options.id,
|
|
57
|
+
unregister,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Registriert ein passives ReplayTarget am Registry. Engine baut den
|
|
62
|
+
* Stream-State live durch; im Inspector erscheint das Target als
|
|
63
|
+
* Eintrag im State-Tab.
|
|
64
|
+
*/
|
|
65
|
+
export function createObservedReplayTarget(registry, options) {
|
|
66
|
+
const unregister = registry.registerReplayTarget({
|
|
67
|
+
id: options.id,
|
|
68
|
+
name: options.name ?? options.id,
|
|
69
|
+
eventStore: options.eventStore,
|
|
70
|
+
streamId: options.streamId,
|
|
71
|
+
aggregate: options.aggregate,
|
|
72
|
+
projection: options.projection,
|
|
73
|
+
});
|
|
74
|
+
return { unregister };
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=observed-helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"observed-helpers.js","sourceRoot":"","sources":["../src/observed-helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,wBAAwB,EAAE,MAAM,yBAAyB,CAAC;AAEnE,OAAO,EAAE,uBAAuB,EAAE,MAAM,yBAAyB,CAAC;AAuBlE;;;;;;GAMG;AACH,MAAM,UAAU,wBAAwB,CACtC,QAA0B,EAC1B,OAAkC;IAElC,MAAM,MAAM,GAAG,uBAAuB,EAAE,CAAC;IACzC,MAAM,UAAU,GAAG,wBAAwB,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,CAAC,CAAC;IACrE,MAAM,UAAU,GAAG,QAAQ,CAAC,kBAAkB,CAAC;QAC7C,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,EAAE;QAChC,UAAU;QACV,WAAW,EAAE,MAAM;KACpB,CAAC,CAAC;IACH,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC;AACpC,CAAC;AAsBD;;;;GAIG;AACH,MAAM,UAAU,2BAA2B,CACzC,QAA0B,EAC1B,OAAqC;IAErC,MAAM,WAAW,GAAG,uBAAuB,EAAE,CAAC;IAC9C,MAAM,UAAU,GAAG,QAAQ,CAAC,qBAAqB,CAAC;QAChD,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,EAAE;QAChC,WAAW;KACZ,CAAC,CAAC;IACH,OAAO;QACL,WAAW;QACX,WAAW,EAAE,OAAO,CAAC,EAAE;QACvB,UAAU;KACX,CAAC;AACJ,CAAC;AAkBD;;;;GAIG;AACH,MAAM,UAAU,0BAA0B,CACxC,QAA0B,EAC1B,OAA0C;IAE1C,MAAM,UAAU,GAAG,QAAQ,CAAC,oBAAoB,CAAC;QAC/C,EAAE,EAAE,OAAO,CAAC,EAAE;QACd,IAAI,EAAE,OAAO,CAAC,IAAI,IAAI,OAAO,CAAC,EAAE;QAChC,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,QAAQ,EAAE,OAAO,CAAC,QAAQ;QAC1B,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,UAAU,EAAE,OAAO,CAAC,UAAU;KAC/B,CAAC,CAAC;IACH,OAAO,EAAE,UAAU,EAAE,CAAC;AACxB,CAAC"}
|
package/dist/registry.d.ts
CHANGED
|
@@ -17,16 +17,63 @@
|
|
|
17
17
|
* **Identity-Modell:** Stores werden über ihre `id` geführt, `name` ist
|
|
18
18
|
* Display-only. Duplikat-Check ist auf `id`.
|
|
19
19
|
*/
|
|
20
|
-
import type { DevtoolsState, EventStoreHandle, Unsubscribe } from './types.js';
|
|
20
|
+
import type { BridgeHandle, DevtoolsState, EventStoreHandle, SyncTransportHandle, Unsubscribe } from './types.js';
|
|
21
|
+
import { type ReplayTargetHandle } from './replay-engine.js';
|
|
22
|
+
import { type TimeTravelController } from './time-travel.js';
|
|
21
23
|
export interface DevtoolsRegistryOptions {
|
|
22
24
|
/**
|
|
23
25
|
* Rolling-window size for captured events. Older events drop out.
|
|
24
26
|
* Default 10 000.
|
|
25
27
|
*/
|
|
26
28
|
readonly maxEvents?: number;
|
|
29
|
+
/**
|
|
30
|
+
* Rolling-window size for captured sync records (v0.15.5).
|
|
31
|
+
* Default 10 000.
|
|
32
|
+
*/
|
|
33
|
+
readonly maxSyncRecords?: number;
|
|
34
|
+
/**
|
|
35
|
+
* Rolling-window size for captured bridge records (v0.16.4).
|
|
36
|
+
* Default 10 000.
|
|
37
|
+
*/
|
|
38
|
+
readonly maxBridgeRecords?: number;
|
|
27
39
|
}
|
|
28
40
|
export interface DevtoolsRegistry {
|
|
29
41
|
registerEventStore(handle: EventStoreHandle): Unsubscribe;
|
|
42
|
+
registerSyncTransport(handle: SyncTransportHandle): Unsubscribe;
|
|
43
|
+
/**
|
|
44
|
+
* Registriert ein ReplayTarget (v0.16.0). Engine wird intern erzeugt
|
|
45
|
+
* und folded den Stream live durch — der Konsument hat nichts zu
|
|
46
|
+
* subscribieren. Snapshot ist über `state.replayTargets` reichbar.
|
|
47
|
+
*
|
|
48
|
+
* Multiple Targets pro Stream sind erlaubt (verschiedene `handle.id`):
|
|
49
|
+
* z. B. ein Domain-Aggregate plus ein statistisches Side-Read-Model.
|
|
50
|
+
* Doppelte `handle.id` wirft.
|
|
51
|
+
*/
|
|
52
|
+
registerReplayTarget(handle: ReplayTargetHandle): Unsubscribe;
|
|
53
|
+
/**
|
|
54
|
+
* Factory für einen ephemeren TimeTravelController über das mit
|
|
55
|
+
* `targetId` registrierte ReplayTarget (v0.16.2). Controller lebt
|
|
56
|
+
* außerhalb des `DevtoolsState`-Snapshots — Slider-Bewegungen churchen
|
|
57
|
+
* nicht die Always-On-Listener im Inspector.
|
|
58
|
+
*
|
|
59
|
+
* Wirft, wenn `targetId` nicht registriert ist. Mehrere Controller
|
|
60
|
+
* auf demselben Target sind erlaubt (z. B. zwei UI-Panels, die
|
|
61
|
+
* unabhängig durch die Historie scrollen).
|
|
62
|
+
*/
|
|
63
|
+
createTimeTravelController(targetId: string): TimeTravelController;
|
|
64
|
+
/**
|
|
65
|
+
* Registriert eine Bridge (v0.16.4). Konsument liefert ein
|
|
66
|
+
* `BridgeHandle` mit eigener `BridgeDiagnosticsSource`; die Registry
|
|
67
|
+
* subscribet darauf und pumpt `BridgeRecord`s ins rolling-window.
|
|
68
|
+
*
|
|
69
|
+
* Doppelte `handle.id` wirft. Mehrere Bridges auf demselben Source/
|
|
70
|
+
* Target-Paar sind erlaubt (z. B. zwei verschiedene `toTopic`-Mappings).
|
|
71
|
+
*
|
|
72
|
+
* Üblicher Aufrufer ist `createObservedBridge`, der das Handle intern
|
|
73
|
+
* baut. Konsumenten mit eigener Bridge-Implementation können direkt
|
|
74
|
+
* registrieren.
|
|
75
|
+
*/
|
|
76
|
+
registerBridge(handle: BridgeHandle): Unsubscribe;
|
|
30
77
|
readonly state: DevtoolsState;
|
|
31
78
|
subscribe(listener: (state: DevtoolsState) => void): Unsubscribe;
|
|
32
79
|
}
|
package/dist/registry.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EACV,YAAY,EAIZ,aAAa,EACb,gBAAgB,EAKhB,mBAAmB,EACnB,WAAW,EACZ,MAAM,YAAY,CAAC;AACpB,OAAO,EAAyC,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACpG,OAAO,EAA8B,KAAK,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAEzF,MAAM,WAAW,uBAAuB;IACtC;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC;;;OAGG;IACH,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACpC;AAED,MAAM,WAAW,gBAAgB;IAC/B,kBAAkB,CAAC,MAAM,EAAE,gBAAgB,GAAG,WAAW,CAAC;IAC1D,qBAAqB,CAAC,MAAM,EAAE,mBAAmB,GAAG,WAAW,CAAC;IAChE;;;;;;;;OAQG;IACH,oBAAoB,CAAC,MAAM,EAAE,kBAAkB,GAAG,WAAW,CAAC;IAC9D;;;;;;;;;OASG;IACH,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG,oBAAoB,CAAC;IACnE;;;;;;;;;;;OAWG;IACH,cAAc,CAAC,MAAM,EAAE,YAAY,GAAG,WAAW,CAAC;IAClD,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC;IAC9B,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,GAAG,WAAW,CAAC;CAClE;AAMD,wBAAgB,sBAAsB,CACpC,OAAO,GAAE,uBAA4B,GACpC,gBAAgB,CA2NlB"}
|
package/dist/registry.js
CHANGED
|
@@ -17,20 +17,46 @@
|
|
|
17
17
|
* **Identity-Modell:** Stores werden über ihre `id` geführt, `name` ist
|
|
18
18
|
* Display-only. Duplikat-Check ist auf `id`.
|
|
19
19
|
*/
|
|
20
|
+
import { createReplayEngine } from './replay-engine.js';
|
|
21
|
+
import { createTimeTravelController } from './time-travel.js';
|
|
20
22
|
const DEFAULT_MAX_EVENTS = 10_000;
|
|
23
|
+
const DEFAULT_MAX_SYNC_RECORDS = 10_000;
|
|
24
|
+
const DEFAULT_MAX_BRIDGE_RECORDS = 10_000;
|
|
21
25
|
export function createDevtoolsRegistry(options = {}) {
|
|
22
26
|
const maxEvents = options.maxEvents ?? DEFAULT_MAX_EVENTS;
|
|
27
|
+
const maxSyncRecords = options.maxSyncRecords ?? DEFAULT_MAX_SYNC_RECORDS;
|
|
28
|
+
const maxBridgeRecords = options.maxBridgeRecords ?? DEFAULT_MAX_BRIDGE_RECORDS;
|
|
23
29
|
const eventStores = new Map();
|
|
24
30
|
const events = [];
|
|
31
|
+
const syncTransports = new Map();
|
|
32
|
+
const syncRecords = [];
|
|
33
|
+
const replayTargets = new Map();
|
|
34
|
+
const bridges = new Map();
|
|
35
|
+
const bridgeRecords = [];
|
|
25
36
|
const listeners = new Set();
|
|
26
37
|
let nextSeq = 1;
|
|
27
38
|
// Reference-stable snapshot. Invalidated on every mutation in notify().
|
|
28
39
|
let cachedSnapshot = null;
|
|
29
40
|
function getSnapshot() {
|
|
30
41
|
if (cachedSnapshot === null) {
|
|
42
|
+
const targetsView = [];
|
|
43
|
+
for (const { handle, engine } of replayTargets.values()) {
|
|
44
|
+
targetsView.push({
|
|
45
|
+
id: handle.id,
|
|
46
|
+
name: handle.name,
|
|
47
|
+
streamId: handle.streamId,
|
|
48
|
+
projectionVersion: handle.projection.version,
|
|
49
|
+
snapshot: engine.snapshot,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
31
52
|
cachedSnapshot = {
|
|
32
53
|
eventStores: Array.from(eventStores.values()),
|
|
33
54
|
events: events.slice(),
|
|
55
|
+
syncTransports: Array.from(syncTransports.values()),
|
|
56
|
+
syncRecords: syncRecords.slice(),
|
|
57
|
+
replayTargets: targetsView,
|
|
58
|
+
bridges: Array.from(bridges.values()),
|
|
59
|
+
bridgeRecords: bridgeRecords.slice(),
|
|
34
60
|
};
|
|
35
61
|
}
|
|
36
62
|
return cachedSnapshot;
|
|
@@ -54,6 +80,18 @@ export function createDevtoolsRegistry(options = {}) {
|
|
|
54
80
|
events.shift();
|
|
55
81
|
}
|
|
56
82
|
}
|
|
83
|
+
function pushSyncRecord(captured) {
|
|
84
|
+
syncRecords.push(captured);
|
|
85
|
+
while (syncRecords.length > maxSyncRecords) {
|
|
86
|
+
syncRecords.shift();
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function pushBridgeRecord(captured) {
|
|
90
|
+
bridgeRecords.push(captured);
|
|
91
|
+
while (bridgeRecords.length > maxBridgeRecords) {
|
|
92
|
+
bridgeRecords.shift();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
57
95
|
return {
|
|
58
96
|
registerEventStore(handle) {
|
|
59
97
|
if (eventStores.has(handle.id)) {
|
|
@@ -80,6 +118,88 @@ export function createDevtoolsRegistry(options = {}) {
|
|
|
80
118
|
notify();
|
|
81
119
|
};
|
|
82
120
|
},
|
|
121
|
+
registerSyncTransport(handle) {
|
|
122
|
+
if (syncTransports.has(handle.id)) {
|
|
123
|
+
throw new Error(`@bimetal/devtools: SyncTransport with id "${handle.id}" already registered`);
|
|
124
|
+
}
|
|
125
|
+
syncTransports.set(handle.id, { id: handle.id, name: handle.name });
|
|
126
|
+
const offDiag = handle.diagnostics.subscribe((record) => {
|
|
127
|
+
if (typeof record.kind === 'string' && record.kind.startsWith('sync-')) {
|
|
128
|
+
pushSyncRecord({
|
|
129
|
+
seq: nextSeq++,
|
|
130
|
+
transportId: handle.id,
|
|
131
|
+
transportName: handle.name,
|
|
132
|
+
capturedAt: Date.now(),
|
|
133
|
+
record: record,
|
|
134
|
+
});
|
|
135
|
+
notify();
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
notify();
|
|
139
|
+
return () => {
|
|
140
|
+
offDiag();
|
|
141
|
+
syncTransports.delete(handle.id);
|
|
142
|
+
notify();
|
|
143
|
+
};
|
|
144
|
+
},
|
|
145
|
+
registerReplayTarget(handle) {
|
|
146
|
+
if (replayTargets.has(handle.id)) {
|
|
147
|
+
throw new Error(`@bimetal/devtools: ReplayTarget with id "${handle.id}" already registered`);
|
|
148
|
+
}
|
|
149
|
+
const engine = createReplayEngine(handle);
|
|
150
|
+
replayTargets.set(handle.id, { handle, engine });
|
|
151
|
+
// Engine-Mutationen invalidieren den Registry-Snapshot, sodass
|
|
152
|
+
// useSyncExternalStore-Konsumenten neu rendern.
|
|
153
|
+
const offEngine = engine.subscribe(() => notify());
|
|
154
|
+
notify();
|
|
155
|
+
return () => {
|
|
156
|
+
offEngine();
|
|
157
|
+
engine.dispose();
|
|
158
|
+
replayTargets.delete(handle.id);
|
|
159
|
+
notify();
|
|
160
|
+
};
|
|
161
|
+
},
|
|
162
|
+
createTimeTravelController(targetId) {
|
|
163
|
+
const entry = replayTargets.get(targetId);
|
|
164
|
+
if (!entry) {
|
|
165
|
+
throw new Error(`@bimetal/devtools: ReplayTarget with id "${targetId}" not registered — register first via registerReplayTarget()`);
|
|
166
|
+
}
|
|
167
|
+
return createTimeTravelController(entry.handle);
|
|
168
|
+
},
|
|
169
|
+
registerBridge(handle) {
|
|
170
|
+
if (bridges.has(handle.id)) {
|
|
171
|
+
throw new Error(`@bimetal/devtools: Bridge with id "${handle.id}" already registered`);
|
|
172
|
+
}
|
|
173
|
+
bridges.set(handle.id, {
|
|
174
|
+
id: handle.id,
|
|
175
|
+
name: handle.name,
|
|
176
|
+
kind: handle.kind,
|
|
177
|
+
source: handle.source,
|
|
178
|
+
target: handle.target,
|
|
179
|
+
});
|
|
180
|
+
const offDiag = handle.diagnostics.subscribe((record) => {
|
|
181
|
+
// structuredClone: das Forward-Pattern in createObservedBridge
|
|
182
|
+
// emittiert den Record VOR dem Publish-Call. Würde der Broker
|
|
183
|
+
// (oder ein Test-Stub) das Event-Objekt mutieren, würde sich der
|
|
184
|
+
// bereits captured Record nachträglich ändern. Pilot-Finding
|
|
185
|
+
// Sub 1. Clone hier zentral, damit jeder Captor (auch ein
|
|
186
|
+
// custom-Konsumenten-Handle) den Snapshot-Vertrag bekommt.
|
|
187
|
+
pushBridgeRecord({
|
|
188
|
+
seq: nextSeq++,
|
|
189
|
+
bridgeId: handle.id,
|
|
190
|
+
bridgeName: handle.name,
|
|
191
|
+
capturedAt: Date.now(),
|
|
192
|
+
record: structuredClone(record),
|
|
193
|
+
});
|
|
194
|
+
notify();
|
|
195
|
+
});
|
|
196
|
+
notify();
|
|
197
|
+
return () => {
|
|
198
|
+
offDiag();
|
|
199
|
+
bridges.delete(handle.id);
|
|
200
|
+
notify();
|
|
201
|
+
};
|
|
202
|
+
},
|
|
83
203
|
get state() {
|
|
84
204
|
return getSnapshot();
|
|
85
205
|
},
|
package/dist/registry.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `createReplayEngine` — pure synchrone Fold-Function über einen Stream.
|
|
3
|
+
*
|
|
4
|
+
* Architektur-Verträge aus `design/calendar/16-replay-target-architecture.md`:
|
|
5
|
+
* - **Passive Beobachtung**: Engine ruft NIEMALS `eventStore.append`,
|
|
6
|
+
* `store.dispatch` oder `broker.publish`. Architecture-Test verriegelt.
|
|
7
|
+
* - **subscribe-first + buffer + read + drain**: kein Race-Window zwischen
|
|
8
|
+
* Catch-up und Live-Tail (v0.15.7-Lehre).
|
|
9
|
+
* - **Sticky Error**: bei `apply`-Wurf wird `status = 'error'` und KEIN
|
|
10
|
+
* weiteres Event mehr gefolded — sonst entstände Fold auf staleren
|
|
11
|
+
* Zustand und der User sähe vorgegaukelten Aggregat-Stand.
|
|
12
|
+
* - **Recovery nur via `dispose()` + neuer `createReplayEngine`-Call.**
|
|
13
|
+
* Engine kennt keine `clearError`-Operation.
|
|
14
|
+
*
|
|
15
|
+
* Implementations-Notes:
|
|
16
|
+
* - `handleEvent` ist **synchron** (Architektur-Vertrag).
|
|
17
|
+
* - Initial-Read ist async (das ist OK — `read()` ist der einzige
|
|
18
|
+
* await; danach läuft alles synchron).
|
|
19
|
+
* - `snapshot`-Getter ist reference-stable bis zur nächsten Mutation
|
|
20
|
+
* (analog Devtools-Registry, useSyncExternalStore-Vertrag).
|
|
21
|
+
*/
|
|
22
|
+
import type { DomainEvent, Unsubscribe } from '@bimetal/event-sourcing';
|
|
23
|
+
import type { EventStoreLike } from './types.js';
|
|
24
|
+
/**
|
|
25
|
+
* Domain-agnostisches Aggregate-Interface. Bewusst KEIN `handle(state, command)`
|
|
26
|
+
* — Command-Semantik gehört nicht in den Devtools-Vertrag.
|
|
27
|
+
*/
|
|
28
|
+
export interface AggregateLike<S> {
|
|
29
|
+
readonly initialState: S;
|
|
30
|
+
apply(state: S, event: DomainEvent): S;
|
|
31
|
+
}
|
|
32
|
+
export interface ProjectionLike<R> {
|
|
33
|
+
readonly initialState: R;
|
|
34
|
+
apply(readModel: R, event: DomainEvent): R;
|
|
35
|
+
readonly version: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Strukturelles SnapshotStore-Interface. Optional — Engine liest nur,
|
|
39
|
+
* schreibt NIE rein. Aktuell für Konsistenz-Checks reserviert (in v0.16.0
|
|
40
|
+
* noch nicht genutzt, kommt mit späterer Slice).
|
|
41
|
+
*/
|
|
42
|
+
export interface SnapshotStoreLike {
|
|
43
|
+
load(streamId: string): Promise<SnapshotLike | null>;
|
|
44
|
+
}
|
|
45
|
+
export interface SnapshotLike {
|
|
46
|
+
readonly state: unknown;
|
|
47
|
+
readonly projectionVersion: number;
|
|
48
|
+
readonly version: number;
|
|
49
|
+
readonly timestamp: number;
|
|
50
|
+
}
|
|
51
|
+
export interface ReplayTargetHandle<S = unknown, R = unknown> {
|
|
52
|
+
readonly id: string;
|
|
53
|
+
readonly name: string;
|
|
54
|
+
readonly eventStore: EventStoreLike;
|
|
55
|
+
readonly streamId: string;
|
|
56
|
+
readonly aggregate: AggregateLike<S>;
|
|
57
|
+
readonly projection: ProjectionLike<R>;
|
|
58
|
+
readonly snapshotStore?: SnapshotStoreLike;
|
|
59
|
+
}
|
|
60
|
+
export interface ReplayEngineSnapshot<S, R> {
|
|
61
|
+
readonly state: S;
|
|
62
|
+
readonly readModel: R;
|
|
63
|
+
/** `envelope.version` des zuletzt erfolgreich applied Events; `0` bei initialState. */
|
|
64
|
+
readonly lastAppliedVersion: number;
|
|
65
|
+
readonly status: 'replaying' | 'live' | 'error';
|
|
66
|
+
/**
|
|
67
|
+
* Bei `status='error'`: gesetzt, mit `event` als der Event, der den
|
|
68
|
+
* Fehler auslöste — oder als der zuletzt blockierte Event (nach
|
|
69
|
+
* status='error' aktualisiert sich `event` mit jedem eingehenden,
|
|
70
|
+
* nicht-applied Event, damit der User „letzter blockierter Event war v9"
|
|
71
|
+
* sehen kann). `event=null` zeigt: Fehler kam aus `read()`, nicht aus `apply()`.
|
|
72
|
+
*/
|
|
73
|
+
readonly lastError?: {
|
|
74
|
+
readonly event: DomainEvent | null;
|
|
75
|
+
readonly error: Error;
|
|
76
|
+
};
|
|
77
|
+
readonly lastAppliedEventId?: string;
|
|
78
|
+
/**
|
|
79
|
+
* `metadata.schemaVersion` des zuletzt erfolgreich applied Events.
|
|
80
|
+
* Konsumenten-UI vergleicht das mit der `projection.version` aus dem
|
|
81
|
+
* Handle, um Schema-Drift sichtbar zu machen (v0.16-Architektur-Doc
|
|
82
|
+
* Leitplanke 6).
|
|
83
|
+
*/
|
|
84
|
+
readonly lastAppliedSchemaVersion?: number;
|
|
85
|
+
/** Events nach `status='error'` eingegangen, aber NICHT applied. */
|
|
86
|
+
readonly blockedEventCount: number;
|
|
87
|
+
}
|
|
88
|
+
export interface ReplayEngine<S, R> {
|
|
89
|
+
/** Reference-stable bis zur nächsten Mutation. */
|
|
90
|
+
readonly snapshot: ReplayEngineSnapshot<S, R>;
|
|
91
|
+
subscribe(listener: () => void): Unsubscribe;
|
|
92
|
+
/** Cleanup: stoppt Live-Subscriber, befreit Listener. */
|
|
93
|
+
dispose(): void;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Initialer Snapshot für eine fresh angesetzte Replay-Sequenz. `status`
|
|
97
|
+
* beginnt bei `'replaying'`; der Konsument (Engine ODER TimeTravelController)
|
|
98
|
+
* entscheidet selbst, wann er auf `'live'`/`'ready'` umschaltet.
|
|
99
|
+
*/
|
|
100
|
+
export declare function createInitialSnapshot<S, R>(aggregate: AggregateLike<S>, projection: ProjectionLike<R>): ReplayEngineSnapshot<S, R>;
|
|
101
|
+
/**
|
|
102
|
+
* Pure synchroner Fold von genau einem Event auf einen Snapshot.
|
|
103
|
+
*
|
|
104
|
+
* **Atomarer Commit:** beide `apply`-Aufrufe (aggregate + projection)
|
|
105
|
+
* laufen ZUERST in lokale `next*`-Variablen. Erst wenn BEIDE
|
|
106
|
+
* erfolgreich sind, wird das neue Snapshot-Objekt mit allen Tracking-
|
|
107
|
+
* Feldern (`lastAppliedVersion`, `lastAppliedEventId`,
|
|
108
|
+
* `lastAppliedSchemaVersion`) zurückgegeben. Wirft `projection.apply`,
|
|
109
|
+
* ist `nextState` lokal verloren, der Input-`snap` bleibt unverändert —
|
|
110
|
+
* Half-Update unmöglich.
|
|
111
|
+
*
|
|
112
|
+
* **Sticky-Error:** Bei Input-`snap.status === 'error'` wird der Event
|
|
113
|
+
* NICHT applied. Output trägt `blockedEventCount + 1` und ein
|
|
114
|
+
* aktualisiertes `lastError.event` (auf den jeweils zuletzt blockierten
|
|
115
|
+
* Event), damit der User „letzter blockierter Event war v9" sehen kann.
|
|
116
|
+
*
|
|
117
|
+
* **Nichts read/subscribe-bezogenes.** Reine Funktion: Snapshot rein,
|
|
118
|
+
* Event rein, Snapshot raus. Live-Engine UND TimeTravelController
|
|
119
|
+
* konsumieren dieselbe Funktion.
|
|
120
|
+
*/
|
|
121
|
+
export declare function applyEventToSnapshot<S, R>(snap: ReplayEngineSnapshot<S, R>, event: DomainEvent, aggregate: AggregateLike<S>, projection: ProjectionLike<R>): ReplayEngineSnapshot<S, R>;
|
|
122
|
+
/**
|
|
123
|
+
* Convenience: Reduce über ein Array von Events — equivalent zu
|
|
124
|
+
* sequenziellem `applyEventToSnapshot`. Pure Funktion, keine Side-Effects.
|
|
125
|
+
*/
|
|
126
|
+
export declare function foldEvents<S, R>(events: readonly DomainEvent[], aggregate: AggregateLike<S>, projection: ProjectionLike<R>, initial: ReplayEngineSnapshot<S, R>): ReplayEngineSnapshot<S, R>;
|
|
127
|
+
export declare function createReplayEngine<S, R>(handle: ReplayTargetHandle<S, R>): ReplayEngine<S, R>;
|
|
128
|
+
//# sourceMappingURL=replay-engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"replay-engine.d.ts","sourceRoot":"","sources":["../src/replay-engine.ts"],"names":[],"mappings":"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"}
|