@unrdf/knowledge-engine 5.0.1

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 (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +84 -0
  3. package/package.json +64 -0
  4. package/src/browser-shims.mjs +343 -0
  5. package/src/browser.mjs +910 -0
  6. package/src/canonicalize.mjs +414 -0
  7. package/src/condition-cache.mjs +109 -0
  8. package/src/condition-evaluator.mjs +722 -0
  9. package/src/dark-matter-core.mjs +742 -0
  10. package/src/define-hook.mjs +213 -0
  11. package/src/effect-sandbox-browser.mjs +283 -0
  12. package/src/effect-sandbox-worker.mjs +170 -0
  13. package/src/effect-sandbox.mjs +517 -0
  14. package/src/engines/index.mjs +11 -0
  15. package/src/engines/rdf-engine.mjs +299 -0
  16. package/src/file-resolver.mjs +387 -0
  17. package/src/hook-executor-batching.mjs +277 -0
  18. package/src/hook-executor.mjs +870 -0
  19. package/src/hook-management.mjs +150 -0
  20. package/src/index.mjs +93 -0
  21. package/src/ken-parliment.mjs +119 -0
  22. package/src/ken.mjs +149 -0
  23. package/src/knowledge-engine/builtin-rules.mjs +190 -0
  24. package/src/knowledge-engine/inference-engine.mjs +418 -0
  25. package/src/knowledge-engine/knowledge-engine.mjs +317 -0
  26. package/src/knowledge-engine/pattern-dsl.mjs +142 -0
  27. package/src/knowledge-engine/pattern-matcher.mjs +215 -0
  28. package/src/knowledge-engine/rules.mjs +184 -0
  29. package/src/knowledge-engine.mjs +319 -0
  30. package/src/knowledge-hook-engine.mjs +360 -0
  31. package/src/knowledge-hook-manager.mjs +469 -0
  32. package/src/knowledge-substrate-core.mjs +927 -0
  33. package/src/lite.mjs +222 -0
  34. package/src/lockchain-writer-browser.mjs +414 -0
  35. package/src/lockchain-writer.mjs +602 -0
  36. package/src/monitoring/andon-signals.mjs +775 -0
  37. package/src/observability.mjs +531 -0
  38. package/src/parse.mjs +290 -0
  39. package/src/performance-optimizer.mjs +678 -0
  40. package/src/policy-pack.mjs +572 -0
  41. package/src/query-cache.mjs +116 -0
  42. package/src/query-optimizer.mjs +1051 -0
  43. package/src/query.mjs +306 -0
  44. package/src/reason.mjs +350 -0
  45. package/src/resolution-layer.mjs +506 -0
  46. package/src/schemas.mjs +1063 -0
  47. package/src/security/error-sanitizer.mjs +257 -0
  48. package/src/security/path-validator.mjs +194 -0
  49. package/src/security/sandbox-restrictions.mjs +331 -0
  50. package/src/security-validator.mjs +389 -0
  51. package/src/store-cache.mjs +137 -0
  52. package/src/telemetry.mjs +167 -0
  53. package/src/transaction.mjs +810 -0
  54. package/src/utils/adaptive-monitor.mjs +746 -0
  55. package/src/utils/circuit-breaker.mjs +513 -0
  56. package/src/utils/edge-case-handler.mjs +503 -0
  57. package/src/utils/memory-manager.mjs +498 -0
  58. package/src/utils/ring-buffer.mjs +282 -0
  59. package/src/validate.mjs +319 -0
  60. package/src/validators/index.mjs +338 -0
package/src/lite.mjs ADDED
@@ -0,0 +1,222 @@
1
+ /**
2
+ * @file knowledge-engine/lite.mjs
3
+ * @description Minimal RDF functionality entry point for bundle size optimization
4
+ *
5
+ * This lite bundle exports ONLY the core RDF primitives from n3:
6
+ * - Store: In-memory quad storage
7
+ * - Parser: Turtle/N-Triples/N-Quads parsing
8
+ * - Writer: RDF serialization
9
+ * - DataFactory: RDF term creation
10
+ *
11
+ * EXCLUDES (for 60%+ bundle reduction):
12
+ * - @comunica/query-sparql (~2.5MB)
13
+ * - isolated-vm (~5MB native bindings)
14
+ * - testcontainers (dev-only)
15
+ * - eyereasoner
16
+ * - rdf-validate-shacl
17
+ * - All hook/policy infrastructure
18
+ *
19
+ * @example
20
+ * // Minimal import for basic RDF operations
21
+ * import { Store, Parser, Writer, DataFactory, parseTurtle, toTurtle } from 'unrdf/knowledge-engine/lite';
22
+ *
23
+ * const store = createStore();
24
+ * const { namedNode, literal, quad } = DataFactory;
25
+ *
26
+ * store.addQuad(quad(
27
+ * namedNode('http://example.org/alice'),
28
+ * namedNode('http://xmlns.com/foaf/0.1/name'),
29
+ * literal('Alice')
30
+ * ));
31
+ *
32
+ * @module knowledge-engine/lite
33
+ * @see {@link https://github.com/rdfjs/N3.js} for N3.js documentation
34
+ */
35
+
36
+ /**
37
+ * JUSTIFIED N3 USAGE (μ(O) Compliance):
38
+ * This lite.mjs module is a justified boundary for N3 exports because:
39
+ * 1. It provides streaming parsing with backpressure control (N3.Parser)
40
+ * 2. It's explicitly designed for lightweight RDF operations without Oxigraph
41
+ * 3. Users opting into lite.mjs accept N3-based implementation
42
+ *
43
+ * For full Oxigraph-based functionality, use the main knowledge-engine exports.
44
+ */
45
+
46
+ // Core N3 exports - the essential RDF primitives
47
+ export { Parser, Writer, UnrdfDataFactory as DataFactory } from '@unrdf/core/rdf/n3-justified-only';
48
+ import { createStore as _createStore } from '@unrdf/oxigraph'; // Oxigraph Store implementation
49
+ export { _createStore as createStore };
50
+
51
+ /**
52
+ * Parse a Turtle string into a Store (lite version - no OTEL tracing)
53
+ * @param {string} ttl - The Turtle string to parse
54
+ * @param {string} [baseIRI='http://example.org/'] - Base IRI for resolving relative URIs
55
+ * @returns {Promise<import('n3').Store>} Promise resolving to a Store containing the parsed quads
56
+ * @throws {TypeError} If ttl is not a string
57
+ * @throws {Error} If parsing fails
58
+ *
59
+ * @example
60
+ * const ttl = `
61
+ * @prefix ex: <http://example.org/> .
62
+ * ex:alice ex:knows ex:bob .
63
+ * `;
64
+ * const store = await parseTurtle(ttl);
65
+ */
66
+ export async function parseTurtle(ttl, baseIRI = 'http://example.org/') {
67
+ if (typeof ttl !== 'string') {
68
+ throw new TypeError('parseTurtle: ttl must be a string');
69
+ }
70
+ if (typeof baseIRI !== 'string') {
71
+ throw new TypeError('parseTurtle: baseIRI must be a string');
72
+ }
73
+
74
+ const { Parser } = await import('n3');
75
+ const parser = new Parser({ baseIRI });
76
+ const quads = parser.parse(ttl);
77
+ return _createStore(quads);
78
+ }
79
+
80
+ /**
81
+ * Serialize a Store to Turtle format (lite version - no OTEL tracing)
82
+ * @param {import('n3').Store} store - The store to serialize
83
+ * @param {Object} [options={}] - Serialization options
84
+ * @param {Object} [options.prefixes] - Prefix mappings for compact output
85
+ * @param {string} [options.baseIRI] - Base IRI for the output
86
+ * @returns {Promise<string>} Promise resolving to the Turtle string
87
+ * @throws {TypeError} If store is not a valid Store instance
88
+ * @throws {Error} If serialization fails
89
+ *
90
+ * @example
91
+ * const turtle = await toTurtle(store, {
92
+ * prefixes: { ex: 'http://example.org/' }
93
+ * });
94
+ */
95
+ export async function toTurtle(store, options = {}) {
96
+ if (!store || typeof store.getQuads !== 'function') {
97
+ throw new TypeError('toTurtle: store must be a valid Store instance');
98
+ }
99
+
100
+ const { Writer } = await import('n3');
101
+ const writer = new Writer({
102
+ format: 'Turtle',
103
+ prefixes: options.prefixes || {},
104
+ });
105
+
106
+ const quads = store.getQuads();
107
+ writer.addQuads(quads);
108
+
109
+ return new Promise((resolve, reject) => {
110
+ writer.end((error, result) => {
111
+ if (error) {
112
+ reject(new Error(`Failed to serialize to Turtle: ${error.message}`));
113
+ } else {
114
+ if (options.baseIRI) {
115
+ result = `@base <${options.baseIRI}> .\n\n${result}`;
116
+ }
117
+ resolve(result);
118
+ }
119
+ });
120
+ });
121
+ }
122
+
123
+ /**
124
+ * Serialize a Store to N-Quads format (lite version - no OTEL tracing)
125
+ * @param {import('n3').Store} store - The store to serialize
126
+ * @param {Object} [options={}] - Serialization options
127
+ * @returns {Promise<string>} Promise resolving to the N-Quads string
128
+ * @throws {TypeError} If store is not a valid Store instance
129
+ * @throws {Error} If serialization fails
130
+ *
131
+ * @example
132
+ * const nquads = await toNQuads(store);
133
+ */
134
+ export async function toNQuads(store, options = {}) {
135
+ if (!store || typeof store.getQuads !== 'function') {
136
+ throw new TypeError('toNQuads: store must be a valid Store instance');
137
+ }
138
+
139
+ const { Writer } = await import('n3');
140
+ const writer = new Writer({
141
+ format: 'N-Quads',
142
+ ...options,
143
+ });
144
+
145
+ const quads = store.getQuads();
146
+ writer.addQuads(quads);
147
+
148
+ return new Promise((resolve, reject) => {
149
+ writer.end((error, result) => {
150
+ if (error) {
151
+ reject(new Error(`Failed to serialize to N-Quads: ${error.message}`));
152
+ } else {
153
+ resolve(result);
154
+ }
155
+ });
156
+ });
157
+ }
158
+
159
+ /**
160
+ * Create a quad using DataFactory (convenience function)
161
+ * @param {string} subject - Subject IRI
162
+ * @param {string} predicate - Predicate IRI
163
+ * @param {string|{value: string, language?: string, datatype?: string}} object - Object value
164
+ * @param {string} [graph] - Optional graph IRI
165
+ * @returns {import('n3').Quad} The created quad
166
+ *
167
+ * @example
168
+ * const q = createQuad(
169
+ * 'http://example.org/alice',
170
+ * 'http://xmlns.com/foaf/0.1/name',
171
+ * { value: 'Alice', language: 'en' }
172
+ * );
173
+ */
174
+ export function createQuad(subject, predicate, object, graph) {
175
+ const { DataFactory } = require('n3');
176
+ const { namedNode, literal, quad, defaultGraph } = DataFactory;
177
+
178
+ const subjectNode = namedNode(subject);
179
+ const predicateNode = namedNode(predicate);
180
+
181
+ let objectNode;
182
+ if (typeof object === 'string') {
183
+ // Check if it looks like a URI
184
+ if (
185
+ object.startsWith('http://') ||
186
+ object.startsWith('https://') ||
187
+ object.startsWith('urn:')
188
+ ) {
189
+ objectNode = namedNode(object);
190
+ } else {
191
+ objectNode = literal(object);
192
+ }
193
+ } else if (typeof object === 'object') {
194
+ if (object.language) {
195
+ objectNode = literal(object.value, object.language);
196
+ } else if (object.datatype) {
197
+ objectNode = literal(object.value, namedNode(object.datatype));
198
+ } else {
199
+ objectNode = literal(object.value);
200
+ }
201
+ }
202
+
203
+ const graphNode = graph ? namedNode(graph) : defaultGraph();
204
+ return quad(subjectNode, predicateNode, objectNode, graphNode);
205
+ }
206
+
207
+ /**
208
+ * Bundle size comparison (approximate):
209
+ *
210
+ * Full knowledge-engine: ~8-10MB
211
+ * - @comunica/query-sparql: ~2.5MB
212
+ * - isolated-vm: ~5MB (native)
213
+ * - eyereasoner: ~500KB
214
+ * - rdf-validate-shacl: ~200KB
215
+ * - n3: ~150KB
216
+ * - hooks/policy: ~100KB
217
+ *
218
+ * Lite knowledge-engine: ~150KB
219
+ * - n3 only: ~150KB
220
+ *
221
+ * Savings: ~98% for basic RDF operations
222
+ */
@@ -0,0 +1,414 @@
1
+ /**
2
+ * @file Browser-compatible Lockchain Writer
3
+ * @module lockchain-writer-browser
4
+ *
5
+ * @description
6
+ * Browser-compatible version of the lockchain writer that stores entries in memory
7
+ * or browser storage instead of Git commits. Provides cryptographic integrity
8
+ * verification without Git dependencies.
9
+ */
10
+
11
+ import { randomUUID, createHash, _fs } from './browser-shims.mjs';
12
+ import { sha3_256 } from '@noble/hashes/sha3.js';
13
+ import { _blake3 } from '@noble/hashes/blake3.js';
14
+ import { utf8ToBytes, bytesToHex } from '@noble/hashes/utils.js';
15
+ import { z } from 'zod';
16
+
17
+ /**
18
+ * Schema for lockchain entry
19
+ */
20
+ const LockchainEntrySchema = z.object({
21
+ id: z.string().uuid(),
22
+ timestamp: z.number(),
23
+ receipt: z.any(), // Transaction receipt
24
+ signature: z.object({
25
+ algorithm: z.string(),
26
+ value: z.string(),
27
+ publicKey: z.string().optional(),
28
+ }),
29
+ previousHash: z.string().optional().nullable(),
30
+ merkleRoot: z.string().optional(),
31
+ storageKey: z.string().optional(), // Browser storage key instead of git commit
32
+ storageRef: z.string().optional(),
33
+ });
34
+
35
+ /**
36
+ * Schema for lockchain configuration
37
+ */
38
+ const LockchainConfigSchema = z.object({
39
+ storageType: z.enum(['memory', 'localStorage', 'sessionStorage', 'indexedDB']).default('memory'),
40
+ algorithm: z.enum(['sha256', 'sha3-256', 'blake3']).default('sha3-256'),
41
+ batchSize: z.number().int().positive().default(10),
42
+ enableMerkle: z.boolean().default(true),
43
+ enablePersistence: z.boolean().default(true),
44
+ storagePrefix: z.string().default('lockchain'),
45
+ maxAgeMs: z.number().int().positive().optional(), // Time to live
46
+ });
47
+
48
+ /**
49
+ * Browser-compatible Lockchain Writer
50
+ */
51
+ export class BrowserLockchainWriter {
52
+ /**
53
+ *
54
+ */
55
+ constructor(config = {}) {
56
+ const validatedConfig = LockchainConfigSchema.parse({
57
+ ...config,
58
+ // Override Git-specific options for browser
59
+ enableGitAnchoring: false,
60
+ gitRepo: undefined,
61
+ });
62
+ this.config = validatedConfig;
63
+
64
+ // Initialize storage
65
+ this.entries = [];
66
+ this.lastHash = null;
67
+ this.processingBatches = new Set();
68
+
69
+ // Browser storage API
70
+ this.storage = this._initStorage();
71
+
72
+ // Load existing entries
73
+ this._loadEntries().catch(err => {
74
+ console.warn('Failed to load entries during initialization:', err.message);
75
+ });
76
+ }
77
+
78
+ /**
79
+ * Initialize browser storage according to config
80
+ * @private
81
+ */
82
+ _initStorage() {
83
+ switch (this.config.storageType) {
84
+ case 'localStorage':
85
+ return {
86
+ get: key => globalThis?.localStorage?.getItem(`${this.config.storagePrefix}_${key}`),
87
+ set: (key, value) =>
88
+ globalThis?.localStorage?.setItem(`${this.config.storagePrefix}_${key}`, value),
89
+ remove: key =>
90
+ globalThis?.localStorage?.removeItem(`${this.config.storagePrefix}_${key}`),
91
+ };
92
+ case 'sessionStorage':
93
+ return {
94
+ get: key => globalThis?.sessionStorage?.getItem(`${this.config.storagePrefix}_${key}`),
95
+ set: (key, value) =>
96
+ globalThis?.sessionStorage?.setItem(`${this.config.storagePrefix}_${key}`, value),
97
+ remove: key =>
98
+ globalThis?.sessionStorage?.removeItem(`${this.config.storagePrefix}_${key}`),
99
+ };
100
+ case 'indexedDB':
101
+ // Simplified IndexedDB implementation would go here
102
+ console.warn('IndexedDB storage not yet implemented, falling back to memory');
103
+ return this._initMemoryStorage();
104
+ default:
105
+ return this._initMemoryStorage();
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Initialize memory-based storage
111
+ * @private
112
+ */
113
+ _initMemoryStorage() {
114
+ const memoryStorage = new Map();
115
+ return {
116
+ get: key => memoryStorage.get(key),
117
+ set: (key, value) => memoryStorage.set(key, value),
118
+ remove: key => memoryStorage.delete(key),
119
+ };
120
+ }
121
+
122
+ /**
123
+ * Load existing entries from storage
124
+ * @private
125
+ */
126
+ async _loadEntries() {
127
+ try {
128
+ const entriesKey = `${this.config.storagePrefix}_entries`;
129
+ const entriesData = this.storage.get(entriesKey);
130
+
131
+ if (entriesData) {
132
+ this.entries = JSON.parse(entriesData);
133
+
134
+ // Restore last hash
135
+ const lastEntry = this.entries[this.entries.length - 1];
136
+ if (lastEntry) {
137
+ this.lastHash = await this._calculateEntryHash(lastEntry);
138
+ }
139
+ }
140
+ } catch (error) {
141
+ console.warn('Failed to load existing entries:', error.message);
142
+ this.entries = [];
143
+ this.lastHash = null;
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Save entries to storage
149
+ * @private
150
+ */
151
+ async _saveEntries() {
152
+ try {
153
+ const entriesKey = `${this.config.storagePrefix}_entries`;
154
+ this.storage.set(entriesKey, JSON.stringify(this.entries));
155
+ } catch (error) {
156
+ console.warn('Failed to save entries:', error.message);
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Write a receipt to the lockchain
162
+ * @param {Object} receipt - Transaction receipt
163
+ * @param {Object} [options] - Write options
164
+ * @returns {Promise<Object>} Lockchain entry
165
+ */
166
+ async writeReceipt(receipt, options = {}) {
167
+ const entryId = randomUUID();
168
+ const timestamp = Date.now();
169
+
170
+ // Create lockchain entry
171
+ const entry = {
172
+ id: entryId,
173
+ timestamp,
174
+ receipt: this._serializeReceipt(receipt),
175
+ signature: await this._signEntry(receipt, entryId, timestamp),
176
+ previousHash: this.lastHash || null,
177
+ merkleRoot: options.merkleRoot,
178
+ };
179
+
180
+ // Validate entry
181
+ const validatedEntry = LockchainEntrySchema.parse(entry);
182
+
183
+ // Store entry
184
+ await this._storeEntry(validatedEntry);
185
+
186
+ // Update hash chain
187
+ this.lastHash = await this._calculateEntryHash(validatedEntry);
188
+
189
+ // Persist to storage
190
+ await this._saveEntries();
191
+
192
+ return validatedEntry;
193
+ }
194
+
195
+ /**
196
+ * Write multiple receipts in a batch
197
+ * @param {Array} receipts - Array of transaction receipts
198
+ * @param {Object} [options] - Batch options
199
+ * @returns {Promise<Array>} Array of lockchain entries
200
+ */
201
+ async writeReceiptBatch(receipts, _options = {}) {
202
+ const batchId = `batch_${randomUUID().slice(0, 8)}`;
203
+ const validatedReceipts = receipts.map(r => (typeof r === 'string' ? JSON.parse(r) : r));
204
+
205
+ const batchPromise = this._processBatch(validatedReceipts, batchId);
206
+ this.processingBatches.add(batchId);
207
+
208
+ try {
209
+ const entries = await batchPromise;
210
+ this.processingBatches.delete(batchId);
211
+ return entries;
212
+ } catch (error) {
213
+ this.processingBatches.delete(batchId);
214
+ throw error;
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Process a batch of receipts
220
+ * @private
221
+ */
222
+ async _processBatch(receipts, batchId) {
223
+ const entries = [];
224
+ const merkleRoot = this.config.enableMerkle ? await this._calculateMerkleRoot(receipts) : null;
225
+
226
+ for (let i = 0; i < receipts.length; i++) {
227
+ const receipt = receipts[i];
228
+ const entry = await this.writeReceipt(receipt, {
229
+ batchId,
230
+ batchIndex: i,
231
+ merkleRoot,
232
+ });
233
+ entries.push(entry);
234
+ }
235
+
236
+ return entries;
237
+ }
238
+
239
+ /**
240
+ * Calculate Merkle root from receipts
241
+ * @private
242
+ */
243
+ async _calculateMerkleRoot(receipts) {
244
+ const hashes = await Promise.all(
245
+ receipts.map(async receipt => {
246
+ const serialized = this._serializeReceipt(receipt);
247
+ return bytesToHex(sha3_256(utf8ToBytes(JSON.stringify(serialized))));
248
+ })
249
+ );
250
+
251
+ // Simple binary tree merkle calculation
252
+ let current = hashes;
253
+ while (current.length > 1) {
254
+ const next = [];
255
+ for (let i = 0; i < current.length; i += 2) {
256
+ const left = current[i];
257
+ const right = current[i + 1] || current[i]; // Pad with self if odd
258
+ const combined = left + right;
259
+ next.push(bytesToHex(sha3_256(utf8ToBytes(combined))));
260
+ }
261
+ current = next;
262
+ }
263
+
264
+ return current[0];
265
+ }
266
+
267
+ /**
268
+ * Store a single entry
269
+ * @private
270
+ */
271
+ async _storeEntry(entry) {
272
+ // Store individual entry
273
+ const storageKey = `entry_${entry.id}`;
274
+ this.storage.set(storageKey, JSON.stringify(entry));
275
+
276
+ // Update entries array
277
+ this.entries.push({
278
+ ...entry,
279
+ storageKey,
280
+ });
281
+ }
282
+
283
+ /**
284
+ * Serialize receipt for storage
285
+ * @private
286
+ */
287
+ _serializeReceipt(receipt) {
288
+ if (typeof receipt === 'string') {
289
+ return JSON.parse(receipt);
290
+ }
291
+ return receipt;
292
+ }
293
+
294
+ /**
295
+ * Sign an entry
296
+ * @private
297
+ */
298
+ async _signEntry(receipt, entryId, timestamp) {
299
+ const hash = createHash(this.config.algorithm);
300
+ const signature = hash.update(
301
+ JSON.stringify({
302
+ receipt,
303
+ entryId,
304
+ timestamp,
305
+ previousHash: this.lastHash,
306
+ })
307
+ );
308
+
309
+ return {
310
+ algorithm: this.config.algorithm,
311
+ value: await signature.digest('hex'),
312
+ };
313
+ }
314
+
315
+ /**
316
+ * Calculate hash for an entry
317
+ * @private
318
+ */
319
+ async _calculateEntryHash(entry) {
320
+ const hash = createHash(this.config.algorithm);
321
+ return await hash.update(JSON.stringify(entry)).digest('hex');
322
+ }
323
+
324
+ /**
325
+ * Verify lockchain integrity
326
+ * @returns {Promise<Object>} Verification result
327
+ */
328
+ async verifyIntegrity() {
329
+ const results = [];
330
+ let previousHash = null;
331
+
332
+ for (let i = 0; i < this.entries.length; i++) {
333
+ const entry = this.entries[i];
334
+ const expectedHash = await this._calculateEntryHash(entry);
335
+
336
+ const isValid =
337
+ expectedHash === entry.previousHash &&
338
+ (previousHash === null || entry.previousHash === previousHash);
339
+
340
+ results.push({
341
+ index: i,
342
+ entryId: entry.id,
343
+ valid: isValid,
344
+ hash: expectedHash,
345
+ });
346
+
347
+ if (!isValid) {
348
+ break; // Chain is broken
349
+ }
350
+
351
+ previousHash = expectedHash;
352
+ }
353
+
354
+ const isValid = results.every(r => r.valid);
355
+
356
+ return {
357
+ valid: isValid,
358
+ totalEntries: this.entries.length,
359
+ results,
360
+ verificationTimestamp: Date.now(),
361
+ };
362
+ }
363
+
364
+ /**
365
+ * Get lockchain statistics
366
+ * @returns {Object} Statistics
367
+ */
368
+ getStats() {
369
+ return {
370
+ totalEntries: this.entries.length,
371
+ processingBatches: this.processingBatches.size,
372
+ config: this.config,
373
+ lastHash: this.lastHash,
374
+ storageType: this.config.storageType,
375
+ oldestEntry: this.entries.length > 0 ? this.entries[0].timestamp : null,
376
+ newestEntry: this.entries.length > 0 ? this.entries[this.entries.length - 1].timestamp : null,
377
+ };
378
+ }
379
+
380
+ /**
381
+ * Clear all entries (use with caution)
382
+ */
383
+ async clear() {
384
+ this.entries = [];
385
+ this.lastHash = null;
386
+ this.processingBatches.clear();
387
+
388
+ if (this.config.enablePersistence) {
389
+ // Clear persisted entries
390
+ this.entries.forEach(entry => {
391
+ if (entry.storageKey) {
392
+ this.storage.remove(entry.storageKey);
393
+ }
394
+ });
395
+
396
+ const entriesKey = `${this.config.storagePrefix}_entries`;
397
+ this.storage.remove(entriesKey);
398
+ }
399
+ }
400
+ }
401
+
402
+ /**
403
+ * Create a browser-compatible lockchain writer
404
+ * @param {Object} [config] - Lockchain configuration
405
+ * @returns {BrowserLockchainWriter} New writer instance
406
+ */
407
+ export function createBrowserLockchainWriter(config = {}) {
408
+ return new BrowserLockchainWriter(config);
409
+ }
410
+
411
+ // Export for compatibility
412
+ export { BrowserLockchainWriter as LockchainWriter };
413
+
414
+ export default BrowserLockchainWriter;