@elsium-ai/observe 0.5.0 → 0.7.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/README.md CHANGED
@@ -565,6 +565,8 @@ type AuditEventType =
565
565
  | 'approval_request'
566
566
  | 'approval_decision'
567
567
  | 'config_change'
568
+ | 'provider_failover'
569
+ | 'circuit_breaker_state_change'
568
570
  ```
569
571
 
570
572
  ### `AuditEvent`
@@ -622,6 +624,20 @@ interface AuditIntegrityResult {
622
624
  }
623
625
  ```
624
626
 
627
+ ### `AuditBatchConfig`
628
+
629
+ ```ts
630
+ interface AuditBatchConfig {
631
+ size?: number
632
+ intervalMs?: number
633
+ }
634
+ ```
635
+
636
+ | Field | Type | Default | Description |
637
+ |---|---|---|---|
638
+ | `size` | `number` | `100` | Flush the buffer when it reaches this many events. |
639
+ | `intervalMs` | `number` | `50` | Flush the buffer on this interval (ms), regardless of size. |
640
+
625
641
  ### `AuditTrailConfig`
626
642
 
627
643
  ```ts
@@ -629,6 +645,7 @@ interface AuditTrailConfig {
629
645
  storage?: AuditStorageAdapter | 'memory'
630
646
  hashChain?: boolean
631
647
  maxEvents?: number
648
+ batch?: AuditBatchConfig
632
649
  onError?: (error: unknown) => void
633
650
  }
634
651
  ```
@@ -642,12 +659,27 @@ interface AuditTrail {
642
659
  data: Record<string, unknown>,
643
660
  options?: { actor?: string; traceId?: string },
644
661
  ): void
662
+ ready(): Promise<void>
645
663
  query(filter: AuditQueryFilter): Promise<AuditEvent[]>
646
664
  verifyIntegrity(): Promise<AuditIntegrityResult>
665
+ flush(): Promise<void>
666
+ dispose(): void
647
667
  readonly count: number
668
+ readonly pending: number
648
669
  }
649
670
  ```
650
671
 
672
+ | Member | Description |
673
+ |---|---|
674
+ | `log()` | Append an event. In batched mode, this buffers the event without hashing — zero CPU on the hot path. |
675
+ | `ready()` | Resolves once async initialization (e.g. `getLastHash`) has completed. |
676
+ | `query()` | Query events by type, actor, traceId, or timestamp range. Auto-flushes pending events in batched mode. |
677
+ | `verifyIntegrity()` | Verify the hash chain has not been tampered with. Auto-flushes pending events in batched mode. |
678
+ | `flush()` | Drain all pending events — computes hashes and writes to storage. No-op when not in batched mode. |
679
+ | `dispose()` | Stop the flush timer and drain remaining events. Call this on shutdown. |
680
+ | `count` | Total events (stored + pending). |
681
+ | `pending` | Number of buffered events not yet flushed (0 when not in batched mode). |
682
+
651
683
  ### `createAuditTrail()`
652
684
 
653
685
  Creates a hash-chained audit trail for tamper-evident event logging.
@@ -660,7 +692,8 @@ function createAuditTrail(config?: AuditTrailConfig): AuditTrail
660
692
  |---|---|---|---|
661
693
  | `config.storage` | `AuditStorageAdapter \| 'memory'` | `'memory'` | Storage backend for audit events |
662
694
  | `config.hashChain` | `boolean` | `true` | Enable SHA-256 hash chaining for tamper detection |
663
- | `config.maxEvents` | `number` | `10000` | Maximum events retained in memory storage |
695
+ | `config.maxEvents` | `number` | `10000` | Maximum events retained (ring buffer O(1) eviction) |
696
+ | `config.batch` | `AuditBatchConfig` | `undefined` | Enable batched mode for high-volume scenarios |
664
697
  | `config.onError` | `(error: unknown) => void` | `undefined` | Error handler for async storage failures |
665
698
 
666
699
  **Returns:** `AuditTrail`
@@ -694,6 +727,33 @@ console.log('Audit chain valid:', integrity.valid)
694
727
  console.log('Total events:', integrity.totalEvents)
695
728
  ```
