@http-client-toolkit/store-sqlite 0.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.
package/lib/index.cjs ADDED
@@ -0,0 +1,1311 @@
1
+ 'use strict';
2
+
3
+ var Database = require('better-sqlite3');
4
+ var drizzleOrm = require('drizzle-orm');
5
+ var betterSqlite3 = require('drizzle-orm/better-sqlite3');
6
+ var sqliteCore = require('drizzle-orm/sqlite-core');
7
+ var crypto = require('crypto');
8
+ var core = require('@http-client-toolkit/core');
9
+
10
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
11
+
12
+ var Database__default = /*#__PURE__*/_interopDefault(Database);
13
+
14
+ var __async = (__this, __arguments, generator) => {
15
+ return new Promise((resolve, reject) => {
16
+ var fulfilled = (value) => {
17
+ try {
18
+ step(generator.next(value));
19
+ } catch (e) {
20
+ reject(e);
21
+ }
22
+ };
23
+ var rejected = (value) => {
24
+ try {
25
+ step(generator.throw(value));
26
+ } catch (e) {
27
+ reject(e);
28
+ }
29
+ };
30
+ var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
31
+ step((generator = generator.apply(__this, __arguments)).next());
32
+ });
33
+ };
34
+ var cacheTable = sqliteCore.sqliteTable("cache", {
35
+ hash: sqliteCore.text("hash").primaryKey(),
36
+ value: sqliteCore.blob("value", { mode: "json" }).notNull(),
37
+ expiresAt: sqliteCore.integer("expires_at").notNull(),
38
+ createdAt: sqliteCore.integer("created_at").notNull()
39
+ });
40
+ var dedupeTable = sqliteCore.sqliteTable("dedupe_jobs", {
41
+ hash: sqliteCore.text("hash").primaryKey(),
42
+ jobId: sqliteCore.text("job_id").notNull(),
43
+ status: sqliteCore.text("status").notNull(),
44
+ // 'pending', 'completed', 'failed'
45
+ result: sqliteCore.blob("result", { mode: "json" }),
46
+ error: sqliteCore.text("error"),
47
+ createdAt: sqliteCore.integer("created_at").notNull(),
48
+ updatedAt: sqliteCore.integer("updated_at").notNull()
49
+ });
50
+ var rateLimitTable = sqliteCore.sqliteTable("rate_limits", {
51
+ resource: sqliteCore.text("resource").notNull(),
52
+ timestamp: sqliteCore.integer("timestamp").notNull(),
53
+ id: sqliteCore.integer("id").primaryKey({ autoIncrement: true })
54
+ });
55
+
56
+ // src/sqlite-cache-store.ts
57
+ var SQLiteCacheStore = class {
58
+ constructor({
59
+ /** File path or existing `better-sqlite3` connection. Defaults to `':memory:'`. */
60
+ database = ":memory:",
61
+ /** Cleanup interval in milliseconds. Defaults to 1 minute. */
62
+ cleanupIntervalMs = 6e4,
63
+ /** Maximum allowed size (in bytes) for a single cache entry. Defaults to 5 MiB. */
64
+ maxEntrySizeBytes = 5 * 1024 * 1024
65
+ } = {}) {
66
+ /** Indicates whether this store is responsible for managing (and therefore closing) the SQLite connection */
67
+ this.isConnectionManaged = false;
68
+ this.isDestroyed = false;
69
+ let sqliteInstance;
70
+ let isConnectionManaged = false;
71
+ if (typeof database === "string") {
72
+ sqliteInstance = new Database__default.default(database);
73
+ isConnectionManaged = true;
74
+ } else {
75
+ sqliteInstance = database;
76
+ }
77
+ this.sqlite = sqliteInstance;
78
+ this.isConnectionManaged = isConnectionManaged;
79
+ this.db = betterSqlite3.drizzle(sqliteInstance);
80
+ this.cleanupIntervalMs = cleanupIntervalMs;
81
+ this.maxEntrySizeBytes = maxEntrySizeBytes;
82
+ this.initializeDatabase();
83
+ this.startCleanupInterval();
84
+ }
85
+ get(hash) {
86
+ return __async(this, null, function* () {
87
+ if (this.isDestroyed) {
88
+ throw new Error("Cache store has been destroyed");
89
+ }
90
+ const result = yield this.db.select().from(cacheTable).where(drizzleOrm.eq(cacheTable.hash, hash)).limit(1);
91
+ if (result.length === 0) {
92
+ return void 0;
93
+ }
94
+ const item = result[0];
95
+ if (!item) {
96
+ return void 0;
97
+ }
98
+ const now = Date.now();
99
+ if (item.expiresAt > 0 && now >= item.expiresAt) {
100
+ yield this.db.delete(cacheTable).where(drizzleOrm.eq(cacheTable.hash, hash));
101
+ return void 0;
102
+ }
103
+ try {
104
+ if (item.value === "__UNDEFINED__") {
105
+ return void 0;
106
+ }
107
+ return JSON.parse(item.value);
108
+ } catch (e) {
109
+ yield this.db.delete(cacheTable).where(drizzleOrm.eq(cacheTable.hash, hash));
110
+ return void 0;
111
+ }
112
+ });
113
+ }
114
+ set(hash, value, ttlSeconds) {
115
+ return __async(this, null, function* () {
116
+ if (this.isDestroyed) {
117
+ throw new Error("Cache store has been destroyed");
118
+ }
119
+ const now = Date.now();
120
+ const expiresAt = ttlSeconds < 0 ? now : ttlSeconds === 0 ? 0 : now + ttlSeconds * 1e3;
121
+ let serializedValue;
122
+ try {
123
+ if (value === void 0) {
124
+ serializedValue = "__UNDEFINED__";
125
+ } else {
126
+ serializedValue = JSON.stringify(value);
127
+ }
128
+ } catch (error) {
129
+ throw new Error(
130
+ `Failed to serialize value: ${error instanceof Error ? error.message : String(error)}`
131
+ );
132
+ }
133
+ if (Buffer.byteLength(serializedValue, "utf8") > this.maxEntrySizeBytes) {
134
+ return;
135
+ }
136
+ yield this.db.insert(cacheTable).values({
137
+ hash,
138
+ value: serializedValue,
139
+ expiresAt,
140
+ createdAt: now
141
+ }).onConflictDoUpdate({
142
+ target: cacheTable.hash,
143
+ set: {
144
+ value: serializedValue,
145
+ expiresAt,
146
+ createdAt: now
147
+ }
148
+ });
149
+ });
150
+ }
151
+ delete(hash) {
152
+ return __async(this, null, function* () {
153
+ if (this.isDestroyed) {
154
+ throw new Error("Cache store has been destroyed");
155
+ }
156
+ yield this.db.delete(cacheTable).where(drizzleOrm.eq(cacheTable.hash, hash));
157
+ });
158
+ }
159
+ clear() {
160
+ return __async(this, null, function* () {
161
+ if (this.isDestroyed) {
162
+ throw new Error("Cache store has been destroyed");
163
+ }
164
+ yield this.db.delete(cacheTable);
165
+ });
166
+ }
167
+ /**
168
+ * Get cache statistics
169
+ */
170
+ getStats() {
171
+ return __async(this, null, function* () {
172
+ var _a, _b, _c, _d;
173
+ const now = Date.now();
174
+ const totalResult = yield this.db.select({ count: drizzleOrm.count() }).from(cacheTable);
175
+ const expiredResult = yield this.db.select({ count: drizzleOrm.count() }).from(cacheTable).where(drizzleOrm.and(drizzleOrm.gt(cacheTable.expiresAt, 0), drizzleOrm.lt(cacheTable.expiresAt, now)));
176
+ const pageCount = Number(
177
+ this.sqlite.pragma("page_count", { simple: true })
178
+ );
179
+ const pageSize = Number(this.sqlite.pragma("page_size", { simple: true }));
180
+ const safePageCount = Number.isFinite(pageCount) ? pageCount : 0;
181
+ const safePageSize = Number.isFinite(pageSize) ? pageSize : 0;
182
+ const databaseSizeKB = Math.round(safePageCount * safePageSize / 1024);
183
+ return {
184
+ databaseSizeKB,
185
+ expiredItems: (_b = (_a = expiredResult[0]) == null ? void 0 : _a.count) != null ? _b : 0,
186
+ totalItems: (_d = (_c = totalResult[0]) == null ? void 0 : _c.count) != null ? _d : 0
187
+ };
188
+ });
189
+ }
190
+ /**
191
+ * Manually trigger cleanup of expired items
192
+ */
193
+ cleanup() {
194
+ return __async(this, null, function* () {
195
+ const now = Date.now();
196
+ yield this.db.delete(cacheTable).where(drizzleOrm.and(drizzleOrm.gt(cacheTable.expiresAt, 0), drizzleOrm.lt(cacheTable.expiresAt, now)));
197
+ });
198
+ }
199
+ /**
200
+ * Close the database connection
201
+ */
202
+ close() {
203
+ return __async(this, null, function* () {
204
+ if (this.cleanupInterval) {
205
+ clearInterval(this.cleanupInterval);
206
+ this.cleanupInterval = void 0;
207
+ }
208
+ this.isDestroyed = true;
209
+ if (this.isConnectionManaged && typeof this.sqlite.close === "function") {
210
+ this.sqlite.close();
211
+ }
212
+ });
213
+ }
214
+ /**
215
+ * Alias for close() to match test expectations
216
+ */
217
+ destroy() {
218
+ this.close();
219
+ }
220
+ initializeDatabase() {
221
+ this.db.run(drizzleOrm.sql`
222
+ CREATE TABLE IF NOT EXISTS cache (
223
+ hash TEXT PRIMARY KEY,
224
+ value BLOB NOT NULL,
225
+ expires_at INTEGER NOT NULL,
226
+ created_at INTEGER NOT NULL
227
+ )
228
+ `);
229
+ this.db.run(drizzleOrm.sql`
230
+ CREATE INDEX IF NOT EXISTS idx_cache_expires_at ON cache(expires_at)
231
+ `);
232
+ }
233
+ startCleanupInterval() {
234
+ if (this.cleanupIntervalMs <= 0) {
235
+ return;
236
+ }
237
+ this.cleanupInterval = setInterval(() => __async(this, null, function* () {
238
+ yield this.cleanup();
239
+ }), this.cleanupIntervalMs);
240
+ if (typeof this.cleanupInterval.unref === "function") {
241
+ this.cleanupInterval.unref();
242
+ }
243
+ }
244
+ cleanupExpiredItems() {
245
+ return __async(this, null, function* () {
246
+ const now = Date.now();
247
+ yield this.db.delete(cacheTable).where(drizzleOrm.lt(cacheTable.expiresAt, now));
248
+ });
249
+ }
250
+ };
251
+ var SQLiteDedupeStore = class {
252
+ constructor({
253
+ /** File path or existing `better-sqlite3` Database instance. Defaults to `':memory:'`. */
254
+ database = ":memory:",
255
+ /** Job timeout in milliseconds. Preferred over timeoutMs. */
256
+ jobTimeoutMs,
257
+ /** Legacy alias for jobTimeoutMs. */
258
+ timeoutMs,
259
+ /** Cleanup interval in milliseconds. Defaults to 1 minute. */
260
+ cleanupIntervalMs = 6e4,
261
+ /** Poll interval for checking pending jobs in milliseconds. Defaults to 100ms. */
262
+ pollIntervalMs = 100
263
+ } = {}) {
264
+ /** Indicates whether this store manages (and should close) the SQLite connection */
265
+ this.isConnectionManaged = false;
266
+ this.jobPromises = /* @__PURE__ */ new Map();
267
+ this.jobSettlers = /* @__PURE__ */ new Map();
268
+ this.isDestroyed = false;
269
+ var _a;
270
+ let sqliteInstance;
271
+ let isConnectionManaged = false;
272
+ if (typeof database === "string") {
273
+ sqliteInstance = new Database__default.default(database);
274
+ isConnectionManaged = true;
275
+ } else {
276
+ sqliteInstance = database;
277
+ }
278
+ this.sqlite = sqliteInstance;
279
+ this.isConnectionManaged = isConnectionManaged;
280
+ this.db = betterSqlite3.drizzle(sqliteInstance);
281
+ this.jobTimeoutMs = (_a = timeoutMs != null ? timeoutMs : jobTimeoutMs) != null ? _a : 3e5;
282
+ this.cleanupIntervalMs = cleanupIntervalMs;
283
+ this.pollIntervalMs = pollIntervalMs;
284
+ this.initializeDatabase();
285
+ this.startCleanupInterval();
286
+ }
287
+ startCleanupInterval() {
288
+ if (this.cleanupIntervalMs > 0) {
289
+ this.cleanupInterval = setInterval(() => {
290
+ this.cleanupExpiredJobs().catch(() => {
291
+ });
292
+ }, this.cleanupIntervalMs);
293
+ if (typeof this.cleanupInterval.unref === "function") {
294
+ this.cleanupInterval.unref();
295
+ }
296
+ }
297
+ }
298
+ cleanupExpiredJobs() {
299
+ return __async(this, null, function* () {
300
+ const noTimeoutConfigured = this.jobTimeoutMs <= 0;
301
+ if (noTimeoutConfigured) {
302
+ return;
303
+ }
304
+ const now = Date.now();
305
+ const expiredThreshold = now - this.jobTimeoutMs;
306
+ yield this.db.delete(dedupeTable).where(
307
+ drizzleOrm.and(
308
+ drizzleOrm.eq(dedupeTable.status, "pending"),
309
+ drizzleOrm.lt(dedupeTable.createdAt, expiredThreshold)
310
+ )
311
+ );
312
+ });
313
+ }
314
+ waitFor(hash) {
315
+ return __async(this, null, function* () {
316
+ if (this.isDestroyed) {
317
+ throw new Error("Dedupe store has been destroyed");
318
+ }
319
+ const existingPromise = this.jobPromises.get(hash);
320
+ if (existingPromise) {
321
+ return existingPromise;
322
+ }
323
+ let result;
324
+ try {
325
+ result = yield this.db.select().from(dedupeTable).where(drizzleOrm.eq(dedupeTable.hash, hash)).limit(1);
326
+ } catch (e) {
327
+ return void 0;
328
+ }
329
+ if (result.length === 0) {
330
+ return void 0;
331
+ }
332
+ const job = result[0];
333
+ if (!job) {
334
+ return void 0;
335
+ }
336
+ if (job.status === "completed") {
337
+ return this.deserializeResult(job.result);
338
+ }
339
+ if (job.status === "failed") {
340
+ return void 0;
341
+ }
342
+ const promise = new Promise((resolve) => {
343
+ let settled = false;
344
+ let timeoutHandle;
345
+ const settle = (value) => {
346
+ if (settled) {
347
+ return;
348
+ }
349
+ settled = true;
350
+ if (timeoutHandle) {
351
+ clearTimeout(timeoutHandle);
352
+ }
353
+ clearInterval(pollHandle);
354
+ this.jobSettlers.delete(hash);
355
+ this.jobPromises.delete(hash);
356
+ resolve(value);
357
+ };
358
+ this.jobSettlers.set(hash, settle);
359
+ const poll = () => __async(this, null, function* () {
360
+ if (this.isDestroyed) {
361
+ settle(void 0);
362
+ return;
363
+ }
364
+ try {
365
+ const latest = yield this.db.select().from(dedupeTable).where(drizzleOrm.eq(dedupeTable.hash, hash)).limit(1);
366
+ const latestJob = latest[0];
367
+ if (!latestJob) {
368
+ settle(void 0);
369
+ return;
370
+ }
371
+ const isExpired = this.jobTimeoutMs > 0 && Date.now() - latestJob.createdAt >= this.jobTimeoutMs;
372
+ if (isExpired) {
373
+ yield this.db.update(dedupeTable).set({
374
+ status: "failed",
375
+ error: "Job timed out",
376
+ updatedAt: Date.now()
377
+ }).where(drizzleOrm.eq(dedupeTable.hash, hash));
378
+ settle(void 0);
379
+ return;
380
+ }
381
+ if (latestJob.status === "completed") {
382
+ settle(this.deserializeResult(latestJob.result));
383
+ return;
384
+ }
385
+ if (latestJob.status === "failed") {
386
+ settle(void 0);
387
+ }
388
+ } catch (e) {
389
+ settle(void 0);
390
+ }
391
+ });
392
+ const pollHandle = setInterval(() => {
393
+ void poll();
394
+ }, this.pollIntervalMs);
395
+ if (typeof pollHandle.unref === "function") {
396
+ pollHandle.unref();
397
+ }
398
+ void poll();
399
+ if (this.jobTimeoutMs > 0) {
400
+ timeoutHandle = setTimeout(() => {
401
+ if (this.isDestroyed) {
402
+ settle(void 0);
403
+ return;
404
+ }
405
+ void (() => __async(this, null, function* () {
406
+ try {
407
+ yield this.db.update(dedupeTable).set({
408
+ status: "failed",
409
+ error: "Job timed out",
410
+ updatedAt: Date.now()
411
+ }).where(drizzleOrm.eq(dedupeTable.hash, hash));
412
+ } catch (e) {
413
+ } finally {
414
+ settle(void 0);
415
+ }
416
+ }))();
417
+ }, this.jobTimeoutMs);
418
+ if (typeof timeoutHandle.unref === "function") {
419
+ timeoutHandle.unref();
420
+ }
421
+ }
422
+ });
423
+ this.jobPromises.set(hash, promise);
424
+ return promise;
425
+ });
426
+ }
427
+ register(hash) {
428
+ return __async(this, null, function* () {
429
+ const registration = yield this.registerOrJoin(hash);
430
+ return registration.jobId;
431
+ });
432
+ }
433
+ registerOrJoin(hash) {
434
+ return __async(this, null, function* () {
435
+ if (this.isDestroyed) {
436
+ throw new Error("Dedupe store has been destroyed");
437
+ }
438
+ const now = Date.now();
439
+ const candidateJobId = crypto.randomUUID();
440
+ const registerTransaction = this.sqlite.transaction(
441
+ (requestHash, createdAt, jobId) => {
442
+ const existing = this.sqlite.prepare(
443
+ "SELECT job_id as jobId, status FROM dedupe_jobs WHERE hash = ? LIMIT 1"
444
+ ).get(requestHash);
445
+ if (existing && existing.status === "pending") {
446
+ return {
447
+ jobId: existing.jobId,
448
+ isOwner: false
449
+ };
450
+ }
451
+ this.sqlite.prepare(
452
+ `
453
+ INSERT INTO dedupe_jobs (hash, job_id, status, result, error, created_at, updated_at)
454
+ VALUES (?, ?, 'pending', NULL, NULL, ?, ?)
455
+ ON CONFLICT(hash) DO UPDATE SET
456
+ job_id = excluded.job_id,
457
+ status = excluded.status,
458
+ result = NULL,
459
+ error = NULL,
460
+ created_at = excluded.created_at,
461
+ updated_at = excluded.updated_at
462
+ `
463
+ ).run(requestHash, jobId, createdAt, createdAt);
464
+ return {
465
+ jobId,
466
+ isOwner: true
467
+ };
468
+ }
469
+ );
470
+ return registerTransaction(hash, now, candidateJobId);
471
+ });
472
+ }
473
+ complete(hash, value) {
474
+ return __async(this, null, function* () {
475
+ var _a;
476
+ if (this.isDestroyed) {
477
+ throw new Error("Dedupe store has been destroyed");
478
+ }
479
+ let serializedResult;
480
+ if (value === void 0) {
481
+ serializedResult = "__UNDEFINED__";
482
+ } else if (value === null) {
483
+ serializedResult = "__NULL__";
484
+ } else {
485
+ try {
486
+ serializedResult = JSON.stringify(value);
487
+ } catch (error) {
488
+ throw new Error(
489
+ `Failed to serialize result: ${error instanceof Error ? error.message : String(error)}`
490
+ );
491
+ }
492
+ }
493
+ const now = Date.now();
494
+ const existingJob = yield this.db.select().from(dedupeTable).where(drizzleOrm.eq(dedupeTable.hash, hash)).limit(1);
495
+ if (existingJob.length > 0 && ((_a = existingJob[0]) == null ? void 0 : _a.status) === "completed") {
496
+ return;
497
+ }
498
+ yield this.db.update(dedupeTable).set({
499
+ status: "completed",
500
+ result: serializedResult,
501
+ updatedAt: now
502
+ }).where(drizzleOrm.eq(dedupeTable.hash, hash));
503
+ const settle = this.jobSettlers.get(hash);
504
+ if (settle) {
505
+ settle(value);
506
+ }
507
+ });
508
+ }
509
+ fail(hash, error) {
510
+ return __async(this, null, function* () {
511
+ if (this.isDestroyed) {
512
+ throw new Error("Dedupe store has been destroyed");
513
+ }
514
+ const now = Date.now();
515
+ yield this.db.update(dedupeTable).set({
516
+ status: "failed",
517
+ error: error.message,
518
+ updatedAt: now
519
+ }).where(drizzleOrm.eq(dedupeTable.hash, hash));
520
+ const settle = this.jobSettlers.get(hash);
521
+ if (settle) {
522
+ settle(void 0);
523
+ }
524
+ });
525
+ }
526
+ isInProgress(hash) {
527
+ return __async(this, null, function* () {
528
+ if (this.isDestroyed) {
529
+ throw new Error("Dedupe store has been destroyed");
530
+ }
531
+ const result = yield this.db.select().from(dedupeTable).where(drizzleOrm.eq(dedupeTable.hash, hash)).limit(1);
532
+ if (result.length === 0) {
533
+ return false;
534
+ }
535
+ const job = result[0];
536
+ if (!job) {
537
+ return false;
538
+ }
539
+ const jobExpired = this.jobTimeoutMs > 0 && Date.now() - job.createdAt >= this.jobTimeoutMs;
540
+ if (jobExpired) {
541
+ yield this.db.delete(dedupeTable).where(drizzleOrm.eq(dedupeTable.hash, hash));
542
+ return false;
543
+ }
544
+ return job.status === "pending";
545
+ });
546
+ }
547
+ getResult(hash) {
548
+ return __async(this, null, function* () {
549
+ const result = yield this.db.select().from(dedupeTable).where(drizzleOrm.eq(dedupeTable.hash, hash)).limit(1);
550
+ if (result.length === 0) {
551
+ return void 0;
552
+ }
553
+ const job = result[0];
554
+ if (!job) {
555
+ return void 0;
556
+ }
557
+ const now = Date.now();
558
+ const isExpired = now - job.createdAt > this.jobTimeoutMs;
559
+ if (isExpired) {
560
+ yield this.db.delete(dedupeTable).where(drizzleOrm.eq(dedupeTable.hash, hash));
561
+ return void 0;
562
+ }
563
+ if (job.status === "completed") {
564
+ try {
565
+ if (job.result === "__UNDEFINED__") {
566
+ return void 0;
567
+ } else if (job.result === "__NULL__") {
568
+ return null;
569
+ } else if (job.result) {
570
+ return JSON.parse(job.result);
571
+ }
572
+ return void 0;
573
+ } catch (e) {
574
+ return void 0;
575
+ }
576
+ }
577
+ return void 0;
578
+ });
579
+ }
580
+ /**
581
+ * Get statistics about dedupe jobs
582
+ */
583
+ getStats() {
584
+ return __async(this, null, function* () {
585
+ var _a, _b, _c, _d, _e;
586
+ const now = Date.now();
587
+ const expiredTime = now - this.jobTimeoutMs;
588
+ const totalResult = yield this.db.select({ count: drizzleOrm.count() }).from(dedupeTable);
589
+ const pendingResult = yield this.db.select({ count: drizzleOrm.count() }).from(dedupeTable).where(drizzleOrm.eq(dedupeTable.status, "pending"));
590
+ const completedResult = yield this.db.select({ count: drizzleOrm.count() }).from(dedupeTable).where(drizzleOrm.eq(dedupeTable.status, "completed"));
591
+ const failedResult = yield this.db.select({ count: drizzleOrm.count() }).from(dedupeTable).where(drizzleOrm.eq(dedupeTable.status, "failed"));
592
+ const expiredResult = yield this.db.select({ count: drizzleOrm.count() }).from(dedupeTable).where(drizzleOrm.lt(dedupeTable.createdAt, expiredTime));
593
+ return {
594
+ totalJobs: ((_a = totalResult[0]) == null ? void 0 : _a.count) || 0,
595
+ pendingJobs: ((_b = pendingResult[0]) == null ? void 0 : _b.count) || 0,
596
+ completedJobs: ((_c = completedResult[0]) == null ? void 0 : _c.count) || 0,
597
+ failedJobs: ((_d = failedResult[0]) == null ? void 0 : _d.count) || 0,
598
+ expiredJobs: ((_e = expiredResult[0]) == null ? void 0 : _e.count) || 0
599
+ };
600
+ });
601
+ }
602
+ /**
603
+ * Clean up expired jobs
604
+ */
605
+ cleanup() {
606
+ return __async(this, null, function* () {
607
+ const now = Date.now();
608
+ const expiredTime = now - this.jobTimeoutMs;
609
+ yield this.db.delete(dedupeTable).where(drizzleOrm.lt(dedupeTable.createdAt, expiredTime));
610
+ });
611
+ }
612
+ /**
613
+ * Clear all jobs
614
+ */
615
+ clear() {
616
+ return __async(this, null, function* () {
617
+ yield this.db.delete(dedupeTable);
618
+ for (const settle of this.jobSettlers.values()) {
619
+ settle(void 0);
620
+ }
621
+ this.jobPromises.clear();
622
+ this.jobSettlers.clear();
623
+ });
624
+ }
625
+ /**
626
+ * Close the database connection
627
+ */
628
+ close() {
629
+ return __async(this, null, function* () {
630
+ if (this.cleanupInterval) {
631
+ clearInterval(this.cleanupInterval);
632
+ this.cleanupInterval = void 0;
633
+ }
634
+ this.isDestroyed = true;
635
+ for (const settle of this.jobSettlers.values()) {
636
+ settle(void 0);
637
+ }
638
+ this.jobPromises.clear();
639
+ this.jobSettlers.clear();
640
+ if (this.isConnectionManaged && typeof this.sqlite.close === "function") {
641
+ this.sqlite.close();
642
+ }
643
+ });
644
+ }
645
+ /**
646
+ * Alias for close() to match test expectations
647
+ */
648
+ destroy() {
649
+ this.close();
650
+ }
651
+ deserializeResult(serializedResult) {
652
+ try {
653
+ if (serializedResult === "__UNDEFINED__") {
654
+ return void 0;
655
+ }
656
+ if (serializedResult === "__NULL__") {
657
+ return null;
658
+ }
659
+ if (serializedResult) {
660
+ return JSON.parse(serializedResult);
661
+ }
662
+ return void 0;
663
+ } catch (e) {
664
+ return void 0;
665
+ }
666
+ }
667
+ initializeDatabase() {
668
+ this.db.run(drizzleOrm.sql`
669
+ CREATE TABLE IF NOT EXISTS dedupe_jobs (
670
+ hash TEXT PRIMARY KEY,
671
+ job_id TEXT NOT NULL,
672
+ status TEXT NOT NULL,
673
+ result BLOB,
674
+ error TEXT,
675
+ created_at INTEGER NOT NULL,
676
+ updated_at INTEGER NOT NULL
677
+ )
678
+ `);
679
+ this.db.run(drizzleOrm.sql`
680
+ CREATE INDEX IF NOT EXISTS idx_dedupe_status ON dedupe_jobs(status)
681
+ `);
682
+ }
683
+ };
684
+ var SQLiteRateLimitStore = class {
685
+ constructor({
686
+ /** File path or existing `better-sqlite3` Database instance. Defaults to `':memory:'`. */
687
+ database = ":memory:",
688
+ /** Global/default rate-limit config applied when a resource-specific override is not provided. */
689
+ defaultConfig = core.DEFAULT_RATE_LIMIT,
690
+ /** Optional per-resource overrides. */
691
+ resourceConfigs = /* @__PURE__ */ new Map()
692
+ } = {}) {
693
+ /** Indicates whether this store manages (and should close) the SQLite connection */
694
+ this.isConnectionManaged = false;
695
+ this.resourceConfigs = /* @__PURE__ */ new Map();
696
+ this.isDestroyed = false;
697
+ let sqliteInstance;
698
+ let isConnectionManaged = false;
699
+ if (typeof database === "string") {
700
+ sqliteInstance = new Database__default.default(database);
701
+ isConnectionManaged = true;
702
+ } else {
703
+ sqliteInstance = database;
704
+ }
705
+ this.sqlite = sqliteInstance;
706
+ this.isConnectionManaged = isConnectionManaged;
707
+ this.db = betterSqlite3.drizzle(sqliteInstance);
708
+ this.defaultConfig = defaultConfig;
709
+ this.resourceConfigs = resourceConfigs;
710
+ this.initializeDatabase();
711
+ }
712
+ canProceed(resource) {
713
+ return __async(this, null, function* () {
714
+ var _a;
715
+ if (this.isDestroyed) {
716
+ throw new Error("Rate limit store has been destroyed");
717
+ }
718
+ const config = this.resourceConfigs.get(resource) || this.defaultConfig;
719
+ const now = Date.now();
720
+ const windowStart = now - config.windowMs;
721
+ yield this.cleanupExpiredRequests(resource, windowStart);
722
+ const result = yield this.db.select({ count: drizzleOrm.count() }).from(rateLimitTable).where(
723
+ drizzleOrm.and(
724
+ drizzleOrm.eq(rateLimitTable.resource, resource),
725
+ drizzleOrm.gte(rateLimitTable.timestamp, windowStart)
726
+ )
727
+ );
728
+ const currentCount = ((_a = result[0]) == null ? void 0 : _a.count) || 0;
729
+ return currentCount < config.limit;
730
+ });
731
+ }
732
+ record(resource) {
733
+ return __async(this, null, function* () {
734
+ if (this.isDestroyed) {
735
+ throw new Error("Rate limit store has been destroyed");
736
+ }
737
+ const now = Date.now();
738
+ yield this.db.insert(rateLimitTable).values({
739
+ resource,
740
+ timestamp: now
741
+ });
742
+ });
743
+ }
744
+ getStatus(resource) {
745
+ return __async(this, null, function* () {
746
+ var _a;
747
+ if (this.isDestroyed) {
748
+ throw new Error("Rate limit store has been destroyed");
749
+ }
750
+ const config = this.resourceConfigs.get(resource) || this.defaultConfig;
751
+ const now = Date.now();
752
+ const windowStart = now - config.windowMs;
753
+ yield this.cleanupExpiredRequests(resource, windowStart);
754
+ const result = yield this.db.select({ count: drizzleOrm.count() }).from(rateLimitTable).where(
755
+ drizzleOrm.and(
756
+ drizzleOrm.eq(rateLimitTable.resource, resource),
757
+ drizzleOrm.gte(rateLimitTable.timestamp, windowStart)
758
+ )
759
+ );
760
+ const currentRequests = ((_a = result[0]) == null ? void 0 : _a.count) || 0;
761
+ const remaining = Math.max(0, config.limit - currentRequests);
762
+ const resetTime = new Date(now + config.windowMs);
763
+ return {
764
+ remaining,
765
+ resetTime,
766
+ limit: config.limit
767
+ };
768
+ });
769
+ }
770
+ reset(resource) {
771
+ return __async(this, null, function* () {
772
+ if (this.isDestroyed) {
773
+ throw new Error("Rate limit store has been destroyed");
774
+ }
775
+ yield this.db.delete(rateLimitTable).where(drizzleOrm.eq(rateLimitTable.resource, resource));
776
+ });
777
+ }
778
+ getWaitTime(resource) {
779
+ return __async(this, null, function* () {
780
+ var _a, _b;
781
+ if (this.isDestroyed) {
782
+ throw new Error("Rate limit store has been destroyed");
783
+ }
784
+ const config = this.resourceConfigs.get(resource) || this.defaultConfig;
785
+ if (config.limit === 0) {
786
+ return config.windowMs;
787
+ }
788
+ const now = Date.now();
789
+ const windowStart = now - config.windowMs;
790
+ yield this.cleanupExpiredRequests(resource, windowStart);
791
+ const countResult = yield this.db.select({ count: drizzleOrm.count() }).from(rateLimitTable).where(
792
+ drizzleOrm.and(
793
+ drizzleOrm.eq(rateLimitTable.resource, resource),
794
+ drizzleOrm.gte(rateLimitTable.timestamp, windowStart)
795
+ )
796
+ );
797
+ const currentRequests = ((_a = countResult[0]) == null ? void 0 : _a.count) || 0;
798
+ if (currentRequests < config.limit) {
799
+ return 0;
800
+ }
801
+ const oldestResult = yield this.db.select({ timestamp: rateLimitTable.timestamp }).from(rateLimitTable).where(
802
+ drizzleOrm.and(
803
+ drizzleOrm.eq(rateLimitTable.resource, resource),
804
+ drizzleOrm.gte(rateLimitTable.timestamp, windowStart)
805
+ )
806
+ ).orderBy(rateLimitTable.timestamp).limit(1);
807
+ if (oldestResult.length === 0) {
808
+ return 0;
809
+ }
810
+ const oldestTimestamp = (_b = oldestResult[0]) == null ? void 0 : _b.timestamp;
811
+ if (oldestTimestamp === void 0) {
812
+ return 0;
813
+ }
814
+ const timeUntilOldestExpires = oldestTimestamp + config.windowMs - now;
815
+ return Math.max(0, timeUntilOldestExpires);
816
+ });
817
+ }
818
+ /**
819
+ * Set rate limit configuration for a specific resource
820
+ */
821
+ setResourceConfig(resource, config) {
822
+ this.resourceConfigs.set(resource, config);
823
+ }
824
+ /**
825
+ * Get rate limit configuration for a resource
826
+ */
827
+ getResourceConfig(resource) {
828
+ return this.resourceConfigs.get(resource) || this.defaultConfig;
829
+ }
830
+ /**
831
+ * Get statistics for all resources
832
+ */
833
+ getStats() {
834
+ return __async(this, null, function* () {
835
+ var _a;
836
+ if (this.isDestroyed) {
837
+ throw new Error("Rate limit store has been destroyed");
838
+ }
839
+ const totalResult = yield this.db.select({ count: drizzleOrm.count() }).from(rateLimitTable);
840
+ const resourcesResult = yield this.db.select({ resource: rateLimitTable.resource }).from(rateLimitTable).groupBy(rateLimitTable.resource);
841
+ const uniqueResources = resourcesResult.length;
842
+ const rateLimitedResources = [];
843
+ for (const { resource } of resourcesResult) {
844
+ const canProceed = yield this.canProceed(resource);
845
+ if (!canProceed) {
846
+ rateLimitedResources.push(resource);
847
+ }
848
+ }
849
+ return {
850
+ totalRequests: ((_a = totalResult[0]) == null ? void 0 : _a.count) || 0,
851
+ uniqueResources,
852
+ rateLimitedResources
853
+ };
854
+ });
855
+ }
856
+ /**
857
+ * Clean up all rate limit data
858
+ */
859
+ clear() {
860
+ return __async(this, null, function* () {
861
+ if (this.isDestroyed) {
862
+ throw new Error("Rate limit store has been destroyed");
863
+ }
864
+ yield this.db.delete(rateLimitTable);
865
+ });
866
+ }
867
+ /**
868
+ * Clean up expired requests for all resources
869
+ */
870
+ cleanup() {
871
+ return __async(this, null, function* () {
872
+ const now = Date.now();
873
+ const resources = yield this.db.select({ resource: rateLimitTable.resource }).from(rateLimitTable).groupBy(rateLimitTable.resource);
874
+ for (const { resource } of resources) {
875
+ const config = this.resourceConfigs.get(resource) || this.defaultConfig;
876
+ const windowStart = now - config.windowMs;
877
+ yield this.cleanupExpiredRequests(resource, windowStart);
878
+ }
879
+ });
880
+ }
881
+ /**
882
+ * Close the database connection
883
+ */
884
+ close() {
885
+ return __async(this, null, function* () {
886
+ this.isDestroyed = true;
887
+ if (this.isConnectionManaged && typeof this.sqlite.close === "function") {
888
+ this.sqlite.close();
889
+ }
890
+ });
891
+ }
892
+ /**
893
+ * Alias for close() to match test expectations
894
+ */
895
+ destroy() {
896
+ this.close();
897
+ }
898
+ cleanupExpiredRequests(resource, windowStart) {
899
+ return __async(this, null, function* () {
900
+ yield this.db.delete(rateLimitTable).where(
901
+ drizzleOrm.and(
902
+ drizzleOrm.eq(rateLimitTable.resource, resource),
903
+ drizzleOrm.lt(rateLimitTable.timestamp, windowStart)
904
+ )
905
+ );
906
+ });
907
+ }
908
+ initializeDatabase() {
909
+ this.db.run(drizzleOrm.sql`
910
+ CREATE TABLE IF NOT EXISTS rate_limits (
911
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
912
+ resource TEXT NOT NULL,
913
+ timestamp INTEGER NOT NULL
914
+ )
915
+ `);
916
+ this.db.run(drizzleOrm.sql`
917
+ CREATE INDEX IF NOT EXISTS idx_rate_limit_resource ON rate_limits(resource)
918
+ `);
919
+ this.db.run(drizzleOrm.sql`
920
+ CREATE INDEX IF NOT EXISTS idx_rate_limit_timestamp ON rate_limits(timestamp)
921
+ `);
922
+ }
923
+ };
924
+ var DEFAULT_RATE_LIMIT2 = {
925
+ limit: 200,
926
+ windowMs: 36e5
927
+ // 1 hour
928
+ };
929
+ var SqliteAdaptiveRateLimitStore = class {
930
+ constructor({
931
+ database = ":memory:",
932
+ defaultConfig = DEFAULT_RATE_LIMIT2,
933
+ resourceConfigs = /* @__PURE__ */ new Map(),
934
+ adaptiveConfig = {}
935
+ } = {}) {
936
+ /** Indicates whether this store manages (and should close) the SQLite connection */
937
+ this.isConnectionManaged = false;
938
+ this.resourceConfigs = /* @__PURE__ */ new Map();
939
+ this.isDestroyed = false;
940
+ this.activityMetrics = /* @__PURE__ */ new Map();
941
+ this.lastCapacityUpdate = /* @__PURE__ */ new Map();
942
+ this.cachedCapacity = /* @__PURE__ */ new Map();
943
+ let sqliteInstance;
944
+ let isConnectionManaged = false;
945
+ if (typeof database === "string") {
946
+ sqliteInstance = new Database__default.default(database);
947
+ isConnectionManaged = true;
948
+ } else {
949
+ sqliteInstance = database;
950
+ }
951
+ this.sqlite = sqliteInstance;
952
+ this.isConnectionManaged = isConnectionManaged;
953
+ this.db = betterSqlite3.drizzle(sqliteInstance);
954
+ this.defaultConfig = defaultConfig;
955
+ this.resourceConfigs = resourceConfigs;
956
+ this.capacityCalculator = new core.AdaptiveCapacityCalculator(adaptiveConfig);
957
+ this.initializeDatabase();
958
+ }
959
+ canProceed(resource, priority = "background") {
960
+ return __async(this, null, function* () {
961
+ if (this.isDestroyed) {
962
+ throw new Error("Rate limit store has been destroyed");
963
+ }
964
+ yield this.ensureActivityMetrics(resource);
965
+ const metrics = this.getOrCreateActivityMetrics(resource);
966
+ const capacity = this.calculateCurrentCapacity(resource, metrics);
967
+ if (priority === "background" && capacity.backgroundPaused) {
968
+ return false;
969
+ }
970
+ const currentUserRequests = yield this.getCurrentUsage(resource, "user");
971
+ const currentBackgroundRequests = yield this.getCurrentUsage(
972
+ resource,
973
+ "background"
974
+ );
975
+ if (priority === "user") {
976
+ return currentUserRequests < capacity.userReserved;
977
+ } else {
978
+ return currentBackgroundRequests < capacity.backgroundMax;
979
+ }
980
+ });
981
+ }
982
+ record(resource, priority = "background") {
983
+ return __async(this, null, function* () {
984
+ if (this.isDestroyed) {
985
+ throw new Error("Rate limit store has been destroyed");
986
+ }
987
+ const now = Date.now();
988
+ this.db.run(drizzleOrm.sql`
989
+ INSERT INTO rate_limits (resource, timestamp, priority)
990
+ VALUES (${resource}, ${now}, ${priority})
991
+ `);
992
+ const metrics = this.getOrCreateActivityMetrics(resource);
993
+ if (priority === "user") {
994
+ metrics.recentUserRequests.push(now);
995
+ this.cleanupOldRequests(metrics.recentUserRequests);
996
+ } else {
997
+ metrics.recentBackgroundRequests.push(now);
998
+ this.cleanupOldRequests(metrics.recentBackgroundRequests);
999
+ }
1000
+ metrics.userActivityTrend = this.capacityCalculator.calculateActivityTrend(
1001
+ metrics.recentUserRequests
1002
+ );
1003
+ });
1004
+ }
1005
+ getStatus(resource) {
1006
+ return __async(this, null, function* () {
1007
+ if (this.isDestroyed) {
1008
+ throw new Error("Rate limit store has been destroyed");
1009
+ }
1010
+ yield this.ensureActivityMetrics(resource);
1011
+ const metrics = this.getOrCreateActivityMetrics(resource);
1012
+ const capacity = this.calculateCurrentCapacity(resource, metrics);
1013
+ const currentUserUsage = yield this.getCurrentUsage(resource, "user");
1014
+ const currentBackgroundUsage = yield this.getCurrentUsage(
1015
+ resource,
1016
+ "background"
1017
+ );
1018
+ const config = this.resourceConfigs.get(resource) || this.defaultConfig;
1019
+ return {
1020
+ remaining: capacity.userReserved - currentUserUsage + (capacity.backgroundMax - currentBackgroundUsage),
1021
+ resetTime: new Date(Date.now() + config.windowMs),
1022
+ limit: this.getResourceLimit(resource),
1023
+ adaptive: {
1024
+ userReserved: capacity.userReserved,
1025
+ backgroundMax: capacity.backgroundMax,
1026
+ backgroundPaused: capacity.backgroundPaused,
1027
+ recentUserActivity: this.capacityCalculator.getRecentActivity(
1028
+ metrics.recentUserRequests
1029
+ ),
1030
+ reason: capacity.reason
1031
+ }
1032
+ };
1033
+ });
1034
+ }
1035
+ reset(resource) {
1036
+ return __async(this, null, function* () {
1037
+ if (this.isDestroyed) {
1038
+ throw new Error("Rate limit store has been destroyed");
1039
+ }
1040
+ yield this.db.delete(rateLimitTable).where(drizzleOrm.eq(rateLimitTable.resource, resource));
1041
+ this.activityMetrics.delete(resource);
1042
+ this.cachedCapacity.delete(resource);
1043
+ this.lastCapacityUpdate.delete(resource);
1044
+ });
1045
+ }
1046
+ getWaitTime(resource, priority = "background") {
1047
+ return __async(this, null, function* () {
1048
+ if (this.isDestroyed) {
1049
+ throw new Error("Rate limit store has been destroyed");
1050
+ }
1051
+ const config = this.resourceConfigs.get(resource) || this.defaultConfig;
1052
+ if (config.limit === 0) {
1053
+ return config.windowMs;
1054
+ }
1055
+ const canProceed = yield this.canProceed(resource, priority);
1056
+ if (canProceed) {
1057
+ return 0;
1058
+ }
1059
+ yield this.ensureActivityMetrics(resource);
1060
+ const metrics = this.getOrCreateActivityMetrics(resource);
1061
+ const capacity = this.calculateCurrentCapacity(resource, metrics);
1062
+ if (priority === "background" && capacity.backgroundPaused) {
1063
+ return this.capacityCalculator.config.recalculationIntervalMs;
1064
+ }
1065
+ const now = Date.now();
1066
+ const windowStart = now - config.windowMs;
1067
+ const oldestResult = this.sqlite.prepare(
1068
+ `
1069
+ SELECT timestamp
1070
+ FROM rate_limits
1071
+ WHERE resource = ? AND COALESCE(priority, 'background') = ? AND timestamp >= ?
1072
+ ORDER BY timestamp
1073
+ LIMIT 1
1074
+ `
1075
+ ).get(resource, priority, windowStart);
1076
+ if (!oldestResult) {
1077
+ return 0;
1078
+ }
1079
+ const oldestTimestamp = oldestResult.timestamp;
1080
+ if (!oldestTimestamp) {
1081
+ return 0;
1082
+ }
1083
+ const timeUntilOldestExpires = oldestTimestamp + config.windowMs - now;
1084
+ return Math.max(0, timeUntilOldestExpires);
1085
+ });
1086
+ }
1087
+ /**
1088
+ * Set rate limit configuration for a specific resource
1089
+ */
1090
+ setResourceConfig(resource, config) {
1091
+ this.resourceConfigs.set(resource, config);
1092
+ }
1093
+ /**
1094
+ * Get rate limit configuration for a resource
1095
+ */
1096
+ getResourceConfig(resource) {
1097
+ return this.resourceConfigs.get(resource) || this.defaultConfig;
1098
+ }
1099
+ /**
1100
+ * Get statistics for all resources
1101
+ */
1102
+ getStats() {
1103
+ return __async(this, null, function* () {
1104
+ var _a;
1105
+ if (this.isDestroyed) {
1106
+ throw new Error("Rate limit store has been destroyed");
1107
+ }
1108
+ const totalResult = yield this.db.select({ count: drizzleOrm.count() }).from(rateLimitTable);
1109
+ const resourcesResult = yield this.db.select({ resource: rateLimitTable.resource }).from(rateLimitTable).groupBy(rateLimitTable.resource);
1110
+ const uniqueResources = resourcesResult.length;
1111
+ const rateLimitedResources = [];
1112
+ for (const { resource } of resourcesResult) {
1113
+ const canProceed = yield this.canProceed(resource);
1114
+ if (!canProceed) {
1115
+ rateLimitedResources.push(resource);
1116
+ }
1117
+ }
1118
+ return {
1119
+ totalRequests: ((_a = totalResult[0]) == null ? void 0 : _a.count) || 0,
1120
+ uniqueResources,
1121
+ rateLimitedResources
1122
+ };
1123
+ });
1124
+ }
1125
+ /**
1126
+ * Clean up all rate limit data
1127
+ */
1128
+ clear() {
1129
+ return __async(this, null, function* () {
1130
+ if (this.isDestroyed) {
1131
+ throw new Error("Rate limit store has been destroyed");
1132
+ }
1133
+ yield this.db.delete(rateLimitTable);
1134
+ this.activityMetrics.clear();
1135
+ this.cachedCapacity.clear();
1136
+ this.lastCapacityUpdate.clear();
1137
+ });
1138
+ }
1139
+ /**
1140
+ * Clean up expired requests for all resources
1141
+ */
1142
+ cleanup() {
1143
+ return __async(this, null, function* () {
1144
+ const now = Date.now();
1145
+ const resources = yield this.db.select({ resource: rateLimitTable.resource }).from(rateLimitTable).groupBy(rateLimitTable.resource);
1146
+ for (const { resource } of resources) {
1147
+ const config = this.resourceConfigs.get(resource) || this.defaultConfig;
1148
+ const windowStart = now - config.windowMs;
1149
+ yield this.cleanupExpiredRequests(resource, windowStart);
1150
+ }
1151
+ });
1152
+ }
1153
+ /**
1154
+ * Close the database connection
1155
+ */
1156
+ close() {
1157
+ return __async(this, null, function* () {
1158
+ this.isDestroyed = true;
1159
+ if (this.isConnectionManaged && typeof this.sqlite.close === "function") {
1160
+ this.sqlite.close();
1161
+ }
1162
+ });
1163
+ }
1164
+ /**
1165
+ * Alias for close() to match test expectations
1166
+ */
1167
+ destroy() {
1168
+ this.close();
1169
+ }
1170
+ // Private helper methods for adaptive functionality
1171
+ calculateCurrentCapacity(resource, metrics) {
1172
+ const lastUpdate = this.lastCapacityUpdate.get(resource) || 0;
1173
+ const recalcInterval = this.capacityCalculator.config.recalculationIntervalMs;
1174
+ if (Date.now() - lastUpdate < recalcInterval) {
1175
+ return this.cachedCapacity.get(resource) || this.getDefaultCapacity(resource);
1176
+ }
1177
+ const totalLimit = this.getResourceLimit(resource);
1178
+ const capacity = this.capacityCalculator.calculateDynamicCapacity(
1179
+ resource,
1180
+ totalLimit,
1181
+ metrics
1182
+ );
1183
+ this.cachedCapacity.set(resource, capacity);
1184
+ this.lastCapacityUpdate.set(resource, Date.now());
1185
+ return capacity;
1186
+ }
1187
+ getOrCreateActivityMetrics(resource) {
1188
+ if (!this.activityMetrics.has(resource)) {
1189
+ this.activityMetrics.set(resource, {
1190
+ recentUserRequests: [],
1191
+ recentBackgroundRequests: [],
1192
+ userActivityTrend: "none"
1193
+ });
1194
+ }
1195
+ return this.activityMetrics.get(resource);
1196
+ }
1197
+ ensureActivityMetrics(resource) {
1198
+ return __async(this, null, function* () {
1199
+ if (this.activityMetrics.has(resource)) {
1200
+ return;
1201
+ }
1202
+ const now = Date.now();
1203
+ const windowStart = now - this.capacityCalculator.config.monitoringWindowMs;
1204
+ const recentRequests = this.sqlite.prepare(
1205
+ `
1206
+ SELECT timestamp, COALESCE(priority, 'background') as priority
1207
+ FROM rate_limits
1208
+ WHERE resource = ? AND timestamp >= ?
1209
+ ORDER BY timestamp
1210
+ `
1211
+ ).all(resource, windowStart);
1212
+ const metrics = {
1213
+ recentUserRequests: [],
1214
+ recentBackgroundRequests: [],
1215
+ userActivityTrend: "none"
1216
+ };
1217
+ for (const request of recentRequests) {
1218
+ if (request.priority === "user") {
1219
+ metrics.recentUserRequests.push(request.timestamp);
1220
+ } else {
1221
+ metrics.recentBackgroundRequests.push(request.timestamp);
1222
+ }
1223
+ }
1224
+ metrics.userActivityTrend = this.capacityCalculator.calculateActivityTrend(
1225
+ metrics.recentUserRequests
1226
+ );
1227
+ this.activityMetrics.set(resource, metrics);
1228
+ });
1229
+ }
1230
+ getCurrentUsage(resource, priority) {
1231
+ return __async(this, null, function* () {
1232
+ const config = this.resourceConfigs.get(resource) || this.defaultConfig;
1233
+ const now = Date.now();
1234
+ const windowStart = now - config.windowMs;
1235
+ yield this.cleanupExpiredRequests(resource, windowStart);
1236
+ const result = this.sqlite.prepare(
1237
+ `
1238
+ SELECT COUNT(*) as count
1239
+ FROM rate_limits
1240
+ WHERE resource = ? AND priority = ? AND timestamp >= ?
1241
+ `
1242
+ ).get(resource, priority, windowStart);
1243
+ return result.count || 0;
1244
+ });
1245
+ }
1246
+ cleanupOldRequests(requests) {
1247
+ const cutoff = Date.now() - this.capacityCalculator.config.monitoringWindowMs;
1248
+ while (requests.length > 0 && requests[0] < cutoff) {
1249
+ requests.shift();
1250
+ }
1251
+ }
1252
+ getResourceLimit(resource) {
1253
+ const config = this.resourceConfigs.get(resource) || this.defaultConfig;
1254
+ return config.limit;
1255
+ }
1256
+ getDefaultCapacity(resource) {
1257
+ const limit = this.getResourceLimit(resource);
1258
+ return {
1259
+ userReserved: Math.floor(limit * 0.3),
1260
+ backgroundMax: Math.floor(limit * 0.7),
1261
+ backgroundPaused: false,
1262
+ reason: "Default capacity allocation"
1263
+ };
1264
+ }
1265
+ cleanupExpiredRequests(resource, windowStart) {
1266
+ return __async(this, null, function* () {
1267
+ yield this.db.delete(rateLimitTable).where(
1268
+ drizzleOrm.and(
1269
+ drizzleOrm.eq(rateLimitTable.resource, resource),
1270
+ drizzleOrm.lt(rateLimitTable.timestamp, windowStart)
1271
+ )
1272
+ );
1273
+ });
1274
+ }
1275
+ initializeDatabase() {
1276
+ this.db.run(drizzleOrm.sql`
1277
+ CREATE TABLE IF NOT EXISTS rate_limits (
1278
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1279
+ resource TEXT NOT NULL,
1280
+ timestamp INTEGER NOT NULL,
1281
+ priority TEXT NOT NULL DEFAULT 'background'
1282
+ )
1283
+ `);
1284
+ try {
1285
+ this.db.run(drizzleOrm.sql`
1286
+ ALTER TABLE rate_limits ADD COLUMN priority TEXT DEFAULT 'background'
1287
+ `);
1288
+ } catch (e) {
1289
+ }
1290
+ this.db.run(drizzleOrm.sql`
1291
+ CREATE INDEX IF NOT EXISTS idx_rate_limit_resource ON rate_limits(resource)
1292
+ `);
1293
+ this.db.run(drizzleOrm.sql`
1294
+ CREATE INDEX IF NOT EXISTS idx_rate_limit_timestamp ON rate_limits(timestamp)
1295
+ `);
1296
+ this.db.run(drizzleOrm.sql`
1297
+ CREATE INDEX IF NOT EXISTS idx_rate_limit_resource_priority_timestamp
1298
+ ON rate_limits(resource, priority, timestamp)
1299
+ `);
1300
+ }
1301
+ };
1302
+
1303
+ exports.SQLiteCacheStore = SQLiteCacheStore;
1304
+ exports.SQLiteDedupeStore = SQLiteDedupeStore;
1305
+ exports.SQLiteRateLimitStore = SQLiteRateLimitStore;
1306
+ exports.SqliteAdaptiveRateLimitStore = SqliteAdaptiveRateLimitStore;
1307
+ exports.cacheTable = cacheTable;
1308
+ exports.dedupeTable = dedupeTable;
1309
+ exports.rateLimitTable = rateLimitTable;
1310
+ //# sourceMappingURL=index.cjs.map
1311
+ //# sourceMappingURL=index.cjs.map