@code-pushup/utils 0.109.0 → 0.111.0

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.
@@ -0,0 +1,77 @@
1
+ import { defaultClock } from '../clock-epoch.js';
2
+ import { decodeTraceEvent, encodeTraceEvent, getCompleteEvent, getInstantEventTracingStartedInBrowser, getTraceFile, } from './trace-file-utils.js';
3
+ /** Name for the trace start margin event */
4
+ const TRACE_START_MARGIN_NAME = '[trace padding start]';
5
+ /** Name for the trace end margin event */
6
+ const TRACE_END_MARGIN_NAME = '[trace padding end]';
7
+ /** Microseconds of padding to add before/after trace events (1000ms = 1,000,000μs) */
8
+ const TRACE_MARGIN_US = 1_000_000;
9
+ /** Duration in microseconds for margin events (20ms = 20,000μs) */
10
+ const TRACE_MARGIN_DURATION_US = 20_000;
11
+ /**
12
+ * Generates a complete Chrome DevTools trace file content as JSON string.
13
+ * Adds margin events around the trace events and includes metadata.
14
+ * @param events - Array of user timing trace events to include
15
+ * @param metadata - Optional custom metadata to include in the trace file
16
+ * @returns JSON string representation of the complete trace file
17
+ */
18
+ export function generateTraceContent(events, metadata) {
19
+ const traceContainer = getTraceFile({
20
+ traceEvents: events,
21
+ startTime: new Date().toISOString(),
22
+ metadata: {
23
+ ...metadata,
24
+ generatedAt: new Date().toISOString(),
25
+ },
26
+ });
27
+ const marginUs = TRACE_MARGIN_US;
28
+ const marginDurUs = TRACE_MARGIN_DURATION_US;
29
+ const sortedEvents = [...events].sort((a, b) => a.ts - b.ts);
30
+ const fallbackTs = defaultClock.epochNowUs();
31
+ const firstTs = sortedEvents.at(0)?.ts ?? fallbackTs;
32
+ const lastTs = sortedEvents.at(-1)?.ts ?? fallbackTs;
33
+ const startTs = firstTs - marginUs;
34
+ const endTs = lastTs + marginUs;
35
+ const traceEvents = [
36
+ getInstantEventTracingStartedInBrowser({
37
+ ts: startTs,
38
+ url: events.length === 0 ? 'empty-trace' : 'generated-trace',
39
+ }),
40
+ getCompleteEvent({
41
+ name: TRACE_START_MARGIN_NAME,
42
+ ts: startTs,
43
+ dur: marginDurUs,
44
+ }),
45
+ ...sortedEvents,
46
+ getCompleteEvent({
47
+ name: TRACE_END_MARGIN_NAME,
48
+ ts: endTs,
49
+ dur: marginDurUs,
50
+ }),
51
+ ];
52
+ return JSON.stringify({ ...traceContainer, traceEvents });
53
+ }
54
+ /**
55
+ * Creates a WAL (Write-Ahead Logging) format configuration for Chrome DevTools trace files.
56
+ * Automatically finalizes shards into complete trace files with proper metadata and margin events.
57
+ * @returns WalFormat configuration object with baseName, codec, extensions, and finalizer
58
+ */
59
+ export function traceEventWalFormat() {
60
+ const baseName = 'trace';
61
+ const walExtension = '.jsonl';
62
+ const finalExtension = '.json';
63
+ return {
64
+ baseName,
65
+ walExtension,
66
+ finalExtension,
67
+ codec: {
68
+ encode: (event) => JSON.stringify(encodeTraceEvent(event)),
69
+ decode: (json) => decodeTraceEvent(JSON.parse(json)),
70
+ },
71
+ finalizer: (records, metadata) => {
72
+ const validRecords = records.filter((r) => !(typeof r === 'object' && r != null && '__invalid' in r));
73
+ return generateTraceContent(validRecords, metadata);
74
+ },
75
+ };
76
+ }
77
+ //# sourceMappingURL=wal-json-trace.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wal-json-trace.js","sourceRoot":"","sources":["../../../../src/lib/profiler/wal-json-trace.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,248 @@
1
+ /**
2
+ * Codec for encoding/decoding values to/from strings for WAL storage.
3
+ * Used to serialize/deserialize records written to and read from WAL files.
4
+ */
5
+ export type Codec<I, O = string> = {
6
+ /** Encode a value to a string for storage */
7
+ encode: (v: I) => O;
8
+ /** Decode a string back to the original value type */
9
+ decode: (data: O) => I;
10
+ };
11
+ export type InvalidEntry<O = string> = {
12
+ __invalid: true;
13
+ raw: O;
14
+ };
15
+ /**
16
+ * Interface for sinks that can append items.
17
+ * Allows for different types of appendable storage (WAL, in-memory, etc.)
18
+ */
19
+ export type AppendableSink<T> = Recoverable & {
20
+ append: (item: T) => void;
21
+ isClosed: () => boolean;
22
+ open?: () => void;
23
+ close?: () => void;
24
+ };
25
+ /**
26
+ * Interface for sinks that support recovery operations.
27
+ * Represents the recoverable subset of AppendableSink functionality.
28
+ */
29
+ export type Recoverable = {
30
+ recover: () => RecoverResult<unknown>;
31
+ repack: (out?: string) => void;
32
+ finalize?: (opt?: Record<string, unknown>) => void;
33
+ };
34
+ /**
35
+ * Result of recovering records from a WAL file.
36
+ * Contains successfully recovered records and any errors encountered during parsing.
37
+ */
38
+ export type RecoverResult<T> = {
39
+ /** Successfully recovered records */
40
+ records: T[];
41
+ /** Errors encountered during recovery with line numbers and context */
42
+ errors: {
43
+ lineNo: number;
44
+ line: string;
45
+ error: Error;
46
+ }[];
47
+ /** Last incomplete line if file was truncated (null if clean) */
48
+ partialTail: string | null;
49
+ };
50
+ /**
51
+ * Statistics about the WAL file state and last recovery operation.
52
+ */
53
+ export type WalStats<T> = {
54
+ /** File path for this WAL */
55
+ filePath: string;
56
+ /** Whether the WAL file is currently closed */
57
+ isClosed: boolean;
58
+ /** Whether the WAL file exists on disk */
59
+ fileExists: boolean;
60
+ /** File size in bytes (0 if file doesn't exist) */
61
+ fileSize: number;
62
+ /** Last recovery state from the most recent {@link recover} or {@link repack} operation */
63
+ lastRecovery: RecoverResult<T | InvalidEntry<string>> | null;
64
+ };
65
+ export declare const createTolerantCodec: <I, O = string>(codec: {
66
+ encode: (v: I) => O;
67
+ decode: (d: O) => I;
68
+ }) => Codec<I | InvalidEntry<O>, O>;
69
+ export declare function filterValidRecords<T>(records: (T | InvalidEntry<unknown>)[]): T[];
70
+ /**
71
+ * Pure helper function to recover records from WAL file content.
72
+ * @param content - Raw file content as string
73
+ * @param decode - function for decoding records
74
+ * @returns Recovery result with records, errors, and partial tail
75
+ */
76
+ export declare function recoverFromContent<T>(content: string, decode: Codec<T>['decode']): RecoverResult<T>;
77
+ /**
78
+ * Write-Ahead Log implementation for crash-safe append-only logging.
79
+ * Provides atomic operations for writing, recovering, and repacking log entries.
80
+ */
81
+ export declare class WriteAheadLogFile<T> implements AppendableSink<T> {
82
+ #private;
83
+ /**
84
+ * Create a new WAL file instance.
85
+ * @param options - Configuration options
86
+ */
87
+ constructor(options: {
88
+ file: string;
89
+ codec: Codec<T>;
90
+ });
91
+ /** Get the file path for this WAL */
92
+ getPath: () => string;
93
+ /** Open the WAL file for writing (creates directories if needed) */
94
+ open: () => void;
95
+ /**
96
+ * Append a record to the WAL.
97
+ * @param v - Record to append
98
+ * @throws Error if WAL cannot be opened
99
+ */
100
+ append: (v: T) => void;
101
+ /** Close the WAL file */
102
+ close: () => void;
103
+ isClosed: () => boolean;
104
+ /**
105
+ * Recover all records from the WAL file.
106
+ * Handles partial writes and decode errors gracefully.
107
+ * Updates the recovery state (accessible via {@link getStats}).
108
+ * @returns Recovery result with records, errors, and partial tail
109
+ */
110
+ recover(): RecoverResult<T | InvalidEntry<string>>;
111
+ /**
112
+ * Repack the WAL by recovering all valid records and rewriting cleanly.
113
+ * Removes corrupted entries and ensures clean formatting.
114
+ * Updates the recovery state (accessible via {@link getStats}).
115
+ * @param out - Output path (defaults to current file)
116
+ */
117
+ repack(out?: string): void;
118
+ /**
119
+ * Get comprehensive statistics about the WAL file state.
120
+ * Includes file information, open/close status, and last recovery state.
121
+ * @returns Statistics object with file info and last recovery state
122
+ */
123
+ getStats(): WalStats<T>;
124
+ }
125
+ /**
126
+ * Format descriptor that binds codec and file extension together.
127
+ * Prevents misconfiguration by keeping related concerns in one object.
128
+ */
129
+ export type WalFormat<T extends object | string> = {
130
+ /** Base name for the WAL (e.g., "trace") */
131
+ baseName: string;
132
+ /** Shard file extension (e.g., ".jsonl") */
133
+ walExtension: string;
134
+ /** Final file extension (e.g., ".json", ".trace.json") falls back to walExtension if not provided */
135
+ finalExtension: string;
136
+ /** Codec for encoding/decoding records */
137
+ codec: Codec<T, string>;
138
+ /** Finalizer for converting records to a string */
139
+ finalizer: (records: (T | InvalidEntry<string>)[], opt?: Record<string, unknown>) => string;
140
+ };
141
+ export declare const stringCodec: <T extends string | object = string>() => Codec<T>;
142
+ /**
143
+ * Parses a partial WalFormat configuration and returns a complete WalFormat object.
144
+ * All fallback values are targeting string types.
145
+ * - baseName defaults to 'wal'
146
+ * - walExtension defaults to '.log'
147
+ * - finalExtension defaults to '.log'
148
+ * - codec defaults to stringCodec<T>()
149
+ * - finalizer defaults to encoding each record using codec.encode() and joining with newlines.
150
+ * For object types, this properly JSON-stringifies them (not [object Object]).
151
+ * InvalidEntry records use their raw string value directly.
152
+ * @param format - Partial WalFormat configuration
153
+ * @returns Parsed WalFormat with defaults filled in
154
+ */
155
+ export declare function parseWalFormat<T extends object | string = object>(format: Partial<WalFormat<T>>): WalFormat<T>;
156
+ /**
157
+ * Determines if this process is the leader WAL process using the origin PID heuristic.
158
+ *
159
+ * The leader is the process that first enabled profiling (the one that set CP_PROFILER_ORIGIN_PID).
160
+ * All descendant processes inherit the environment but have different PIDs.
161
+ *
162
+ * @returns true if this is the leader WAL process, false otherwise
163
+ */
164
+ export declare function isLeaderWal(envVarName: string, profilerID: string): boolean;
165
+ /**
166
+ * Initialize the origin PID environment variable if not already set.
167
+ * This must be done as early as possible before any user code runs.
168
+ * Sets envVarName to the current process ID if not already defined.
169
+ */
170
+ export declare function setLeaderWal(envVarName: string, profilerID: string): void;
171
+ /**
172
+ * Generates a human-readable shard ID.
173
+ * This ID is unique per process/thread/shard combination and used in the file name.
174
+ * Format: readable-timestamp.pid.threadId.shardCount
175
+ * Example: "20240101-120000-000.12345.1.1"
176
+ * Becomes file: trace.20240101-120000-000.12345.1.1.log
177
+ */
178
+ export declare function getShardId(): string;
179
+ /**
180
+ * Generates a human-readable sharded group ID.
181
+ * This ID is a globally unique, sortable, human-readable date string per run.
182
+ * Used directly as the folder name to group shards.
183
+ * Format: yyyymmdd-hhmmss-ms
184
+ * Example: "20240101-120000-000"
185
+ */
186
+ export declare function getShardedGroupId(): string;
187
+ /**
188
+ * Regex patterns for validating WAL ID formats
189
+ */
190
+ export declare const WAL_ID_PATTERNS: {
191
+ /** Readable date format: yyyymmdd-hhmmss-ms */
192
+ readonly READABLE_DATE: RegExp;
193
+ /** Group ID format: yyyymmdd-hhmmss-ms */
194
+ readonly GROUP_ID: RegExp;
195
+ /** Shard ID format: readable-date.pid.threadId.count */
196
+ readonly SHARD_ID: RegExp;
197
+ };
198
+ export declare function sortableReadableDateString(timestampMs: string): string;
199
+ /**
200
+ * Generates a path to a shard file using human-readable IDs.
201
+ * Both groupId and shardId are already in readable date format.
202
+ *
203
+ * Example with groupId "20240101-120000-000" and shardId "20240101-120000-000.12345.1.1":
204
+ * Full path: /base/20240101-120000-000/trace.20240101-120000-000.12345.1.1.log
205
+ *
206
+ * @param opt.dir - The directory to store the shard file
207
+ * @param opt.format - The WalFormat to use for the shard file
208
+ * @param opt.groupId - The human-readable group ID (yyyymmdd-hhmmss-ms format)
209
+ * @param opt.shardId - The human-readable shard ID (readable-timestamp.pid.threadId.count format)
210
+ * @returns The path to the shard file
211
+ */
212
+ export declare function getShardedPath<T extends object | string = object>(opt: {
213
+ dir?: string;
214
+ format: WalFormat<T>;
215
+ groupId: string;
216
+ shardId: string;
217
+ }): string;
218
+ export declare function getShardedFinalPath<T extends object | string = object>(opt: {
219
+ dir?: string;
220
+ format: WalFormat<T>;
221
+ groupId: string;
222
+ }): string;
223
+ /**
224
+ * Sharded Write-Ahead Log manager for coordinating multiple WAL shards.
225
+ * Handles distributed logging across multiple processes/files with atomic finalization.
226
+ */
227
+ export declare class ShardedWal<T extends object | string = object> {
228
+ #private;
229
+ readonly groupId: string;
230
+ /**
231
+ * Create a sharded WAL manager.
232
+ */
233
+ constructor(opt: {
234
+ dir?: string;
235
+ format: Partial<WalFormat<T>>;
236
+ groupId?: string;
237
+ });
238
+ shard(shardId?: string): WriteAheadLogFile<T>;
239
+ /** Get all shard file paths matching this WAL's base name */
240
+ private shardFiles;
241
+ /**
242
+ * Finalize all shards by merging them into a single output file.
243
+ * Recovers all records from all shards, validates no errors, and writes merged result.
244
+ * @throws Error if any shard contains decode errors
245
+ */
246
+ finalize(opt?: Record<string, unknown>): void;
247
+ cleanup(): void;
248
+ }