696
729
 
730
+ #### Batched mode (high-volume)
731
+
732
+ In high-volume scenarios, `log()` computes a SHA-256 hash on every call — blocking the hot path. Batched mode moves hashing off the critical path: `log()` buffers raw event data, and hashing + storage writes happen asynchronously on flush.
733
+
734
+ ```ts
735
+ import { createAuditTrail } from '@elsium-ai/observe'
736
+
737
+ const audit = createAuditTrail({
738
+ batch: {
739
+ size: 500, // Flush after 500 events
740
+ intervalMs: 100, // Or every 100ms, whichever comes first
741
+ },
742
+ maxEvents: 100_000,
743
+ })
744
+
745
+ // log() is now near-zero cost — just pushes to an internal buffer
746
+ audit.log('llm_call', { model: 'gpt-4o', tokens: 100 })
747
+
748
+ // Force-flush before reading (query/verifyIntegrity auto-flush)
749
+ await audit.flush()
750
+
751
+ // Clean up on shutdown
752
+ process.on('SIGTERM', () => audit.dispose())
753
+ ```
754
+
755
+ Hash chain integrity is fully preserved — events are hashed sequentially during flush, not during `log()`.
756
+
697
757
  ### `auditMiddleware()`
698
758
 
699
759
  Creates an ElsiumAI middleware that automatically logs every LLM call (success or failure) to the given audit trail.
