@noego/wood 0.4.6 → 0.4.7
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/bin/wood.js +6 -1
- package/dist/client/app_env.cjs +39 -0
- package/dist/client/app_env.cjs.map +1 -0
- package/dist/client/app_env.d.cts +7 -0
- package/dist/client/app_env.d.ts +7 -0
- package/dist/client/app_env.js +14 -0
- package/dist/client/app_env.js.map +1 -0
- package/dist/client/index.cjs +33 -9
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +5 -1
- package/dist/client/index.d.ts +5 -1
- package/dist/client/index.js +32 -9
- package/dist/client/index.js.map +1 -1
- package/dist/codegen/channel_map_generator.cjs +102 -0
- package/dist/codegen/channel_map_generator.cjs.map +1 -0
- package/dist/codegen/channel_map_generator.d.cts +6 -0
- package/dist/codegen/channel_map_generator.d.ts +6 -0
- package/dist/codegen/channel_map_generator.js +78 -0
- package/dist/codegen/channel_map_generator.js.map +1 -0
- package/dist/codegen/controller_registration_generator.cjs +6 -1
- package/dist/codegen/controller_registration_generator.cjs.map +1 -1
- package/dist/codegen/controller_registration_generator.js +6 -1
- package/dist/codegen/controller_registration_generator.js.map +1 -1
- package/dist/codegen/generated_file_notice.cjs +40 -0
- package/dist/codegen/generated_file_notice.cjs.map +1 -0
- package/dist/codegen/generated_file_notice.d.cts +3 -0
- package/dist/codegen/generated_file_notice.d.ts +3 -0
- package/dist/codegen/generated_file_notice.js +16 -0
- package/dist/codegen/generated_file_notice.js.map +1 -0
- package/dist/codegen/index.cjs +3 -0
- package/dist/codegen/index.cjs.map +1 -1
- package/dist/codegen/index.d.cts +1 -0
- package/dist/codegen/index.d.ts +1 -0
- package/dist/codegen/index.js +2 -0
- package/dist/codegen/index.js.map +1 -1
- package/dist/codegen/preload_generator.cjs +97 -1
- package/dist/codegen/preload_generator.cjs.map +1 -1
- package/dist/codegen/preload_generator.js +97 -1
- package/dist/codegen/preload_generator.js.map +1 -1
- package/dist/codegen/renderer_app_generator.cjs +6 -1
- package/dist/codegen/renderer_app_generator.cjs.map +1 -1
- package/dist/codegen/renderer_app_generator.js +6 -1
- package/dist/codegen/renderer_app_generator.js.map +1 -1
- package/dist/codegen/renderer_controller_registration_generator.cjs +6 -1
- package/dist/codegen/renderer_controller_registration_generator.cjs.map +1 -1
- package/dist/codegen/renderer_controller_registration_generator.js +6 -1
- package/dist/codegen/renderer_controller_registration_generator.js.map +1 -1
- package/dist/codegen/renderer_view_manifest_generator.cjs +6 -1
- package/dist/codegen/renderer_view_manifest_generator.cjs.map +1 -1
- package/dist/codegen/renderer_view_manifest_generator.js +6 -1
- package/dist/codegen/renderer_view_manifest_generator.js.map +1 -1
- package/dist/codegen/runtime_generator.cjs +6 -1
- package/dist/codegen/runtime_generator.cjs.map +1 -1
- package/dist/codegen/runtime_generator.js +6 -1
- package/dist/codegen/runtime_generator.js.map +1 -1
- package/dist/codegen/test_mock_generator.cjs +5 -1
- package/dist/codegen/test_mock_generator.cjs.map +1 -1
- package/dist/codegen/test_mock_generator.js +5 -1
- package/dist/codegen/test_mock_generator.js.map +1 -1
- package/dist/codegen/type_generator.cjs +6 -1
- package/dist/codegen/type_generator.cjs.map +1 -1
- package/dist/codegen/type_generator.js +6 -1
- package/dist/codegen/type_generator.js.map +1 -1
- package/dist/index.cjs +19 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +7 -1
- package/dist/index.d.ts +7 -1
- package/dist/index.js +13 -1
- package/dist/index.js.map +1 -1
- package/dist/main/index.cjs +133 -55
- package/dist/main/index.cjs.map +1 -1
- package/dist/main/index.d.cts +30 -2
- package/dist/main/index.d.ts +30 -2
- package/dist/main/index.js +134 -55
- package/dist/main/index.js.map +1 -1
- package/dist/main/renderer_trace_drain_coordinator.cjs +127 -0
- package/dist/main/renderer_trace_drain_coordinator.cjs.map +1 -0
- package/dist/main/renderer_trace_drain_coordinator.d.cts +46 -0
- package/dist/main/renderer_trace_drain_coordinator.d.ts +46 -0
- package/dist/main/renderer_trace_drain_coordinator.js +101 -0
- package/dist/main/renderer_trace_drain_coordinator.js.map +1 -0
- package/dist/router/wood_router.cjs +9 -1
- package/dist/router/wood_router.cjs.map +1 -1
- package/dist/router/wood_router.d.cts +1 -1
- package/dist/router/wood_router.d.ts +1 -1
- package/dist/router/wood_router.js +9 -1
- package/dist/router/wood_router.js.map +1 -1
- package/dist/runtime/app_env.cjs +52 -0
- package/dist/runtime/app_env.cjs.map +1 -0
- package/dist/runtime/app_env.d.cts +10 -0
- package/dist/runtime/app_env.d.ts +10 -0
- package/dist/runtime/app_env.js +18 -0
- package/dist/runtime/app_env.js.map +1 -0
- package/dist/runtime/index.cjs +9 -2
- package/dist/runtime/index.cjs.map +1 -1
- package/dist/runtime/index.d.cts +5 -1
- package/dist/runtime/index.d.ts +5 -1
- package/dist/runtime/index.js +9 -1
- package/dist/runtime/index.js.map +1 -1
- package/dist/runtime/renderer_log_dispatcher.cjs.map +1 -1
- package/dist/runtime/renderer_log_dispatcher.d.cts +1 -1
- package/dist/runtime/renderer_log_dispatcher.d.ts +1 -1
- package/dist/runtime/renderer_log_dispatcher.js.map +1 -1
- package/dist/runtime/renderer_trace_batch_receiver_registry.cjs +50 -0
- package/dist/runtime/renderer_trace_batch_receiver_registry.cjs.map +1 -0
- package/dist/runtime/renderer_trace_batch_receiver_registry.d.cts +10 -0
- package/dist/runtime/renderer_trace_batch_receiver_registry.d.ts +10 -0
- package/dist/runtime/renderer_trace_batch_receiver_registry.js +24 -0
- package/dist/runtime/renderer_trace_batch_receiver_registry.js.map +1 -0
- package/dist/runtime/runtime.cjs +75 -57
- package/dist/runtime/runtime.cjs.map +1 -1
- package/dist/runtime/runtime.d.cts +8 -1
- package/dist/runtime/runtime.d.ts +8 -1
- package/dist/runtime/runtime.js +80 -58
- package/dist/runtime/runtime.js.map +1 -1
- package/dist/testing/browser.cjs +540 -11
- package/dist/testing/browser.cjs.map +1 -1
- package/dist/testing/browser.d.cts +219 -3
- package/dist/testing/browser.d.ts +219 -3
- package/dist/testing/browser.js +540 -11
- package/dist/testing/browser.js.map +1 -1
- package/dist/testing/create_controller_harness.d.cts +1 -1
- package/dist/testing/create_controller_harness.d.ts +1 -1
- package/dist/testing/index.cjs +13 -0
- package/dist/testing/index.cjs.map +1 -1
- package/dist/testing/index.d.cts +7 -2
- package/dist/testing/index.d.ts +7 -2
- package/dist/testing/index.js +14 -0
- package/dist/testing/index.js.map +1 -1
- package/dist/trace-testing/index.d.cts +1 -1
- package/dist/trace-testing/index.d.ts +1 -1
- package/dist/trace-testing/trace_recorder.d.cts +1 -1
- package/dist/trace-testing/trace_recorder.d.ts +1 -1
- package/dist/trace-transport/index.cjs +32 -0
- package/dist/trace-transport/index.cjs.map +1 -0
- package/dist/trace-transport/index.d.cts +6 -0
- package/dist/trace-transport/index.d.ts +6 -0
- package/dist/trace-transport/index.js +7 -0
- package/dist/trace-transport/index.js.map +1 -0
- package/dist/trace_hub-Cob54qRx.d.cts +56 -0
- package/dist/trace_hub-Cob54qRx.d.ts +56 -0
- package/dist/tracing/index.cjs +14 -0
- package/dist/tracing/index.cjs.map +1 -1
- package/dist/tracing/index.d.cts +7 -1
- package/dist/tracing/index.d.ts +7 -1
- package/dist/tracing/index.js +12 -0
- package/dist/tracing/index.js.map +1 -1
- package/dist/tracing/renderer_trace_batch_host.cjs +80 -0
- package/dist/tracing/renderer_trace_batch_host.cjs.map +1 -0
- package/dist/tracing/renderer_trace_batch_host.d.cts +16 -0
- package/dist/tracing/renderer_trace_batch_host.d.ts +16 -0
- package/dist/tracing/renderer_trace_batch_host.js +56 -0
- package/dist/tracing/renderer_trace_batch_host.js.map +1 -0
- package/dist/tracing/renderer_trace_batch_transport.cjs +313 -0
- package/dist/tracing/renderer_trace_batch_transport.cjs.map +1 -0
- package/dist/tracing/renderer_trace_batch_transport.d.cts +53 -0
- package/dist/tracing/renderer_trace_batch_transport.d.ts +53 -0
- package/dist/tracing/renderer_trace_batch_transport.js +292 -0
- package/dist/tracing/renderer_trace_batch_transport.js.map +1 -0
- package/dist/tracing/trace_commit_writer.cjs +159 -0
- package/dist/tracing/trace_commit_writer.cjs.map +1 -0
- package/dist/tracing/trace_commit_writer.d.cts +97 -0
- package/dist/tracing/trace_commit_writer.d.ts +97 -0
- package/dist/tracing/trace_commit_writer.js +135 -0
- package/dist/tracing/trace_commit_writer.js.map +1 -0
- package/dist/tracing/trace_hub.cjs +58 -3
- package/dist/tracing/trace_hub.cjs.map +1 -1
- package/dist/tracing/trace_hub.d.cts +2 -46
- package/dist/tracing/trace_hub.d.ts +2 -46
- package/dist/tracing/trace_hub.js +56 -3
- package/dist/tracing/trace_hub.js.map +1 -1
- package/dist/tracing/trace_provider.cjs +42 -12
- package/dist/tracing/trace_provider.cjs.map +1 -1
- package/dist/tracing/trace_provider.d.cts +15 -1
- package/dist/tracing/trace_provider.d.ts +15 -1
- package/dist/tracing/trace_provider.js +43 -12
- package/dist/tracing/trace_provider.js.map +1 -1
- package/dist/tracing/types/wood_trace_batch.types.cjs +17 -0
- package/dist/tracing/types/wood_trace_batch.types.cjs.map +1 -0
- package/dist/tracing/types/wood_trace_batch.types.d.cts +124 -0
- package/dist/tracing/types/wood_trace_batch.types.d.ts +124 -0
- package/dist/tracing/types/wood_trace_batch.types.js +1 -0
- package/dist/tracing/types/wood_trace_batch.types.js.map +1 -0
- package/dist/tracing/types/wood_trace_sink.types.cjs +17 -0
- package/dist/tracing/types/wood_trace_sink.types.cjs.map +1 -0
- package/dist/tracing/types/wood_trace_sink.types.d.cts +2 -0
- package/dist/tracing/types/wood_trace_sink.types.d.ts +2 -0
- package/dist/tracing/types/wood_trace_sink.types.js +1 -0
- package/dist/tracing/types/wood_trace_sink.types.js.map +1 -0
- package/dist/tracing/wood_trace_batch_contract.cjs +229 -0
- package/dist/tracing/wood_trace_batch_contract.cjs.map +1 -0
- package/dist/tracing/wood_trace_batch_contract.d.cts +23 -0
- package/dist/tracing/wood_trace_batch_contract.d.ts +23 -0
- package/dist/tracing/wood_trace_batch_contract.js +205 -0
- package/dist/tracing/wood_trace_batch_contract.js.map +1 -0
- package/dist/transport/build_transport_bridge.cjs +62 -0
- package/dist/transport/build_transport_bridge.cjs.map +1 -0
- package/dist/transport/build_transport_bridge.d.cts +29 -0
- package/dist/transport/build_transport_bridge.d.ts +29 -0
- package/dist/transport/build_transport_bridge.js +38 -0
- package/dist/transport/build_transport_bridge.js.map +1 -0
- package/dist/transport/in_process_router_transport.cjs +41 -0
- package/dist/transport/in_process_router_transport.cjs.map +1 -0
- package/dist/transport/in_process_router_transport.d.cts +31 -0
- package/dist/transport/in_process_router_transport.d.ts +31 -0
- package/dist/transport/in_process_router_transport.js +17 -0
- package/dist/transport/in_process_router_transport.js.map +1 -0
- package/dist/transport/index.cjs +42 -0
- package/dist/transport/index.cjs.map +1 -0
- package/dist/transport/index.d.cts +8 -0
- package/dist/transport/index.d.ts +8 -0
- package/dist/transport/index.js +19 -0
- package/dist/transport/index.js.map +1 -0
- package/dist/transport/loopback_remote_transport.cjs +270 -0
- package/dist/transport/loopback_remote_transport.cjs.map +1 -0
- package/dist/transport/loopback_remote_transport.d.cts +49 -0
- package/dist/transport/loopback_remote_transport.d.ts +49 -0
- package/dist/transport/loopback_remote_transport.js +237 -0
- package/dist/transport/loopback_remote_transport.js.map +1 -0
- package/dist/transport/loopback_serialization.cjs +116 -0
- package/dist/transport/loopback_serialization.cjs.map +1 -0
- package/dist/transport/loopback_serialization.d.cts +28 -0
- package/dist/transport/loopback_serialization.d.ts +28 -0
- package/dist/transport/loopback_serialization.js +91 -0
- package/dist/transport/loopback_serialization.js.map +1 -0
- package/dist/transport/transport_contract_suite.cjs +110 -0
- package/dist/transport/transport_contract_suite.cjs.map +1 -0
- package/dist/transport/transport_contract_suite.d.cts +23 -0
- package/dist/transport/transport_contract_suite.d.ts +23 -0
- package/dist/transport/transport_contract_suite.js +85 -0
- package/dist/transport/transport_contract_suite.js.map +1 -0
- package/dist/transport/wood_rpc_transport.cjs +17 -0
- package/dist/transport/wood_rpc_transport.cjs.map +1 -0
- package/dist/transport/wood_rpc_transport.d.cts +44 -0
- package/dist/transport/wood_rpc_transport.d.ts +44 -0
- package/dist/transport/wood_rpc_transport.js +1 -0
- package/dist/transport/wood_rpc_transport.js.map +1 -0
- package/docs/browser-testing.md +139 -1
- package/package.json +22 -3
- package/src/components/NavigationShell.browser.test.ts +236 -0
- package/src/components/NavigationShell.svelte +162 -49
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BoundedMetadataSnapshotter,
|
|
3
|
+
DEFAULT_METADATA_SNAPSHOT_LIMITS
|
|
4
|
+
} from "@noego/telemetry/core";
|
|
5
|
+
import { WoodTraceBatchContract } from "./wood_trace_batch_contract.js";
|
|
6
|
+
class RendererTraceBatchTransport {
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.options = options;
|
|
9
|
+
this.contract = new WoodTraceBatchContract();
|
|
10
|
+
this.queue = [];
|
|
11
|
+
this.state = "accepting";
|
|
12
|
+
this.port = null;
|
|
13
|
+
this.unsubscribePort = null;
|
|
14
|
+
this.inFlight = null;
|
|
15
|
+
this.nextSequence = 0;
|
|
16
|
+
this.drainScheduled = false;
|
|
17
|
+
this.droppedCapacity = 0;
|
|
18
|
+
this.droppedClosed = 0;
|
|
19
|
+
this.droppedRejected = 0;
|
|
20
|
+
this.droppedRetryExhausted = 0;
|
|
21
|
+
this.committedBatches = 0;
|
|
22
|
+
this.retryAttempts = 0;
|
|
23
|
+
this.metadataTruncatedValues = 0;
|
|
24
|
+
this.metadataRedactedValues = 0;
|
|
25
|
+
this.metadataSerializationFailures = 0;
|
|
26
|
+
this.validateOptions();
|
|
27
|
+
this.metadataSnapshotter = new BoundedMetadataSnapshotter(
|
|
28
|
+
options.metadataSnapshotLimits ?? DEFAULT_METADATA_SNAPSHOT_LIMITS
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
admit(event) {
|
|
32
|
+
const sequence = this.nextSequence;
|
|
33
|
+
this.nextSequence += 1;
|
|
34
|
+
if (this.state !== "accepting") {
|
|
35
|
+
this.droppedClosed += 1;
|
|
36
|
+
return { kind: "dropped", sequence, reason: "closed" };
|
|
37
|
+
}
|
|
38
|
+
const retainedEvents = this.queue.length + (this.inFlight?.batch.events.length ?? 0);
|
|
39
|
+
if (retainedEvents >= this.options.maxQueuedEvents) {
|
|
40
|
+
this.droppedCapacity += 1;
|
|
41
|
+
return { kind: "dropped", sequence, reason: "capacity" };
|
|
42
|
+
}
|
|
43
|
+
this.queue.push(Object.freeze({ sequence, event: this.snapshotEvent(event, sequence) }));
|
|
44
|
+
this.scheduleDrain();
|
|
45
|
+
return { kind: "admitted", sequence };
|
|
46
|
+
}
|
|
47
|
+
attachPort(port) {
|
|
48
|
+
this.detachPort();
|
|
49
|
+
this.port = port;
|
|
50
|
+
this.unsubscribePort = port.subscribe((message) => this.handleAcknowledgement(message));
|
|
51
|
+
if (this.inFlight) {
|
|
52
|
+
this.inFlight.awaitingAcknowledgement = false;
|
|
53
|
+
this.inFlight.retryReady = true;
|
|
54
|
+
this.inFlight.retryGeneration += 1;
|
|
55
|
+
}
|
|
56
|
+
this.scheduleDrain();
|
|
57
|
+
}
|
|
58
|
+
status() {
|
|
59
|
+
return Object.freeze({
|
|
60
|
+
state: this.state,
|
|
61
|
+
producerId: this.options.producerId,
|
|
62
|
+
nextSequence: this.nextSequence,
|
|
63
|
+
queuedEvents: this.queue.length,
|
|
64
|
+
inFlightBatchId: this.inFlight?.batch.batchId ?? null,
|
|
65
|
+
droppedCapacity: this.droppedCapacity,
|
|
66
|
+
droppedClosed: this.droppedClosed,
|
|
67
|
+
droppedRejected: this.droppedRejected,
|
|
68
|
+
droppedRetryExhausted: this.droppedRetryExhausted,
|
|
69
|
+
metadataTruncatedValues: this.metadataTruncatedValues,
|
|
70
|
+
metadataRedactedValues: this.metadataRedactedValues,
|
|
71
|
+
metadataSerializationFailures: this.metadataSerializationFailures,
|
|
72
|
+
committedBatches: this.committedBatches,
|
|
73
|
+
retryAttempts: this.retryAttempts
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
async flush(deadlineMs) {
|
|
77
|
+
this.scheduleDrain();
|
|
78
|
+
while (this.hasPendingEvents() && this.options.now() < deadlineMs) {
|
|
79
|
+
await this.options.delay(1);
|
|
80
|
+
this.scheduleDrain();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async close(deadlineMs) {
|
|
84
|
+
if (this.state === "stopped") {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
this.state = "draining";
|
|
88
|
+
await this.flush(deadlineMs);
|
|
89
|
+
const abandonedEvents = this.queue.length + (this.inFlight?.batch.events.length ?? 0);
|
|
90
|
+
this.droppedClosed += abandonedEvents;
|
|
91
|
+
this.queue.length = 0;
|
|
92
|
+
this.inFlight = null;
|
|
93
|
+
this.detachPort();
|
|
94
|
+
this.state = "stopped";
|
|
95
|
+
}
|
|
96
|
+
abandonRetryExhausted() {
|
|
97
|
+
if (!this.inFlight) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
this.droppedRetryExhausted += this.inFlight.batch.events.length;
|
|
101
|
+
this.inFlight = null;
|
|
102
|
+
this.scheduleDrain();
|
|
103
|
+
}
|
|
104
|
+
createInFlightBatch() {
|
|
105
|
+
if (this.inFlight || this.queue.length === 0 || !this.port) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const entries = this.queue.splice(0, this.options.maxBatchEvents);
|
|
109
|
+
const first = entries[0];
|
|
110
|
+
if (!first) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
this.inFlight = {
|
|
114
|
+
batch: this.contract.create(
|
|
115
|
+
this.options.producerId,
|
|
116
|
+
first.sequence,
|
|
117
|
+
entries.map((entry) => entry.event)
|
|
118
|
+
),
|
|
119
|
+
attempts: 0,
|
|
120
|
+
awaitingAcknowledgement: false,
|
|
121
|
+
retryReady: true,
|
|
122
|
+
retryGeneration: 0
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
detachPort() {
|
|
126
|
+
this.unsubscribePort?.();
|
|
127
|
+
this.unsubscribePort = null;
|
|
128
|
+
this.port?.close();
|
|
129
|
+
this.port = null;
|
|
130
|
+
}
|
|
131
|
+
drain() {
|
|
132
|
+
this.createInFlightBatch();
|
|
133
|
+
this.sendInFlightBatch();
|
|
134
|
+
}
|
|
135
|
+
handleAcknowledgement(message) {
|
|
136
|
+
const parsed = this.contract.parseAcknowledgement(message);
|
|
137
|
+
if (!parsed.ok || !this.inFlight) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const acknowledgement = parsed.acknowledgement;
|
|
141
|
+
if (acknowledgement.batchId !== this.inFlight.batch.batchId) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
this.inFlight.retryGeneration += 1;
|
|
145
|
+
this.inFlight.awaitingAcknowledgement = false;
|
|
146
|
+
if (acknowledgement.kind === "committed") {
|
|
147
|
+
if (acknowledgement.acceptedEvents !== this.inFlight.batch.events.length) {
|
|
148
|
+
this.scheduleRetry(0);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
this.committedBatches += 1;
|
|
152
|
+
this.inFlight = null;
|
|
153
|
+
this.scheduleDrain();
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (acknowledgement.kind === "rejected") {
|
|
157
|
+
this.droppedRejected += this.inFlight.batch.events.length;
|
|
158
|
+
this.inFlight = null;
|
|
159
|
+
this.scheduleDrain();
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
this.scheduleRetry(acknowledgement.retryAfterMs);
|
|
163
|
+
}
|
|
164
|
+
handleAcknowledgementTimeout(batchId, attempt) {
|
|
165
|
+
if (!this.inFlight || this.inFlight.batch.batchId !== batchId || this.inFlight.attempts !== attempt || !this.inFlight.awaitingAcknowledgement) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
this.inFlight.awaitingAcknowledgement = false;
|
|
169
|
+
this.scheduleRetry(this.retryDelay(attempt));
|
|
170
|
+
}
|
|
171
|
+
hasPendingEvents() {
|
|
172
|
+
return this.queue.length > 0 || this.inFlight !== null;
|
|
173
|
+
}
|
|
174
|
+
retryDelay(attempt) {
|
|
175
|
+
return this.options.retryBaseDelayMs * 2 ** Math.max(0, attempt - 1);
|
|
176
|
+
}
|
|
177
|
+
scheduleDrain() {
|
|
178
|
+
if (this.drainScheduled) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
this.drainScheduled = true;
|
|
182
|
+
this.options.schedule(() => {
|
|
183
|
+
this.drainScheduled = false;
|
|
184
|
+
this.drain();
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
scheduleRetry(requestedDelayMs) {
|
|
188
|
+
if (!this.inFlight) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (this.inFlight.attempts > this.options.retryLimit) {
|
|
192
|
+
this.abandonRetryExhausted();
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
this.inFlight.retryReady = false;
|
|
196
|
+
const generation = this.inFlight.retryGeneration + 1;
|
|
197
|
+
this.inFlight.retryGeneration = generation;
|
|
198
|
+
const delayMs = Math.max(requestedDelayMs, this.retryDelay(this.inFlight.attempts));
|
|
199
|
+
void this.options.delay(delayMs).then(() => {
|
|
200
|
+
if (!this.inFlight || this.inFlight.retryGeneration !== generation) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
this.inFlight.retryReady = true;
|
|
204
|
+
this.scheduleDrain();
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
sendInFlightBatch() {
|
|
208
|
+
const current = this.inFlight;
|
|
209
|
+
const port = this.port;
|
|
210
|
+
if (!current || !port || current.awaitingAcknowledgement || !current.retryReady) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
if (current.attempts > this.options.retryLimit) {
|
|
214
|
+
this.abandonRetryExhausted();
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
current.attempts += 1;
|
|
218
|
+
if (current.attempts > 1) {
|
|
219
|
+
this.retryAttempts += 1;
|
|
220
|
+
}
|
|
221
|
+
current.awaitingAcknowledgement = true;
|
|
222
|
+
current.retryReady = false;
|
|
223
|
+
const attempt = current.attempts;
|
|
224
|
+
try {
|
|
225
|
+
port.postMessage(current.batch);
|
|
226
|
+
} catch {
|
|
227
|
+
current.awaitingAcknowledgement = false;
|
|
228
|
+
this.detachPort();
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
void this.options.delay(this.options.acknowledgementTimeoutMs).then(() => {
|
|
232
|
+
this.handleAcknowledgementTimeout(current.batch.batchId, attempt);
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
snapshotEvent(event, sequence) {
|
|
236
|
+
const snapshot = this.metadataSnapshotter.snapshot(event);
|
|
237
|
+
this.metadataTruncatedValues += snapshot.stats.truncatedValues;
|
|
238
|
+
this.metadataRedactedValues += snapshot.stats.redactedValues;
|
|
239
|
+
this.metadataSerializationFailures += snapshot.stats.serializationFailures;
|
|
240
|
+
const input = snapshot.metadata;
|
|
241
|
+
const occurredAt = new Date(this.options.now());
|
|
242
|
+
const timestamp = Number.isNaN(occurredAt.getTime()) ? (/* @__PURE__ */ new Date(0)).toISOString() : occurredAt.toISOString();
|
|
243
|
+
return Object.freeze({
|
|
244
|
+
source: this.snapshotString(input.source, "unknown"),
|
|
245
|
+
type: this.snapshotString(input.type, "unknown"),
|
|
246
|
+
level: this.snapshotString(input.level, "info"),
|
|
247
|
+
process: "renderer",
|
|
248
|
+
timestamp,
|
|
249
|
+
traceSeq: sequence + 1,
|
|
250
|
+
...this.snapshotOptionalString("message", input.message),
|
|
251
|
+
...this.snapshotConversationId(input.conversationId),
|
|
252
|
+
...this.snapshotPayload(input.payload),
|
|
253
|
+
...this.snapshotOptionalString("windowId", input.windowId),
|
|
254
|
+
...this.snapshotOptionalNumber("webContentsId", input.webContentsId),
|
|
255
|
+
...this.snapshotOptionalString("routeKey", input.routeKey),
|
|
256
|
+
...this.snapshotOptionalString("harnessId", input.harnessId)
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
snapshotConversationId(value) {
|
|
260
|
+
return typeof value === "string" || typeof value === "number" ? { conversationId: value } : {};
|
|
261
|
+
}
|
|
262
|
+
snapshotOptionalNumber(key, value) {
|
|
263
|
+
return typeof value === "number" && Number.isFinite(value) ? { [key]: value } : {};
|
|
264
|
+
}
|
|
265
|
+
snapshotOptionalString(key, value) {
|
|
266
|
+
return typeof value === "string" ? { [key]: value } : {};
|
|
267
|
+
}
|
|
268
|
+
snapshotPayload(value) {
|
|
269
|
+
if (value === void 0) {
|
|
270
|
+
return {};
|
|
271
|
+
}
|
|
272
|
+
const payload = this.isJsonObject(value) ? value : Object.freeze({ __telemetry: value });
|
|
273
|
+
return { payload };
|
|
274
|
+
}
|
|
275
|
+
snapshotString(value, fallback) {
|
|
276
|
+
return typeof value === "string" && value.trim().length > 0 ? value : fallback;
|
|
277
|
+
}
|
|
278
|
+
isJsonObject(value) {
|
|
279
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
280
|
+
}
|
|
281
|
+
validateOptions() {
|
|
282
|
+
const validCapacity = Number.isSafeInteger(this.options.maxQueuedEvents) && this.options.maxQueuedEvents > 0 && Number.isSafeInteger(this.options.maxBatchEvents) && this.options.maxBatchEvents > 0 && this.options.maxBatchEvents <= this.options.maxQueuedEvents;
|
|
283
|
+
const validRetry = Number.isSafeInteger(this.options.retryLimit) && this.options.retryLimit >= 0 && Number.isFinite(this.options.retryBaseDelayMs) && this.options.retryBaseDelayMs >= 0 && Number.isFinite(this.options.acknowledgementTimeoutMs) && this.options.acknowledgementTimeoutMs > 0;
|
|
284
|
+
if (!this.options.producerId.trim() || !validCapacity || !validRetry) {
|
|
285
|
+
throw new Error("Renderer trace batch transport options are invalid");
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
export {
|
|
290
|
+
RendererTraceBatchTransport
|
|
291
|
+
};
|
|
292
|
+
//# sourceMappingURL=renderer_trace_batch_transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/tracing/renderer_trace_batch_transport.ts"],"sourcesContent":["import {\n BoundedMetadataSnapshotter,\n DEFAULT_METADATA_SNAPSHOT_LIMITS,\n type TelemetryJsonObject,\n type TelemetryJsonValue,\n} from '@noego/telemetry/core';\nimport { WoodTraceBatchContract } from './wood_trace_batch_contract.js';\nimport type {\n WoodRendererTraceEvent,\n WoodRendererTraceInput,\n WoodTraceBatch,\n WoodTraceBatchAdmissionResult,\n WoodTraceBatchPort,\n WoodTraceBatchTransportOptions,\n WoodTraceBatchTransportStatus,\n} from './types/wood_trace_batch.types.js';\n\ntype QueuedTraceEvent = Readonly<{\n sequence: number;\n event: WoodRendererTraceEvent;\n}>;\n\ntype InFlightTraceBatch = {\n batch: WoodTraceBatch;\n attempts: number;\n awaitingAcknowledgement: boolean;\n retryReady: boolean;\n retryGeneration: number;\n};\n\nexport class RendererTraceBatchTransport {\n private readonly contract = new WoodTraceBatchContract();\n private readonly metadataSnapshotter: BoundedMetadataSnapshotter;\n private readonly queue: QueuedTraceEvent[] = [];\n private state: 'accepting' | 'draining' | 'stopped' = 'accepting';\n private port: WoodTraceBatchPort | null = null;\n private unsubscribePort: (() => void) | null = null;\n private inFlight: InFlightTraceBatch | null = null;\n private nextSequence = 0;\n private drainScheduled = false;\n private droppedCapacity = 0;\n private droppedClosed = 0;\n private droppedRejected = 0;\n private droppedRetryExhausted = 0;\n private committedBatches = 0;\n private retryAttempts = 0;\n private metadataTruncatedValues = 0;\n private metadataRedactedValues = 0;\n private metadataSerializationFailures = 0;\n\n constructor(private readonly options: WoodTraceBatchTransportOptions) {\n this.validateOptions();\n this.metadataSnapshotter = new BoundedMetadataSnapshotter(\n options.metadataSnapshotLimits ?? DEFAULT_METADATA_SNAPSHOT_LIMITS,\n );\n }\n\n admit(event: WoodRendererTraceInput): WoodTraceBatchAdmissionResult {\n const sequence = this.nextSequence;\n this.nextSequence += 1;\n if (this.state !== 'accepting') {\n this.droppedClosed += 1;\n return { kind: 'dropped', sequence, reason: 'closed' };\n }\n\n const retainedEvents = this.queue.length + (this.inFlight?.batch.events.length ?? 0);\n if (retainedEvents >= this.options.maxQueuedEvents) {\n this.droppedCapacity += 1;\n return { kind: 'dropped', sequence, reason: 'capacity' };\n }\n\n this.queue.push(Object.freeze({ sequence, event: this.snapshotEvent(event, sequence) }));\n this.scheduleDrain();\n return { kind: 'admitted', sequence };\n }\n\n attachPort(port: WoodTraceBatchPort): void {\n this.detachPort();\n this.port = port;\n this.unsubscribePort = port.subscribe((message) => this.handleAcknowledgement(message));\n if (this.inFlight) {\n this.inFlight.awaitingAcknowledgement = false;\n this.inFlight.retryReady = true;\n this.inFlight.retryGeneration += 1;\n }\n this.scheduleDrain();\n }\n\n status(): WoodTraceBatchTransportStatus {\n return Object.freeze({\n state: this.state,\n producerId: this.options.producerId,\n nextSequence: this.nextSequence,\n queuedEvents: this.queue.length,\n inFlightBatchId: this.inFlight?.batch.batchId ?? null,\n droppedCapacity: this.droppedCapacity,\n droppedClosed: this.droppedClosed,\n droppedRejected: this.droppedRejected,\n droppedRetryExhausted: this.droppedRetryExhausted,\n metadataTruncatedValues: this.metadataTruncatedValues,\n metadataRedactedValues: this.metadataRedactedValues,\n metadataSerializationFailures: this.metadataSerializationFailures,\n committedBatches: this.committedBatches,\n retryAttempts: this.retryAttempts,\n });\n }\n\n async flush(deadlineMs: number): Promise<void> {\n this.scheduleDrain();\n while (this.hasPendingEvents() && this.options.now() < deadlineMs) {\n await this.options.delay(1);\n this.scheduleDrain();\n }\n }\n\n async close(deadlineMs: number): Promise<void> {\n if (this.state === 'stopped') {\n return;\n }\n this.state = 'draining';\n await this.flush(deadlineMs);\n const abandonedEvents = this.queue.length + (this.inFlight?.batch.events.length ?? 0);\n this.droppedClosed += abandonedEvents;\n this.queue.length = 0;\n this.inFlight = null;\n this.detachPort();\n this.state = 'stopped';\n }\n\n private abandonRetryExhausted(): void {\n if (!this.inFlight) {\n return;\n }\n this.droppedRetryExhausted += this.inFlight.batch.events.length;\n this.inFlight = null;\n this.scheduleDrain();\n }\n\n private createInFlightBatch(): void {\n if (this.inFlight || this.queue.length === 0 || !this.port) {\n return;\n }\n const entries = this.queue.splice(0, this.options.maxBatchEvents);\n const first = entries[0];\n if (!first) {\n return;\n }\n this.inFlight = {\n batch: this.contract.create(\n this.options.producerId,\n first.sequence,\n entries.map((entry) => entry.event),\n ),\n attempts: 0,\n awaitingAcknowledgement: false,\n retryReady: true,\n retryGeneration: 0,\n };\n }\n\n private detachPort(): void {\n this.unsubscribePort?.();\n this.unsubscribePort = null;\n this.port?.close();\n this.port = null;\n }\n\n private drain(): void {\n this.createInFlightBatch();\n this.sendInFlightBatch();\n }\n\n private handleAcknowledgement(message: unknown): void {\n const parsed = this.contract.parseAcknowledgement(message);\n if (!parsed.ok || !this.inFlight) {\n return;\n }\n const acknowledgement = parsed.acknowledgement;\n if (acknowledgement.batchId !== this.inFlight.batch.batchId) {\n return;\n }\n\n this.inFlight.retryGeneration += 1;\n this.inFlight.awaitingAcknowledgement = false;\n if (acknowledgement.kind === 'committed') {\n if (acknowledgement.acceptedEvents !== this.inFlight.batch.events.length) {\n this.scheduleRetry(0);\n return;\n }\n this.committedBatches += 1;\n this.inFlight = null;\n this.scheduleDrain();\n return;\n }\n if (acknowledgement.kind === 'rejected') {\n this.droppedRejected += this.inFlight.batch.events.length;\n this.inFlight = null;\n this.scheduleDrain();\n return;\n }\n this.scheduleRetry(acknowledgement.retryAfterMs);\n }\n\n private handleAcknowledgementTimeout(batchId: string, attempt: number): void {\n if (!this.inFlight\n || this.inFlight.batch.batchId !== batchId\n || this.inFlight.attempts !== attempt\n || !this.inFlight.awaitingAcknowledgement) {\n return;\n }\n this.inFlight.awaitingAcknowledgement = false;\n this.scheduleRetry(this.retryDelay(attempt));\n }\n\n private hasPendingEvents(): boolean {\n return this.queue.length > 0 || this.inFlight !== null;\n }\n\n private retryDelay(attempt: number): number {\n return this.options.retryBaseDelayMs * (2 ** Math.max(0, attempt - 1));\n }\n\n private scheduleDrain(): void {\n if (this.drainScheduled) {\n return;\n }\n this.drainScheduled = true;\n this.options.schedule(() => {\n this.drainScheduled = false;\n this.drain();\n });\n }\n\n private scheduleRetry(requestedDelayMs: number): void {\n if (!this.inFlight) {\n return;\n }\n if (this.inFlight.attempts > this.options.retryLimit) {\n this.abandonRetryExhausted();\n return;\n }\n\n this.inFlight.retryReady = false;\n const generation = this.inFlight.retryGeneration + 1;\n this.inFlight.retryGeneration = generation;\n const delayMs = Math.max(requestedDelayMs, this.retryDelay(this.inFlight.attempts));\n void this.options.delay(delayMs).then(() => {\n if (!this.inFlight || this.inFlight.retryGeneration !== generation) {\n return;\n }\n this.inFlight.retryReady = true;\n this.scheduleDrain();\n });\n }\n\n private sendInFlightBatch(): void {\n const current = this.inFlight;\n const port = this.port;\n if (!current || !port || current.awaitingAcknowledgement || !current.retryReady) {\n return;\n }\n if (current.attempts > this.options.retryLimit) {\n this.abandonRetryExhausted();\n return;\n }\n\n current.attempts += 1;\n if (current.attempts > 1) {\n this.retryAttempts += 1;\n }\n current.awaitingAcknowledgement = true;\n current.retryReady = false;\n const attempt = current.attempts;\n try {\n port.postMessage(current.batch);\n } catch {\n current.awaitingAcknowledgement = false;\n this.detachPort();\n return;\n }\n\n void this.options.delay(this.options.acknowledgementTimeoutMs).then(() => {\n this.handleAcknowledgementTimeout(current.batch.batchId, attempt);\n });\n }\n\n private snapshotEvent(\n event: WoodRendererTraceInput,\n sequence: number,\n ): WoodRendererTraceEvent {\n const snapshot = this.metadataSnapshotter.snapshot(event);\n this.metadataTruncatedValues += snapshot.stats.truncatedValues;\n this.metadataRedactedValues += snapshot.stats.redactedValues;\n this.metadataSerializationFailures += snapshot.stats.serializationFailures;\n const input = snapshot.metadata;\n const occurredAt = new Date(this.options.now());\n const timestamp = Number.isNaN(occurredAt.getTime())\n ? new Date(0).toISOString()\n : occurredAt.toISOString();\n return Object.freeze({\n source: this.snapshotString(input.source, 'unknown'),\n type: this.snapshotString(input.type, 'unknown'),\n level: this.snapshotString(input.level, 'info'),\n process: 'renderer',\n timestamp,\n traceSeq: sequence + 1,\n ...this.snapshotOptionalString('message', input.message),\n ...this.snapshotConversationId(input.conversationId),\n ...this.snapshotPayload(input.payload),\n ...this.snapshotOptionalString('windowId', input.windowId),\n ...this.snapshotOptionalNumber('webContentsId', input.webContentsId),\n ...this.snapshotOptionalString('routeKey', input.routeKey),\n ...this.snapshotOptionalString('harnessId', input.harnessId),\n });\n }\n\n private snapshotConversationId(\n value: TelemetryJsonValue | undefined,\n ): Pick<WoodRendererTraceEvent, 'conversationId'> | Record<string, never> {\n return typeof value === 'string' || typeof value === 'number'\n ? { conversationId: value }\n : {};\n }\n\n private snapshotOptionalNumber<Key extends 'webContentsId'>(\n key: Key,\n value: TelemetryJsonValue | undefined,\n ): Partial<Pick<WoodRendererTraceEvent, Key>> {\n return typeof value === 'number' && Number.isFinite(value)\n ? { [key]: value } as Pick<WoodRendererTraceEvent, Key>\n : {};\n }\n\n private snapshotOptionalString<Key extends 'message' | 'windowId' | 'routeKey' | 'harnessId'>(\n key: Key,\n value: TelemetryJsonValue | undefined,\n ): Partial<Pick<WoodRendererTraceEvent, Key>> {\n return typeof value === 'string'\n ? { [key]: value } as Pick<WoodRendererTraceEvent, Key>\n : {};\n }\n\n private snapshotPayload(\n value: TelemetryJsonValue | undefined,\n ): Pick<WoodRendererTraceEvent, 'payload'> | Record<string, never> {\n if (value === undefined) {\n return {};\n }\n const payload: TelemetryJsonObject = this.isJsonObject(value)\n ? value\n : Object.freeze({ __telemetry: value });\n return { payload };\n }\n\n private snapshotString(value: TelemetryJsonValue | undefined, fallback: string): string {\n return typeof value === 'string' && value.trim().length > 0 ? value : fallback;\n }\n\n private isJsonObject(value: TelemetryJsonValue): value is TelemetryJsonObject {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n }\n\n private validateOptions(): void {\n const validCapacity = Number.isSafeInteger(this.options.maxQueuedEvents)\n && this.options.maxQueuedEvents > 0\n && Number.isSafeInteger(this.options.maxBatchEvents)\n && this.options.maxBatchEvents > 0\n && this.options.maxBatchEvents <= this.options.maxQueuedEvents;\n const validRetry = Number.isSafeInteger(this.options.retryLimit)\n && this.options.retryLimit >= 0\n && Number.isFinite(this.options.retryBaseDelayMs)\n && this.options.retryBaseDelayMs >= 0\n && Number.isFinite(this.options.acknowledgementTimeoutMs)\n && this.options.acknowledgementTimeoutMs > 0;\n if (!this.options.producerId.trim() || !validCapacity || !validRetry) {\n throw new Error('Renderer trace batch transport options are invalid');\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var trace_commit_writer_exports = {};
|
|
20
|
+
__export(trace_commit_writer_exports, {
|
|
21
|
+
TraceCommitWriter: () => TraceCommitWriter
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(trace_commit_writer_exports);
|
|
24
|
+
class TraceCommitWriter {
|
|
25
|
+
constructor(port, options = {}) {
|
|
26
|
+
this.seen = /* @__PURE__ */ new Set();
|
|
27
|
+
this.inflight = [];
|
|
28
|
+
this.nextCommitSeq = 1;
|
|
29
|
+
this.committed = 0;
|
|
30
|
+
this.pendingError = null;
|
|
31
|
+
this.rendererFlush = null;
|
|
32
|
+
/** WoodTraceEventSink shape — register via `tracer.registerSink(writer.sink)`. */
|
|
33
|
+
this.sink = (event) => {
|
|
34
|
+
this.admit(event);
|
|
35
|
+
};
|
|
36
|
+
this.port = port;
|
|
37
|
+
this.mainProducerId = options.mainProducerId ?? "main";
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Admit one trace event. Returns the committed row (with its assigned
|
|
41
|
+
* `commitSeq`), or `null` if it is a duplicate of an already-admitted
|
|
42
|
+
* (producerId, producerSeq) pair — this is the batch-retry idempotency guard.
|
|
43
|
+
*/
|
|
44
|
+
admit(event) {
|
|
45
|
+
const producerId = this.resolveProducerId(event);
|
|
46
|
+
const producerSeq = event.traceSeq;
|
|
47
|
+
const identity = `${producerId}#${producerSeq}`;
|
|
48
|
+
if (this.seen.has(identity)) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
this.seen.add(identity);
|
|
52
|
+
const row = this.toRow(event, producerId, producerSeq);
|
|
53
|
+
this.enqueue(row);
|
|
54
|
+
return row;
|
|
55
|
+
}
|
|
56
|
+
highWaterMark() {
|
|
57
|
+
return this.committed;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Install the renderer-flush hook (Phase 4 browser mode). The browser harness
|
|
61
|
+
* supplies a function that drains the page's in-flight renderer `__trace`
|
|
62
|
+
* round-trips; `flush()` awaits it before the main-writer barrier so
|
|
63
|
+
* renderer-origin traces are persisted deterministically. Backend mode leaves
|
|
64
|
+
* this unset and `requestRendererFlush` resolves immediately.
|
|
65
|
+
*/
|
|
66
|
+
setRendererFlush(fn) {
|
|
67
|
+
this.rendererFlush = fn;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Request that renderer producers flush their pending batches and await acks.
|
|
71
|
+
* In browser mode the installed hook (see {@link setRendererFlush}) round-trips
|
|
72
|
+
* the page to drain pending `__trace` deliveries. With no hook (backend mode,
|
|
73
|
+
* no renderer) this resolves immediately.
|
|
74
|
+
*/
|
|
75
|
+
async requestRendererFlush() {
|
|
76
|
+
if (this.rendererFlush) {
|
|
77
|
+
await this.rendererFlush();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Drain the write queue and return the committed high-water mark. Awaits a
|
|
82
|
+
* barrier over all in-flight persists; with `timeoutMs` it rejects on a
|
|
83
|
+
* timer race rather than polling. Rejects if any persist failed.
|
|
84
|
+
*/
|
|
85
|
+
async flush(options = {}) {
|
|
86
|
+
await this.requestRendererFlush();
|
|
87
|
+
const barrier = Promise.allSettled(this.inflight.slice());
|
|
88
|
+
await this.awaitBarrier(barrier, options.timeoutMs);
|
|
89
|
+
if (this.pendingError !== null) {
|
|
90
|
+
const error = this.pendingError;
|
|
91
|
+
this.pendingError = null;
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
return { committedHighWaterMark: this.committed };
|
|
95
|
+
}
|
|
96
|
+
async awaitBarrier(barrier, timeoutMs) {
|
|
97
|
+
if (timeoutMs === void 0) {
|
|
98
|
+
await barrier;
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
let timer = null;
|
|
102
|
+
const timeout = new Promise((_, reject) => {
|
|
103
|
+
timer = setTimeout(
|
|
104
|
+
() => reject(new Error(`trace flush barrier exceeded ${timeoutMs}ms`)),
|
|
105
|
+
timeoutMs
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
try {
|
|
109
|
+
await Promise.race([barrier.then(() => void 0), timeout]);
|
|
110
|
+
} finally {
|
|
111
|
+
if (timer !== null) {
|
|
112
|
+
clearTimeout(timer);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
enqueue(row) {
|
|
117
|
+
const op = Promise.resolve().then(() => this.port.persist(row)).then(
|
|
118
|
+
() => {
|
|
119
|
+
if (row.commitSeq > this.committed) {
|
|
120
|
+
this.committed = row.commitSeq;
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
(error) => {
|
|
124
|
+
this.pendingError = error;
|
|
125
|
+
}
|
|
126
|
+
);
|
|
127
|
+
this.inflight.push(op);
|
|
128
|
+
}
|
|
129
|
+
resolveProducerId(event) {
|
|
130
|
+
if (event.process === "renderer") {
|
|
131
|
+
const rendererId = event.payload?.woodRendererProducerId;
|
|
132
|
+
return typeof rendererId === "string" ? rendererId : "renderer";
|
|
133
|
+
}
|
|
134
|
+
if (event.process === "main") {
|
|
135
|
+
return this.mainProducerId;
|
|
136
|
+
}
|
|
137
|
+
return event.process;
|
|
138
|
+
}
|
|
139
|
+
toRow(event, producerId, producerSeq) {
|
|
140
|
+
return {
|
|
141
|
+
commitSeq: this.nextCommitSeq++,
|
|
142
|
+
producerId,
|
|
143
|
+
producerSeq,
|
|
144
|
+
process: event.process,
|
|
145
|
+
level: event.level,
|
|
146
|
+
type: event.type,
|
|
147
|
+
source: event.source,
|
|
148
|
+
...event.message !== void 0 ? { message: event.message } : {},
|
|
149
|
+
...event.conversationId !== void 0 ? { conversationId: event.conversationId } : {},
|
|
150
|
+
timestamp: event.timestamp,
|
|
151
|
+
...event.payload ? { payload: { ...event.payload } } : {}
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
156
|
+
0 && (module.exports = {
|
|
157
|
+
TraceCommitWriter
|
|
158
|
+
});
|
|
159
|
+
//# sourceMappingURL=trace_commit_writer.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/tracing/trace_commit_writer.ts"],"sourcesContent":["import type { TraceLevel, TraceProcess } from './trace_hub.js';\nimport type { WoodTraceEventSnapshot } from './types/wood_trace_sink.types.js';\n\n/**\n * A committed trace row. Ordering is authoritative on `commitSeq` — the\n * writer-assigned monotonic sequence stamped at the single ingestion/commit\n * boundary. `producerId` + `producerSeq` identify the producer-side stream;\n * `timestamp` is retained for display but is NEVER used for cross-producer\n * ordering (clocks across main/renderer are not causal).\n */\nexport interface TraceCommitRow {\n readonly commitSeq: number;\n readonly producerId: string;\n readonly producerSeq: number;\n readonly process: TraceProcess;\n readonly level: TraceLevel;\n readonly type: string;\n readonly source: string;\n readonly message?: string;\n readonly conversationId?: number | string;\n readonly timestamp: string;\n readonly payload?: Record<string, unknown>;\n}\n\n/**\n * Storage boundary for committed trace rows. Implemented per-runtime: the app's\n * test backend harness supplies a SQLite-backed adapter (better-sqlite3, which\n * wood does not depend on). `persist` may be sync or async; the writer barrier\n * awaits whatever it returns.\n */\nexport interface WoodTracePersistencePort {\n persist(row: TraceCommitRow): void | Promise<void>;\n close?(): void | Promise<void>;\n}\n\nexport interface TraceFlushOptions {\n timeoutMs?: number;\n}\n\nexport interface TraceFlushResult {\n /** Highest `commitSeq` durably persisted at flush completion. */\n readonly committedHighWaterMark: number;\n}\n\nexport interface TraceCommitWriterOptions {\n /** Stable id for the main-process producer stream (default `'main'`). */\n readonly mainProducerId?: string;\n}\n\n/**\n * The single trace ingestion/commit boundary. Every trace event (main or, in\n * later phases, renderer batches) is admitted here, assigned a monotonic\n * `commitSeq`, and enqueued for persistence. `flush` drains the async write\n * queue and returns the committed high-water mark — no sleeps, no polling.\n */\nexport class TraceCommitWriter {\n private readonly port: WoodTracePersistencePort;\n private readonly mainProducerId: string;\n private readonly seen = new Set<string>();\n private inflight: Promise<void>[] = [];\n private nextCommitSeq = 1;\n private committed = 0;\n private pendingError: unknown = null;\n private rendererFlush: (() => Promise<void>) | null = null;\n\n constructor(port: WoodTracePersistencePort, options: TraceCommitWriterOptions = {}) {\n this.port = port;\n this.mainProducerId = options.mainProducerId ?? 'main';\n }\n\n /**\n * Admit one trace event. Returns the committed row (with its assigned\n * `commitSeq`), or `null` if it is a duplicate of an already-admitted\n * (producerId, producerSeq) pair — this is the batch-retry idempotency guard.\n */\n admit(event: WoodTraceEventSnapshot): TraceCommitRow | null {\n const producerId = this.resolveProducerId(event);\n const producerSeq = event.traceSeq;\n const identity = `${producerId}#${producerSeq}`;\n if (this.seen.has(identity)) {\n return null;\n }\n this.seen.add(identity);\n const row = this.toRow(event, producerId, producerSeq);\n this.enqueue(row);\n return row;\n }\n\n /** WoodTraceEventSink shape — register via `tracer.registerSink(writer.sink)`. */\n readonly sink = (event: WoodTraceEventSnapshot): void => {\n this.admit(event);\n };\n\n highWaterMark(): number {\n return this.committed;\n }\n\n /**\n * Install the renderer-flush hook (Phase 4 browser mode). The browser harness\n * supplies a function that drains the page's in-flight renderer `__trace`\n * round-trips; `flush()` awaits it before the main-writer barrier so\n * renderer-origin traces are persisted deterministically. Backend mode leaves\n * this unset and `requestRendererFlush` resolves immediately.\n */\n setRendererFlush(fn: (() => Promise<void>) | null): void {\n this.rendererFlush = fn;\n }\n\n /**\n * Request that renderer producers flush their pending batches and await acks.\n * In browser mode the installed hook (see {@link setRendererFlush}) round-trips\n * the page to drain pending `__trace` deliveries. With no hook (backend mode,\n * no renderer) this resolves immediately.\n */\n async requestRendererFlush(): Promise<void> {\n if (this.rendererFlush) {\n await this.rendererFlush();\n }\n }\n\n /**\n * Drain the write queue and return the committed high-water mark. Awaits a\n * barrier over all in-flight persists; with `timeoutMs` it rejects on a\n * timer race rather than polling. Rejects if any persist failed.\n */\n async flush(options: TraceFlushOptions = {}): Promise<TraceFlushResult> {\n await this.requestRendererFlush();\n const barrier = Promise.allSettled(this.inflight.slice());\n await this.awaitBarrier(barrier, options.timeoutMs);\n if (this.pendingError !== null) {\n const error = this.pendingError;\n this.pendingError = null;\n throw error;\n }\n return { committedHighWaterMark: this.committed };\n }\n\n private async awaitBarrier(\n barrier: Promise<unknown>,\n timeoutMs: number | undefined,\n ): Promise<void> {\n if (timeoutMs === undefined) {\n await barrier;\n return;\n }\n let timer: ReturnType<typeof setTimeout> | null = null;\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(\n () => reject(new Error(`trace flush barrier exceeded ${timeoutMs}ms`)),\n timeoutMs,\n );\n });\n try {\n await Promise.race([barrier.then(() => undefined), timeout]);\n } finally {\n if (timer !== null) {\n clearTimeout(timer);\n }\n }\n }\n\n private enqueue(row: TraceCommitRow): void {\n const op = Promise.resolve()\n .then(() => this.port.persist(row))\n .then(\n () => {\n if (row.commitSeq > this.committed) {\n this.committed = row.commitSeq;\n }\n },\n (error: unknown) => {\n this.pendingError = error;\n },\n );\n this.inflight.push(op);\n }\n\n private resolveProducerId(event: WoodTraceEventSnapshot): string {\n if (event.process === 'renderer') {\n const rendererId = event.payload?.woodRendererProducerId;\n return typeof rendererId === 'string' ? rendererId : 'renderer';\n }\n if (event.process === 'main') {\n return this.mainProducerId;\n }\n return event.process;\n }\n\n private toRow(\n event: WoodTraceEventSnapshot,\n producerId: string,\n producerSeq: number,\n ): TraceCommitRow {\n return {\n commitSeq: this.nextCommitSeq++,\n producerId,\n producerSeq,\n process: event.process,\n level: event.level,\n type: event.type,\n source: event.source,\n ...(event.message !== undefined ? { message: event.message } : {}),\n ...(event.conversationId !== undefined ? { conversationId: event.conversationId } : {}),\n timestamp: event.timestamp,\n ...(event.payload ? { payload: { ...event.payload } } : {}),\n };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAuDO,MAAM,kBAAkB;AAAA,EAU7B,YAAY,MAAgC,UAAoC,CAAC,GAAG;AAPpF,SAAiB,OAAO,oBAAI,IAAY;AACxC,SAAQ,WAA4B,CAAC;AACrC,SAAQ,gBAAgB;AACxB,SAAQ,YAAY;AACpB,SAAQ,eAAwB;AAChC,SAAQ,gBAA8C;AA0BtD;AAAA,SAAS,OAAO,CAAC,UAAwC;AACvD,WAAK,MAAM,KAAK;AAAA,IAClB;AAzBE,SAAK,OAAO;AACZ,SAAK,iBAAiB,QAAQ,kBAAkB;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,OAAsD;AAC1D,UAAM,aAAa,KAAK,kBAAkB,KAAK;AAC/C,UAAM,cAAc,MAAM;AAC1B,UAAM,WAAW,GAAG,UAAU,IAAI,WAAW;AAC7C,QAAI,KAAK,KAAK,IAAI,QAAQ,GAAG;AAC3B,aAAO;AAAA,IACT;AACA,SAAK,KAAK,IAAI,QAAQ;AACtB,UAAM,MAAM,KAAK,MAAM,OAAO,YAAY,WAAW;AACrD,SAAK,QAAQ,GAAG;AAChB,WAAO;AAAA,EACT;AAAA,EAOA,gBAAwB;AACtB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,iBAAiB,IAAwC;AACvD,SAAK,gBAAgB;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,uBAAsC;AAC1C,QAAI,KAAK,eAAe;AACtB,YAAM,KAAK,cAAc;AAAA,IAC3B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,MAAM,UAA6B,CAAC,GAA8B;AACtE,UAAM,KAAK,qBAAqB;AAChC,UAAM,UAAU,QAAQ,WAAW,KAAK,SAAS,MAAM,CAAC;AACxD,UAAM,KAAK,aAAa,SAAS,QAAQ,SAAS;AAClD,QAAI,KAAK,iBAAiB,MAAM;AAC9B,YAAM,QAAQ,KAAK;AACnB,WAAK,eAAe;AACpB,YAAM;AAAA,IACR;AACA,WAAO,EAAE,wBAAwB,KAAK,UAAU;AAAA,EAClD;AAAA,EAEA,MAAc,aACZ,SACA,WACe;AACf,QAAI,cAAc,QAAW;AAC3B,YAAM;AACN;AAAA,IACF;AACA,QAAI,QAA8C;AAClD,UAAM,UAAU,IAAI,QAAe,CAAC,GAAG,WAAW;AAChD,cAAQ;AAAA,QACN,MAAM,OAAO,IAAI,MAAM,gCAAgC,SAAS,IAAI,CAAC;AAAA,QACrE;AAAA,MACF;AAAA,IACF,CAAC;AACD,QAAI;AACF,YAAM,QAAQ,KAAK,CAAC,QAAQ,KAAK,MAAM,MAAS,GAAG,OAAO,CAAC;AAAA,IAC7D,UAAE;AACA,UAAI,UAAU,MAAM;AAClB,qBAAa,KAAK;AAAA,MACpB;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,QAAQ,KAA2B;AACzC,UAAM,KAAK,QAAQ,QAAQ,EACxB,KAAK,MAAM,KAAK,KAAK,QAAQ,GAAG,CAAC,EACjC;AAAA,MACC,MAAM;AACJ,YAAI,IAAI,YAAY,KAAK,WAAW;AAClC,eAAK,YAAY,IAAI;AAAA,QACvB;AAAA,MACF;AAAA,MACA,CAAC,UAAmB;AAClB,aAAK,eAAe;AAAA,MACtB;AAAA,IACF;AACF,SAAK,SAAS,KAAK,EAAE;AAAA,EACvB;AAAA,EAEQ,kBAAkB,OAAuC;AAC/D,QAAI,MAAM,YAAY,YAAY;AAChC,YAAM,aAAa,MAAM,SAAS;AAClC,aAAO,OAAO,eAAe,WAAW,aAAa;AAAA,IACvD;AACA,QAAI,MAAM,YAAY,QAAQ;AAC5B,aAAO,KAAK;AAAA,IACd;AACA,WAAO,MAAM;AAAA,EACf;AAAA,EAEQ,MACN,OACA,YACA,aACgB;AAChB,WAAO;AAAA,MACL,WAAW,KAAK;AAAA,MAChB;AAAA,MACA;AAAA,MACA,SAAS,MAAM;AAAA,MACf,OAAO,MAAM;AAAA,MACb,MAAM,MAAM;AAAA,MACZ,QAAQ,MAAM;AAAA,MACd,GAAI,MAAM,YAAY,SAAY,EAAE,SAAS,MAAM,QAAQ,IAAI,CAAC;AAAA,MAChE,GAAI,MAAM,mBAAmB,SAAY,EAAE,gBAAgB,MAAM,eAAe,IAAI,CAAC;AAAA,MACrF,WAAW,MAAM;AAAA,MACjB,GAAI,MAAM,UAAU,EAAE,SAAS,EAAE,GAAG,MAAM,QAAQ,EAAE,IAAI,CAAC;AAAA,IAC3D;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { d as TraceProcess, c as TraceLevel, g as WoodTraceEventSnapshot } from '../trace_hub-Cob54qRx.cjs';
|
|
2
|
+
import 'rxjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A committed trace row. Ordering is authoritative on `commitSeq` — the
|
|
6
|
+
* writer-assigned monotonic sequence stamped at the single ingestion/commit
|
|
7
|
+
* boundary. `producerId` + `producerSeq` identify the producer-side stream;
|
|
8
|
+
* `timestamp` is retained for display but is NEVER used for cross-producer
|
|
9
|
+
* ordering (clocks across main/renderer are not causal).
|
|
10
|
+
*/
|
|
11
|
+
interface TraceCommitRow {
|
|
12
|
+
readonly commitSeq: number;
|
|
13
|
+
readonly producerId: string;
|
|
14
|
+
readonly producerSeq: number;
|
|
15
|
+
readonly process: TraceProcess;
|
|
16
|
+
readonly level: TraceLevel;
|
|
17
|
+
readonly type: string;
|
|
18
|
+
readonly source: string;
|
|
19
|
+
readonly message?: string;
|
|
20
|
+
readonly conversationId?: number | string;
|
|
21
|
+
readonly timestamp: string;
|
|
22
|
+
readonly payload?: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Storage boundary for committed trace rows. Implemented per-runtime: the app's
|
|
26
|
+
* test backend harness supplies a SQLite-backed adapter (better-sqlite3, which
|
|
27
|
+
* wood does not depend on). `persist` may be sync or async; the writer barrier
|
|
28
|
+
* awaits whatever it returns.
|
|
29
|
+
*/
|
|
30
|
+
interface WoodTracePersistencePort {
|
|
31
|
+
persist(row: TraceCommitRow): void | Promise<void>;
|
|
32
|
+
close?(): void | Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
interface TraceFlushOptions {
|
|
35
|
+
timeoutMs?: number;
|
|
36
|
+
}
|
|
37
|
+
interface TraceFlushResult {
|
|
38
|
+
/** Highest `commitSeq` durably persisted at flush completion. */
|
|
39
|
+
readonly committedHighWaterMark: number;
|
|
40
|
+
}
|
|
41
|
+
interface TraceCommitWriterOptions {
|
|
42
|
+
/** Stable id for the main-process producer stream (default `'main'`). */
|
|
43
|
+
readonly mainProducerId?: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* The single trace ingestion/commit boundary. Every trace event (main or, in
|
|
47
|
+
* later phases, renderer batches) is admitted here, assigned a monotonic
|
|
48
|
+
* `commitSeq`, and enqueued for persistence. `flush` drains the async write
|
|
49
|
+
* queue and returns the committed high-water mark — no sleeps, no polling.
|
|
50
|
+
*/
|
|
51
|
+
declare class TraceCommitWriter {
|
|
52
|
+
private readonly port;
|
|
53
|
+
private readonly mainProducerId;
|
|
54
|
+
private readonly seen;
|
|
55
|
+
private inflight;
|
|
56
|
+
private nextCommitSeq;
|
|
57
|
+
private committed;
|
|
58
|
+
private pendingError;
|
|
59
|
+
private rendererFlush;
|
|
60
|
+
constructor(port: WoodTracePersistencePort, options?: TraceCommitWriterOptions);
|
|
61
|
+
/**
|
|
62
|
+
* Admit one trace event. Returns the committed row (with its assigned
|
|
63
|
+
* `commitSeq`), or `null` if it is a duplicate of an already-admitted
|
|
64
|
+
* (producerId, producerSeq) pair — this is the batch-retry idempotency guard.
|
|
65
|
+
*/
|
|
66
|
+
admit(event: WoodTraceEventSnapshot): TraceCommitRow | null;
|
|
67
|
+
/** WoodTraceEventSink shape — register via `tracer.registerSink(writer.sink)`. */
|
|
68
|
+
readonly sink: (event: WoodTraceEventSnapshot) => void;
|
|
69
|
+
highWaterMark(): number;
|
|
70
|
+
/**
|
|
71
|
+
* Install the renderer-flush hook (Phase 4 browser mode). The browser harness
|
|
72
|
+
* supplies a function that drains the page's in-flight renderer `__trace`
|
|
73
|
+
* round-trips; `flush()` awaits it before the main-writer barrier so
|
|
74
|
+
* renderer-origin traces are persisted deterministically. Backend mode leaves
|
|
75
|
+
* this unset and `requestRendererFlush` resolves immediately.
|
|
76
|
+
*/
|
|
77
|
+
setRendererFlush(fn: (() => Promise<void>) | null): void;
|
|
78
|
+
/**
|
|
79
|
+
* Request that renderer producers flush their pending batches and await acks.
|
|
80
|
+
* In browser mode the installed hook (see {@link setRendererFlush}) round-trips
|
|
81
|
+
* the page to drain pending `__trace` deliveries. With no hook (backend mode,
|
|
82
|
+
* no renderer) this resolves immediately.
|
|
83
|
+
*/
|
|
84
|
+
requestRendererFlush(): Promise<void>;
|
|
85
|
+
/**
|
|
86
|
+
* Drain the write queue and return the committed high-water mark. Awaits a
|
|
87
|
+
* barrier over all in-flight persists; with `timeoutMs` it rejects on a
|
|
88
|
+
* timer race rather than polling. Rejects if any persist failed.
|
|
89
|
+
*/
|
|
90
|
+
flush(options?: TraceFlushOptions): Promise<TraceFlushResult>;
|
|
91
|
+
private awaitBarrier;
|
|
92
|
+
private enqueue;
|
|
93
|
+
private resolveProducerId;
|
|
94
|
+
private toRow;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export { type TraceCommitRow, TraceCommitWriter, type TraceCommitWriterOptions, type TraceFlushOptions, type TraceFlushResult, type WoodTracePersistencePort };
|