@openrfid/core 0.1.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/dist/index.mjs ADDED
@@ -0,0 +1,742 @@
1
+ // src/lifecycle.ts
2
+ var ComponentState = /* @__PURE__ */ ((ComponentState2) => {
3
+ ComponentState2["UNINITIALIZED"] = "UNINITIALIZED";
4
+ ComponentState2["INITIALIZED"] = "INITIALIZED";
5
+ ComponentState2["RUNNING"] = "RUNNING";
6
+ ComponentState2["STOPPED"] = "STOPPED";
7
+ ComponentState2["DISPOSED"] = "DISPOSED";
8
+ ComponentState2["ERROR"] = "ERROR";
9
+ return ComponentState2;
10
+ })(ComponentState || {});
11
+
12
+ // src/event-bus.ts
13
+ var EventBus = class {
14
+ listeners = /* @__PURE__ */ new Map();
15
+ on(event, handler) {
16
+ const eventName = String(event);
17
+ if (!this.listeners.has(eventName)) {
18
+ this.listeners.set(eventName, /* @__PURE__ */ new Set());
19
+ }
20
+ this.listeners.get(eventName).add(handler);
21
+ return () => this.off(event, handler);
22
+ }
23
+ off(event, handler) {
24
+ const eventName = String(event);
25
+ const handlers = this.listeners.get(eventName);
26
+ if (handlers) {
27
+ handlers.delete(handler);
28
+ if (handlers.size === 0) {
29
+ this.listeners.delete(eventName);
30
+ }
31
+ }
32
+ }
33
+ async emit(event, payload) {
34
+ const eventName = String(event);
35
+ const handlers = this.listeners.get(eventName);
36
+ if (handlers) {
37
+ const promises = [];
38
+ for (const handler of handlers) {
39
+ try {
40
+ promises.push(handler(payload));
41
+ } catch (err) {
42
+ console.error(`Error in event handler for event '${eventName}':`, err);
43
+ }
44
+ }
45
+ await Promise.all(promises);
46
+ }
47
+ }
48
+ removeAllListeners(event) {
49
+ if (event) {
50
+ this.listeners.delete(String(event));
51
+ } else {
52
+ this.listeners.clear();
53
+ }
54
+ }
55
+ listenerCount(event) {
56
+ return this.listeners.get(String(event))?.size ?? 0;
57
+ }
58
+ };
59
+
60
+ // src/di-container.ts
61
+ var Container = class {
62
+ services = /* @__PURE__ */ new Map();
63
+ register(key, factory, singleton = true) {
64
+ this.services.set(key, { factory, singleton });
65
+ }
66
+ registerInstance(key, instance) {
67
+ this.services.set(key, { factory: () => instance, singleton: true, instance });
68
+ }
69
+ resolve(key) {
70
+ const service = this.services.get(key);
71
+ if (!service) {
72
+ throw new Error(`Service '${key}' is not registered in the DI Container.`);
73
+ }
74
+ if (service.singleton) {
75
+ if (!service.instance) {
76
+ service.instance = service.factory(this);
77
+ }
78
+ return service.instance;
79
+ }
80
+ return service.factory(this);
81
+ }
82
+ has(key) {
83
+ return this.services.has(key);
84
+ }
85
+ reset() {
86
+ this.services.clear();
87
+ }
88
+ };
89
+
90
+ // src/config-manager.ts
91
+ var defaultConfig = {
92
+ server: {
93
+ port: 3e3,
94
+ host: "0.0.0.0"
95
+ },
96
+ mqtt: {
97
+ enabled: false,
98
+ brokerUrl: "mqtt://localhost:1883",
99
+ topicPrefix: "openrfid"
100
+ },
101
+ websocket: {
102
+ port: 3001
103
+ },
104
+ storage: {
105
+ dbPath: "./openrfid.db",
106
+ memoryEventLimit: 1e4,
107
+ eventWarningThreshold: 8e3,
108
+ autoTrimEnabled: true
109
+ },
110
+ simulator: {
111
+ defaultReadIntervalMs: 500,
112
+ defaultRssi: -55
113
+ }
114
+ };
115
+ var ConfigManager = class {
116
+ config;
117
+ listeners = /* @__PURE__ */ new Set();
118
+ constructor(initialConfig = {}) {
119
+ this.config = this.deepMerge({ ...defaultConfig }, initialConfig);
120
+ }
121
+ get(path) {
122
+ const keys = path.split(".");
123
+ let current = this.config;
124
+ for (const key of keys) {
125
+ if (current === void 0 || current === null) return void 0;
126
+ current = current[key];
127
+ }
128
+ return current;
129
+ }
130
+ set(path, value) {
131
+ const keys = path.split(".");
132
+ let current = this.config;
133
+ for (let i = 0; i < keys.length - 1; i++) {
134
+ const key = keys[i];
135
+ if (!current[key] || typeof current[key] !== "object") {
136
+ current[key] = {};
137
+ }
138
+ current = current[key];
139
+ }
140
+ current[keys[keys.length - 1]] = value;
141
+ this.notify();
142
+ }
143
+ getAll() {
144
+ return { ...this.config };
145
+ }
146
+ onChange(listener) {
147
+ this.listeners.add(listener);
148
+ return () => this.listeners.delete(listener);
149
+ }
150
+ notify() {
151
+ const freezeCopy = Object.freeze({ ...this.config });
152
+ for (const listener of this.listeners) {
153
+ listener(freezeCopy);
154
+ }
155
+ }
156
+ deepMerge(target, source) {
157
+ const output = { ...target };
158
+ for (const key of Object.keys(source)) {
159
+ if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key])) {
160
+ output[key] = this.deepMerge(target[key] ?? {}, source[key]);
161
+ } else {
162
+ output[key] = source[key];
163
+ }
164
+ }
165
+ return output;
166
+ }
167
+ };
168
+
169
+ // src/logger.ts
170
+ var Logger = class {
171
+ constructor(context = "App", minLevel = "info") {
172
+ this.context = context;
173
+ this.minLevel = minLevel;
174
+ }
175
+ context;
176
+ minLevel;
177
+ levelWeights = {
178
+ debug: 0,
179
+ info: 1,
180
+ warn: 2,
181
+ error: 3
182
+ };
183
+ debug(message, data) {
184
+ this.log("debug", message, data);
185
+ }
186
+ info(message, data) {
187
+ this.log("info", message, data);
188
+ }
189
+ warn(message, data) {
190
+ this.log("warn", message, data);
191
+ }
192
+ error(message, data) {
193
+ this.log("error", message, data);
194
+ }
195
+ log(level, message, data) {
196
+ if (this.levelWeights[level] < this.levelWeights[this.minLevel]) {
197
+ return;
198
+ }
199
+ const entry = {
200
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
201
+ level,
202
+ context: this.context,
203
+ message,
204
+ data
205
+ };
206
+ const formatted = `[${entry.timestamp}] [${level.toUpperCase()}] [${entry.context}]: ${message}`;
207
+ if (level === "error") {
208
+ console.error(formatted, data !== void 0 ? data : "");
209
+ } else if (level === "warn") {
210
+ console.warn(formatted, data !== void 0 ? data : "");
211
+ } else {
212
+ console.log(formatted, data !== void 0 ? data : "");
213
+ }
214
+ }
215
+ };
216
+
217
+ // src/storage-memory.ts
218
+ var MemoryStorageDriver = class {
219
+ constructor(config) {
220
+ this.config = config;
221
+ this.maxEvents = config?.get("storage.memoryEventLimit") ?? 1e4;
222
+ this.warningThreshold = config?.get("storage.eventWarningThreshold") ?? 8e3;
223
+ this.autoTrim = config?.get("storage.autoTrimEnabled") ?? true;
224
+ if (config) {
225
+ this.configUnsubscribe = config.onChange((cfg) => {
226
+ this.maxEvents = cfg.storage?.memoryEventLimit ?? 1e4;
227
+ this.warningThreshold = cfg.storage?.eventWarningThreshold ?? 8e3;
228
+ this.autoTrim = cfg.storage?.autoTrimEnabled ?? true;
229
+ if (this.events.length > this.maxEvents) {
230
+ this.events.length = this.maxEvents;
231
+ }
232
+ });
233
+ }
234
+ }
235
+ config;
236
+ readers = /* @__PURE__ */ new Map();
237
+ tags = /* @__PURE__ */ new Map();
238
+ antennas = /* @__PURE__ */ new Map();
239
+ events = [];
240
+ connected = false;
241
+ maxEvents;
242
+ warningThreshold;
243
+ autoTrim;
244
+ configUnsubscribe;
245
+ async connect() {
246
+ this.connected = true;
247
+ }
248
+ async disconnect() {
249
+ this.connected = false;
250
+ this.configUnsubscribe?.();
251
+ }
252
+ // Reader CRUD
253
+ async getReaders() {
254
+ return Array.from(this.readers.values());
255
+ }
256
+ async getReaderById(id) {
257
+ return this.readers.get(id) ?? null;
258
+ }
259
+ async saveReader(reader) {
260
+ this.readers.set(reader.id, reader);
261
+ }
262
+ async deleteReader(id) {
263
+ this.readers.delete(id);
264
+ for (const key of Array.from(this.antennas.keys())) {
265
+ if (this.antennas.get(key)?.readerId === id) {
266
+ this.antennas.delete(key);
267
+ }
268
+ }
269
+ }
270
+ // Antenna Persistence
271
+ async saveAntenna(antenna) {
272
+ this.antennas.set(antenna.id, antenna);
273
+ }
274
+ async getAntennasByReaderId(readerId) {
275
+ return Array.from(this.antennas.values()).filter((a) => a.readerId === readerId);
276
+ }
277
+ // Tag CRUD
278
+ async getTags() {
279
+ return Array.from(this.tags.values());
280
+ }
281
+ async getTagByEpc(epc) {
282
+ for (const tag of this.tags.values()) {
283
+ if (tag.epc === epc) return tag;
284
+ }
285
+ return null;
286
+ }
287
+ async saveTag(tag) {
288
+ this.tags.set(tag.id, tag);
289
+ }
290
+ async deleteTag(id) {
291
+ this.tags.delete(id);
292
+ }
293
+ // Event Log — basic
294
+ async logEvent(event) {
295
+ this.events.unshift(event);
296
+ if (this.autoTrim && this.events.length > this.maxEvents) {
297
+ this.events.length = this.maxEvents;
298
+ }
299
+ }
300
+ // Event Log — advanced queries
301
+ async getEvents(options = {}) {
302
+ let results = [...this.events];
303
+ if (options.date) results = results.filter((e) => e.date === options.date);
304
+ if (options.from) results = results.filter((e) => e.timestamp >= options.from);
305
+ if (options.to) results = results.filter((e) => e.timestamp <= options.to);
306
+ if (options.readerId) results = results.filter((e) => e.readerId === options.readerId);
307
+ if (options.epc) results = results.filter((e) => e.epc === options.epc);
308
+ if (options.protocol) results = results.filter((e) => e.protocol === options.protocol);
309
+ const offset = options.offset ?? 0;
310
+ const limit = options.limit ?? 1e3;
311
+ return results.slice(offset, offset + limit);
312
+ }
313
+ async getEventsByDate(date, limit = 1e3, offset = 0) {
314
+ const filtered = this.events.filter((e) => e.date === date);
315
+ return filtered.slice(offset, offset + limit);
316
+ }
317
+ async getEventDates() {
318
+ const map = /* @__PURE__ */ new Map();
319
+ for (const e of this.events) {
320
+ map.set(e.date, (map.get(e.date) ?? 0) + 1);
321
+ }
322
+ return Array.from(map.entries()).map(([date, count]) => ({ date, count })).sort((a, b) => b.date.localeCompare(a.date));
323
+ }
324
+ async getEventStats() {
325
+ const byDate = await this.getEventDates();
326
+ const dates = byDate.map((d) => d.date).sort();
327
+ return {
328
+ totalEvents: this.events.length,
329
+ totalDays: byDate.length,
330
+ oldestEventDate: dates[0] ?? null,
331
+ newestEventDate: dates[dates.length - 1] ?? null,
332
+ eventsByDate: byDate
333
+ };
334
+ }
335
+ async getEventCount(options = {}) {
336
+ const results = await this.getEvents({ ...options, limit: Number.MAX_SAFE_INTEGER, offset: 0 });
337
+ return results.length;
338
+ }
339
+ // Event Log — management
340
+ async deleteEventsByDate(date) {
341
+ const before = this.events.length;
342
+ this.events = this.events.filter((e) => e.date !== date);
343
+ return { deleted: before - this.events.length };
344
+ }
345
+ async clearAllEvents() {
346
+ const deleted = this.events.length;
347
+ this.events = [];
348
+ return { deleted };
349
+ }
350
+ // Nuclear clear
351
+ async clearAllData() {
352
+ this.readers.clear();
353
+ this.tags.clear();
354
+ this.antennas.clear();
355
+ this.events = [];
356
+ }
357
+ // Memory stats for dashboard UI
358
+ getMemoryUsage() {
359
+ const percent = Math.round(this.events.length / this.maxEvents * 100);
360
+ return {
361
+ current: this.events.length,
362
+ limit: this.maxEvents,
363
+ percent,
364
+ isWarning: this.events.length >= this.warningThreshold
365
+ };
366
+ }
367
+ setMemoryLimit(limit) {
368
+ const clamped = Math.max(100, Math.min(5e5, limit));
369
+ this.maxEvents = clamped;
370
+ if (this.events.length > clamped) {
371
+ this.events.length = clamped;
372
+ }
373
+ this.config?.set("storage.memoryEventLimit", clamped);
374
+ }
375
+ };
376
+
377
+ // src/storage-sqlite.ts
378
+ import Database from "better-sqlite3";
379
+ var SqliteStorageDriver = class {
380
+ constructor(dbPath = ":memory:") {
381
+ this.dbPath = dbPath;
382
+ }
383
+ dbPath;
384
+ db = null;
385
+ async connect() {
386
+ this.db = new Database(this.dbPath);
387
+ this.db.pragma("journal_mode = WAL");
388
+ this.initTables();
389
+ }
390
+ async disconnect() {
391
+ if (this.db) {
392
+ this.db.close();
393
+ this.db = null;
394
+ }
395
+ }
396
+ initTables() {
397
+ if (!this.db) return;
398
+ this.db.exec(`
399
+ CREATE TABLE IF NOT EXISTS readers (
400
+ id TEXT PRIMARY KEY,
401
+ name TEXT NOT NULL,
402
+ vendor TEXT NOT NULL,
403
+ model TEXT NOT NULL,
404
+ ip TEXT NOT NULL,
405
+ port INTEGER NOT NULL,
406
+ protocol TEXT NOT NULL,
407
+ status TEXT NOT NULL,
408
+ createdAt TEXT NOT NULL,
409
+ readRate INTEGER DEFAULT 0,
410
+ readMode TEXT DEFAULT 'continuous',
411
+ readIntervalValue INTEGER DEFAULT 1,
412
+ readIntervalUnit TEXT DEFAULT 'seconds',
413
+ epcFilterStart TEXT DEFAULT '',
414
+ epcFilterEnd TEXT DEFAULT '',
415
+ epcFilterPrefix TEXT DEFAULT ''
416
+ );
417
+
418
+ CREATE TABLE IF NOT EXISTS antennas (
419
+ id TEXT PRIMARY KEY,
420
+ reader_id TEXT NOT NULL,
421
+ antenna_index INTEGER NOT NULL,
422
+ gain REAL NOT NULL,
423
+ power REAL NOT NULL,
424
+ frequency REAL NOT NULL,
425
+ rssi_offset REAL NOT NULL,
426
+ read_zone TEXT NOT NULL,
427
+ enabled INTEGER NOT NULL DEFAULT 1,
428
+ FOREIGN KEY (reader_id) REFERENCES readers(id) ON DELETE CASCADE
429
+ );
430
+
431
+ CREATE TABLE IF NOT EXISTS tags (
432
+ id TEXT PRIMARY KEY,
433
+ epc TEXT UNIQUE NOT NULL,
434
+ tid TEXT,
435
+ userMemory TEXT,
436
+ accessPassword TEXT,
437
+ killPassword TEXT,
438
+ protocol TEXT
439
+ );
440
+
441
+ CREATE TABLE IF NOT EXISTS events (
442
+ id TEXT PRIMARY KEY,
443
+ date TEXT NOT NULL,
444
+ timestamp TEXT NOT NULL,
445
+ readerId TEXT NOT NULL,
446
+ antennaId INTEGER NOT NULL,
447
+ epc TEXT NOT NULL,
448
+ rssi REAL NOT NULL,
449
+ protocol TEXT NOT NULL,
450
+ payload TEXT
451
+ );
452
+
453
+ CREATE TABLE IF NOT EXISTS settings (
454
+ key TEXT PRIMARY KEY,
455
+ value TEXT NOT NULL
456
+ );
457
+
458
+ CREATE TABLE IF NOT EXISTS plugins (
459
+ id TEXT PRIMARY KEY,
460
+ name TEXT NOT NULL,
461
+ version TEXT NOT NULL,
462
+ status TEXT NOT NULL,
463
+ config TEXT
464
+ );
465
+
466
+ CREATE INDEX IF NOT EXISTS idx_events_date ON events(date);
467
+ CREATE INDEX IF NOT EXISTS idx_events_timestamp ON events(timestamp);
468
+ CREATE INDEX IF NOT EXISTS idx_events_epc ON events(epc);
469
+ CREATE INDEX IF NOT EXISTS idx_events_reader ON events(readerId);
470
+ CREATE INDEX IF NOT EXISTS idx_tags_epc ON tags(epc);
471
+ `);
472
+ }
473
+ // ─── Readers ─────────────────────────────────────────────────────────────
474
+ async getReaders() {
475
+ this.checkConnection();
476
+ return this.db.prepare("SELECT * FROM readers").all();
477
+ }
478
+ async getReaderById(id) {
479
+ this.checkConnection();
480
+ const row = this.db.prepare("SELECT * FROM readers WHERE id = ?").get(id);
481
+ return row ? row : null;
482
+ }
483
+ async saveReader(reader) {
484
+ this.checkConnection();
485
+ this.db.prepare(`
486
+ INSERT INTO readers (id, name, vendor, model, ip, port, protocol, status, createdAt,
487
+ readRate, readMode, readIntervalValue, readIntervalUnit,
488
+ epcFilterStart, epcFilterEnd, epcFilterPrefix)
489
+ VALUES (@id, @name, @vendor, @model, @ip, @port, @protocol, @status, @createdAt,
490
+ @readRate, @readMode, @readIntervalValue, @readIntervalUnit,
491
+ @epcFilterStart, @epcFilterEnd, @epcFilterPrefix)
492
+ ON CONFLICT(id) DO UPDATE SET
493
+ name=excluded.name, vendor=excluded.vendor, model=excluded.model,
494
+ ip=excluded.ip, port=excluded.port, protocol=excluded.protocol,
495
+ status=excluded.status, readRate=excluded.readRate, readMode=excluded.readMode,
496
+ readIntervalValue=excluded.readIntervalValue, readIntervalUnit=excluded.readIntervalUnit,
497
+ epcFilterStart=excluded.epcFilterStart, epcFilterEnd=excluded.epcFilterEnd,
498
+ epcFilterPrefix=excluded.epcFilterPrefix
499
+ `).run({
500
+ ...reader,
501
+ readRate: reader.readRate ?? 0,
502
+ readMode: reader.readMode ?? "continuous",
503
+ readIntervalValue: reader.readIntervalValue ?? 1,
504
+ readIntervalUnit: reader.readIntervalUnit ?? "seconds",
505
+ epcFilterStart: reader.epcFilterStart ?? "",
506
+ epcFilterEnd: reader.epcFilterEnd ?? "",
507
+ epcFilterPrefix: reader.epcFilterPrefix ?? ""
508
+ });
509
+ }
510
+ async deleteReader(id) {
511
+ this.checkConnection();
512
+ this.db.prepare("DELETE FROM readers WHERE id = ?").run(id);
513
+ }
514
+ // ─── Antennas ─────────────────────────────────────────────────────────────
515
+ async saveAntenna(antenna) {
516
+ this.checkConnection();
517
+ this.db.prepare(`
518
+ INSERT INTO antennas (id, reader_id, antenna_index, gain, power, frequency, rssi_offset, read_zone, enabled)
519
+ VALUES (@id, @readerId, @index, @gain, @power, @frequency, @rssiOffset, @readZone, @enabled)
520
+ ON CONFLICT(id) DO UPDATE SET
521
+ gain=excluded.gain, power=excluded.power, frequency=excluded.frequency,
522
+ rssi_offset=excluded.rssi_offset, read_zone=excluded.read_zone, enabled=excluded.enabled
523
+ `).run({
524
+ id: antenna.id,
525
+ readerId: antenna.readerId,
526
+ index: antenna.index,
527
+ gain: antenna.gain,
528
+ power: antenna.power,
529
+ frequency: antenna.frequency,
530
+ rssiOffset: antenna.rssiOffset,
531
+ readZone: antenna.readZone,
532
+ enabled: antenna.enabled ? 1 : 0
533
+ });
534
+ }
535
+ async getAntennasByReaderId(readerId) {
536
+ this.checkConnection();
537
+ const rows = this.db.prepare("SELECT * FROM antennas WHERE reader_id = ?").all(readerId);
538
+ return rows.map((r) => ({
539
+ id: r.id,
540
+ readerId: r.reader_id,
541
+ index: r.antenna_index,
542
+ gain: r.gain,
543
+ power: r.power,
544
+ frequency: r.frequency,
545
+ rssiOffset: r.rssi_offset,
546
+ readZone: r.read_zone,
547
+ enabled: r.enabled === 1
548
+ }));
549
+ }
550
+ // ─── Tags ─────────────────────────────────────────────────────────────────
551
+ async getTags() {
552
+ this.checkConnection();
553
+ return this.db.prepare("SELECT * FROM tags").all();
554
+ }
555
+ async getTagByEpc(epc) {
556
+ this.checkConnection();
557
+ const row = this.db.prepare("SELECT * FROM tags WHERE epc = ?").get(epc);
558
+ return row ? row : null;
559
+ }
560
+ async saveTag(tag) {
561
+ this.checkConnection();
562
+ this.db.prepare(`
563
+ INSERT INTO tags (id, epc, tid, userMemory, accessPassword, killPassword, protocol)
564
+ VALUES (@id, @epc, @tid, @userMemory, @accessPassword, @killPassword, @protocol)
565
+ ON CONFLICT(epc) DO UPDATE SET
566
+ tid=excluded.tid, userMemory=excluded.userMemory,
567
+ accessPassword=excluded.accessPassword, killPassword=excluded.killPassword,
568
+ protocol=excluded.protocol
569
+ `).run({
570
+ id: tag.id,
571
+ epc: tag.epc,
572
+ tid: tag.tid ?? null,
573
+ userMemory: tag.userMemory ?? null,
574
+ accessPassword: tag.accessPassword ?? null,
575
+ killPassword: tag.killPassword ?? null,
576
+ protocol: tag.protocol ?? null
577
+ });
578
+ }
579
+ async deleteTag(id) {
580
+ this.checkConnection();
581
+ this.db.prepare("DELETE FROM tags WHERE id = ?").run(id);
582
+ }
583
+ // ─── Events — basic ───────────────────────────────────────────────────────
584
+ async logEvent(event) {
585
+ this.checkConnection();
586
+ this.db.prepare(`
587
+ INSERT INTO events (id, date, timestamp, readerId, antennaId, epc, rssi, protocol, payload)
588
+ VALUES (@id, @date, @timestamp, @readerId, @antennaId, @epc, @rssi, @protocol, @payload)
589
+ `).run({
590
+ id: event.id,
591
+ date: event.date,
592
+ timestamp: event.timestamp,
593
+ readerId: event.readerId,
594
+ antennaId: event.antennaId,
595
+ epc: event.epc,
596
+ rssi: event.rssi,
597
+ protocol: event.protocol,
598
+ payload: event.payload ?? null
599
+ });
600
+ }
601
+ // ─── Events — advanced queries ────────────────────────────────────────────
602
+ async getEvents(options = {}) {
603
+ this.checkConnection();
604
+ let sql = "SELECT * FROM events WHERE 1=1";
605
+ const params = [];
606
+ if (options.date) {
607
+ sql += " AND date = ?";
608
+ params.push(options.date);
609
+ }
610
+ if (options.from) {
611
+ sql += " AND timestamp >= ?";
612
+ params.push(options.from);
613
+ }
614
+ if (options.to) {
615
+ sql += " AND timestamp <= ?";
616
+ params.push(options.to);
617
+ }
618
+ if (options.readerId) {
619
+ sql += " AND readerId = ?";
620
+ params.push(options.readerId);
621
+ }
622
+ if (options.epc) {
623
+ sql += " AND epc = ?";
624
+ params.push(options.epc);
625
+ }
626
+ if (options.protocol) {
627
+ sql += " AND protocol = ?";
628
+ params.push(options.protocol);
629
+ }
630
+ sql += " ORDER BY timestamp DESC";
631
+ sql += ` LIMIT ${options.limit ?? 1e3} OFFSET ${options.offset ?? 0}`;
632
+ return this.db.prepare(sql).all(...params);
633
+ }
634
+ async getEventsByDate(date, limit = 1e3, offset = 0) {
635
+ this.checkConnection();
636
+ return this.db.prepare(
637
+ "SELECT * FROM events WHERE date = ? ORDER BY timestamp DESC LIMIT ? OFFSET ?"
638
+ ).all(date, limit, offset);
639
+ }
640
+ async getEventDates() {
641
+ this.checkConnection();
642
+ return this.db.prepare(
643
+ "SELECT date, COUNT(*) as count FROM events GROUP BY date ORDER BY date DESC"
644
+ ).all();
645
+ }
646
+ async getEventStats() {
647
+ this.checkConnection();
648
+ const total = this.db.prepare("SELECT COUNT(*) as n FROM events").get().n;
649
+ const oldest = this.db.prepare("SELECT MIN(date) as d FROM events").get().d;
650
+ const newest = this.db.prepare("SELECT MAX(date) as d FROM events").get().d;
651
+ const byDate = await this.getEventDates();
652
+ return {
653
+ totalEvents: total,
654
+ totalDays: byDate.length,
655
+ oldestEventDate: oldest,
656
+ newestEventDate: newest,
657
+ eventsByDate: byDate
658
+ };
659
+ }
660
+ async getEventCount(options = {}) {
661
+ this.checkConnection();
662
+ let sql = "SELECT COUNT(*) as n FROM events WHERE 1=1";
663
+ const params = [];
664
+ if (options.date) {
665
+ sql += " AND date = ?";
666
+ params.push(options.date);
667
+ }
668
+ if (options.from) {
669
+ sql += " AND timestamp >= ?";
670
+ params.push(options.from);
671
+ }
672
+ if (options.to) {
673
+ sql += " AND timestamp <= ?";
674
+ params.push(options.to);
675
+ }
676
+ if (options.readerId) {
677
+ sql += " AND readerId = ?";
678
+ params.push(options.readerId);
679
+ }
680
+ if (options.epc) {
681
+ sql += " AND epc = ?";
682
+ params.push(options.epc);
683
+ }
684
+ if (options.protocol) {
685
+ sql += " AND protocol = ?";
686
+ params.push(options.protocol);
687
+ }
688
+ return this.db.prepare(sql).get(...params).n;
689
+ }
690
+ // ─── Events — management ─────────────────────────────────────────────────
691
+ async deleteEventsByDate(date) {
692
+ this.checkConnection();
693
+ const result = this.db.prepare("DELETE FROM events WHERE date = ?").run(date);
694
+ return { deleted: result.changes };
695
+ }
696
+ async clearAllEvents() {
697
+ this.checkConnection();
698
+ const result = this.db.prepare("DELETE FROM events").run();
699
+ return { deleted: result.changes };
700
+ }
701
+ // Nuclear clear — wipes all data
702
+ async clearAllData() {
703
+ this.checkConnection();
704
+ this.db.exec("DELETE FROM events; DELETE FROM tags; DELETE FROM readers; DELETE FROM antennas;");
705
+ }
706
+ // ─── Internal ────────────────────────────────────────────────────────────
707
+ checkConnection() {
708
+ if (!this.db) {
709
+ throw new Error("Database is not connected. Call connect() first.");
710
+ }
711
+ }
712
+ };
713
+
714
+ // src/branding.ts
715
+ var BRAND = {
716
+ name: "OpenRFID Simulator",
717
+ tagline: "The Open Source RFID Reader Simulator for Developers",
718
+ organization: "RFID Software India Private Limited",
719
+ website: "https://rfidsoftwares.com",
720
+ docsUrl: "https://rfidsoftwares.com/docs",
721
+ githubUrl: "https://github.com/rfidsoftwares/openrfid-simulator",
722
+ license: "MIT",
723
+ copyright: "Copyright (c) 2026 RFID Software India Private Limited - https://rfidsoftwares.com",
724
+ headerBanner: `/**
725
+ * OpenRFID Simulator - The Open Source RFID Reader Simulator for Developers.
726
+ * Copyright (c) 2026 RFID Software India Private Limited - https://rfidsoftwares.com
727
+ *
728
+ * Licensed under the MIT License.
729
+ * For RFID software, enterprise tools, and hardware drivers, visit https://rfidsoftwares.com
730
+ */`
731
+ };
732
+ export {
733
+ BRAND,
734
+ ComponentState,
735
+ ConfigManager,
736
+ Container,
737
+ EventBus,
738
+ Logger,
739
+ MemoryStorageDriver,
740
+ SqliteStorageDriver,
741
+ defaultConfig
742
+ };