package/dist/audit.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { Middleware } from '@elsium-ai/core';
2
- export type AuditEventType = 'llm_call' | 'tool_execution' | 'security_violation' | 'budget_alert' | 'policy_violation' | 'auth_event' | 'approval_request' | 'approval_decision' | 'config_change';
2
+ export type AuditEventType = 'llm_call' | 'tool_execution' | 'security_violation' | 'budget_alert' | 'policy_violation' | 'auth_event' | 'approval_request' | 'approval_decision' | 'config_change' | 'provider_failover' | 'circuit_breaker_state_change';
3
3
  export interface AuditEvent {
4
4
  id: string;
5
5
  sequenceId: number;
@@ -33,10 +33,15 @@ export interface AuditIntegrityResult {
33
33
  brokenAt?: number;
34
34
  chainComplete?: boolean;
35
35
  }
36
+ export interface AuditBatchConfig {
37
+ size?: number;
38
+ intervalMs?: number;
39
+ }
36
40
  export interface AuditTrailConfig {
37
41
  storage?: AuditStorageAdapter | 'memory';
38
42
  hashChain?: boolean;
39
43
  maxEvents?: number;
44
+ batch?: AuditBatchConfig;
40
45
  onError?: (error: unknown) => void;
41
46
  }
42
47
  export interface AuditTrail {
@@ -48,7 +53,10 @@ export interface AuditTrail {
48
53
  ready(): Promise<void>;
49
54
  query(filter: AuditQueryFilter): Promise<AuditEvent[]>;
50
55
  verifyIntegrity(): Promise<AuditIntegrityResult>;
56
+ flush(): Promise<void>;
57
+ dispose(): void;
51
58
  readonly count: number;
59
+ readonly pending: number;
52
60
  }
53
61
  export declare function createAuditTrail(config?: AuditTrailConfig): AuditTrail;
54
62
  export declare function auditMiddleware(auditTrail: AuditTrail): Middleware;
@@ -1 +1 @@
1
- {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAqC,MAAM,iBAAiB,CAAA;AAEpF,MAAM,MAAM,cAAc,GACvB,UAAU,GACV,gBAAgB,GAChB,oBAAoB,GACpB,cAAc,GACd,kBAAkB,GAClB,YAAY,GACZ,kBAAkB,GAClB,mBAAmB,GACnB,eAAe,CAAA;AAElB,MAAM,WAAW,UAAU;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,UAAU,EAAE,MAAM,CAAA;IAClB,IAAI,EAAE,cAAc,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,YAAY,EAAE,MAAM,CAAA;CACpB;AAED,MAAM,WAAW,mBAAmB;IACnC,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC/C,KAAK,CAAC,MAAM,EAAE,gBAAgB,GAAG,UAAU,EAAE,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAA;IACrE,KAAK,IAAI,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IACjC,eAAe,IAAI,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAA;IACvE,WAAW,CAAC,IAAI,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;CACxC;AAED,MAAM,WAAW,gBAAgB;IAChC,IAAI,CAAC,EAAE,cAAc,GAAG,cAAc,EAAE,CAAA;IACxC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,oBAAoB;IACpC,KAAK,EAAE,OAAO,CAAA;IACd,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,aAAa,CAAC,EAAE,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,gBAAgB;IAChC,OAAO,CAAC,EAAE,mBAAmB,GAAG,QAAQ,CAAA;IACxC,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;CAClC;AAED,MAAM,WAAW,UAAU;IAC1B,GAAG,CACF,IAAI,EAAE,cAAc,EACpB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAC5C,IAAI,CAAA;IACP,2EAA2E;IAC3E,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACtB,KAAK,CAAC,MAAM,EAAE,gBAAgB,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAA;IACtD,eAAe,IAAI,OAAO,CAAC,oBAAoB,CAAC,CAAA;IAChD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACtB;AAyFD,wBAAgB,gBAAgB,CAAC,MAAM,CAAC,EAAE,gBAAgB,GAAG,UAAU,CAqGtE;AAED,wBAAgB,eAAe,CAAC,UAAU,EAAE,UAAU,GAAG,UAAU,CA0ClE"}
1
+ {"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../src/audit.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -7,7 +7,7 @@ export type { Tracer, TracerConfig, TracerOutput, TracerExporter, CostReport } f
7
7
  export { createMetrics } from './metrics';
8
8
  export type { MetricsCollector, MetricEntry } from './metrics';
9
9
  export { createAuditTrail, auditMiddleware } from './audit';
10
- export type { AuditEventType, AuditEvent, AuditStorageAdapter, AuditQueryFilter, AuditIntegrityResult, AuditTrailConfig, AuditTrail, } from './audit';
10
+ export type { AuditEventType, AuditEvent, AuditStorageAdapter, AuditQueryFilter, AuditIntegrityResult, AuditTrailConfig, AuditBatchConfig, AuditTrail, } from './audit';
11
11
  export { createProvenanceTracker } from './provenance';
12
12
  export type { ProvenanceRecord, ProvenanceTracker } from './provenance';
13
13
  export { createExperiment, createFileExperimentStore } from './experiment';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AACnC,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAA;AAG1F,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AACnE,YAAY,EACX,UAAU,EACV,gBAAgB,EAChB,YAAY,EACZ,mBAAmB,EACnB,SAAS,EACT,aAAa,EACb,sBAAsB,EACtB,eAAe,EACf,cAAc,GACd,MAAM,eAAe,CAAA;AAGtB,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAA;AAClC,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAG9F,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AACzC,YAAY,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAG9D,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAC3D,YAAY,EACX,cAAc,EACd,UAAU,EACV,mBAAmB,EACnB,gBAAgB,EAChB,oBAAoB,EACpB,gBAAgB,EAChB,UAAU,GACV,MAAM,SAAS,CAAA;AAGhB,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAA;AACtD,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAGvE,OAAO,EAAE,gBAAgB,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAA;AAC1E,YAAY,EACX,UAAU,EACV,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,GACf,MAAM,cAAc,CAAA;AAGrB,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAClE,YAAY,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAGvD,OAAO,EACN,UAAU,EACV,mBAAmB,EACnB,aAAa,EACb,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,GAClB,MAAM,QAAQ,CAAA;AACf,YAAY,EACX,QAAQ,EACR,YAAY,EACZ,cAAc,EACd,aAAa,EACb,kBAAkB,EAClB,SAAS,EACT,YAAY,EACZ,iBAAiB,EACjB,YAAY,EACZ,kBAAkB,GAClB,MAAM,QAAQ,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AACnC,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,QAAQ,CAAA;AAG1F,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AACnE,YAAY,EACX,UAAU,EACV,gBAAgB,EAChB,YAAY,EACZ,mBAAmB,EACnB,SAAS,EACT,aAAa,EACb,sBAAsB,EACtB,eAAe,EACf,cAAc,GACd,MAAM,eAAe,CAAA;AAGtB,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAA;AAClC,YAAY,EAAE,MAAM,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,UAAU,CAAA;AAG9F,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AACzC,YAAY,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAG9D,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAA;AAC3D,YAAY,EACX,cAAc,EACd,UAAU,EACV,mBAAmB,EACnB,gBAAgB,EAChB,oBAAoB,EACpB,gBAAgB,EAChB,gBAAgB,EAChB,UAAU,GACV,MAAM,SAAS,CAAA;AAGhB,OAAO,EAAE,uBAAuB,EAAE,MAAM,cAAc,CAAA;AACtD,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAGvE,OAAO,EAAE,gBAAgB,EAAE,yBAAyB,EAAE,MAAM,cAAc,CAAA;AAC1E,YAAY,EACX,UAAU,EACV,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,eAAe,GACf,MAAM,cAAc,CAAA;AAGrB,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAClE,YAAY,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAGvD,OAAO,EACN,UAAU,EACV,mBAAmB,EACnB,aAAa,EACb,gBAAgB,EAChB,kBAAkB,EAClB,mBAAmB,EACnB,kBAAkB,GAClB,MAAM,QAAQ,CAAA;AACf,YAAY,EACX,QAAQ,EACR,YAAY,EACZ,cAAc,EACd,aAAa,EACb,kBAAkB,EAClB,SAAS,EACT,YAAY,EACZ,iBAAiB,EACjB,YAAY,EACZ,kBAAkB,GAClB,MAAM,QAAQ,CAAA"}
package/dist/index.js CHANGED
@@ -708,21 +708,53 @@ function computeEventHash(event, previousHash) {
708
708
  });
709
709
  return createHash("sha256").update(content).digest("hex");
710
710
  }
711
+ var ZERO_HASH = "0".repeat(64);
712
+
713
+ class RingBuffer {
714
+ buffer;
715
+ head = 0;
716
+ size = 0;
717
+ capacity;
718
+ constructor(capacity) {
719
+ this.capacity = capacity;
720
+ this.buffer = new Array(capacity);
721
+ }
722
+ push(item) {
723
+ const index = (this.head + this.size) % this.capacity;
724
+ if (this.size === this.capacity) {
725
+ this.head = (this.head + 1) % this.capacity;
726
+ } else {
727
+ this.size++;
728
+ }
729
+ this.buffer[index] = item;
730
+ }
731
+ toArray() {
732
+ const result = new Array(this.size);
733
+ for (let i = 0;i < this.size; i++) {
734
+ result[i] = this.buffer[(this.head + i) % this.capacity];
735
+ }
736
+ return result;
737
+ }
738
+ get length() {
739
+ return this.size;
740
+ }
741
+ last() {
742
+ if (this.size === 0)
743
+ return;
744
+ return this.buffer[(this.head + this.size - 1) % this.capacity];
745
+ }
746
+ }
711
747
 
712
748
  class InMemoryAuditStorage {
713
- events = [];
714
- maxEvents;
749
+ ring;
715
750
  constructor(maxEvents) {
716
- this.maxEvents = maxEvents ?? 1e4;
751
+ this.ring = new RingBuffer(maxEvents ?? 1e4);
717
752
  }
718
753
  append(event) {
719
- this.events.push(event);
720
- if (this.events.length > this.maxEvents) {
721
- this.events = this.events.slice(-this.maxEvents);
722
- }
754
+ this.ring.push(event);
723
755
  }
724
756
  query(filter) {
725
- let results = [...this.events];
757
+ let results = this.ring.toArray();
726
758
  if (filter.type) {
727
759
  const types = Array.isArray(filter.type) ? filter.type : [filter.type];
728
760
  results = results.filter((e) => types.includes(e.type));
@@ -746,29 +778,29 @@ class InMemoryAuditStorage {
746
778
  return results.slice(offset, offset + limit);
747
779
  }
748
780
  count() {
749
- return this.events.length;
781
+ return this.ring.length;
750
782
  }
751
783
  verifyIntegrity() {
752
- if (this.events.length === 0) {
784
+ const events = this.ring.toArray();
785
+ if (events.length === 0) {
753
786
  return { valid: true, totalEvents: 0, chainComplete: true };
754
787
  }
755
- for (let i = 0;i < this.events.length; i++) {
756
- const event = this.events[i];
788
+ for (let i = 0;i < events.length; i++) {
789
+ const event = events[i];
757
790
  const expectedHash = computeEventHash(event, event.previousHash);
758
791
  if (event.hash !== expectedHash) {
759
- return { valid: false, totalEvents: this.events.length, brokenAt: i };
792
+ return { valid: false, totalEvents: events.length, brokenAt: i };
760
793
  }
761
- if (i > 0 && event.previousHash !== this.events[i - 1].hash) {
762
- return { valid: false, totalEvents: this.events.length, brokenAt: i };
794
+ if (i > 0 && event.previousHash !== events[i - 1].hash) {
795
+ return { valid: false, totalEvents: events.length, brokenAt: i };
763
796
  }
764
797
  }
765
- const chainComplete = this.events[0].previousHash === "0".repeat(64);
766
- return { valid: true, totalEvents: this.events.length, chainComplete };
798
+ const chainComplete = events[0].previousHash === ZERO_HASH;
799
+ return { valid: true, totalEvents: events.length, chainComplete };
767
800
  }
768
801
  getLastHash() {
769
- if (this.events.length === 0)
770
- return "0".repeat(64);
771
- return this.events[this.events.length - 1].hash;
802
+ const last = this.ring.last();
803
+ return last ? last.hash : ZERO_HASH;
772
804
  }
773
805
  }
774
806
  function createAuditTrail(config) {
@@ -776,7 +808,7 @@ function createAuditTrail(config) {
776
808
  const storage = config?.storage && typeof config.storage !== "string" ? config.storage : new InMemoryAuditStorage(config?.maxEvents);
777
809
  let sequenceId = 0;
778
810
  let idCounter = 0;
779
- let previousHash = "0".repeat(64);
811
+ let previousHash = ZERO_HASH;
780
812
  let isReady = true;
781
813
  let readyPromise = Promise.resolve();
782
814
  if (useHashChain && storage.getLastHash) {
@@ -792,18 +824,25 @@ function createAuditTrail(config) {
792
824
  });
793
825
  }
794
826
  }
795
- function appendEntry(type, data, options) {
827
+ const batchConfig = config?.batch;
828
+ const batchSize = batchConfig?.size ?? 100;
829
+ const batchIntervalMs = batchConfig?.intervalMs ?? 50;
830
+ const isBatched = !!batchConfig;
831
+ const pendingBuffer = [];
832
+ let flushTimer = null;
833
+ const flushPromise = Promise.resolve();
834
+ function buildAndAppend(entry) {
796
835
  sequenceId++;
797
836
  idCounter++;
798
837
  const event = {
799
- id: `audit_${idCounter.toString(36)}_${Date.now().toString(36)}`,
838
+ id: `audit_${idCounter.toString(36)}_${entry.timestamp.toString(36)}`,
800
839
  sequenceId,
801
- type,
802
- timestamp: Date.now(),
803
- actor: options?.actor,
804
- traceId: options?.traceId,
805
- data,
806
- previousHash: useHashChain ? previousHash : "0".repeat(64)
840
+ type: entry.type,
841
+ timestamp: entry.timestamp,
842
+ actor: entry.actor,
843
+ traceId: entry.traceId,
844
+ data: entry.data,
845
+ previousHash: useHashChain ? previousHash : ZERO_HASH
807
846
  };
808
847
  const hash = useHashChain ? computeEventHash(event, event.previousHash) : createHash("sha256").update(JSON.stringify(event)).digest("hex");
809
848
  const finalEvent = { ...event, hash };
@@ -815,26 +854,77 @@ function createAuditTrail(config) {
815
854
  result.catch((err2) => config?.onError?.(err2));
816
855
  }
817
856
  }
857
+ function drainBuffer() {
858
+ let entry = pendingBuffer.shift();
859
+ while (entry) {
860
+ buildAndAppend(entry);
861
+ entry = pendingBuffer.shift();
862
+ }
863
+ }
864
+ if (isBatched) {
865
+ flushTimer = setInterval(() => {
866
+ if (pendingBuffer.length > 0)
867
+ drainBuffer();
868
+ }, batchIntervalMs);
869
+ if (typeof flushTimer === "object" && "unref" in flushTimer) {
870
+ flushTimer.unref();
871
+ }
872
+ }
873
+ function logEntry(type, data, options) {
874
+ const entry = {
875
+ type,
876
+ data,
877
+ timestamp: Date.now(),
878
+ actor: options?.actor,
879
+ traceId: options?.traceId
880
+ };
881
+ if (isBatched) {
882
+ pendingBuffer.push(entry);
883
+ if (pendingBuffer.length >= batchSize)
884
+ drainBuffer();
885
+ return;
886
+ }
887
+ buildAndAppend(entry);
888
+ }
818
889
  return {
819
890
  log(type, data, options) {
820
891
  if (isReady) {
821
- appendEntry(type, data, options);
892
+ logEntry(type, data, options);
822
893
  } else {
823
- readyPromise = readyPromise.then(() => appendEntry(type, data, options));
894
+ readyPromise = readyPromise.then(() => logEntry(type, data, options));
824
895
  }
825
896
  },
826
897
  ready() {
827
898
  return readyPromise;
828
899
  },
900
+ async flush() {
901
+ await readyPromise;
902
+ drainBuffer();
903
+ await flushPromise;
904
+ },
905
+ dispose() {
906
+ if (flushTimer) {
907
+ clearInterval(flushTimer);
908
+ flushTimer = null;
909
+ }
910
+ drainBuffer();
911
+ },
829
912
  async query(filter) {
913
+ if (isBatched)
914
+ drainBuffer();
830
915
  return storage.query(filter);
831
916
  },
832
917
  async verifyIntegrity() {
918
+ if (isBatched)
919
+ drainBuffer();
833
920
  return storage.verifyIntegrity();
834
921
  },
835
922
  get count() {
836
923
  const result = storage.count();
837
- return typeof result === "number" ? result : 0;
924
+ return (typeof result === "number" ? result : 0) + pendingBuffer.length;
925
+ },
926
+ get pending() {
927
+ return pendingBuffer.length;
838
928
  }
839
929
  };
840
930
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elsium-ai/observe",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Observability, tracing, and cost tracking for ElsiumAI",
5
5
  "license": "MIT",
6
6
  "author": "Eric Utrera <ebutrera9103@gmail.com>",
@@ -26,7 +26,7 @@
26
26
  "dev": "bun --watch src/index.ts"
27
27
  },
28
28
  "dependencies": {
29
- "@elsium-ai/core": "^0.5.0"
29
+ "@elsium-ai/core": "^0.7.0"
30
30
  },
31
31
  "devDependencies": {
32
32
  "typescript": "^5.7.0"