@volidator/node 1.0.0 → 1.0.2

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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +72 -2
  3. package/dist/{chunk-KEXIKZGN.js → chunk-AJL3OYF2.js} +7 -3
  4. package/dist/chunk-AJL3OYF2.js.map +1 -0
  5. package/dist/{chunk-YI4DCCHT.js → chunk-UDBMJC25.js} +207 -26
  6. package/dist/chunk-UDBMJC25.js.map +1 -0
  7. package/dist/index.cjs +206 -25
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.d.cts +91 -3
  10. package/dist/index.d.ts +91 -3
  11. package/dist/index.js +1 -1
  12. package/dist/middleware/next.cjs +211 -26
  13. package/dist/middleware/next.cjs.map +1 -1
  14. package/dist/middleware/next.d.cts +2 -0
  15. package/dist/middleware/next.d.ts +2 -0
  16. package/dist/middleware/next.js +2 -2
  17. package/dist/plugins/agent-langchain.cjs +93 -0
  18. package/dist/plugins/agent-langchain.cjs.map +1 -0
  19. package/dist/plugins/agent-langchain.d.cts +43 -0
  20. package/dist/plugins/agent-langchain.d.ts +43 -0
  21. package/dist/plugins/agent-langchain.js +68 -0
  22. package/dist/plugins/agent-langchain.js.map +1 -0
  23. package/dist/plugins/agent-vercel.cjs +64 -0
  24. package/dist/plugins/agent-vercel.cjs.map +1 -0
  25. package/dist/plugins/agent-vercel.d.cts +21 -0
  26. package/dist/plugins/agent-vercel.d.ts +21 -0
  27. package/dist/plugins/agent-vercel.js +39 -0
  28. package/dist/plugins/agent-vercel.js.map +1 -0
  29. package/dist/plugins/clerk.cjs +215 -27
  30. package/dist/plugins/clerk.cjs.map +1 -1
  31. package/dist/plugins/clerk.d.cts +3 -1
  32. package/dist/plugins/clerk.d.ts +3 -1
  33. package/dist/plugins/clerk.js +6 -3
  34. package/dist/plugins/clerk.js.map +1 -1
  35. package/dist/plugins/universal.cjs +217 -27
  36. package/dist/plugins/universal.cjs.map +1 -1
  37. package/dist/plugins/universal.d.cts +3 -1
  38. package/dist/plugins/universal.d.ts +3 -1
  39. package/dist/plugins/universal.js +8 -3
  40. package/dist/plugins/universal.js.map +1 -1
  41. package/package.json +13 -3
  42. package/dist/chunk-KEXIKZGN.js.map +0 -1
  43. package/dist/chunk-YI4DCCHT.js.map +0 -1
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Volidator Authors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -7,6 +7,8 @@ Volidator encrypts every log entry locally — on your server — before sending
7
7
  [![npm](https://img.shields.io/npm/v/@volidator/node)](https://www.npmjs.com/package/@volidator/node)
8
8
  [![license](https://img.shields.io/npm/l/@volidator/node)](LICENSE)
9
9
 
10
+ **[Website & Sign Up](https://volidator.com) | [Developer Documentation](https://docs.volidator.com)**
11
+
10
12
  ---
11
13
 
12
14
  ## What is Volidator?
@@ -52,6 +54,7 @@ Volidator solves this with **Zero-Knowledge Audit Trails**:
52
54
  14. [`@volidator/react`](#14-volidatorreact)
53
55
  15. [AI Agent Auditing (VolidatorAgent)](#15-ai-agent-auditing-volidatoragent)
54
56
  16. [Batch Ingestion (logBatch)](#16-batch-ingestion-logbatch)
57
+ 17. [Large Payloads (Claim Check Pattern)](#17-large-payloads-claim-check-pattern)
55
58
 
56
59
  ---
57
60
 
@@ -65,8 +68,12 @@ npm install @volidator/node
65
68
 
66
69
  ## 2. Environment Setup
67
70
 
68
- Store secrets as environment variables. Never hardcode them.
71
+ Generate a secure 32-byte encryption key (64 hex characters) with the `vol-dek-` prefix:
72
+ ```bash
73
+ node -e "const b=require('crypto').randomBytes(32);console.log('vol-dek-'+b.toString('hex'))"
74
+ ```
69
75
 
76
+ Then add it as an environment secret:
70
77
  ```bash
71
78
  # Authenticates your server with the Volidator ingestion endpoint
72
79
  VOLIDATOR_API_KEY="val_live_xxxxxxxx..."
@@ -87,9 +94,29 @@ import { VolidatorClient } from "@volidator/node";
87
94
  export const volidator = new VolidatorClient({
88
95
  apiKey: process.env.VOLIDATOR_API_KEY!,
89
96
  encryptionKey: process.env.VOLIDATOR_ENCRYPTION_KEY!,
97
+
98
+ // Optional configuration for delivery retries and sizes:
99
+ maxRetries: 3, // Retry up to 3 times on transient errors (default: 3)
100
+ maxMetadataSize: 10240, // Cap serialized metadata size at 10KB (default: 10KB)
101
+
102
+ onDeliveryFailure: (payload, error) => {
103
+ // Callback invoked when a log permanently fails to deliver after all retries
104
+ console.error(`Log delivery failed permanently for action "${payload.action}": ${error.message}`);
105
+ }
90
106
  });
91
107
  ```
92
108
 
109
+ ### Transient Errors & Retry Strategy
110
+ By default, the SDK automatically retries log delivery on network errors or 5xx server responses using an exponential backoff strategy (delays: ~500ms → ~1500ms → ~4500ms). Client errors (4xx) are never retried.
111
+
112
+ > **⚠️ Serverless / Edge Function execution time caveat:**
113
+ > Worst-case retry attempts take up to ~6.5 seconds. If you are running inside a serverless or Edge environment (e.g. Vercel, Next.js Edge, Cloudflare Workers) with strict duration limits:
114
+ > - Wrap log calls in `ctx.waitUntil(volidator.log(...))` so the worker doesn't block response delivery.
115
+ > - Or reduce `maxRetries` to `1` (or `0` to disable retries) to avoid hitting runtime execution limits.
116
+
117
+ ### Metadata Limits & Truncation
118
+ Log metadata is subject to a hard depth limit of **5 levels** and string value length limit of **1000 characters**. In non-production environments (`NODE_ENV !== 'production'`), the SDK will emit warnings in the console if any metadata is truncated.
119
+
93
120
  ---
94
121
 
95
122
  ## 4. Log an Event
@@ -476,7 +503,7 @@ await volidator.agent.handoff({
476
503
  });
477
504
  ```
478
505
 
479
- ### Trace Correlation (OpenTelemetry Compatibility)
506
+ ### Trace Correlation & Logical Clocks (Lamport Timestamps)
480
507
 
481
508
  Pass standard trace metadata (`traceId`, `spanId`, `parentSpanId`) to map out parent-child relationships and causality graphs between agent steps.
482
509
 
@@ -490,6 +517,11 @@ await volidator.agent.toolCall({
490
517
  });
491
518
  ```
492
519
 
520
+ #### Deterministic Edge Trace Ordering
521
+ To resolve NTP (Network Time Protocol) clock drift anomalies between distributed edge servers or serverless invocations, the SDK automatically maintains and propagates a **Lamport Logical Clock** value inside request headers (`x-volidator-clock`). Clocks are synchronized across boundary calls using:
522
+ $$\text{localClock} = \max(\text{localClock}, \text{incomingClock}) + 1$$
523
+ This guarantees that visualizer graphs and dashboard causality lines render completely deterministically in chronological order.
524
+
493
525
  ---
494
526
 
495
527
  ## 16. Batch Ingestion (`logBatch`)
@@ -513,6 +545,44 @@ console.log(`Successfully ingested ${accepted} logs, failed to prepare ${rejecte
513
545
 
514
546
  ---
515
547
 
548
+ ## 17. Large Payloads (Claim Check Pattern)
549
+
550
+ When logging autonomous agent thinking processes, prompt contexts, or tool data, payloads can easily grow quite large. If your encrypted log payload exceeds **30KB**, the SDK automatically and transparently switches to the **Claim Check Pattern**:
551
+ * The SDK uploads the encrypted ciphertext chunk to Cloudflare R2 object storage securely before database ingestion.
552
+ * It writes a content-addressed SHA-256 hash pointer to the database log record instead of the full payload, setting `isClaimCheck` to true.
553
+ * The Volidator dashboard and embed widgets detect this flag and automatically retrieve the encrypted chunk from the storage proxy to decrypt it locally in the browser.
554
+
555
+ This maintains absolute Zero-Knowledge privacy guarantees for large payloads without bloat.
556
+
557
+ ---
558
+
559
+ ## 18. Fluent Batcher (`batcher`)
560
+
561
+ Instead of managing arrays and calling `logBatch` manually, you can use the fluent `batcher()` client helper. This is highly recommended for loop-heavy agent runs or script execution paths.
562
+
563
+ ```typescript
564
+ const batcher = volidator.batcher({
565
+ autoFlushCount: 50, // Automatically flush and send when buffer hits 50 logs
566
+ autoFlushInterval: 5000, // Or automatically flush every 5 seconds (Node-only, see warning)
567
+ });
568
+
569
+ // Inside your loop or agent reasoning cycle:
570
+ batcher.push({
571
+ actor: "agent-1",
572
+ action: "thought",
573
+ metadata: { step: 1, text: "Thinking..." }
574
+ });
575
+
576
+ // Always call flush manually at the end of the script or request path to send any leftovers:
577
+ await batcher.flush();
578
+ ```
579
+
580
+ > **⚠️ Serverless / Edge Warning:**
581
+ > `autoFlushInterval` uses `setInterval` internally. In serverless and Edge environments (like Cloudflare Workers, Vercel Edge, Next.js Edge), the V8 isolate is **frozen or destroyed** as soon as the response is returned to the user. Background timers will silently fail to execute, resulting in dropped logs.
582
+ > **Only use `autoFlushInterval` in long-lived Node.js applications** (Express, Fastify, CLI tools). For serverless/edge functions, **always manually call `await batcher.flush()`** or pass the promise to `ctx.waitUntil()`.
583
+
584
+ ---
585
+
516
586
  ## License
517
587
 
518
588
  MIT © Volidator Contributors
@@ -1,11 +1,13 @@
1
1
  import {
2
2
  VolidatorClient
3
- } from "./chunk-YI4DCCHT.js";
3
+ } from "./chunk-UDBMJC25.js";
4
4
 
5
5
  // src/middleware/next.ts
6
6
  function withVolidator(client, handler) {
7
7
  return async (req, ctx = {}, ...args) => {
8
8
  const extractedContext = VolidatorClient.extractContext(req);
9
+ const extractedTrace = VolidatorClient.extractTraceContext(req);
10
+ const incomingClock = extractedTrace.logicalClock || 0;
9
11
  const scopedLog = async (payload) => {
10
12
  return client.log({
11
13
  ...payload,
@@ -46,11 +48,13 @@ function withVolidator(client, handler) {
46
48
  compliance: scopedCompliance
47
49
  }
48
50
  };
49
- return handler(req, newCtx, ...args);
51
+ return VolidatorClient.logicalClockStore.run({ clock: incomingClock }, () => {
52
+ return handler(req, newCtx, ...args);
53
+ });
50
54
  };
51
55
  }
52
56
 
53
57
  export {
54
58
  withVolidator
55
59
  };
56
- //# sourceMappingURL=chunk-KEXIKZGN.js.map
60
+ //# sourceMappingURL=chunk-AJL3OYF2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/middleware/next.ts"],"sourcesContent":["import { VolidatorClient, LogPayload } from \"../index\";\n\n/**\n * Next.js App Router Middleware for Volidator.\n * Automatically extracts telemetry context (IP, User Agent, Location)\n * from the Next.js standard Request object and injects it into every\n * `volidator.log()` and `volidator.compliance.*()` call made inside\n * the wrapped handler.\n */\nexport function withVolidator<T extends Function>(\n client: VolidatorClient,\n handler: T\n): (req: Request, ctx?: any, ...args: any[]) => Promise<any> {\n return async (req: Request, ctx: any = {}, ...args: any[]) => {\n const extractedContext = VolidatorClient.extractContext(req);\n const extractedTrace = VolidatorClient.extractTraceContext(req);\n const incomingClock = extractedTrace.logicalClock || 0;\n\n // ---------------------------------------------------------------------------\n // Scoped log — merges request-level IP/UA context into every log call.\n // ---------------------------------------------------------------------------\n const scopedLog = async (payload: LogPayload) => {\n return client.log({\n ...payload,\n context: {\n ...extractedContext,\n ...payload.context,\n location: {\n ...extractedContext.location,\n ...(payload.context?.location || {}),\n },\n device: {\n ...extractedContext.device,\n ...(payload.context?.device || {}),\n },\n },\n });\n };\n\n // ---------------------------------------------------------------------------\n // Scoped compliance — mirrors VolidatorCompliance but routes through\n // scopedLog so that IP/UA telemetry is preserved in compliance events.\n // ---------------------------------------------------------------------------\n const withControl = (\n action: string,\n soc2Control: string,\n isoControl: string,\n payload: Omit<LogPayload, \"action\">\n ) =>\n scopedLog({\n ...payload,\n action,\n metadata: {\n ...payload.metadata,\n soc2_control: soc2Control,\n iso27001: isoControl,\n },\n });\n\n const scopedCompliance = {\n accessRevoked: (p: Omit<LogPayload, \"action\">) =>\n withControl(\"access.revoked\", \"CC6.1\", \"A.9.2.6\", p),\n accessGranted: (p: Omit<LogPayload, \"action\">) =>\n withControl(\"access.granted\", \"CC6.1\", \"A.9.2.1\", p),\n dataExported: (p: Omit<LogPayload, \"action\">) =>\n withControl(\"data.exported\", \"CC6.6\", \"A.12.4.1\", p),\n systemConfigChanged: (p: Omit<LogPayload, \"action\">) =>\n withControl(\"system.config_changed\", \"CC6.2\", \"A.12.1.2\", p),\n mfaEnabled: (p: Omit<LogPayload, \"action\">) =>\n withControl(\"mfa.enabled\", \"CC6.3\", \"A.9.4.2\", p),\n };\n\n const newCtx = {\n ...ctx,\n volidator: {\n log: scopedLog,\n compliance: scopedCompliance,\n },\n };\n\n return VolidatorClient.logicalClockStore.run({ clock: incomingClock }, () => {\n return handler(req, newCtx, ...args);\n });\n };\n}\n"],"mappings":";;;;;AASO,SAAS,cACd,QACA,SAC2D;AAC3D,SAAO,OAAO,KAAc,MAAW,CAAC,MAAM,SAAgB;AAC5D,UAAM,mBAAmB,gBAAgB,eAAe,GAAG;AAC3D,UAAM,iBAAiB,gBAAgB,oBAAoB,GAAG;AAC9D,UAAM,gBAAgB,eAAe,gBAAgB;AAKrD,UAAM,YAAY,OAAO,YAAwB;AAC/C,aAAO,OAAO,IAAI;AAAA,QAChB,GAAG;AAAA,QACH,SAAS;AAAA,UACP,GAAG;AAAA,UACH,GAAG,QAAQ;AAAA,UACX,UAAU;AAAA,YACR,GAAG,iBAAiB;AAAA,YACpB,GAAI,QAAQ,SAAS,YAAY,CAAC;AAAA,UACpC;AAAA,UACA,QAAQ;AAAA,YACN,GAAG,iBAAiB;AAAA,YACpB,GAAI,QAAQ,SAAS,UAAU,CAAC;AAAA,UAClC;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AAMA,UAAM,cAAc,CAClB,QACA,aACA,YACA,YAEA,UAAU;AAAA,MACR,GAAG;AAAA,MACH;AAAA,MACA,UAAU;AAAA,QACR,GAAG,QAAQ;AAAA,QACX,cAAc;AAAA,QACd,UAAU;AAAA,MACZ;AAAA,IACF,CAAC;AAEH,UAAM,mBAAmB;AAAA,MACvB,eAAe,CAAC,MACd,YAAY,kBAAkB,SAAS,WAAW,CAAC;AAAA,MACrD,eAAe,CAAC,MACd,YAAY,kBAAkB,SAAS,WAAW,CAAC;AAAA,MACrD,cAAc,CAAC,MACb,YAAY,iBAAiB,SAAS,YAAY,CAAC;AAAA,MACrD,qBAAqB,CAAC,MACpB,YAAY,yBAAyB,SAAS,YAAY,CAAC;AAAA,MAC7D,YAAY,CAAC,MACX,YAAY,eAAe,SAAS,WAAW,CAAC;AAAA,IACpD;AAEA,UAAM,SAAS;AAAA,MACb,GAAG;AAAA,MACH,WAAW;AAAA,QACT,KAAK;AAAA,QACL,YAAY;AAAA,MACd;AAAA,IACF;AAEA,WAAO,gBAAgB,kBAAkB,IAAI,EAAE,OAAO,cAAc,GAAG,MAAM;AAC3E,aAAO,QAAQ,KAAK,QAAQ,GAAG,IAAI;AAAA,IACrC,CAAC;AAAA,EACH;AACF;","names":[]}
@@ -1,6 +1,8 @@
1
1
  // src/index.ts
2
- var VolidatorClient = class _VolidatorClient {
2
+ import { AsyncLocalStorage } from "async_hooks";
3
+ var _VolidatorClient = class _VolidatorClient {
3
4
  constructor(config) {
5
+ this.fallbackLogicalClock = 0;
4
6
  this.apiKey = config.apiKey;
5
7
  this.endpoint = config.endpoint || "https://ingestion.volidator.com";
6
8
  this.projectId = config.projectId;
@@ -8,6 +10,8 @@ var VolidatorClient = class _VolidatorClient {
8
10
  this.redactKeys = config.redactKeys || [];
9
11
  this.referenceKeys = config.referenceKeys || [];
10
12
  this.maxMetadataSize = config.maxMetadataSize || 10240;
13
+ this.maxRetries = config.maxRetries !== void 0 ? config.maxRetries : 3;
14
+ this.onDeliveryFailure = config.onDeliveryFailure;
11
15
  if (config.keyring && config.activeEncryptionKeyId) {
12
16
  this.keyring = config.keyring;
13
17
  this.activeKeyId = config.activeEncryptionKeyId;
@@ -28,6 +32,22 @@ var VolidatorClient = class _VolidatorClient {
28
32
  this.compliance = new VolidatorCompliance(this);
29
33
  this.agent = new VolidatorAgent(this);
30
34
  }
35
+ /**
36
+ * Increments and returns the current Lamport Logical Clock counter.
37
+ * If a logicalClockStore context is active, it reads, syncs, and updates
38
+ * the thread-scoped clock; otherwise, it falls back to a global instance-scoped counter.
39
+ *
40
+ * @param incomingClock Optional logical clock sequence from incoming trace parent header
41
+ */
42
+ getAndIncrementClock(incomingClock) {
43
+ const store = _VolidatorClient.logicalClockStore.getStore();
44
+ if (store) {
45
+ store.clock = Math.max(store.clock, incomingClock || 0) + 1;
46
+ return store.clock;
47
+ }
48
+ this.fallbackLogicalClock = Math.max(this.fallbackLogicalClock, incomingClock || 0) + 1;
49
+ return this.fallbackLogicalClock;
50
+ }
31
51
  // ---------------------------------------------------------------------------
32
52
  // Telemetry Context Parser (Zero-latency header parsing)
33
53
  // ---------------------------------------------------------------------------
@@ -69,11 +89,23 @@ var VolidatorClient = class _VolidatorClient {
69
89
  }
70
90
  return "";
71
91
  };
92
+ const result = {};
93
+ const clockVal = getHeader("x-volidator-clock");
94
+ if (clockVal) {
95
+ const parsed = parseInt(clockVal, 10);
96
+ if (!isNaN(parsed)) {
97
+ result.logicalClock = parsed;
98
+ }
99
+ }
72
100
  const traceparent = getHeader("traceparent");
73
- if (!traceparent) return {};
74
- const parts = traceparent.split("-");
75
- if (parts.length !== 4) return {};
76
- return { traceId: parts[1], spanId: parts[2] };
101
+ if (traceparent) {
102
+ const parts = traceparent.split("-");
103
+ if (parts.length === 4) {
104
+ result.traceId = parts[1];
105
+ result.spanId = parts[2];
106
+ }
107
+ }
108
+ return result;
77
109
  }
78
110
  static resolveTelemetryConfig(config) {
79
111
  const preset = config.preset || "standard";
@@ -196,6 +228,12 @@ var VolidatorClient = class _VolidatorClient {
196
228
  // prepareLogEntry() — Prepares, redacts, and encrypts a single log entry payload
197
229
  // ---------------------------------------------------------------------------
198
230
  async prepareLogEntry(payload, maxMetaOverride) {
231
+ const PAYLOAD_5MB_LIMIT = 5 * 1024 * 1024;
232
+ const metadata = payload.metadata || {};
233
+ const payloadSize = new TextEncoder().encode(JSON.stringify(metadata)).length;
234
+ if (payloadSize > PAYLOAD_5MB_LIMIT) {
235
+ throw new Error(`Volidator SDK Error: Audit log payload exceeds the 5MB hard limit (${payloadSize} bytes).`);
236
+ }
199
237
  const actorRaw = payload.actor || payload.actorId || "unknown";
200
238
  const targetRaw = payload.target || payload.targetId || "unknown";
201
239
  const tenantRaw = payload.tenant || payload.tenantId || "";
@@ -277,13 +315,20 @@ var VolidatorClient = class _VolidatorClient {
277
315
  safeMetadata[k] = v;
278
316
  }
279
317
  }
318
+ let didTruncateDepth = false;
319
+ let didTruncateString = false;
280
320
  const limitDepth = (obj, currentDepth = 1) => {
281
321
  if (currentDepth > 5) {
322
+ didTruncateDepth = true;
282
323
  return "[Truncated - Depth Exceeded]";
283
324
  }
284
325
  if (typeof obj !== "object" || obj === null) {
285
326
  if (typeof obj === "string") {
286
- return truncate(obj, 1e3);
327
+ const truncatedVal = truncate(obj, 1e3);
328
+ if (truncatedVal.length !== obj.length) {
329
+ didTruncateString = true;
330
+ }
331
+ return truncatedVal;
287
332
  }
288
333
  return obj;
289
334
  }
@@ -297,6 +342,14 @@ var VolidatorClient = class _VolidatorClient {
297
342
  return result;
298
343
  };
299
344
  const processedMetadata = limitDepth(safeMetadata);
345
+ if (typeof process !== "undefined" && process.env?.NODE_ENV !== "production") {
346
+ if (didTruncateDepth) {
347
+ console.warn("[Volidator] Warning: Log metadata exceeded maximum depth limit (5) and was truncated.");
348
+ }
349
+ if (didTruncateString) {
350
+ console.warn("[Volidator] Warning: One or more log metadata string values exceeded the 1000-character limit and were truncated.");
351
+ }
352
+ }
300
353
  const serializedMeta = JSON.stringify(processedMetadata);
301
354
  const maxMetaLimit = maxMetaOverride || this.maxMetadataSize;
302
355
  if (serializedMeta.length > maxMetaLimit) {
@@ -320,6 +373,7 @@ var VolidatorClient = class _VolidatorClient {
320
373
  let traceId = payload.traceId;
321
374
  let spanId = payload.spanId;
322
375
  let parentSpanId = payload.parentSpanId;
376
+ let logicalClock = payload.logicalClock;
323
377
  if (payload.req) {
324
378
  const extractedTrace = _VolidatorClient.extractTraceContext(payload.req);
325
379
  if (!traceId && extractedTrace.traceId) {
@@ -328,7 +382,11 @@ var VolidatorClient = class _VolidatorClient {
328
382
  if (!spanId && extractedTrace.spanId) {
329
383
  spanId = extractedTrace.spanId;
330
384
  }
385
+ if (logicalClock === void 0 && extractedTrace.logicalClock !== void 0) {
386
+ logicalClock = extractedTrace.logicalClock;
387
+ }
331
388
  }
389
+ const resolvedClock = this.getAndIncrementClock(logicalClock);
332
390
  if (spanId) {
333
391
  enrichedPayload.spanId = spanId;
334
392
  }
@@ -341,14 +399,39 @@ var VolidatorClient = class _VolidatorClient {
341
399
  const targetBlindIndex = await this.generateBlindIndex(target, activeKeyBuffer);
342
400
  const tenantBlindIndex = tenant ? await this.generateBlindIndex(tenant, activeKeyBuffer) : void 0;
343
401
  const traceBlindIndex = traceId ? await this.generateBlindIndex(traceId, activeKeyBuffer) : void 0;
344
- const encryptedPayload = await this.encryptPayload(enrichedPayload);
402
+ let encryptedPayload = await this.encryptPayload(enrichedPayload);
403
+ let isClaimCheck = false;
404
+ if (encryptedPayload.length > 30720) {
405
+ const binaryBuffer = new TextEncoder().encode(encryptedPayload);
406
+ const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", binaryBuffer);
407
+ const hashHex = Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
408
+ try {
409
+ const uploadRes = await fetch(`${this.endpoint}/v1/log/upload/${hashHex}`, {
410
+ method: "PUT",
411
+ headers: {
412
+ Authorization: `Bearer ${this.apiKey}`,
413
+ "Content-Type": "application/octet-stream"
414
+ },
415
+ body: encryptedPayload
416
+ });
417
+ if (!uploadRes.ok) {
418
+ throw new Error(`Upload failed with status: ${uploadRes.status}`);
419
+ }
420
+ encryptedPayload = hashHex;
421
+ isClaimCheck = true;
422
+ } catch (err) {
423
+ console.error(`[Volidator] Claim check upload failed: ${err.message}`);
424
+ }
425
+ }
345
426
  return {
346
427
  actorBlindIndex,
347
428
  actionBlindIndex,
348
429
  targetBlindIndex,
349
430
  tenantBlindIndex,
350
431
  traceBlindIndex,
351
- encryptedPayload
432
+ encryptedPayload,
433
+ logicalClock: resolvedClock,
434
+ isClaimCheck
352
435
  };
353
436
  }
354
437
  // ---------------------------------------------------------------------------
@@ -356,20 +439,44 @@ var VolidatorClient = class _VolidatorClient {
356
439
  // ---------------------------------------------------------------------------
357
440
  async log(payload, maxMetaOverride) {
358
441
  const entry = await this.prepareLogEntry(payload, maxMetaOverride);
359
- try {
360
- const res = await fetch(`${this.endpoint}/v1/log`, {
361
- method: "POST",
362
- headers: {
363
- Authorization: `Bearer ${this.apiKey}`,
364
- "Content-Type": "application/json"
365
- },
366
- body: JSON.stringify(entry)
367
- });
368
- return res.ok;
369
- } catch (err) {
370
- console.error(`[Volidator] Failed to send log: ${err.message}`);
371
- return false;
442
+ let attempt = 0;
443
+ let delay = 500;
444
+ let lastError = new Error("Unknown error");
445
+ while (attempt <= this.maxRetries) {
446
+ try {
447
+ const res = await fetch(`${this.endpoint}/v1/log`, {
448
+ method: "POST",
449
+ headers: {
450
+ Authorization: `Bearer ${this.apiKey}`,
451
+ "Content-Type": "application/json"
452
+ },
453
+ body: JSON.stringify(entry)
454
+ });
455
+ if (res.ok) {
456
+ return true;
457
+ }
458
+ lastError = new Error(`Server returned status ${res.status}`);
459
+ if (res.status >= 400 && res.status < 500) {
460
+ break;
461
+ }
462
+ } catch (err) {
463
+ lastError = err instanceof Error ? err : new Error(String(err));
464
+ }
465
+ attempt++;
466
+ if (attempt <= this.maxRetries) {
467
+ await new Promise((resolve) => setTimeout(resolve, delay));
468
+ delay *= 3;
469
+ }
470
+ }
471
+ console.error(`[Volidator] Failed to send log after ${attempt} attempts: ${lastError.message}`);
472
+ if (this.onDeliveryFailure) {
473
+ try {
474
+ this.onDeliveryFailure(payload, lastError);
475
+ } catch (cbErr) {
476
+ console.error(`[Volidator] Error in onDeliveryFailure callback: ${cbErr.message}`);
477
+ }
372
478
  }
479
+ return false;
373
480
  }
374
481
  // ---------------------------------------------------------------------------
375
482
  // logBatch() — Bulk encrypt and ingest a batch of up to 100 audit events
@@ -416,12 +523,79 @@ var VolidatorClient = class _VolidatorClient {
416
523
  }
417
524
  }
418
525
  // ---------------------------------------------------------------------------
526
+ // batcher() — Convenience batcher for buffered log ingestion
527
+ // ---------------------------------------------------------------------------
528
+ /**
529
+ * Creates a convenience batcher instance for buffering and ingestion.
530
+ *
531
+ * ⚠️ SERVERLESS/EDGE CAVEAT:
532
+ * autoFlushInterval uses setInterval internally. In serverless/edge environments
533
+ * (e.g. Cloudflare Workers, Vercel Edge), the V8 isolate is frozen or destroyed
534
+ * once the response is sent. Background timers will silently fail to fire, leading
535
+ * to dropped logs. Only use autoFlushInterval in long-lived Node.js processes.
536
+ * In serverless/edge environments, always call await batcher.flush() explicitly
537
+ * before returning the response, or wrap it in ctx.waitUntil().
538
+ */
539
+ batcher(options) {
540
+ const client = this;
541
+ let buffer = [];
542
+ let intervalId = null;
543
+ const flush = async () => {
544
+ if (intervalId) {
545
+ clearInterval(intervalId);
546
+ intervalId = null;
547
+ }
548
+ if (buffer.length === 0) {
549
+ if (options?.autoFlushInterval) {
550
+ startTimer();
551
+ }
552
+ return { accepted: 0, rejected: 0 };
553
+ }
554
+ const payloadsToFlush = [...buffer];
555
+ buffer = [];
556
+ if (options?.autoFlushInterval) {
557
+ startTimer();
558
+ }
559
+ return client.logBatch(payloadsToFlush);
560
+ };
561
+ const startTimer = () => {
562
+ if (options?.autoFlushInterval && !intervalId) {
563
+ intervalId = setInterval(() => {
564
+ flush().catch((err) => {
565
+ console.error(`[Volidator] Batcher auto-flush failed: ${err.message}`);
566
+ });
567
+ }, options.autoFlushInterval);
568
+ if (intervalId && typeof intervalId.unref === "function") {
569
+ intervalId.unref();
570
+ }
571
+ }
572
+ };
573
+ const push = (payload) => {
574
+ buffer.push(payload);
575
+ const maxCount = options?.autoFlushCount || 100;
576
+ if (buffer.length >= Math.min(maxCount, 100)) {
577
+ flush().catch((err) => {
578
+ console.error(`[Volidator] Batcher auto-flush on count failed: ${err.message}`);
579
+ });
580
+ }
581
+ };
582
+ const size = () => buffer.length;
583
+ startTimer();
584
+ return {
585
+ push,
586
+ flush,
587
+ size
588
+ };
589
+ }
590
+ // ---------------------------------------------------------------------------
419
591
  // generateEmbedToken() — Sign a HS256 JWT for the embeddable dashboard widget
420
592
  // ---------------------------------------------------------------------------
421
593
  async generateEmbedToken(config) {
422
- if (!this.projectId || !this.clientSecret) {
594
+ const projectId = config.projectId || this.projectId;
595
+ const clientSecret = config.clientSecret || this.clientSecret;
596
+ if (!projectId || !clientSecret) {
423
597
  throw new Error(
424
- "generateEmbedToken() requires projectId and clientSecret in the VolidatorClient constructor."
598
+ "generateEmbedToken() requires projectId and clientSecret to be provided in either the configuration or the VolidatorClient constructor."
425
599
  );
426
600
  }
427
601
  const {
@@ -448,7 +622,7 @@ var VolidatorClient = class _VolidatorClient {
448
622
  const expiresInSeconds = Math.min(parsedExpiry, maxExpiry);
449
623
  const now = Math.floor(Date.now() / 1e3);
450
624
  const payload = {
451
- pid: this.projectId,
625
+ pid: projectId,
452
626
  scope: defaultScope,
453
627
  iat: now,
454
628
  exp: now + expiresInSeconds
@@ -481,7 +655,7 @@ var VolidatorClient = class _VolidatorClient {
481
655
  }
482
656
  payload.view = compressed;
483
657
  }
484
- const token = await this.signHS256JWT(payload, this.clientSecret);
658
+ const token = await this.signHS256JWT(payload, clientSecret);
485
659
  const keyringString = Object.entries(this.keyring).map(([id, key]) => `${id}:${key}`).join(",");
486
660
  const hostParam = hostOrigin ? `?host=${encodeURIComponent(hostOrigin)}` : "";
487
661
  const embedUrl = `${dashboardUrl}/embed/${token}${hostParam}#${keyringString}`;
@@ -530,6 +704,13 @@ var VolidatorClient = class _VolidatorClient {
530
704
  }
531
705
  }
532
706
  };
707
+ /**
708
+ * Thread-local asynchronous context storage used to maintain
709
+ * and increment Lamport logical clock sequences across execution boundaries
710
+ * (e.g. within API requests or serverless handler contexts).
711
+ */
712
+ _VolidatorClient.logicalClockStore = new AsyncLocalStorage();
713
+ var VolidatorClient = _VolidatorClient;
533
714
  var VolidatorCompliance = class {
534
715
  constructor(client) {
535
716
  this.client = client;
@@ -660,4 +841,4 @@ export {
660
841
  VolidatorCompliance,
661
842
  VolidatorAgent
662
843
  };
663
- //# sourceMappingURL=chunk-YI4DCCHT.js.map
844
+ //# sourceMappingURL=chunk-UDBMJC25.js.map