@nullbridge/sdk 1.0.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 ADDED
@@ -0,0 +1,232 @@
1
+ # @nullbridge/sdk
2
+
3
+ **AI Agent Identity Governance for Node.js**
4
+
5
+ NullBridge gives your AI agents a cryptographic identity, secure credential vault, continuous audit trail, and behavioral anomaly detection — in three lines of code.
6
+
7
+ ---
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install @nullbridge/sdk
13
+ ```
14
+
15
+ ---
16
+
17
+ ## Quick Start
18
+
19
+ ```js
20
+ const { NullBridge } = require('@nullbridge/sdk');
21
+
22
+ const nb = new NullBridge({
23
+ licenseKey: process.env.NULLBRIDGE_LICENSE_KEY,
24
+ });
25
+
26
+ await nb.init();
27
+
28
+ const agent = await nb.agents.register({
29
+ name: 'claims-processor',
30
+ type: 'llm',
31
+ model: 'gpt-4o',
32
+ scopes: ['read:claims', 'write:reports'],
33
+ });
34
+ ```
35
+
36
+ That's it. NullBridge validates your license on startup, registers your agent with a cryptographic identity, and begins monitoring automatically.
37
+
38
+ ---
39
+
40
+ ## Configuration
41
+
42
+ ```js
43
+ const nb = new NullBridge({
44
+ licenseKey: process.env.NULLBRIDGE_LICENSE_KEY, // required
45
+ serverUrl: 'https://nullbridge-license-server-production.up.railway.app', // default
46
+ apiUrl: 'https://api.nullbridge.ai', // default
47
+ skipLicense: false, // set true for local development only
48
+ autoShutdown: true, // shut down process if license is revoked
49
+ checkInterval: 86400000, // license check interval in ms (default: 24 hours)
50
+ debug: false, // enable verbose logging
51
+ siem: {
52
+ endpoint: 'https://your-siem.com/ingest', // optional
53
+ format: 'json', // 'json' | 'cef' | 'leef'
54
+ },
55
+ });
56
+ ```
57
+
58
+ ---
59
+
60
+ ## Environment Variables
61
+
62
+ ```bash
63
+ NULLBRIDGE_LICENSE_KEY=NB-XXXX-XXXX-XXXX-XXXX
64
+ ```
65
+
66
+ Contact brian@nullbridge.ai to obtain a license key.
67
+
68
+ ---
69
+
70
+ ## API Reference
71
+
72
+ ### `nb.init()`
73
+ Initialize NullBridge. Call once on application startup before serving traffic.
74
+ Validates license and starts background services.
75
+
76
+ ```js
77
+ await nb.init();
78
+ ```
79
+
80
+ ---
81
+
82
+ ### `nb.agents`
83
+
84
+ #### `nb.agents.register(options)`
85
+ Register an AI agent with NullBridge.
86
+
87
+ ```js
88
+ const agent = await nb.agents.register({
89
+ name: 'fraud-detector', // required — human readable name
90
+ type: 'llm', // required — 'llm' | 'rpa' | 'workflow' | 'custom'
91
+ model: 'claude-3-5-sonnet', // optional
92
+ scopes: ['read:transactions', 'write:alerts'], // optional
93
+ metadata: { team: 'risk' }, // optional
94
+ });
95
+ ```
96
+
97
+ #### `agent.logAction(action, details)`
98
+ Log an action this agent performed.
99
+
100
+ ```js
101
+ await agent.logAction('analyze_transaction', { txId: 'TX-1234', amount: 5000 });
102
+ ```
103
+
104
+ #### `agent.hasScope(scope)`
105
+ Check if agent has a specific permission.
106
+
107
+ ```js
108
+ if (!agent.hasScope('write:alerts')) throw new Error('Not authorized');
109
+ ```
110
+
111
+ #### `agent.deregister()`
112
+ Deregister agent and revoke its identity.
113
+
114
+ ---
115
+
116
+ ### `nb.credentials`
117
+
118
+ #### `nb.credentials.store(options)`
119
+ Store a credential securely (AES-256-GCM encrypted).
120
+
121
+ ```js
122
+ const credId = await nb.credentials.store({
123
+ agentId: agent.id,
124
+ name: 'openai-api-key',
125
+ value: process.env.OPENAI_API_KEY,
126
+ type: 'api_key', // 'api_key' | 'oauth_token' | 'password' | 'certificate'
127
+ ttl: 86400, // optional: expire after 24 hours
128
+ });
129
+ ```
130
+
131
+ #### `nb.credentials.get(credId)`
132
+ Retrieve a credential value.
133
+
134
+ ```js
135
+ const apiKey = await nb.credentials.get(credId);
136
+ ```
137
+
138
+ #### `nb.credentials.rotate(credId, newValue)`
139
+ Rotate a credential and log the rotation in audit trail.
140
+
141
+ ```js
142
+ await nb.credentials.rotate(credId, newApiKey);
143
+ ```
144
+
145
+ #### `nb.credentials.revoke(credId)`
146
+ Permanently revoke a credential.
147
+
148
+ ---
149
+
150
+ ### `nb.audit`
151
+
152
+ #### `nb.audit.log(event)`
153
+ Log an agent action to the audit trail.
154
+
155
+ ```js
156
+ nb.audit.log({
157
+ agentId: agent.id,
158
+ agentName: agent.name,
159
+ action: 'process_claim', // required
160
+ resource: 'claim:CLM-20240001', // optional
161
+ outcome: 'success', // 'success' | 'failure' | 'blocked'
162
+ details: { amount: 15000 }, // optional
163
+ ip: req.ip, // optional
164
+ duration: 1240, // optional: ms
165
+ });
166
+ ```
167
+
168
+ #### `nb.audit.logViolation(agentId, agentName, action, reason)`
169
+ Log a policy violation (outcome is automatically set to 'blocked').
170
+
171
+ ```js
172
+ nb.audit.logViolation(agent.id, agent.name, 'delete_record', 'scope_denied');
173
+ ```
174
+
175
+ ---
176
+
177
+ ### `nb.anomaly`
178
+
179
+ #### `nb.anomaly.record(agentId, metric, value)`
180
+ Record a behavioral metric. NullBridge builds a statistical baseline and alerts on deviations.
181
+
182
+ ```js
183
+ nb.anomaly.record(agent.id, 'api_calls_per_minute', 14);
184
+ nb.anomaly.record(agent.id, 'tokens_used', 3200);
185
+ nb.anomaly.record(agent.id, 'unique_endpoints', 3);
186
+ ```
187
+
188
+ #### `nb.anomaly.onAlert(handler)`
189
+ Register a callback for anomaly alerts.
190
+
191
+ ```js
192
+ nb.anomaly.onAlert(alert => {
193
+ console.error(`Anomaly: ${alert.agentId} — ${alert.metric} (z=${alert.zScore})`);
194
+ // Send to PagerDuty, Slack, etc.
195
+ });
196
+ ```
197
+
198
+ #### `nb.anomaly.check(agentId, metric, value)`
199
+ Check if a value is anomalous without recording it.
200
+
201
+ ```js
202
+ const { anomalous, zScore } = nb.anomaly.check(agent.id, 'api_calls', 500);
203
+ if (anomalous) console.warn('Unusual API call volume');
204
+ ```
205
+
206
+ ---
207
+
208
+ ### `nb.shutdown()`
209
+ Gracefully shut down — flushes audit logs and stops background services.
210
+
211
+ ```js
212
+ process.on('SIGTERM', async () => {
213
+ await nb.shutdown();
214
+ process.exit(0);
215
+ });
216
+ ```
217
+
218
+ ---
219
+
220
+ ## License
221
+
222
+ This software is proprietary and confidential. Use requires a valid NullBridge license key.
223
+ Contact brian@nullbridge.ai for licensing.
224
+
225
+ **NullBridge Technologies**
226
+ brian@nullbridge.ai | nullbridge.ai
227
+
228
+ ---
229
+
230
+ CONFIDENTIALITY & IP NOTICE: This software and documentation contain proprietary and confidential
231
+ information belonging to NullBridge Technologies. Protected under applicable intellectual property
232
+ laws. Unauthorized use, disclosure, or distribution is strictly prohibited.
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@nullbridge/sdk",
3
+ "version": "1.0.0",
4
+ "description": "NullBridge AI Agent Identity Governance SDK",
5
+ "main": "src/index.js",
6
+ "types": "src/index.d.ts",
7
+ "license": "UNLICENSED",
8
+ "author": "NullBridge Technologies <brian@nullbridge.ai>",
9
+ "homepage": "https://nullbridge.ai",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/bwes03-afk/nullbridge-sdk"
13
+ },
14
+ "keywords": [
15
+ "ai", "agents", "identity", "governance", "iam",
16
+ "security", "audit", "nullbridge", "license"
17
+ ],
18
+ "engines": { "node": ">=16.0.0" },
19
+ "files": ["src/", "README.md", "LICENSE"],
20
+ "scripts": {
21
+ "test": "node examples/basic.js",
22
+ "prepublishOnly": "node -e \"console.log('Publishing @nullbridge/sdk...')\""
23
+ },
24
+ "dependencies": {}
25
+ }
package/src/agents.js ADDED
@@ -0,0 +1,192 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('crypto');
4
+
5
+ /**
6
+ * AgentRegistry — register, manage, and monitor AI agents
7
+ */
8
+ class AgentRegistry {
9
+ constructor(config, http) {
10
+ this._config = config;
11
+ this._http = http;
12
+ this._agents = new Map(); // local cache
13
+ }
14
+
15
+ /**
16
+ * Register a new AI agent with NullBridge.
17
+ *
18
+ * @param {object} options
19
+ * @param {string} options.name - Human-readable agent name (e.g. 'claims-processor')
20
+ * @param {string} options.type - Agent type: 'llm' | 'rpa' | 'workflow' | 'custom'
21
+ * @param {string} [options.model] - Model name (e.g. 'gpt-4o', 'claude-3-5-sonnet')
22
+ * @param {string[]} [options.scopes] - What this agent is allowed to do (e.g. ['read:claims', 'write:reports'])
23
+ * @param {object} [options.metadata] - Any additional metadata
24
+ * @returns {Promise<Agent>}
25
+ */
26
+ async register(options = {}) {
27
+ if (!options.name) throw new Error('[NullBridge] agent.name is required');
28
+ if (!options.type) throw new Error('[NullBridge] agent.type is required');
29
+
30
+ const agentId = this._generateAgentId(options.name);
31
+
32
+ const payload = {
33
+ id: agentId,
34
+ name: options.name,
35
+ type: options.type,
36
+ model: options.model || null,
37
+ scopes: options.scopes || [],
38
+ metadata: options.metadata || {},
39
+ sdk_version: '1.0.0',
40
+ registered_at: Math.floor(Date.now() / 1000),
41
+ };
42
+
43
+ try {
44
+ const { status, body } = await this._http.post(
45
+ this._config.apiUrl,
46
+ '/sdk/agents/register',
47
+ { license_key: this._config.licenseKey, agent: payload }
48
+ );
49
+
50
+ const agent = status === 200 || status === 201
51
+ ? { ...payload, ...body.agent, _registered: true }
52
+ : { ...payload, _registered: false, _local: true };
53
+
54
+ this._agents.set(agentId, agent);
55
+ console.info(`[NullBridge] Agent registered: ${options.name} (${agentId})`);
56
+ return new Agent(agent, this._config, this._http);
57
+
58
+ } catch (err) {
59
+ // API unreachable — register locally and continue
60
+ const agent = { ...payload, _registered: false, _local: true };
61
+ this._agents.set(agentId, agent);
62
+ console.warn(`[NullBridge] Agent registered locally (API unreachable): ${options.name}`);
63
+ return new Agent(agent, this._config, this._http);
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Get all registered agents
69
+ */
70
+ async list() {
71
+ try {
72
+ const { status, body } = await this._http.get(
73
+ this._config.apiUrl,
74
+ `/sdk/agents?license_key=${encodeURIComponent(this._config.licenseKey)}`
75
+ );
76
+ return status === 200 ? body.agents : Array.from(this._agents.values());
77
+ } catch {
78
+ return Array.from(this._agents.values());
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Get a registered agent by ID
84
+ */
85
+ get(agentId) {
86
+ const agent = this._agents.get(agentId);
87
+ if (!agent) return null;
88
+ return new Agent(agent, this._config, this._http);
89
+ }
90
+
91
+ _generateAgentId(name) {
92
+ const slug = name.toLowerCase().replace(/[^a-z0-9]/g, '-');
93
+ const hash = crypto.createHash('sha256')
94
+ .update(this._config.licenseKey + name + Date.now())
95
+ .digest('hex')
96
+ .slice(0, 8);
97
+ return `agent-${slug}-${hash}`;
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Agent — represents a single registered AI agent
103
+ */
104
+ class Agent {
105
+ constructor(data, config, http) {
106
+ this._data = data;
107
+ this._config = config;
108
+ this._http = http;
109
+
110
+ // Expose agent properties
111
+ this.id = data.id;
112
+ this.name = data.name;
113
+ this.type = data.type;
114
+ this.model = data.model;
115
+ this.scopes = data.scopes || [];
116
+ this.metadata = data.metadata || {};
117
+ }
118
+
119
+ /**
120
+ * Log an action this agent performed (creates an audit trail entry)
121
+ *
122
+ * @param {string} action - What the agent did (e.g. 'read_claim', 'send_email')
123
+ * @param {object} [details] - Additional context
124
+ */
125
+ async logAction(action, details = {}) {
126
+ try {
127
+ await this._http.post(
128
+ this._config.apiUrl,
129
+ '/sdk/audit',
130
+ {
131
+ license_key: this._config.licenseKey,
132
+ agent_id: this.id,
133
+ agent_name: this.name,
134
+ action,
135
+ details,
136
+ timestamp: Math.floor(Date.now() / 1000),
137
+ }
138
+ );
139
+ } catch {
140
+ // Never throw from audit logging — don't break agent operation
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Check if this agent has a specific scope/permission
146
+ *
147
+ * @param {string} scope - e.g. 'write:claims'
148
+ * @returns {boolean}
149
+ */
150
+ hasScope(scope) {
151
+ return this.scopes.includes(scope) || this.scopes.includes('*');
152
+ }
153
+
154
+ /**
155
+ * Update agent metadata or status
156
+ */
157
+ async update(updates = {}) {
158
+ try {
159
+ await this._http.post(
160
+ this._config.apiUrl,
161
+ `/sdk/agents/${this.id}/update`,
162
+ { license_key: this._config.licenseKey, ...updates }
163
+ );
164
+ Object.assign(this._data, updates);
165
+ } catch {}
166
+ }
167
+
168
+ /**
169
+ * Deregister this agent — revokes its identity and credentials
170
+ */
171
+ async deregister() {
172
+ try {
173
+ await this._http.post(
174
+ this._config.apiUrl,
175
+ `/sdk/agents/${this.id}/deregister`,
176
+ { license_key: this._config.licenseKey }
177
+ );
178
+ console.info(`[NullBridge] Agent deregistered: ${this.name}`);
179
+ } catch (err) {
180
+ console.warn(`[NullBridge] Could not deregister agent remotely: ${err.message}`);
181
+ }
182
+ }
183
+
184
+ toJSON() {
185
+ return {
186
+ id: this.id, name: this.name, type: this.type,
187
+ model: this.model, scopes: this.scopes, metadata: this.metadata,
188
+ };
189
+ }
190
+ }
191
+
192
+ module.exports = { AgentRegistry, Agent };
package/src/anomaly.js ADDED
@@ -0,0 +1,148 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * AnomalyDetector — behavioral baseline monitoring for AI agents
5
+ * Detects unusual patterns and alerts when agents deviate from normal behavior
6
+ */
7
+ class AnomalyDetector {
8
+ constructor(config, http) {
9
+ this._config = config;
10
+ this._http = http;
11
+ this._baselines = new Map(); // agentId -> baseline metrics
12
+ this._handlers = []; // alert handlers
13
+ }
14
+
15
+ /**
16
+ * Record a metric for an agent — used to build behavioral baseline
17
+ *
18
+ * @param {string} agentId
19
+ * @param {string} metric - e.g. 'api_calls_per_minute', 'tokens_used', 'unique_endpoints'
20
+ * @param {number} value
21
+ */
22
+ record(agentId, metric, value) {
23
+ if (!this._baselines.has(agentId)) {
24
+ this._baselines.set(agentId, {});
25
+ }
26
+
27
+ const baseline = this._baselines.get(agentId);
28
+ if (!baseline[metric]) {
29
+ baseline[metric] = { samples: [], mean: 0, stddev: 0, count: 0 };
30
+ }
31
+
32
+ const m = baseline[metric];
33
+ m.samples.push({ value, ts: Date.now() });
34
+
35
+ // Keep last 100 samples
36
+ if (m.samples.length > 100) m.samples.shift();
37
+
38
+ // Recalculate mean and stddev
39
+ const values = m.samples.map(s => s.value);
40
+ m.count = values.length;
41
+ m.mean = values.reduce((a, b) => a + b, 0) / m.count;
42
+ m.stddev = Math.sqrt(values.map(v => Math.pow(v - m.mean, 2)).reduce((a, b) => a + b, 0) / m.count);
43
+
44
+ // Check for anomaly (> 3 standard deviations from mean)
45
+ if (m.count >= 10 && m.stddev > 0) {
46
+ const zScore = Math.abs((value - m.mean) / m.stddev);
47
+ if (zScore > 3) {
48
+ this._triggerAlert(agentId, metric, value, m.mean, zScore);
49
+ }
50
+ }
51
+ }
52
+
53
+ /**
54
+ * Check if a specific value is anomalous for an agent/metric
55
+ *
56
+ * @param {string} agentId
57
+ * @param {string} metric
58
+ * @param {number} value
59
+ * @returns {{ anomalous: boolean, zScore: number, mean: number }}
60
+ */
61
+ check(agentId, metric, value) {
62
+ const baseline = this._baselines.get(agentId);
63
+ if (!baseline || !baseline[metric] || baseline[metric].count < 10) {
64
+ return { anomalous: false, zScore: 0, mean: value, reason: 'insufficient_data' };
65
+ }
66
+
67
+ const m = baseline[metric];
68
+ const zScore = m.stddev > 0 ? Math.abs((value - m.mean) / m.stddev) : 0;
69
+
70
+ return {
71
+ anomalous: zScore > 3,
72
+ zScore: Math.round(zScore * 100) / 100,
73
+ mean: Math.round(m.mean * 100) / 100,
74
+ stddev: Math.round(m.stddev * 100) / 100,
75
+ samples: m.count,
76
+ };
77
+ }
78
+
79
+ /**
80
+ * Register an alert handler — called when anomaly is detected
81
+ *
82
+ * @param {function} handler - function(alert) where alert = { agentId, metric, value, mean, zScore }
83
+ */
84
+ onAlert(handler) {
85
+ this._handlers.push(handler);
86
+ return this; // chainable
87
+ }
88
+
89
+ /**
90
+ * Get baseline metrics for an agent
91
+ */
92
+ getBaseline(agentId) {
93
+ const baseline = this._baselines.get(agentId);
94
+ if (!baseline) return null;
95
+
96
+ const result = {};
97
+ for (const [metric, data] of Object.entries(baseline)) {
98
+ result[metric] = {
99
+ mean: Math.round(data.mean * 100) / 100,
100
+ stddev: Math.round(data.stddev * 100) / 100,
101
+ samples: data.count,
102
+ };
103
+ }
104
+ return result;
105
+ }
106
+
107
+ /**
108
+ * Reset baseline for an agent (e.g. after a known behavior change)
109
+ */
110
+ resetBaseline(agentId, metric = null) {
111
+ if (metric) {
112
+ const baseline = this._baselines.get(agentId);
113
+ if (baseline) delete baseline[metric];
114
+ } else {
115
+ this._baselines.delete(agentId);
116
+ }
117
+ }
118
+
119
+ async _triggerAlert(agentId, metric, value, mean, zScore) {
120
+ const alert = {
121
+ agentId,
122
+ metric,
123
+ value,
124
+ mean: Math.round(mean * 100) / 100,
125
+ zScore: Math.round(zScore * 100) / 100,
126
+ severity: zScore > 6 ? 'critical' : zScore > 4 ? 'high' : 'medium',
127
+ timestamp: Math.floor(Date.now() / 1000),
128
+ };
129
+
130
+ console.warn(`[NullBridge] Anomaly detected — agent: ${agentId}, metric: ${metric}, value: ${value}, mean: ${mean.toFixed(2)}, z-score: ${zScore.toFixed(2)}`);
131
+
132
+ // Call registered handlers
133
+ for (const handler of this._handlers) {
134
+ try { handler(alert); } catch {}
135
+ }
136
+
137
+ // Send to NullBridge API
138
+ try {
139
+ await this._http.post(
140
+ this._config.apiUrl,
141
+ '/sdk/anomalies',
142
+ { license_key: this._config.licenseKey, alert }
143
+ );
144
+ } catch {}
145
+ }
146
+ }
147
+
148
+ module.exports = { AnomalyDetector };
package/src/audit.js ADDED
@@ -0,0 +1,112 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * AuditLogger — tamper-evident audit trail for all AI agent actions
5
+ */
6
+ class AuditLogger {
7
+ constructor(config, http) {
8
+ this._config = config;
9
+ this._http = http;
10
+ this._queue = [];
11
+ this._flushing = false;
12
+
13
+ // Flush queue every 30 seconds
14
+ this._flushTimer = setInterval(() => this.flush(), 30 * 1000);
15
+ if (this._flushTimer.unref) this._flushTimer.unref();
16
+ }
17
+
18
+ /**
19
+ * Log an audit event
20
+ *
21
+ * @param {object} event
22
+ * @param {string} event.agentId - Agent that performed the action
23
+ * @param {string} event.agentName - Agent name
24
+ * @param {string} event.action - Action performed (e.g. 'read_claim', 'send_email', 'call_api')
25
+ * @param {string} [event.resource] - Resource acted on (e.g. 'claim:CLM-1234')
26
+ * @param {string} [event.outcome] - 'success' | 'failure' | 'blocked'
27
+ * @param {object} [event.details] - Additional context
28
+ * @param {string} [event.ip] - IP address if applicable
29
+ * @param {number} [event.duration] - Duration in ms
30
+ */
31
+ log(event = {}) {
32
+ if (!event.agentId) throw new Error('[NullBridge] audit.log: agentId is required');
33
+ if (!event.action) throw new Error('[NullBridge] audit.log: action is required');
34
+
35
+ const entry = {
36
+ id: this._generateEventId(),
37
+ license_key: this._config.licenseKey,
38
+ agent_id: event.agentId,
39
+ agent_name: event.agentName || 'unknown',
40
+ action: event.action,
41
+ resource: event.resource || null,
42
+ outcome: event.outcome || 'success',
43
+ details: event.details || {},
44
+ ip: event.ip || null,
45
+ duration_ms: event.duration || null,
46
+ timestamp: Math.floor(Date.now() / 1000),
47
+ };
48
+
49
+ this._queue.push(entry);
50
+
51
+ // Flush immediately for critical events
52
+ if (event.outcome === 'failure' || event.outcome === 'blocked') {
53
+ this.flush();
54
+ }
55
+
56
+ return entry.id;
57
+ }
58
+
59
+ /**
60
+ * Log a policy violation — automatically sets outcome to 'blocked'
61
+ */
62
+ logViolation(agentId, agentName, action, reason, details = {}) {
63
+ return this.log({
64
+ agentId, agentName, action,
65
+ outcome: 'blocked',
66
+ details: { reason, ...details },
67
+ });
68
+ }
69
+
70
+ /**
71
+ * Flush queued events to NullBridge API
72
+ */
73
+ async flush() {
74
+ if (this._flushing || this._queue.length === 0) return;
75
+ this._flushing = true;
76
+
77
+ const batch = this._queue.splice(0, 100); // max 100 per flush
78
+
79
+ try {
80
+ await this._http.post(
81
+ this._config.apiUrl,
82
+ '/sdk/audit/batch',
83
+ { events: batch }
84
+ );
85
+ } catch {
86
+ // Put events back in queue if flush fails
87
+ this._queue.unshift(...batch);
88
+ } finally {
89
+ this._flushing = false;
90
+ }
91
+ }
92
+
93
+ /**
94
+ * Get queued (unflushed) events
95
+ */
96
+ getQueue() {
97
+ return [...this._queue];
98
+ }
99
+
100
+ /**
101
+ * Stop the flush timer
102
+ */
103
+ stop() {
104
+ if (this._flushTimer) clearInterval(this._flushTimer);
105
+ }
106
+
107
+ _generateEventId() {
108
+ return 'evt-' + Date.now().toString(36) + '-' + Math.random().toString(36).slice(2, 8);
109
+ }
110
+ }
111
+
112
+ module.exports = { AuditLogger };