lemon-model 1.2.2 → 1.2.3
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/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/logtrace/index.js +11 -0
- package/dist/esm/logtrace/index.js.map +1 -0
- package/dist/esm/logtrace/logtrace.js +251 -0
- package/dist/esm/logtrace/logtrace.js.map +1 -0
- package/dist/esm/logtrace/testing.js +77 -0
- package/dist/esm/logtrace/testing.js.map +1 -0
- package/dist/esm/progress/index.js +9 -0
- package/dist/esm/progress/index.js.map +1 -0
- package/dist/esm/progress/progress.js +256 -0
- package/dist/esm/progress/progress.js.map +1 -0
- package/dist/esm/progress/testing.js +98 -0
- package/dist/esm/progress/testing.js.map +1 -0
- package/dist/esm/socket/websocket.js +18 -4
- package/dist/esm/socket/websocket.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/logtrace/index.d.ts +7 -0
- package/dist/logtrace/index.js +27 -0
- package/dist/logtrace/index.js.map +1 -0
- package/dist/logtrace/logtrace.d.ts +99 -0
- package/dist/logtrace/logtrace.js +256 -0
- package/dist/logtrace/logtrace.js.map +1 -0
- package/dist/logtrace/testing.d.ts +40 -0
- package/dist/logtrace/testing.js +81 -0
- package/dist/logtrace/testing.js.map +1 -0
- package/dist/progress/index.d.ts +8 -0
- package/dist/progress/index.js +25 -0
- package/dist/progress/index.js.map +1 -0
- package/dist/progress/progress.d.ts +112 -0
- package/dist/progress/progress.js +263 -0
- package/dist/progress/progress.js.map +1 -0
- package/dist/progress/testing.d.ts +33 -0
- package/dist/progress/testing.js +102 -0
- package/dist/progress/testing.js.map +1 -0
- package/dist/socket/websocket.d.ts +14 -0
- package/dist/socket/websocket.js +19 -5
- package/dist/socket/websocket.js.map +1 -1
- package/package.json +13 -1
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createLogTraceConsumer = exports.createLogTraceReporter = void 0;
|
|
4
|
+
const websocket_1 = require("../socket/websocket");
|
|
5
|
+
const DEFAULT_TYPE = 'log:trace';
|
|
6
|
+
const DEFAULT_TYPE_PREFIX = 'log:';
|
|
7
|
+
const DEFAULT_MIN_LEVEL = 'debug';
|
|
8
|
+
const DEFAULT_FLUSH_COUNT = 20;
|
|
9
|
+
const DEFAULT_FLUSH_INTERVAL_MS = 250;
|
|
10
|
+
const DEFAULT_MAX_PACKET_BYTES = 64 * 1024;
|
|
11
|
+
const DEFAULT_MAX_ENTRIES = 1000;
|
|
12
|
+
const LEVEL_WEIGHT = { debug: 0, info: 1, warn: 2, error: 3 };
|
|
13
|
+
/** create a batching log reporter over an injected send path */
|
|
14
|
+
const createLogTraceReporter = (sink, options) => new LogTraceReporter(sink, options);
|
|
15
|
+
exports.createLogTraceReporter = createLogTraceReporter;
|
|
16
|
+
/** create a log consumer over a shared raw string network */
|
|
17
|
+
const createLogTraceConsumer = (network, options) => new LogTraceConsumer(network, options);
|
|
18
|
+
exports.createLogTraceConsumer = createLogTraceConsumer;
|
|
19
|
+
/** server-side log reporter - batches entries and emits SocketMessage<LogTraceBatch> via the sink */
|
|
20
|
+
class LogTraceReporter {
|
|
21
|
+
constructor(sink, options) {
|
|
22
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
23
|
+
this.buffer = [];
|
|
24
|
+
this.bufferBytes = 0;
|
|
25
|
+
this.seqNo = 0;
|
|
26
|
+
this.midNo = 0;
|
|
27
|
+
this.sentOnce = false;
|
|
28
|
+
this.closed = false;
|
|
29
|
+
const maxPacketBytes = (_a = options === null || options === void 0 ? void 0 : options.maxPacketBytes) !== null && _a !== void 0 ? _a : DEFAULT_MAX_PACKET_BYTES;
|
|
30
|
+
this.sink = sink;
|
|
31
|
+
this.type = (_b = options === null || options === void 0 ? void 0 : options.type) !== null && _b !== void 0 ? _b : DEFAULT_TYPE;
|
|
32
|
+
this.source = (_c = options === null || options === void 0 ? void 0 : options.source) !== null && _c !== void 0 ? _c : nextRandomSource();
|
|
33
|
+
this.minLevel = LEVEL_WEIGHT[(_d = options === null || options === void 0 ? void 0 : options.minLevel) !== null && _d !== void 0 ? _d : DEFAULT_MIN_LEVEL];
|
|
34
|
+
this.flushCount = (_e = options === null || options === void 0 ? void 0 : options.flushCount) !== null && _e !== void 0 ? _e : DEFAULT_FLUSH_COUNT;
|
|
35
|
+
this.flushIntervalMs = (_f = options === null || options === void 0 ? void 0 : options.flushIntervalMs) !== null && _f !== void 0 ? _f : DEFAULT_FLUSH_INTERVAL_MS;
|
|
36
|
+
this.maxBatchBytes = (_g = options === null || options === void 0 ? void 0 : options.maxBatchBytes) !== null && _g !== void 0 ? _g : Math.floor((maxPacketBytes * 3) / 4);
|
|
37
|
+
this.onError = options === null || options === void 0 ? void 0 : options.onError;
|
|
38
|
+
}
|
|
39
|
+
/** record one log entry. auto-flushes when a flush condition is reached */
|
|
40
|
+
log(level, message, json) {
|
|
41
|
+
if (this.closed)
|
|
42
|
+
return;
|
|
43
|
+
//! seq is issued only past this gate so a raised minLevel never creates seq gaps.
|
|
44
|
+
if (LEVEL_WEIGHT[level] < this.minLevel)
|
|
45
|
+
return;
|
|
46
|
+
const entry = { level, ts: Date.now(), message, seq: ++this.seqNo };
|
|
47
|
+
if (json !== undefined)
|
|
48
|
+
entry.json = json;
|
|
49
|
+
let bytes = byteLength(JSON.stringify(entry));
|
|
50
|
+
if (bytes > this.maxBatchBytes)
|
|
51
|
+
bytes = this.truncate(entry, bytes);
|
|
52
|
+
//! adding this entry would exceed the batch budget: flush the current buffer, start a new batch.
|
|
53
|
+
if (this.buffer.length > 0 && this.bufferBytes + bytes + 1 > this.maxBatchBytes)
|
|
54
|
+
this.flush();
|
|
55
|
+
this.buffer.push(entry);
|
|
56
|
+
this.bufferBytes += bytes + 1;
|
|
57
|
+
if (level === 'error' || this.buffer.length >= this.flushCount)
|
|
58
|
+
return this.flush();
|
|
59
|
+
if (this.flushIntervalMs <= 0)
|
|
60
|
+
return;
|
|
61
|
+
//! leading edge: the first batch of this reporter flushes immediately so the viewer never waits a window.
|
|
62
|
+
if (!this.sentOnce)
|
|
63
|
+
return this.flush();
|
|
64
|
+
if (!this.timer)
|
|
65
|
+
this.timer = setTimeout(() => this.flush(), this.flushIntervalMs);
|
|
66
|
+
}
|
|
67
|
+
debug(message, json) {
|
|
68
|
+
this.log('debug', message, json);
|
|
69
|
+
}
|
|
70
|
+
info(message, json) {
|
|
71
|
+
this.log('info', message, json);
|
|
72
|
+
}
|
|
73
|
+
warn(message, json) {
|
|
74
|
+
this.log('warn', message, json);
|
|
75
|
+
}
|
|
76
|
+
error(message, json) {
|
|
77
|
+
this.log('error', message, json);
|
|
78
|
+
}
|
|
79
|
+
/** send the buffered entries now */
|
|
80
|
+
flush() {
|
|
81
|
+
this.clearTimer();
|
|
82
|
+
if (this.buffer.length <= 0)
|
|
83
|
+
return;
|
|
84
|
+
const entries = this.buffer;
|
|
85
|
+
this.buffer = [];
|
|
86
|
+
this.bufferBytes = 0;
|
|
87
|
+
this.sentOnce = true;
|
|
88
|
+
const message = {
|
|
89
|
+
type: this.type,
|
|
90
|
+
data: { entries, source: this.source },
|
|
91
|
+
mid: `l${++this.midNo}`,
|
|
92
|
+
};
|
|
93
|
+
//! at-most-once: a failed batch is reported to onError and dropped. never breaks the caller.
|
|
94
|
+
try {
|
|
95
|
+
const result = this.sink(message);
|
|
96
|
+
if (result && typeof result.then === 'function')
|
|
97
|
+
result.catch(e => this.emitError(e, entries));
|
|
98
|
+
}
|
|
99
|
+
catch (e) {
|
|
100
|
+
this.emitError(e, entries);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/** flush + release the timer. later log calls are ignored */
|
|
104
|
+
close() {
|
|
105
|
+
this.flush();
|
|
106
|
+
this.closed = true;
|
|
107
|
+
}
|
|
108
|
+
/** fit an oversized entry into the batch budget: drop json first, then cut the message */
|
|
109
|
+
truncate(entry, bytes) {
|
|
110
|
+
const error = new Error(`@entry[${entry.seq}] exceeds batch budget(${this.maxBatchBytes}) - logtrace.truncate`);
|
|
111
|
+
entry.truncated = true;
|
|
112
|
+
if (entry.json !== undefined) {
|
|
113
|
+
delete entry.json;
|
|
114
|
+
bytes = byteLength(JSON.stringify(entry));
|
|
115
|
+
}
|
|
116
|
+
//! every removed character frees at least 1 byte, so this loop converges quickly.
|
|
117
|
+
while (bytes > this.maxBatchBytes && entry.message.length > 0) {
|
|
118
|
+
entry.message = entry.message.slice(0, Math.max(0, entry.message.length - (bytes - this.maxBatchBytes)));
|
|
119
|
+
bytes = byteLength(JSON.stringify(entry));
|
|
120
|
+
}
|
|
121
|
+
this.emitError(error, [entry]);
|
|
122
|
+
return bytes;
|
|
123
|
+
}
|
|
124
|
+
emitError(error, entries) {
|
|
125
|
+
var _a;
|
|
126
|
+
try {
|
|
127
|
+
(_a = this.onError) === null || _a === void 0 ? void 0 : _a.call(this, error, entries);
|
|
128
|
+
}
|
|
129
|
+
catch (_b) {
|
|
130
|
+
// The error observer must never break the reporter.
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
clearTimer() {
|
|
134
|
+
if (!this.timer)
|
|
135
|
+
return;
|
|
136
|
+
clearTimeout(this.timer);
|
|
137
|
+
this.timer = undefined;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/** client-side log consumer - unpacks batches into a (ts, seq)-sorted ring buffer */
|
|
141
|
+
class LogTraceConsumer {
|
|
142
|
+
constructor(network, options) {
|
|
143
|
+
var _a, _b, _c;
|
|
144
|
+
this.handlers = new Set();
|
|
145
|
+
this.sources = new Map();
|
|
146
|
+
this.entries = [];
|
|
147
|
+
this.typePrefix = (_a = options === null || options === void 0 ? void 0 : options.typePrefix) !== null && _a !== void 0 ? _a : DEFAULT_TYPE_PREFIX;
|
|
148
|
+
this.minLevel = LEVEL_WEIGHT[(_b = options === null || options === void 0 ? void 0 : options.minLevel) !== null && _b !== void 0 ? _b : DEFAULT_MIN_LEVEL];
|
|
149
|
+
this.maxEntries = (_c = options === null || options === void 0 ? void 0 : options.maxEntries) !== null && _c !== void 0 ? _c : DEFAULT_MAX_ENTRIES;
|
|
150
|
+
//! raw stage of the 2-stage filter: substring check without parsing.
|
|
151
|
+
const marker = `"type":"${this.typePrefix}`;
|
|
152
|
+
const filtered = (0, websocket_1.createFilteredNetwork)(network, raw => raw.includes(marker));
|
|
153
|
+
this.unsubscribe = filtered.onMessage(raw => this.receive(raw));
|
|
154
|
+
}
|
|
155
|
+
/** subscribe to received entries */
|
|
156
|
+
onEntry(handler) {
|
|
157
|
+
this.handlers.add(handler);
|
|
158
|
+
return () => {
|
|
159
|
+
this.handlers.delete(handler);
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/** list retained entries in (ts, seq) ascending order */
|
|
163
|
+
list(query) {
|
|
164
|
+
var _a;
|
|
165
|
+
const min = LEVEL_WEIGHT[(_a = query === null || query === void 0 ? void 0 : query.minLevel) !== null && _a !== void 0 ? _a : DEFAULT_MIN_LEVEL];
|
|
166
|
+
let listed = min > 0 ? this.entries.filter(entry => LEVEL_WEIGHT[entry.level] >= min) : [...this.entries];
|
|
167
|
+
const limit = query === null || query === void 0 ? void 0 : query.limit;
|
|
168
|
+
if (limit !== undefined && limit >= 0 && listed.length > limit)
|
|
169
|
+
listed = listed.slice(listed.length - limit);
|
|
170
|
+
return listed;
|
|
171
|
+
}
|
|
172
|
+
/** observed wire loss - sum of (max seq - received count) per source */
|
|
173
|
+
get gapCount() {
|
|
174
|
+
let gaps = 0;
|
|
175
|
+
for (const state of this.sources.values())
|
|
176
|
+
gaps += Math.max(0, state.maxSeq - state.seen.size);
|
|
177
|
+
return gaps;
|
|
178
|
+
}
|
|
179
|
+
/** drop retained entries (subscriptions and loss accounting stay) */
|
|
180
|
+
clear() {
|
|
181
|
+
this.entries = [];
|
|
182
|
+
}
|
|
183
|
+
/** unsubscribe from the network. does NOT close the network (shared socket) */
|
|
184
|
+
close() {
|
|
185
|
+
this.unsubscribe();
|
|
186
|
+
this.handlers.clear();
|
|
187
|
+
}
|
|
188
|
+
receive(raw) {
|
|
189
|
+
let message;
|
|
190
|
+
try {
|
|
191
|
+
message = JSON.parse(raw);
|
|
192
|
+
}
|
|
193
|
+
catch (_a) {
|
|
194
|
+
return; // the raw substring filter is best-effort; a non-JSON hit is simply not ours.
|
|
195
|
+
}
|
|
196
|
+
if (!message || typeof message.type !== 'string' || !message.type.startsWith(this.typePrefix))
|
|
197
|
+
return;
|
|
198
|
+
const batch = message.data;
|
|
199
|
+
if (!batch || typeof batch.source !== 'string' || !Array.isArray(batch.entries))
|
|
200
|
+
return;
|
|
201
|
+
const state = this.stateOf(batch.source);
|
|
202
|
+
for (const entry of batch.entries) {
|
|
203
|
+
if (!isLogTraceEntry(entry))
|
|
204
|
+
continue;
|
|
205
|
+
//! dedup + loss accounting happen BEFORE the minLevel gate so gapCount stays a pure wire-loss metric.
|
|
206
|
+
if (state.seen.has(entry.seq))
|
|
207
|
+
continue;
|
|
208
|
+
state.seen.add(entry.seq);
|
|
209
|
+
if (entry.seq > state.maxSeq)
|
|
210
|
+
state.maxSeq = entry.seq;
|
|
211
|
+
if (LEVEL_WEIGHT[entry.level] < this.minLevel)
|
|
212
|
+
continue;
|
|
213
|
+
const accepted = Object.assign(Object.assign({}, entry), { source: batch.source });
|
|
214
|
+
this.store(accepted);
|
|
215
|
+
for (const handler of [...this.handlers])
|
|
216
|
+
handler(accepted);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/** binary-insert by (ts, seq) so list() never re-sorts; evict the oldest on overflow */
|
|
220
|
+
store(entry) {
|
|
221
|
+
let lo = 0;
|
|
222
|
+
let hi = this.entries.length;
|
|
223
|
+
while (lo < hi) {
|
|
224
|
+
const mid = (lo + hi) >> 1;
|
|
225
|
+
if (compareEntry(this.entries[mid], entry) <= 0)
|
|
226
|
+
lo = mid + 1;
|
|
227
|
+
else
|
|
228
|
+
hi = mid;
|
|
229
|
+
}
|
|
230
|
+
this.entries.splice(lo, 0, entry);
|
|
231
|
+
if (this.entries.length > this.maxEntries)
|
|
232
|
+
this.entries.shift();
|
|
233
|
+
}
|
|
234
|
+
stateOf(source) {
|
|
235
|
+
let state = this.sources.get(source);
|
|
236
|
+
if (!state) {
|
|
237
|
+
// ponytail: the per-source seen Set grows with log volume; swap to seq-range tracking if sessions get huge.
|
|
238
|
+
state = { maxSeq: 0, seen: new Set() };
|
|
239
|
+
this.sources.set(source, state);
|
|
240
|
+
}
|
|
241
|
+
return state;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
let sourceNo = 0;
|
|
245
|
+
const nextRandomSource = () => `src-${Math.random().toString(36).slice(2, 8)}${++sourceNo}`;
|
|
246
|
+
const compareEntry = (a, b) => a.ts - b.ts || a.seq - b.seq;
|
|
247
|
+
const isLogTraceEntry = (entry) => !!entry &&
|
|
248
|
+
typeof entry === 'object' &&
|
|
249
|
+
LEVEL_WEIGHT[entry.level] !== undefined &&
|
|
250
|
+
typeof entry.ts === 'number' &&
|
|
251
|
+
typeof entry.message === 'string' &&
|
|
252
|
+
Number.isInteger(entry.seq) &&
|
|
253
|
+
entry.seq > 0;
|
|
254
|
+
const textEncoder = new TextEncoder();
|
|
255
|
+
const byteLength = (value) => textEncoder.encode(value).length;
|
|
256
|
+
//# sourceMappingURL=logtrace.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logtrace.js","sourceRoot":"","sources":["../../src/logtrace/logtrace.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { SocketNetworkOptions } from '../socket/types';
|
|
2
|
+
import { LogTraceConsumerOptions, LogTraceEntry, LogTraceLevel, LogTraceReporterOptions } from './logtrace';
|
|
3
|
+
export interface LogTraceLoopOptions {
|
|
4
|
+
/** log sequence to replay */
|
|
5
|
+
entries: Array<{
|
|
6
|
+
level: LogTraceLevel;
|
|
7
|
+
message: string;
|
|
8
|
+
json?: Record<string, any>;
|
|
9
|
+
}>;
|
|
10
|
+
reporterOptions?: LogTraceReporterOptions;
|
|
11
|
+
consumerOptions?: LogTraceConsumerOptions;
|
|
12
|
+
/** in-memory network conditions (unordered, jitterMs, maxPacketBytes, ...) */
|
|
13
|
+
networkOptions?: SocketNetworkOptions & {
|
|
14
|
+
id?: string;
|
|
15
|
+
};
|
|
16
|
+
/** wait time for async delivery */
|
|
17
|
+
settleMs?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface LogTraceLoopMetrics {
|
|
20
|
+
elapsedMs: number;
|
|
21
|
+
/** raw packet count / total bytes / largest packet bytes (batching efficiency + packet limit checks) */
|
|
22
|
+
packets: number;
|
|
23
|
+
packetBytes: number;
|
|
24
|
+
maxPacketBytes: number;
|
|
25
|
+
/** emitted batch count (flush 3-axis checks) */
|
|
26
|
+
batches: number;
|
|
27
|
+
/** entries reaching the consumer / truncated entries / observed seq gaps */
|
|
28
|
+
delivered: number;
|
|
29
|
+
truncated: number;
|
|
30
|
+
gapCount: number;
|
|
31
|
+
/** consumer retention at the end ((ts, seq) sorted) */
|
|
32
|
+
finalEntries: LogTraceEntry[];
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Run an end-to-end verification loop:
|
|
36
|
+
* entries -> LogTraceReporter (batching) -> NetworkSupportable -> LogTraceConsumer (ring buffer).
|
|
37
|
+
*
|
|
38
|
+
* Metrics-based assertions belong in specs; this helper only records numbers.
|
|
39
|
+
*/
|
|
40
|
+
export declare const runLogTraceLoop: (options: LogTraceLoopOptions) => Promise<LogTraceLoopMetrics>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.runLogTraceLoop = void 0;
|
|
13
|
+
/**
|
|
14
|
+
* `logtrace/testing.ts`
|
|
15
|
+
* - isolated entry for the logtrace verification loop harness.
|
|
16
|
+
* - kept OUT of the package root barrel so production bundles do not include the in-memory socket simulator.
|
|
17
|
+
*
|
|
18
|
+
* Usage (tests / dev only):
|
|
19
|
+
* import { runLogTraceLoop } from 'lemon-model/logtrace/testing';
|
|
20
|
+
*
|
|
21
|
+
* @copyright (C) 2026 LemonCloud Co Ltd. - All Rights Reserved.
|
|
22
|
+
*/
|
|
23
|
+
const socket_1 = require("../socket/socket");
|
|
24
|
+
const logtrace_1 = require("./logtrace");
|
|
25
|
+
/**
|
|
26
|
+
* Run an end-to-end verification loop:
|
|
27
|
+
* entries -> LogTraceReporter (batching) -> NetworkSupportable -> LogTraceConsumer (ring buffer).
|
|
28
|
+
*
|
|
29
|
+
* Metrics-based assertions belong in specs; this helper only records numbers.
|
|
30
|
+
*/
|
|
31
|
+
const runLogTraceLoop = (options) => __awaiter(void 0, void 0, void 0, function* () {
|
|
32
|
+
const startedAt = Date.now();
|
|
33
|
+
const network = (0, socket_1.createNetwork)(options.networkOptions);
|
|
34
|
+
// Raw packets measure batching and packet budgets, separate from delivery.
|
|
35
|
+
const rawPackets = [];
|
|
36
|
+
const unsubscribeRaw = network.onMessage(packet => rawPackets.push(packet));
|
|
37
|
+
const consumer = (0, logtrace_1.createLogTraceConsumer)(network, options.consumerOptions);
|
|
38
|
+
let delivered = 0;
|
|
39
|
+
let truncated = 0;
|
|
40
|
+
// Consumer callbacks are the source of delivered/truncated metrics.
|
|
41
|
+
const unsubscribeEntry = consumer.onEntry(entry => {
|
|
42
|
+
delivered += 1;
|
|
43
|
+
if (entry.truncated)
|
|
44
|
+
truncated += 1;
|
|
45
|
+
});
|
|
46
|
+
let batches = 0;
|
|
47
|
+
const reporter = (0, logtrace_1.createLogTraceReporter)(message => {
|
|
48
|
+
batches += 1;
|
|
49
|
+
network.send(JSON.stringify(message));
|
|
50
|
+
}, options.reporterOptions);
|
|
51
|
+
try {
|
|
52
|
+
// Replay input synchronously; reporter options decide when batches flush.
|
|
53
|
+
for (const record of options.entries)
|
|
54
|
+
reporter.log(record.level, record.message, record.json);
|
|
55
|
+
reporter.close();
|
|
56
|
+
yield new Promise(resolve => { var _a; return setTimeout(resolve, (_a = options.settleMs) !== null && _a !== void 0 ? _a : 50); });
|
|
57
|
+
const packetBytes = rawPackets.reduce((sum, packet) => sum + byteLength(packet), 0);
|
|
58
|
+
const maxPacketBytes = rawPackets.reduce((max, packet) => Math.max(max, byteLength(packet)), 0);
|
|
59
|
+
return {
|
|
60
|
+
elapsedMs: Date.now() - startedAt,
|
|
61
|
+
packets: rawPackets.length,
|
|
62
|
+
packetBytes,
|
|
63
|
+
maxPacketBytes,
|
|
64
|
+
batches,
|
|
65
|
+
delivered,
|
|
66
|
+
truncated,
|
|
67
|
+
gapCount: consumer.gapCount,
|
|
68
|
+
finalEntries: consumer.list(),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
finally {
|
|
72
|
+
unsubscribeRaw();
|
|
73
|
+
unsubscribeEntry();
|
|
74
|
+
consumer.close();
|
|
75
|
+
network.close();
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
exports.runLogTraceLoop = runLogTraceLoop;
|
|
79
|
+
const textEncoder = new TextEncoder();
|
|
80
|
+
const byteLength = (value) => textEncoder.encode(value).length;
|
|
81
|
+
//# sourceMappingURL=testing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"testing.js","sourceRoot":"","sources":["../../src/logtrace/testing.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA;;;;;;;;;GASG;AACH,6CAAiD;AAEjD,yCAOoB;AA6BpB;;;;;GAKG;AACI,MAAM,eAAe,GAAG,CAAO,OAA4B,EAAgC,EAAE;IAChG,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IAC7B,MAAM,OAAO,GAAG,IAAA,sBAAa,EAAC,OAAO,CAAC,cAAc,CAAC,CAAC;IACtD,2EAA2E;IAC3E,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,MAAM,cAAc,GAAG,OAAO,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;IAC5E,MAAM,QAAQ,GAAG,IAAA,iCAAsB,EAAC,OAAO,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;IAC1E,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,oEAAoE;IACpE,MAAM,gBAAgB,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE;QAC9C,SAAS,IAAI,CAAC,CAAC;QACf,IAAI,KAAK,CAAC,SAAS;YAAE,SAAS,IAAI,CAAC,CAAC;IACxC,CAAC,CAAC,CAAC;IACH,IAAI,OAAO,GAAG,CAAC,CAAC;IAChB,MAAM,QAAQ,GAAG,IAAA,iCAAsB,EAAC,OAAO,CAAC,EAAE;QAC9C,OAAO,IAAI,CAAC,CAAC;QACb,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC;IAC1C,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;IAE5B,IAAI;QACA,0EAA0E;QAC1E,KAAK,MAAM,MAAM,IAAI,OAAO,CAAC,OAAO;YAAE,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;QAC9F,QAAQ,CAAC,KAAK,EAAE,CAAC;QACjB,MAAM,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,WAAC,OAAA,UAAU,CAAC,OAAO,EAAE,MAAA,OAAO,CAAC,QAAQ,mCAAI,EAAE,CAAC,CAAA,EAAA,CAAC,CAAC;QAE1E,MAAM,WAAW,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,CAAC,GAAG,GAAG,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC;QACpF,MAAM,cAAc,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAChG,OAAO;YACH,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;YACjC,OAAO,EAAE,UAAU,CAAC,MAAM;YAC1B,WAAW;YACX,cAAc;YACd,OAAO;YACP,SAAS;YACT,SAAS;YACT,QAAQ,EAAE,QAAQ,CAAC,QAAQ;YAC3B,YAAY,EAAE,QAAQ,CAAC,IAAI,EAAE;SAChC,CAAC;KACL;YAAS;QACN,cAAc,EAAE,CAAC;QACjB,gBAAgB,EAAE,CAAC;QACnB,QAAQ,CAAC,KAAK,EAAE,CAAC;QACjB,OAAO,CAAC,KAAK,EAAE,CAAC;KACnB;AACL,CAAC,CAAA,CAAC;AA7CW,QAAA,eAAe,mBA6C1B;AAEF,MAAM,WAAW,GAAG,IAAI,WAAW,EAAE,CAAC;AACtC,MAAM,UAAU,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `progress/index.ts`
|
|
3
|
+
* - public re-export for the progress module.
|
|
4
|
+
* - `./testing` is intentionally excluded from this barrel (subpath-only, see SPEC.md).
|
|
5
|
+
*
|
|
6
|
+
* @copyright (C) 2026 LemonCloud Co Ltd. - All Rights Reserved.
|
|
7
|
+
*/
|
|
8
|
+
export * from './progress';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
/**
|
|
18
|
+
* `progress/index.ts`
|
|
19
|
+
* - public re-export for the progress module.
|
|
20
|
+
* - `./testing` is intentionally excluded from this barrel (subpath-only, see SPEC.md).
|
|
21
|
+
*
|
|
22
|
+
* @copyright (C) 2026 LemonCloud Co Ltd. - All Rights Reserved.
|
|
23
|
+
*/
|
|
24
|
+
__exportStar(require("./progress"), exports);
|
|
25
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/progress/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA;;;;;;GAMG;AACH,6CAA2B"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `progress/progress.ts`
|
|
3
|
+
* - one-way (server -> client) task progress reporting over a shared WebSocket.
|
|
4
|
+
* - contract + reporter + gauges + consumer in one file (see SPEC.md - SSOT).
|
|
5
|
+
*
|
|
6
|
+
* @copyright (C) 2026 LemonCloud Co Ltd. - All Rights Reserved.
|
|
7
|
+
*/
|
|
8
|
+
import { NetworkSupportable, SocketMessage, SocketUnsubscribe } from '../socket/types';
|
|
9
|
+
/** default serialized packet size limit (same family as `src/socket`) */
|
|
10
|
+
export declare const DEFAULT_MAX_PACKET_BYTES: number;
|
|
11
|
+
/** task execution status */
|
|
12
|
+
export declare type ProgressStatus = 'pending' | 'running' | 'done' | 'error';
|
|
13
|
+
/** progress snapshot of one task - the unit that travels the wire */
|
|
14
|
+
export interface ProgressState {
|
|
15
|
+
/** task id (assigned by the service) */
|
|
16
|
+
id: string;
|
|
17
|
+
/** execution status */
|
|
18
|
+
status: ProgressStatus;
|
|
19
|
+
/** 0~100 progress ratio (omitted when not measurable) */
|
|
20
|
+
percent?: number;
|
|
21
|
+
/** current step / total steps */
|
|
22
|
+
step?: number;
|
|
23
|
+
totalSteps?: number;
|
|
24
|
+
/** human-readable current step description */
|
|
25
|
+
label?: string;
|
|
26
|
+
/** error summary when status is error */
|
|
27
|
+
error?: string;
|
|
28
|
+
/** emitted timestamp (epoch ms) - display only */
|
|
29
|
+
ts: number;
|
|
30
|
+
/** reporter-wide monotonic sequence - latest-wins judgement */
|
|
31
|
+
seq: number;
|
|
32
|
+
/** service-defined extra info. keep it small (see packet limit) */
|
|
33
|
+
meta?: Record<string, any>;
|
|
34
|
+
}
|
|
35
|
+
/** measurement produced by a gauge - subset merged into snapshots */
|
|
36
|
+
export declare type ProgressMeasure = Partial<Pick<ProgressState, 'percent' | 'step' | 'totalSteps' | 'label'>>;
|
|
37
|
+
/**
|
|
38
|
+
* progress measurement strategy - separates "how" percent is computed from the reporter.
|
|
39
|
+
* must be a pure read with no side effects. one function fits every strategy.
|
|
40
|
+
*/
|
|
41
|
+
export declare type ProgressGauge = () => ProgressMeasure;
|
|
42
|
+
/** time-flow gauge - elapsed ratio against expectedMs, capped at 99% (only done() declares completion) */
|
|
43
|
+
export declare const createTimeProgressGauge: (expectedMs: number, now?: () => number) => ProgressGauge;
|
|
44
|
+
/** buffer-fill gauge - reads percent/bufferPercent from a GenAIStreamProgress-compatible source (structural type, no src/buffer dependency) */
|
|
45
|
+
export declare const createBufferProgressGauge: (source: () => {
|
|
46
|
+
percent?: number;
|
|
47
|
+
bufferPercent?: number;
|
|
48
|
+
} | undefined) => ProgressGauge;
|
|
49
|
+
/** server-side send path injection (Ports & Adapters). a rejected Promise is wired to onError */
|
|
50
|
+
export declare type ProgressSink = (message: SocketMessage<ProgressState>) => void | Promise<void>;
|
|
51
|
+
export interface ProgressReporterOptions {
|
|
52
|
+
/** envelope type (default 'progress:update') */
|
|
53
|
+
type?: string;
|
|
54
|
+
/** update() emit coalescing window. 0 emits every update immediately (default 0) */
|
|
55
|
+
throttleMs?: number;
|
|
56
|
+
/** re-emit interval for running snapshots. 0 disables (default 0) */
|
|
57
|
+
heartbeatMs?: number;
|
|
58
|
+
/** serialized size limit. an oversized snapshot is emitted without meta and reported via onError (default DEFAULT_MAX_PACKET_BYTES = 64kb) */
|
|
59
|
+
maxPacketBytes?: number;
|
|
60
|
+
/** observe emit failures / size overruns */
|
|
61
|
+
onError?: (error: any, state: ProgressState) => void;
|
|
62
|
+
}
|
|
63
|
+
/** reporting handle for one task */
|
|
64
|
+
export interface ProgressTaskSupportable {
|
|
65
|
+
readonly id: string;
|
|
66
|
+
/** current local snapshot */
|
|
67
|
+
readonly state: ProgressState;
|
|
68
|
+
/** patch then emit (throttled). ignored after done/error. status regression (running -> pending) drops status only, rest of the patch applies */
|
|
69
|
+
update(patch: Partial<Omit<ProgressState, 'id' | 'ts' | 'seq' | 'status'>> & {
|
|
70
|
+
status?: 'pending' | 'running';
|
|
71
|
+
}): void;
|
|
72
|
+
/** transition to a terminal status and emit immediately (throttle bypassed). later update is ignored */
|
|
73
|
+
done(patch?: Partial<Pick<ProgressState, 'label' | 'meta'>>): void;
|
|
74
|
+
error(error: string | Error, patch?: Partial<Pick<ProgressState, 'label' | 'meta'>>): void;
|
|
75
|
+
}
|
|
76
|
+
/** start options for one task */
|
|
77
|
+
export interface ProgressTaskOptions {
|
|
78
|
+
/** percent auto-measurement strategy. gauge() is merged into the snapshot right before update/heartbeat emits. explicit update values win over gauge */
|
|
79
|
+
gauge?: ProgressGauge;
|
|
80
|
+
}
|
|
81
|
+
export interface ProgressReporterSupportable {
|
|
82
|
+
/** declare a task start and get its handle. calling again with the same id returns the existing handle */
|
|
83
|
+
start(id: string, initial?: Partial<Pick<ProgressState, 'label' | 'percent' | 'step' | 'totalSteps' | 'meta'>>, options?: ProgressTaskOptions): ProgressTaskSupportable;
|
|
84
|
+
/** flush unemitted snapshots + release timers. must be called before the lambda invocation ends */
|
|
85
|
+
close(): void;
|
|
86
|
+
}
|
|
87
|
+
/** change notification */
|
|
88
|
+
export interface ProgressChangeEvent {
|
|
89
|
+
state: ProgressState;
|
|
90
|
+
/** true when this consumer sees the task for the first time */
|
|
91
|
+
created: boolean;
|
|
92
|
+
}
|
|
93
|
+
export interface ProgressConsumerOptions {
|
|
94
|
+
/** inbound type prefix (default 'progress:') */
|
|
95
|
+
typePrefix?: string;
|
|
96
|
+
/** retained task count limit. evicts oldest terminal (done/error) tasks first (default 100) */
|
|
97
|
+
maxTasks?: number;
|
|
98
|
+
}
|
|
99
|
+
export interface ProgressConsumerSupportable {
|
|
100
|
+
/** latest snapshot by task id */
|
|
101
|
+
get(id: string): ProgressState | undefined;
|
|
102
|
+
/** every retained snapshot - in first-applied order (seq is per-reporter, not a cross-task sort key) */
|
|
103
|
+
list(): ProgressState[];
|
|
104
|
+
/** subscribe to applied changes (stale drops are not notified) */
|
|
105
|
+
onChange(handler: (event: ProgressChangeEvent) => void): SocketUnsubscribe;
|
|
106
|
+
/** unsubscribe. the network stays open (shared socket) */
|
|
107
|
+
close(): void;
|
|
108
|
+
}
|
|
109
|
+
/** create a server-side progress reporter over an injected sink */
|
|
110
|
+
export declare const createProgressReporter: (sink: ProgressSink, options?: ProgressReporterOptions) => ProgressReporterSupportable;
|
|
111
|
+
/** create a client-side progress consumer on a shared network */
|
|
112
|
+
export declare const createProgressConsumer: (network: NetworkSupportable, options?: ProgressConsumerOptions) => ProgressConsumerSupportable;
|