@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
@@ -25,8 +25,10 @@ __export(next_exports, {
25
25
  module.exports = __toCommonJS(next_exports);
26
26
 
27
27
  // src/index.ts
28
- var VolidatorClient = class _VolidatorClient {
28
+ var import_node_async_hooks = require("async_hooks");
29
+ var _VolidatorClient = class _VolidatorClient {
29
30
  constructor(config) {
31
+ this.fallbackLogicalClock = 0;
30
32
  this.apiKey = config.apiKey;
31
33
  this.endpoint = config.endpoint || "https://ingestion.volidator.com";
32
34
  this.projectId = config.projectId;
@@ -34,6 +36,8 @@ var VolidatorClient = class _VolidatorClient {
34
36
  this.redactKeys = config.redactKeys || [];
35
37
  this.referenceKeys = config.referenceKeys || [];
36
38
  this.maxMetadataSize = config.maxMetadataSize || 10240;
39
+ this.maxRetries = config.maxRetries !== void 0 ? config.maxRetries : 3;
40
+ this.onDeliveryFailure = config.onDeliveryFailure;
37
41
  if (config.keyring && config.activeEncryptionKeyId) {
38
42
  this.keyring = config.keyring;
39
43
  this.activeKeyId = config.activeEncryptionKeyId;
@@ -54,6 +58,22 @@ var VolidatorClient = class _VolidatorClient {
54
58
  this.compliance = new VolidatorCompliance(this);
55
59
  this.agent = new VolidatorAgent(this);
56
60
  }
61
+ /**
62
+ * Increments and returns the current Lamport Logical Clock counter.
63
+ * If a logicalClockStore context is active, it reads, syncs, and updates
64
+ * the thread-scoped clock; otherwise, it falls back to a global instance-scoped counter.
65
+ *
66
+ * @param incomingClock Optional logical clock sequence from incoming trace parent header
67
+ */
68
+ getAndIncrementClock(incomingClock) {
69
+ const store = _VolidatorClient.logicalClockStore.getStore();
70
+ if (store) {
71
+ store.clock = Math.max(store.clock, incomingClock || 0) + 1;
72
+ return store.clock;
73
+ }
74
+ this.fallbackLogicalClock = Math.max(this.fallbackLogicalClock, incomingClock || 0) + 1;
75
+ return this.fallbackLogicalClock;
76
+ }
57
77
  // ---------------------------------------------------------------------------
58
78
  // Telemetry Context Parser (Zero-latency header parsing)
59
79
  // ---------------------------------------------------------------------------
@@ -95,11 +115,23 @@ var VolidatorClient = class _VolidatorClient {
95
115
  }
96
116
  return "";
97
117
  };
118
+ const result = {};
119
+ const clockVal = getHeader("x-volidator-clock");
120
+ if (clockVal) {
121
+ const parsed = parseInt(clockVal, 10);
122
+ if (!isNaN(parsed)) {
123
+ result.logicalClock = parsed;
124
+ }
125
+ }
98
126
  const traceparent = getHeader("traceparent");
99
- if (!traceparent) return {};
100
- const parts = traceparent.split("-");
101
- if (parts.length !== 4) return {};
102
- return { traceId: parts[1], spanId: parts[2] };
127
+ if (traceparent) {
128
+ const parts = traceparent.split("-");
129
+ if (parts.length === 4) {
130
+ result.traceId = parts[1];
131
+ result.spanId = parts[2];
132
+ }
133
+ }
134
+ return result;
103
135
  }
104
136
  static resolveTelemetryConfig(config) {
105
137
  const preset = config.preset || "standard";
@@ -222,6 +254,12 @@ var VolidatorClient = class _VolidatorClient {
222
254
  // prepareLogEntry() — Prepares, redacts, and encrypts a single log entry payload
223
255
  // ---------------------------------------------------------------------------
224
256
  async prepareLogEntry(payload, maxMetaOverride) {
257
+ const PAYLOAD_5MB_LIMIT = 5 * 1024 * 1024;
258
+ const metadata = payload.metadata || {};
259
+ const payloadSize = new TextEncoder().encode(JSON.stringify(metadata)).length;
260
+ if (payloadSize > PAYLOAD_5MB_LIMIT) {
261
+ throw new Error(`Volidator SDK Error: Audit log payload exceeds the 5MB hard limit (${payloadSize} bytes).`);
262
+ }
225
263
  const actorRaw = payload.actor || payload.actorId || "unknown";
226
264
  const targetRaw = payload.target || payload.targetId || "unknown";
227
265
  const tenantRaw = payload.tenant || payload.tenantId || "";
@@ -303,13 +341,20 @@ var VolidatorClient = class _VolidatorClient {
303
341
  safeMetadata[k] = v;
304
342
  }
305
343
  }
344
+ let didTruncateDepth = false;
345
+ let didTruncateString = false;
306
346
  const limitDepth = (obj, currentDepth = 1) => {
307
347
  if (currentDepth > 5) {
348
+ didTruncateDepth = true;
308
349
  return "[Truncated - Depth Exceeded]";
309
350
  }
310
351
  if (typeof obj !== "object" || obj === null) {
311
352
  if (typeof obj === "string") {
312
- return truncate(obj, 1e3);
353
+ const truncatedVal = truncate(obj, 1e3);
354
+ if (truncatedVal.length !== obj.length) {
355
+ didTruncateString = true;
356
+ }
357
+ return truncatedVal;
313
358
  }
314
359
  return obj;
315
360
  }
@@ -323,6 +368,14 @@ var VolidatorClient = class _VolidatorClient {
323
368
  return result;
324
369
  };
325
370
  const processedMetadata = limitDepth(safeMetadata);
371
+ if (typeof process !== "undefined" && process.env?.NODE_ENV !== "production") {
372
+ if (didTruncateDepth) {
373
+ console.warn("[Volidator] Warning: Log metadata exceeded maximum depth limit (5) and was truncated.");
374
+ }
375
+ if (didTruncateString) {
376
+ console.warn("[Volidator] Warning: One or more log metadata string values exceeded the 1000-character limit and were truncated.");
377
+ }
378
+ }
326
379
  const serializedMeta = JSON.stringify(processedMetadata);
327
380
  const maxMetaLimit = maxMetaOverride || this.maxMetadataSize;
328
381
  if (serializedMeta.length > maxMetaLimit) {
@@ -346,6 +399,7 @@ var VolidatorClient = class _VolidatorClient {
346
399
  let traceId = payload.traceId;
347
400
  let spanId = payload.spanId;
348
401
  let parentSpanId = payload.parentSpanId;
402
+ let logicalClock = payload.logicalClock;
349
403
  if (payload.req) {
350
404
  const extractedTrace = _VolidatorClient.extractTraceContext(payload.req);
351
405
  if (!traceId && extractedTrace.traceId) {
@@ -354,7 +408,11 @@ var VolidatorClient = class _VolidatorClient {
354
408
  if (!spanId && extractedTrace.spanId) {
355
409
  spanId = extractedTrace.spanId;
356
410
  }
411
+ if (logicalClock === void 0 && extractedTrace.logicalClock !== void 0) {
412
+ logicalClock = extractedTrace.logicalClock;
413
+ }
357
414
  }
415
+ const resolvedClock = this.getAndIncrementClock(logicalClock);
358
416
  if (spanId) {
359
417
  enrichedPayload.spanId = spanId;
360
418
  }
@@ -367,14 +425,39 @@ var VolidatorClient = class _VolidatorClient {
367
425
  const targetBlindIndex = await this.generateBlindIndex(target, activeKeyBuffer);
368
426
  const tenantBlindIndex = tenant ? await this.generateBlindIndex(tenant, activeKeyBuffer) : void 0;
369
427
  const traceBlindIndex = traceId ? await this.generateBlindIndex(traceId, activeKeyBuffer) : void 0;
370
- const encryptedPayload = await this.encryptPayload(enrichedPayload);
428
+ let encryptedPayload = await this.encryptPayload(enrichedPayload);
429
+ let isClaimCheck = false;
430
+ if (encryptedPayload.length > 30720) {
431
+ const binaryBuffer = new TextEncoder().encode(encryptedPayload);
432
+ const hashBuffer = await globalThis.crypto.subtle.digest("SHA-256", binaryBuffer);
433
+ const hashHex = Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
434
+ try {
435
+ const uploadRes = await fetch(`${this.endpoint}/v1/log/upload/${hashHex}`, {
436
+ method: "PUT",
437
+ headers: {
438
+ Authorization: `Bearer ${this.apiKey}`,
439
+ "Content-Type": "application/octet-stream"
440
+ },
441
+ body: encryptedPayload
442
+ });
443
+ if (!uploadRes.ok) {
444
+ throw new Error(`Upload failed with status: ${uploadRes.status}`);
445
+ }
446
+ encryptedPayload = hashHex;
447
+ isClaimCheck = true;
448
+ } catch (err) {
449
+ console.error(`[Volidator] Claim check upload failed: ${err.message}`);
450
+ }
451
+ }
371
452
  return {
372
453
  actorBlindIndex,
373
454
  actionBlindIndex,
374
455
  targetBlindIndex,
375
456
  tenantBlindIndex,
376
457
  traceBlindIndex,
377
- encryptedPayload
458
+ encryptedPayload,
459
+ logicalClock: resolvedClock,
460
+ isClaimCheck
378
461
  };
379
462
  }
380
463
  // ---------------------------------------------------------------------------
@@ -382,20 +465,44 @@ var VolidatorClient = class _VolidatorClient {
382
465
  // ---------------------------------------------------------------------------
383
466
  async log(payload, maxMetaOverride) {
384
467
  const entry = await this.prepareLogEntry(payload, maxMetaOverride);
385
- try {
386
- const res = await fetch(`${this.endpoint}/v1/log`, {
387
- method: "POST",
388
- headers: {
389
- Authorization: `Bearer ${this.apiKey}`,
390
- "Content-Type": "application/json"
391
- },
392
- body: JSON.stringify(entry)
393
- });
394
- return res.ok;
395
- } catch (err) {
396
- console.error(`[Volidator] Failed to send log: ${err.message}`);
397
- return false;
468
+ let attempt = 0;
469
+ let delay = 500;
470
+ let lastError = new Error("Unknown error");
471
+ while (attempt <= this.maxRetries) {
472
+ try {
473
+ const res = await fetch(`${this.endpoint}/v1/log`, {
474
+ method: "POST",
475
+ headers: {
476
+ Authorization: `Bearer ${this.apiKey}`,
477
+ "Content-Type": "application/json"
478
+ },
479
+ body: JSON.stringify(entry)
480
+ });
481
+ if (res.ok) {
482
+ return true;
483
+ }
484
+ lastError = new Error(`Server returned status ${res.status}`);
485
+ if (res.status >= 400 && res.status < 500) {
486
+ break;
487
+ }
488
+ } catch (err) {
489
+ lastError = err instanceof Error ? err : new Error(String(err));
490
+ }
491
+ attempt++;
492
+ if (attempt <= this.maxRetries) {
493
+ await new Promise((resolve) => setTimeout(resolve, delay));
494
+ delay *= 3;
495
+ }
496
+ }
497
+ console.error(`[Volidator] Failed to send log after ${attempt} attempts: ${lastError.message}`);
498
+ if (this.onDeliveryFailure) {
499
+ try {
500
+ this.onDeliveryFailure(payload, lastError);
501
+ } catch (cbErr) {
502
+ console.error(`[Volidator] Error in onDeliveryFailure callback: ${cbErr.message}`);
503
+ }
398
504
  }
505
+ return false;
399
506
  }
400
507
  // ---------------------------------------------------------------------------
401
508
  // logBatch() — Bulk encrypt and ingest a batch of up to 100 audit events
@@ -442,12 +549,79 @@ var VolidatorClient = class _VolidatorClient {
442
549
  }
443
550
  }
444
551
  // ---------------------------------------------------------------------------
552
+ // batcher() — Convenience batcher for buffered log ingestion
553
+ // ---------------------------------------------------------------------------
554
+ /**
555
+ * Creates a convenience batcher instance for buffering and ingestion.
556
+ *
557
+ * ⚠️ SERVERLESS/EDGE CAVEAT:
558
+ * autoFlushInterval uses setInterval internally. In serverless/edge environments
559
+ * (e.g. Cloudflare Workers, Vercel Edge), the V8 isolate is frozen or destroyed
560
+ * once the response is sent. Background timers will silently fail to fire, leading
561
+ * to dropped logs. Only use autoFlushInterval in long-lived Node.js processes.
562
+ * In serverless/edge environments, always call await batcher.flush() explicitly
563
+ * before returning the response, or wrap it in ctx.waitUntil().
564
+ */
565
+ batcher(options) {
566
+ const client = this;
567
+ let buffer = [];
568
+ let intervalId = null;
569
+ const flush = async () => {
570
+ if (intervalId) {
571
+ clearInterval(intervalId);
572
+ intervalId = null;
573
+ }
574
+ if (buffer.length === 0) {
575
+ if (options?.autoFlushInterval) {
576
+ startTimer();
577
+ }
578
+ return { accepted: 0, rejected: 0 };
579
+ }
580
+ const payloadsToFlush = [...buffer];
581
+ buffer = [];
582
+ if (options?.autoFlushInterval) {
583
+ startTimer();
584
+ }
585
+ return client.logBatch(payloadsToFlush);
586
+ };
587
+ const startTimer = () => {
588
+ if (options?.autoFlushInterval && !intervalId) {
589
+ intervalId = setInterval(() => {
590
+ flush().catch((err) => {
591
+ console.error(`[Volidator] Batcher auto-flush failed: ${err.message}`);
592
+ });
593
+ }, options.autoFlushInterval);
594
+ if (intervalId && typeof intervalId.unref === "function") {
595
+ intervalId.unref();
596
+ }
597
+ }
598
+ };
599
+ const push = (payload) => {
600
+ buffer.push(payload);
601
+ const maxCount = options?.autoFlushCount || 100;
602
+ if (buffer.length >= Math.min(maxCount, 100)) {
603
+ flush().catch((err) => {
604
+ console.error(`[Volidator] Batcher auto-flush on count failed: ${err.message}`);
605
+ });
606
+ }
607
+ };
608
+ const size = () => buffer.length;
609
+ startTimer();
610
+ return {
611
+ push,
612
+ flush,
613
+ size
614
+ };
615
+ }
616
+ // ---------------------------------------------------------------------------
445
617
  // generateEmbedToken() — Sign a HS256 JWT for the embeddable dashboard widget
446
618
  // ---------------------------------------------------------------------------
447
619
  async generateEmbedToken(config) {
448
- if (!this.projectId || !this.clientSecret) {
620
+ const projectId = config.projectId || this.projectId;
621
+ const clientSecret = config.clientSecret || this.clientSecret;
622
+ if (!projectId || !clientSecret) {
449
623
  throw new Error(
450
- "generateEmbedToken() requires projectId and clientSecret in the VolidatorClient constructor."
624
+ "generateEmbedToken() requires projectId and clientSecret to be provided in either the configuration or the VolidatorClient constructor."
451
625
  );
452
626
  }
453
627
  const {
@@ -474,7 +648,7 @@ var VolidatorClient = class _VolidatorClient {
474
648
  const expiresInSeconds = Math.min(parsedExpiry, maxExpiry);
475
649
  const now = Math.floor(Date.now() / 1e3);
476
650
  const payload = {
477
- pid: this.projectId,
651
+ pid: projectId,
478
652
  scope: defaultScope,
479
653
  iat: now,
480
654
  exp: now + expiresInSeconds
@@ -507,7 +681,7 @@ var VolidatorClient = class _VolidatorClient {
507
681
  }
508
682
  payload.view = compressed;
509
683
  }
510
- const token = await this.signHS256JWT(payload, this.clientSecret);
684
+ const token = await this.signHS256JWT(payload, clientSecret);
511
685
  const keyringString = Object.entries(this.keyring).map(([id, key]) => `${id}:${key}`).join(",");
512
686
  const hostParam = hostOrigin ? `?host=${encodeURIComponent(hostOrigin)}` : "";
513
687
  const embedUrl = `${dashboardUrl}/embed/${token}${hostParam}#${keyringString}`;
@@ -556,6 +730,13 @@ var VolidatorClient = class _VolidatorClient {
556
730
  }
557
731
  }
558
732
  };
733
+ /**
734
+ * Thread-local asynchronous context storage used to maintain
735
+ * and increment Lamport logical clock sequences across execution boundaries
736
+ * (e.g. within API requests or serverless handler contexts).
737
+ */
738
+ _VolidatorClient.logicalClockStore = new import_node_async_hooks.AsyncLocalStorage();
739
+ var VolidatorClient = _VolidatorClient;
559
740
  var VolidatorCompliance = class {
560
741
  constructor(client) {
561
742
  this.client = client;
@@ -685,6 +866,8 @@ var VolidatorAgent = class {
685
866
  function withVolidator(client, handler) {
686
867
  return async (req, ctx = {}, ...args) => {
687
868
  const extractedContext = VolidatorClient.extractContext(req);
869
+ const extractedTrace = VolidatorClient.extractTraceContext(req);
870
+ const incomingClock = extractedTrace.logicalClock || 0;
688
871
  const scopedLog = async (payload) => {
689
872
  return client.log({
690
873
  ...payload,
@@ -725,7 +908,9 @@ function withVolidator(client, handler) {
725
908
  compliance: scopedCompliance
726
909
  }
727
910
  };
728
- return handler(req, newCtx, ...args);
911
+ return VolidatorClient.logicalClockStore.run({ clock: incomingClock }, () => {
912
+ return handler(req, newCtx, ...args);
913
+ });
729
914
  };
730
915
  }
731
916
  // Annotate the CommonJS export names for ESM import in node: