@ubercode/chronicler 0.1.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.
package/dist/index.js ADDED
@@ -0,0 +1,759 @@
1
+ // src/core/constants.ts
2
+ var LOG_LEVELS = {
3
+ fatal: 0,
4
+ // System is unusable
5
+ critical: 1,
6
+ // Critical conditions requiring immediate attention
7
+ alert: 2,
8
+ // Action must be taken immediately
9
+ error: 3,
10
+ // Error conditions
11
+ warn: 4,
12
+ // Warning conditions
13
+ audit: 5,
14
+ // Audit trail events (compliance, security)
15
+ info: 6,
16
+ // Informational messages
17
+ debug: 7,
18
+ // Debug-level messages
19
+ trace: 8
20
+ // Trace-level messages (very verbose)
21
+ };
22
+ var DEFAULT_REQUIRED_LEVELS = Object.keys(LOG_LEVELS);
23
+ var DEFAULT_CORRELATION_TIMEOUT_MS = 5 * 60 * 1e3;
24
+ var ROOT_FORK_ID = "0";
25
+ var FORK_ID_SEPARATOR = ".";
26
+ var DEFAULT_MAX_CONTEXT_KEYS = 100;
27
+ var DEFAULT_MAX_FORK_DEPTH = 10;
28
+ var DEFAULT_MAX_ACTIVE_CORRELATIONS = 1e3;
29
+
30
+ // src/core/errors.ts
31
+ var ChroniclerError = class extends Error {
32
+ code;
33
+ /**
34
+ * @param code - Machine-readable error category discriminator
35
+ * @param message - Human-readable description of the error
36
+ */
37
+ constructor(code, message) {
38
+ super(message);
39
+ this.name = "ChroniclerError";
40
+ this.code = code;
41
+ }
42
+ };
43
+
44
+ // src/core/backend.ts
45
+ var CONSOLE_LEVEL_MAP = {
46
+ fatal: "error",
47
+ critical: "error",
48
+ alert: "error",
49
+ error: "error",
50
+ warn: "warn",
51
+ audit: "info",
52
+ info: "info",
53
+ debug: "debug",
54
+ trace: "debug"
55
+ };
56
+ var LEVEL_FALLBACK_CHAINS = {
57
+ fatal: ["critical", "error", "warn", "info"],
58
+ critical: ["error", "warn", "info"],
59
+ alert: ["error", "warn", "info"],
60
+ error: ["warn", "info"],
61
+ warn: ["info"],
62
+ audit: ["info"],
63
+ info: [],
64
+ debug: ["info"],
65
+ trace: ["debug", "info"]
66
+ };
67
+ var callBackendMethod = (backend, level, message, payload) => {
68
+ if (typeof backend[level] !== "function") {
69
+ throw new ChroniclerError("BACKEND_METHOD", `Backend does not support log level: ${level}`);
70
+ }
71
+ try {
72
+ backend[level](message, payload);
73
+ } catch (err) {
74
+ console.error(
75
+ "[chronicler] Backend error during log emission:",
76
+ err instanceof Error ? err.message : "Unknown error"
77
+ );
78
+ }
79
+ };
80
+ var createConsoleBackend = () => {
81
+ const backend = {};
82
+ for (const level of DEFAULT_REQUIRED_LEVELS) {
83
+ const method = CONSOLE_LEVEL_MAP[level];
84
+ backend[level] = (message, payload) => console[method](message, payload);
85
+ }
86
+ return backend;
87
+ };
88
+ var createBackend = (partial) => {
89
+ const backend = {};
90
+ for (const level of DEFAULT_REQUIRED_LEVELS) {
91
+ if (typeof partial[level] === "function") {
92
+ backend[level] = partial[level];
93
+ continue;
94
+ }
95
+ const fallback = LEVEL_FALLBACK_CHAINS[level].find((fb) => typeof partial[fb] === "function");
96
+ if (fallback) {
97
+ backend[level] = partial[fallback];
98
+ } else {
99
+ const method = CONSOLE_LEVEL_MAP[level];
100
+ backend[level] = (message, payload) => console[method](message, payload);
101
+ }
102
+ }
103
+ return backend;
104
+ };
105
+ var createRouterBackend = (routes) => {
106
+ if (routes.length === 0) {
107
+ throw new Error("createRouterBackend requires at least one route.");
108
+ }
109
+ const backend = {};
110
+ for (const level of DEFAULT_REQUIRED_LEVELS) {
111
+ backend[level] = (message, payload) => {
112
+ for (const route of routes) {
113
+ if (!route.filter || route.filter(level, payload)) {
114
+ callBackendMethod(route.backend, level, message, payload);
115
+ }
116
+ }
117
+ };
118
+ }
119
+ return backend;
120
+ };
121
+
122
+ // src/core/reserved.ts
123
+ var RESERVED_TOP_LEVEL_FIELDS = [
124
+ "eventKey",
125
+ "level",
126
+ "message",
127
+ "correlationId",
128
+ "forkId",
129
+ "timestamp",
130
+ "fields",
131
+ "_validation"
132
+ ];
133
+ var TOP_LEVEL_SET = new Set(RESERVED_TOP_LEVEL_FIELDS);
134
+ var isReservedTopLevelField = (key) => TOP_LEVEL_SET.has(key);
135
+ var assertNoReservedKeys = (record) => {
136
+ const invalid = [];
137
+ for (const key of Object.keys(record)) {
138
+ if (isReservedTopLevelField(key)) {
139
+ invalid.push(key);
140
+ }
141
+ }
142
+ return invalid;
143
+ };
144
+
145
+ // src/core/context.ts
146
+ var isSimpleValue = (value) => value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
147
+ var DANGEROUS_KEYS = /* @__PURE__ */ new Set([
148
+ "__proto__",
149
+ "constructor",
150
+ "prototype",
151
+ "toString",
152
+ "valueOf",
153
+ "hasOwnProperty",
154
+ "isPrototypeOf",
155
+ "propertyIsEnumerable",
156
+ "toLocaleString",
157
+ "__defineGetter__",
158
+ "__defineSetter__",
159
+ "__lookupGetter__",
160
+ "__lookupSetter__"
161
+ ]);
162
+ var sanitizeContextInput = (context, existingContext = {}, maxNewKeys = Infinity) => {
163
+ const sanitized = {};
164
+ const reserved = [];
165
+ const collisionDetails = [];
166
+ const dropped = [];
167
+ let acceptedCount = 0;
168
+ for (const [key, rawValue] of Object.entries(context)) {
169
+ if (isReservedTopLevelField(key)) {
170
+ reserved.push(key);
171
+ continue;
172
+ }
173
+ if (DANGEROUS_KEYS.has(key)) {
174
+ reserved.push(key);
175
+ continue;
176
+ }
177
+ if (!isSimpleValue(rawValue)) continue;
178
+ if (Object.hasOwn(existingContext, key) || Object.hasOwn(sanitized, key)) {
179
+ const existingValue = Object.hasOwn(sanitized, key) ? sanitized[key] : existingContext[key];
180
+ collisionDetails.push({ key, existingValue, attemptedValue: rawValue });
181
+ continue;
182
+ }
183
+ if (acceptedCount >= maxNewKeys) {
184
+ dropped.push(key);
185
+ continue;
186
+ }
187
+ sanitized[key] = rawValue;
188
+ acceptedCount++;
189
+ }
190
+ return { context: sanitized, validation: { reserved, collisionDetails, dropped } };
191
+ };
192
+ var ContextStore = class {
193
+ context = {};
194
+ maxKeys;
195
+ constructor(initial = {}, maxKeys = Infinity) {
196
+ this.maxKeys = maxKeys;
197
+ const { context } = sanitizeContextInput(initial, {}, maxKeys);
198
+ this.context = context;
199
+ }
200
+ /**
201
+ * Merge new context into the store.
202
+ *
203
+ * @param raw - Key-value pairs to add
204
+ * @returns Validation result with any collisions or reserved field attempts
205
+ */
206
+ add(raw) {
207
+ const remaining = Math.max(0, this.maxKeys - Object.keys(this.context).length);
208
+ const { context, validation } = sanitizeContextInput(raw, this.context, remaining);
209
+ Object.assign(this.context, context);
210
+ return validation;
211
+ }
212
+ /**
213
+ * Return a shallow copy of the current context.
214
+ *
215
+ * @returns A new object containing all accumulated context key-value pairs
216
+ */
217
+ snapshot() {
218
+ return { ...this.context };
219
+ }
220
+ };
221
+
222
+ // src/core/fields.ts
223
+ function makeOptional(type, doc) {
224
+ return {
225
+ _type: type,
226
+ _required: false,
227
+ _doc: doc,
228
+ doc: (description) => makeOptional(type, description)
229
+ };
230
+ }
231
+ function makeRequired(type, doc) {
232
+ return {
233
+ _type: type,
234
+ _required: true,
235
+ _doc: doc,
236
+ optional: () => makeOptional(type, doc),
237
+ doc: (description) => makeRequired(type, description)
238
+ };
239
+ }
240
+ var field = {
241
+ string: () => makeRequired("string", void 0),
242
+ number: () => makeRequired("number", void 0),
243
+ boolean: () => makeRequired("boolean", void 0),
244
+ error: () => makeRequired("error", void 0)
245
+ };
246
+
247
+ // src/core/events.ts
248
+ var correlationAutoFields = {
249
+ complete: {
250
+ duration: field.number().optional().doc("Duration of the correlation in milliseconds")
251
+ },
252
+ fail: {
253
+ duration: field.number().optional().doc("Duration of the correlation in milliseconds"),
254
+ error: field.error().optional().doc("Error that caused the failure")
255
+ }};
256
+ var MAX_EVENT_KEY_LENGTH = 256;
257
+ var EVENT_KEY_RE = /^[a-z][a-zA-Z0-9]*(\.[a-z][a-zA-Z0-9]*)*$/;
258
+ var validateEventKey = (key, label) => {
259
+ if (key.length > MAX_EVENT_KEY_LENGTH) {
260
+ throw new Error(
261
+ `${label} key "${key.slice(0, 50)}..." exceeds maximum length of ${MAX_EVENT_KEY_LENGTH} characters.`
262
+ );
263
+ }
264
+ if (!EVENT_KEY_RE.test(key)) {
265
+ throw new Error(
266
+ `Invalid ${label} key "${key}". Keys must be dotted camelCase identifiers (e.g. "user.created", "http.request.started").`
267
+ );
268
+ }
269
+ };
270
+ var defineEvent = (event) => {
271
+ validateEventKey(event.key, "Event");
272
+ return event;
273
+ };
274
+ var prefixEventKeys = (groupKey, events) => {
275
+ if (!events) return events;
276
+ const result = {};
277
+ for (const [name, event] of Object.entries(events)) {
278
+ if (event.key.startsWith(`${groupKey}.`)) {
279
+ result[name] = event;
280
+ } else {
281
+ result[name] = { ...event, key: `${groupKey}.${name}` };
282
+ }
283
+ }
284
+ return result;
285
+ };
286
+ var defineEventGroup = (group) => {
287
+ validateEventKey(group.key, "Group");
288
+ const prefixed = prefixEventKeys(group.key, group.events);
289
+ if (prefixed !== group.events) {
290
+ return { ...group, events: prefixed };
291
+ }
292
+ return group;
293
+ };
294
+ var buildAutoEvents = (groupKey) => ({
295
+ start: {
296
+ key: `${groupKey}.start`,
297
+ level: "info",
298
+ message: `${groupKey} started`,
299
+ doc: "Auto-generated correlation start event"
300
+ },
301
+ complete: {
302
+ key: `${groupKey}.complete`,
303
+ level: "info",
304
+ message: `${groupKey} completed`,
305
+ doc: "Auto-generated correlation completion event",
306
+ fields: correlationAutoFields.complete
307
+ },
308
+ fail: {
309
+ key: `${groupKey}.fail`,
310
+ level: "error",
311
+ message: `${groupKey} failed`,
312
+ doc: "Auto-generated correlation failure event",
313
+ fields: correlationAutoFields.fail
314
+ },
315
+ timeout: {
316
+ key: `${groupKey}.timeout`,
317
+ level: "warn",
318
+ message: `${groupKey} timed out`,
319
+ doc: "Auto-generated correlation timeout event"
320
+ }
321
+ });
322
+ var defineCorrelationGroup = (group) => {
323
+ validateEventKey(group.key, "Group");
324
+ const autoEvents = buildAutoEvents(group.key);
325
+ const prefixed = prefixEventKeys(group.key, group.events) ?? {};
326
+ const autoKeys = Object.keys(autoEvents);
327
+ const conflicts = autoKeys.filter((k) => Object.hasOwn(prefixed, k));
328
+ if (conflicts.length > 0) {
329
+ throw new Error(
330
+ `Correlation group "${group.key}" defines events that collide with auto-generated lifecycle events: ${conflicts.join(", ")}. Rename these events to avoid the conflict.`
331
+ );
332
+ }
333
+ return {
334
+ ...group,
335
+ timeout: group.timeout ?? DEFAULT_CORRELATION_TIMEOUT_MS,
336
+ events: {
337
+ ...prefixed,
338
+ ...autoEvents
339
+ }
340
+ };
341
+ };
342
+
343
+ // src/core/validation.ts
344
+ var ANSI_ESCAPE_RE = /\x1b(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g;
345
+ var NEWLINE_RE = /[\r\n]/g;
346
+ var sanitizeString = (value) => value.replace(ANSI_ESCAPE_RE, "").replace(NEWLINE_RE, "\\n");
347
+ var sanitizeLogFields = (fields) => {
348
+ const result = {};
349
+ for (const [key, value] of Object.entries(fields)) {
350
+ result[key] = typeof value === "string" ? sanitizeString(value) : value;
351
+ }
352
+ return result;
353
+ };
354
+ var checkFieldType = (value, type) => {
355
+ switch (type) {
356
+ case "error":
357
+ return value instanceof Error || typeof value === "string" ? "ok" : "type_error";
358
+ case "string":
359
+ return typeof value === "string" ? "ok" : "type_error";
360
+ case "number":
361
+ if (typeof value !== "number") return "type_error";
362
+ return Number.isFinite(value) ? "ok" : "invalid_value";
363
+ case "boolean":
364
+ return typeof value === "boolean" ? "ok" : "type_error";
365
+ default:
366
+ return "type_error";
367
+ }
368
+ };
369
+ var validateFields = (event, payload) => {
370
+ const providedFields = payload ?? {};
371
+ const normalizedFields = {};
372
+ const missingFields = [];
373
+ const typeErrors = [];
374
+ const invalidValues = [];
375
+ const unknownFields = [];
376
+ const fieldBuilders = event.fields ?? {};
377
+ const definedFieldNames = new Set(Object.keys(fieldBuilders));
378
+ for (const [name, builder] of Object.entries(fieldBuilders)) {
379
+ const value = providedFields[name];
380
+ const fieldType = builder._type;
381
+ const isRequired = builder._required;
382
+ if (value === void 0 || value === null) {
383
+ if (isRequired) {
384
+ missingFields.push(name);
385
+ }
386
+ continue;
387
+ }
388
+ const typeCheck = checkFieldType(value, fieldType);
389
+ if (typeCheck === "type_error") {
390
+ typeErrors.push(name);
391
+ continue;
392
+ }
393
+ if (typeCheck === "invalid_value") {
394
+ invalidValues.push(name);
395
+ continue;
396
+ }
397
+ if (fieldType === "error") {
398
+ try {
399
+ normalizedFields[name] = value instanceof Error ? value.stack ?? value.message : typeof value === "string" ? value : "[unknown error]";
400
+ } catch {
401
+ normalizedFields[name] = "[unserializable error]";
402
+ }
403
+ } else if (fieldType === "string" && typeof value === "string") {
404
+ normalizedFields[name] = sanitizeString(value);
405
+ } else {
406
+ normalizedFields[name] = value;
407
+ }
408
+ }
409
+ for (const [name, value] of Object.entries(providedFields)) {
410
+ if (!definedFieldNames.has(name) && value !== void 0 && value !== null) {
411
+ unknownFields.push(name);
412
+ if (typeof value === "function" || typeof value === "symbol") {
413
+ continue;
414
+ }
415
+ if (typeof value === "string") {
416
+ normalizedFields[name] = sanitizeString(value);
417
+ } else {
418
+ normalizedFields[name] = value;
419
+ }
420
+ }
421
+ }
422
+ return { missingFields, typeErrors, invalidValues, unknownFields, normalizedFields };
423
+ };
424
+ var buildValidationMetadata = (fieldValidation) => {
425
+ const entries = Object.entries({
426
+ missingFields: fieldValidation.missingFields,
427
+ typeErrors: fieldValidation.typeErrors,
428
+ invalidValues: fieldValidation.invalidValues,
429
+ unknownFields: fieldValidation.unknownFields
430
+ }).filter(([, v]) => Array.isArray(v) && v.length > 0);
431
+ return entries.length > 0 ? Object.fromEntries(entries) : void 0;
432
+ };
433
+
434
+ // src/core/chronicle.ts
435
+ var buildPayload = (args) => {
436
+ const fieldValidation = validateFields(args.eventDef, args.fields);
437
+ if (args.strict) {
438
+ const issues = [];
439
+ if (fieldValidation.missingFields.length > 0) {
440
+ issues.push(`missing required fields: ${fieldValidation.missingFields.join(", ")}`);
441
+ }
442
+ if (fieldValidation.typeErrors.length > 0) {
443
+ issues.push(`type errors on fields: ${fieldValidation.typeErrors.join(", ")}`);
444
+ }
445
+ if (fieldValidation.invalidValues.length > 0) {
446
+ issues.push(`invalid values on fields: ${fieldValidation.invalidValues.join(", ")}`);
447
+ }
448
+ if (issues.length > 0) {
449
+ throw new ChroniclerError(
450
+ "FIELD_VALIDATION",
451
+ `Event "${args.eventDef.key}" failed validation: ${issues.join("; ")}`
452
+ );
453
+ }
454
+ }
455
+ const validationMetadata = buildValidationMetadata(fieldValidation);
456
+ return {
457
+ eventKey: args.eventDef.key,
458
+ fields: fieldValidation.normalizedFields,
459
+ correlationId: args.currentCorrelationId(),
460
+ forkId: args.forkId,
461
+ metadata: args.contextStore.snapshot(),
462
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
463
+ ...validationMetadata ? { _validation: validationMetadata } : {}
464
+ };
465
+ };
466
+ var forkDepthFromId = (forkId) => forkId === ROOT_FORK_ID ? 0 : forkId.split(FORK_ID_SEPARATOR).length;
467
+ var nextForkId = (parentForkId, counter, maxDepth) => {
468
+ const childForkId = parentForkId === ROOT_FORK_ID ? String(counter) : `${parentForkId}${FORK_ID_SEPARATOR}${counter}`;
469
+ const depth = forkDepthFromId(childForkId);
470
+ if (depth > maxDepth) {
471
+ throw new ChroniclerError(
472
+ "FORK_DEPTH_EXCEEDED",
473
+ `Fork depth ${depth} exceeds maximum allowed depth of ${maxDepth}`
474
+ );
475
+ }
476
+ return childForkId;
477
+ };
478
+ var isAlreadyNormalized = (group) => typeof group.timeout === "number" && group.events !== void 0 && "start" in group.events;
479
+ var resolveCorrelationGroup = (group) => isAlreadyNormalized(group) ? group : defineCorrelationGroup(group);
480
+ var createChronicleInstance = (args) => {
481
+ const {
482
+ config,
483
+ contextStore,
484
+ currentCorrelationId,
485
+ correlationIdGenerator,
486
+ forkId,
487
+ hooks = {},
488
+ activeCorrelations = { count: 0 }
489
+ } = args;
490
+ let forkCounter = 0;
491
+ return {
492
+ event(eventDef, fields) {
493
+ if (LOG_LEVELS[eventDef.level] > config.minLevel) return;
494
+ const payload = buildPayload({
495
+ contextStore,
496
+ eventDef,
497
+ // Deliberate type erasure: EventFields<E> → Record<string, unknown>
498
+ fields,
499
+ currentCorrelationId,
500
+ forkId,
501
+ strict: config.strict
502
+ });
503
+ callBackendMethod(config.backend, eventDef.level, eventDef.message, payload);
504
+ hooks.onActivity?.();
505
+ },
506
+ log(level, message, fields = {}) {
507
+ if (LOG_LEVELS[level] > config.minLevel) return;
508
+ const payload = {
509
+ eventKey: "",
510
+ fields: sanitizeLogFields(fields),
511
+ correlationId: currentCorrelationId(),
512
+ forkId,
513
+ metadata: contextStore.snapshot(),
514
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
515
+ };
516
+ callBackendMethod(config.backend, level, message, payload);
517
+ hooks.onActivity?.();
518
+ },
519
+ addContext(context) {
520
+ return contextStore.add(context);
521
+ },
522
+ fork(extraContext = {}) {
523
+ forkCounter++;
524
+ const childForkId = nextForkId(forkId, forkCounter, config.limits.maxForkDepth);
525
+ const forkStore = new ContextStore(contextStore.snapshot(), config.limits.maxContextKeys);
526
+ const forkChronicle = createChronicleInstance({
527
+ config,
528
+ contextStore: forkStore,
529
+ currentCorrelationId,
530
+ correlationIdGenerator,
531
+ forkId: childForkId,
532
+ hooks,
533
+ activeCorrelations
534
+ });
535
+ if (Object.keys(extraContext).length > 0) {
536
+ forkChronicle.addContext(extraContext);
537
+ }
538
+ return forkChronicle;
539
+ },
540
+ startCorrelation(group, metadata = {}) {
541
+ if (activeCorrelations.count >= config.limits.maxActiveCorrelations) {
542
+ throw new ChroniclerError(
543
+ "CORRELATION_LIMIT_EXCEEDED",
544
+ `Active correlation limit of ${config.limits.maxActiveCorrelations} exceeded`
545
+ );
546
+ }
547
+ const definedGroup = resolveCorrelationGroup(group);
548
+ activeCorrelations.count++;
549
+ const correlationStore = new ContextStore(
550
+ contextStore.snapshot(),
551
+ config.limits.maxContextKeys
552
+ );
553
+ if (Object.keys(metadata).length > 0) {
554
+ correlationStore.add(metadata);
555
+ }
556
+ const correlationId = correlationIdGenerator();
557
+ return new CorrelationChronicleImpl({
558
+ config,
559
+ group: definedGroup,
560
+ contextStore: correlationStore,
561
+ currentCorrelationId: () => correlationId,
562
+ correlationIdGenerator,
563
+ forkId,
564
+ activeCorrelations
565
+ });
566
+ }
567
+ };
568
+ };
569
+ var CorrelationTimer = class {
570
+ constructor(timeout, onTimeout) {
571
+ this.timeout = timeout;
572
+ this.onTimeout = onTimeout;
573
+ }
574
+ timeoutId;
575
+ start() {
576
+ this.clear();
577
+ if (this.timeout > 0) {
578
+ this.timeoutId = setTimeout(this.onTimeout, this.timeout);
579
+ this.timeoutId.unref();
580
+ }
581
+ }
582
+ /** Reset the timer (keep-alive on activity). */
583
+ touch() {
584
+ this.start();
585
+ }
586
+ clear() {
587
+ if (this.timeoutId !== void 0) {
588
+ clearTimeout(this.timeoutId);
589
+ this.timeoutId = void 0;
590
+ }
591
+ }
592
+ };
593
+ var CorrelationChronicleImpl = class {
594
+ config;
595
+ contextStore;
596
+ currentCorrelationId;
597
+ correlationIdGenerator;
598
+ forkId;
599
+ activeCorrelations;
600
+ timer;
601
+ completed = false;
602
+ startedAt = Date.now();
603
+ autoEvents;
604
+ forkCounter = 0;
605
+ constructor(args) {
606
+ this.config = args.config;
607
+ this.contextStore = args.contextStore;
608
+ this.currentCorrelationId = args.currentCorrelationId;
609
+ this.correlationIdGenerator = args.correlationIdGenerator;
610
+ this.forkId = args.forkId;
611
+ this.activeCorrelations = args.activeCorrelations ?? { count: 0 };
612
+ this.timer = new CorrelationTimer(args.group.timeout, () => this.timeout());
613
+ this.autoEvents = args.group.events;
614
+ this.timer.start();
615
+ this.emitAutoEvent(this.autoEvents.start, {});
616
+ }
617
+ event(eventDef, fields) {
618
+ if (this.completed) return;
619
+ if (LOG_LEVELS[eventDef.level] > this.config.minLevel) return;
620
+ const payload = buildPayload({
621
+ contextStore: this.contextStore,
622
+ eventDef,
623
+ // Deliberate type erasure: EventFields<E> → Record<string, unknown>
624
+ fields,
625
+ currentCorrelationId: this.currentCorrelationId,
626
+ forkId: this.forkId,
627
+ strict: this.config.strict
628
+ });
629
+ callBackendMethod(this.config.backend, eventDef.level, eventDef.message, payload);
630
+ this.timer.touch();
631
+ }
632
+ log(level, message, fields = {}) {
633
+ if (this.completed) return;
634
+ if (LOG_LEVELS[level] > this.config.minLevel) return;
635
+ const payload = {
636
+ eventKey: "",
637
+ fields: sanitizeLogFields(fields),
638
+ correlationId: this.currentCorrelationId(),
639
+ forkId: this.forkId,
640
+ metadata: this.contextStore.snapshot(),
641
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
642
+ };
643
+ callBackendMethod(this.config.backend, level, message, payload);
644
+ this.timer.touch();
645
+ }
646
+ addContext(context) {
647
+ return this.contextStore.add(context);
648
+ }
649
+ fork(extraContext = {}) {
650
+ this.forkCounter++;
651
+ const childForkId = nextForkId(this.forkId, this.forkCounter, this.config.limits.maxForkDepth);
652
+ const forkStore = new ContextStore(
653
+ this.contextStore.snapshot(),
654
+ this.config.limits.maxContextKeys
655
+ );
656
+ const forkChronicle = createChronicleInstance({
657
+ config: this.config,
658
+ contextStore: forkStore,
659
+ currentCorrelationId: this.currentCorrelationId,
660
+ correlationIdGenerator: this.correlationIdGenerator,
661
+ forkId: childForkId,
662
+ hooks: { onActivity: () => this.timer.touch() },
663
+ activeCorrelations: this.activeCorrelations
664
+ });
665
+ if (Object.keys(extraContext).length > 0) {
666
+ forkChronicle.addContext(extraContext);
667
+ }
668
+ return forkChronicle;
669
+ }
670
+ complete(fields = {}) {
671
+ if (!this.finalize()) return;
672
+ this.emitAutoEvent(this.autoEvents.complete, {
673
+ duration: Date.now() - this.startedAt,
674
+ ...fields
675
+ });
676
+ }
677
+ fail(error, fields = {}) {
678
+ if (!this.finalize()) return;
679
+ this.emitAutoEvent(this.autoEvents.fail, {
680
+ duration: Date.now() - this.startedAt,
681
+ error,
682
+ ...fields
683
+ });
684
+ }
685
+ timeout() {
686
+ if (!this.finalize()) return;
687
+ this.emitAutoEvent(this.autoEvents.timeout, {});
688
+ }
689
+ /** Mark correlation as done: decrement counter, clear timer. Returns false if already completed. */
690
+ finalize() {
691
+ if (this.completed) return false;
692
+ this.activeCorrelations.count--;
693
+ this.completed = true;
694
+ this.timer.clear();
695
+ return true;
696
+ }
697
+ emitAutoEvent(eventDef, fields) {
698
+ if (LOG_LEVELS[eventDef.level] > this.config.minLevel) return;
699
+ const payload = buildPayload({
700
+ contextStore: this.contextStore,
701
+ eventDef,
702
+ fields,
703
+ currentCorrelationId: this.currentCorrelationId,
704
+ forkId: this.forkId,
705
+ strict: this.config.strict
706
+ });
707
+ callBackendMethod(this.config.backend, eventDef.level, eventDef.message, payload);
708
+ }
709
+ };
710
+ var resolveChroniclerConfig = (config) => {
711
+ const resolvedBackend = config.backend ?? createConsoleBackend();
712
+ const missingLevels = DEFAULT_REQUIRED_LEVELS.filter(
713
+ (level) => typeof resolvedBackend[level] !== "function"
714
+ );
715
+ if (missingLevels.length > 0) {
716
+ throw new ChroniclerError(
717
+ "UNSUPPORTED_LOG_LEVEL",
718
+ `Log backend is missing level(s): ${missingLevels.join(", ")}. A valid backend must implement all 9 levels: ${DEFAULT_REQUIRED_LEVELS.join(", ")}. Use createBackend() for automatic fallback handling.`
719
+ );
720
+ }
721
+ const reservedMetadata = assertNoReservedKeys(config.metadata);
722
+ if (reservedMetadata.length > 0) {
723
+ throw new ChroniclerError(
724
+ "RESERVED_FIELD",
725
+ `Reserved fields cannot be used in metadata: ${reservedMetadata.join(", ")}`
726
+ );
727
+ }
728
+ const resolvedLimits = {
729
+ maxContextKeys: config.limits?.maxContextKeys ?? DEFAULT_MAX_CONTEXT_KEYS,
730
+ maxForkDepth: config.limits?.maxForkDepth ?? DEFAULT_MAX_FORK_DEPTH,
731
+ maxActiveCorrelations: config.limits?.maxActiveCorrelations ?? DEFAULT_MAX_ACTIVE_CORRELATIONS
732
+ };
733
+ return {
734
+ resolved: {
735
+ backend: resolvedBackend,
736
+ limits: resolvedLimits,
737
+ minLevel: LOG_LEVELS[config.minLevel ?? "trace"],
738
+ strict: config.strict
739
+ },
740
+ correlationIdGenerator: config.correlationIdGenerator ?? (() => crypto.randomUUID())
741
+ };
742
+ };
743
+ var createChronicle = (config) => {
744
+ const { resolved, correlationIdGenerator } = resolveChroniclerConfig(config);
745
+ const baseContextStore = new ContextStore(config.metadata, resolved.limits.maxContextKeys);
746
+ return createChronicleInstance({
747
+ config: resolved,
748
+ contextStore: baseContextStore,
749
+ currentCorrelationId: () => "",
750
+ correlationIdGenerator,
751
+ forkId: ROOT_FORK_ID,
752
+ /** Shared mutable counter tracking uncompleted correlations for limit enforcement. */
753
+ activeCorrelations: { count: 0 }
754
+ });
755
+ };
756
+
757
+ export { ChroniclerError, createBackend, createChronicle, createConsoleBackend, createRouterBackend, defineCorrelationGroup, defineEvent, defineEventGroup, field };
758
+ //# sourceMappingURL=index.js.map
759
+ //# sourceMappingURL=index.js.map