@usebetterdev/audit-drizzle 0.6.1 → 0.8.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.js CHANGED
@@ -1,4 +1,5 @@
1
1
  // src/adapter.ts
2
+ import { encodeCursor, assembleStats } from "@usebetterdev/audit-core";
2
3
  import { and as and2, eq as eq2, gte as gte2, lt as lt2, isNotNull, sql as sql2, desc as desc2 } from "drizzle-orm";
3
4
 
4
5
  // src/schema.ts
@@ -50,23 +51,7 @@ var auditLogs = pgTable(
50
51
  );
51
52
 
52
53
  // src/column-map.ts
53
- var VALID_OPERATIONS = /* @__PURE__ */ new Set([
54
- "INSERT",
55
- "UPDATE",
56
- "DELETE"
57
- ]);
58
- var VALID_SEVERITIES = /* @__PURE__ */ new Set([
59
- "low",
60
- "medium",
61
- "high",
62
- "critical"
63
- ]);
64
- function isAuditOperation(value) {
65
- return VALID_OPERATIONS.has(value);
66
- }
67
- function isAuditSeverity(value) {
68
- return VALID_SEVERITIES.has(value);
69
- }
54
+ import { isAuditOperation, isAuditSeverity } from "@usebetterdev/audit-core";
70
55
  function auditLogToRow(log) {
71
56
  const row = {
72
57
  id: log.id,
@@ -171,7 +156,10 @@ function rowToAuditLog(row) {
171
156
  }
172
157
 
173
158
  // src/query.ts
174
- import { parseDuration } from "@usebetterdev/audit-core";
159
+ import {
160
+ decodeCursor,
161
+ interpretFilters
162
+ } from "@usebetterdev/audit-core";
175
163
  import {
176
164
  and,
177
165
  or,
@@ -186,85 +174,66 @@ import {
186
174
  desc,
187
175
  sql
188
176
  } from "drizzle-orm";
189
- function escapeLikePattern(input) {
190
- return input.replace(/[%_\\]/g, "\\$&");
191
- }
192
- function resolveTimeFilter(filter) {
193
- if ("date" in filter && filter.date !== void 0) {
194
- return filter.date;
195
- }
196
- if ("duration" in filter && filter.duration !== void 0) {
197
- return parseDuration(filter.duration);
198
- }
199
- throw new Error("TimeFilter must have either date or duration");
200
- }
201
- function buildWhereConditions(filters) {
202
- const conditions = [];
203
- if (filters.resource !== void 0) {
204
- conditions.push(eq(auditLogs.tableName, filters.resource.tableName));
205
- if (filters.resource.recordId !== void 0) {
206
- conditions.push(eq(auditLogs.recordId, filters.resource.recordId));
177
+ var FIELD_COLUMNS = {
178
+ tableName: auditLogs.tableName,
179
+ recordId: auditLogs.recordId,
180
+ actorId: auditLogs.actorId,
181
+ severity: auditLogs.severity,
182
+ operation: auditLogs.operation
183
+ };
184
+ function mapCondition(condition) {
185
+ switch (condition.kind) {
186
+ case "eq": {
187
+ return eq(FIELD_COLUMNS[condition.field], condition.value);
207
188
  }
208
- }
209
- if (filters.actorIds !== void 0 && filters.actorIds.length > 0) {
210
- if (filters.actorIds.length === 1) {
211
- conditions.push(eq(auditLogs.actorId, filters.actorIds[0]));
212
- } else {
213
- conditions.push(inArray(auditLogs.actorId, filters.actorIds));
189
+ case "in": {
190
+ return inArray(FIELD_COLUMNS[condition.field], condition.values);
214
191
  }
215
- }
216
- if (filters.severities !== void 0 && filters.severities.length > 0) {
217
- if (filters.severities.length === 1) {
218
- conditions.push(eq(auditLogs.severity, filters.severities[0]));
219
- } else {
220
- conditions.push(inArray(auditLogs.severity, filters.severities));
192
+ case "timestampGte": {
193
+ return gte(auditLogs.timestamp, condition.value);
221
194
  }
222
- }
223
- if (filters.operations !== void 0 && filters.operations.length > 0) {
224
- if (filters.operations.length === 1) {
225
- conditions.push(eq(auditLogs.operation, filters.operations[0]));
226
- } else {
227
- conditions.push(inArray(auditLogs.operation, filters.operations));
195
+ case "timestampLte": {
196
+ return lte(auditLogs.timestamp, condition.value);
228
197
  }
229
- }
230
- if (filters.since !== void 0) {
231
- conditions.push(gte(auditLogs.timestamp, resolveTimeFilter(filters.since)));
232
- }
233
- if (filters.until !== void 0) {
234
- conditions.push(lte(auditLogs.timestamp, resolveTimeFilter(filters.until)));
235
- }
236
- if (filters.searchText !== void 0 && filters.searchText.length > 0) {
237
- const escaped = escapeLikePattern(filters.searchText);
238
- const pattern = `%${escaped}%`;
239
- const searchCondition = or(
240
- ilike(auditLogs.label, pattern),
241
- ilike(auditLogs.description, pattern)
242
- );
243
- if (searchCondition !== void 0) {
244
- conditions.push(searchCondition);
198
+ case "search": {
199
+ return or(
200
+ ilike(auditLogs.label, condition.pattern),
201
+ ilike(auditLogs.description, condition.pattern)
202
+ );
203
+ }
204
+ case "compliance": {
205
+ return sql`${auditLogs.compliance} @> ${JSON.stringify(condition.tags)}::jsonb`;
206
+ }
207
+ case "cursor": {
208
+ const tsCompare = condition.sortOrder === "asc" ? gt : lt;
209
+ const idCompare = condition.sortOrder === "asc" ? gt : lt;
210
+ return or(
211
+ tsCompare(auditLogs.timestamp, condition.timestamp),
212
+ and(
213
+ eq(auditLogs.timestamp, condition.timestamp),
214
+ idCompare(auditLogs.id, condition.id)
215
+ )
216
+ );
245
217
  }
246
218
  }
247
- if (filters.compliance !== void 0 && filters.compliance.length > 0) {
248
- conditions.push(
249
- sql`${auditLogs.compliance} @> ${JSON.stringify(filters.compliance)}::jsonb`
250
- );
219
+ }
220
+ function buildWhereConditions(filters, cursor, sortOrder) {
221
+ const decoded = cursor !== void 0 ? decodeCursor(cursor) : void 0;
222
+ const irConditions = interpretFilters(filters, {
223
+ cursor: decoded,
224
+ sortOrder
225
+ });
226
+ const sqlConditions = [];
227
+ for (const c of irConditions) {
228
+ const mapped = mapCondition(c);
229
+ if (mapped !== void 0) {
230
+ sqlConditions.push(mapped);
231
+ }
251
232
  }
252
- if (conditions.length === 0) {
233
+ if (sqlConditions.length === 0) {
253
234
  return void 0;
254
235
  }
255
- return and(...conditions);
256
- }
257
- function buildCursorCondition(cursor, sortOrder) {
258
- const decoded = decodeCursor(cursor);
259
- const tsCompare = sortOrder === "asc" ? gt : lt;
260
- const idCompare = sortOrder === "asc" ? gt : lt;
261
- return or(
262
- tsCompare(auditLogs.timestamp, decoded.timestamp),
263
- and(
264
- eq(auditLogs.timestamp, decoded.timestamp),
265
- idCompare(auditLogs.id, decoded.id)
266
- )
267
- );
236
+ return and(...sqlConditions);
268
237
  }
269
238
  function buildOrderBy(sortOrder) {
270
239
  if (sortOrder === "asc") {
@@ -272,47 +241,10 @@ function buildOrderBy(sortOrder) {
272
241
  }
273
242
  return [desc(auditLogs.timestamp), desc(auditLogs.id)];
274
243
  }
275
- function encodeCursor(timestamp2, id) {
276
- const payload = JSON.stringify({ t: timestamp2.toISOString(), i: id });
277
- return btoa(payload);
278
- }
279
- var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
280
- function decodeCursor(cursor) {
281
- let parsed;
282
- try {
283
- parsed = JSON.parse(atob(cursor));
284
- } catch {
285
- throw new Error("Invalid cursor: failed to decode");
286
- }
287
- if (typeof parsed !== "object" || parsed === null || !("t" in parsed) || !("i" in parsed)) {
288
- throw new Error("Invalid cursor: missing required fields");
289
- }
290
- const { t, i } = parsed;
291
- if (typeof t !== "string" || typeof i !== "string") {
292
- throw new Error("Invalid cursor: fields must be strings");
293
- }
294
- const timestamp2 = new Date(t);
295
- if (isNaN(timestamp2.getTime())) {
296
- throw new Error("Invalid cursor: invalid timestamp");
297
- }
298
- if (!UUID_PATTERN.test(i)) {
299
- throw new Error("Invalid cursor: id must be a valid UUID");
300
- }
301
- return { timestamp: timestamp2, id: i };
302
- }
303
244
 
304
245
  // src/adapter.ts
305
246
  var DEFAULT_LIMIT = 50;
306
247
  var MAX_LIMIT = 250;
307
- function toCount(value) {
308
- if (typeof value === "number") {
309
- return value;
310
- }
311
- if (typeof value === "string") {
312
- return Number(value);
313
- }
314
- return 0;
315
- }
316
248
  function drizzleAuditAdapter(db) {
317
249
  return {
318
250
  async writeLog(log) {
@@ -322,9 +254,7 @@ function drizzleAuditAdapter(db) {
322
254
  async queryLogs(spec) {
323
255
  const sortOrder = spec.sortOrder ?? "desc";
324
256
  const limit = Math.min(spec.limit ?? DEFAULT_LIMIT, MAX_LIMIT);
325
- const whereCondition = buildWhereConditions(spec.filters);
326
- const cursorCondition = spec.cursor !== void 0 ? buildCursorCondition(spec.cursor, sortOrder) : void 0;
327
- const combined = and2(whereCondition, cursorCondition);
257
+ const combined = buildWhereConditions(spec.filters, spec.cursor, sortOrder);
328
258
  const fetchLimit = limit + 1;
329
259
  const orderColumns = buildOrderBy(sortOrder);
330
260
  const query = db.select().from(auditLogs).where(combined).orderBy(...orderColumns).limit(fetchLimit);
@@ -368,30 +298,32 @@ function drizzleAuditAdapter(db) {
368
298
  },
369
299
  async getStats(options) {
370
300
  const sinceCondition = options?.since !== void 0 ? gte2(auditLogs.timestamp, options.since) : void 0;
301
+ const untilCondition = options?.until !== void 0 ? lt2(auditLogs.timestamp, options.until) : void 0;
302
+ const timeCondition = and2(sinceCondition, untilCondition);
371
303
  const summaryQuery = db.select({
372
304
  totalLogs: sql2`count(*)`,
373
305
  tablesAudited: sql2`count(DISTINCT ${auditLogs.tableName})`
374
- }).from(auditLogs).where(sinceCondition);
306
+ }).from(auditLogs).where(timeCondition);
375
307
  const eventsPerDayQuery = db.select({
376
308
  date: sql2`date_trunc('day', ${auditLogs.timestamp})::date`,
377
309
  count: sql2`count(*)`
378
- }).from(auditLogs).where(sinceCondition).groupBy(sql2`date_trunc('day', ${auditLogs.timestamp})`).orderBy(sql2`date_trunc('day', ${auditLogs.timestamp})`).limit(365);
310
+ }).from(auditLogs).where(timeCondition).groupBy(sql2`date_trunc('day', ${auditLogs.timestamp})`).orderBy(sql2`date_trunc('day', ${auditLogs.timestamp})`).limit(365);
379
311
  const topActorsQuery = db.select({
380
312
  actorId: auditLogs.actorId,
381
313
  count: sql2`count(*)`
382
- }).from(auditLogs).where(and2(sinceCondition, isNotNull(auditLogs.actorId))).groupBy(auditLogs.actorId).orderBy(desc2(sql2`count(*)`)).limit(10);
314
+ }).from(auditLogs).where(and2(timeCondition, isNotNull(auditLogs.actorId))).groupBy(auditLogs.actorId).orderBy(desc2(sql2`count(*)`)).limit(10);
383
315
  const topTablesQuery = db.select({
384
316
  tableName: auditLogs.tableName,
385
317
  count: sql2`count(*)`
386
- }).from(auditLogs).where(sinceCondition).groupBy(auditLogs.tableName).orderBy(desc2(sql2`count(*)`)).limit(10);
318
+ }).from(auditLogs).where(timeCondition).groupBy(auditLogs.tableName).orderBy(desc2(sql2`count(*)`)).limit(10);
387
319
  const operationQuery = db.select({
388
320
  operation: auditLogs.operation,
389
321
  count: sql2`count(*)`
390
- }).from(auditLogs).where(sinceCondition).groupBy(auditLogs.operation);
322
+ }).from(auditLogs).where(timeCondition).groupBy(auditLogs.operation);
391
323
  const severityQuery = db.select({
392
324
  severity: auditLogs.severity,
393
325
  count: sql2`count(*)`
394
- }).from(auditLogs).where(and2(sinceCondition, isNotNull(auditLogs.severity))).groupBy(auditLogs.severity);
326
+ }).from(auditLogs).where(and2(timeCondition, isNotNull(auditLogs.severity))).groupBy(auditLogs.severity);
395
327
  const results = await Promise.all([
396
328
  summaryQuery,
397
329
  eventsPerDayQuery,
@@ -419,42 +351,9 @@ function drizzleAuditAdapter(db) {
419
351
  }
420
352
  };
421
353
  }
422
- function assembleStats(summaryRows, eventsPerDayRows, topActorsRows, topTablesRows, operationRows, severityRows) {
423
- const summary = summaryRows[0];
424
- const totalLogs = summary !== void 0 ? toCount(summary.totalLogs) : 0;
425
- const tablesAudited = summary !== void 0 ? toCount(summary.tablesAudited) : 0;
426
- const eventsPerDay = eventsPerDayRows.map((row) => ({
427
- date: row.date instanceof Date ? row.date.toISOString().split("T")[0] : String(row.date),
428
- count: toCount(row.count)
429
- }));
430
- const topActors = topActorsRows.map((row) => ({
431
- actorId: String(row.actorId),
432
- count: toCount(row.count)
433
- }));
434
- const topTables = topTablesRows.map((row) => ({
435
- tableName: String(row.tableName),
436
- count: toCount(row.count)
437
- }));
438
- const operationBreakdown = {};
439
- for (const row of operationRows) {
440
- operationBreakdown[String(row.operation)] = toCount(row.count);
441
- }
442
- const severityBreakdown = {};
443
- for (const row of severityRows) {
444
- severityBreakdown[String(row.severity)] = toCount(row.count);
445
- }
446
- return {
447
- totalLogs,
448
- tablesAudited,
449
- eventsPerDay,
450
- topActors,
451
- topTables,
452
- operationBreakdown,
453
- severityBreakdown
454
- };
455
- }
456
354
 
457
355
  // src/sqlite-adapter.ts
356
+ import { encodeCursor as encodeCursor2, toCount, assembleStats as assembleStats2 } from "@usebetterdev/audit-core";
458
357
  import { and as and4, eq as eq4, gte as gte4, lt as lt4, isNotNull as isNotNull2, sql as sql4, desc as desc4 } from "drizzle-orm";
459
358
 
460
359
  // src/sqlite-schema.ts
@@ -503,23 +402,7 @@ var sqliteAuditLogs = sqliteTable(
503
402
  );
504
403
 
505
404
  // src/sqlite-column-map.ts
506
- var VALID_OPERATIONS2 = /* @__PURE__ */ new Set([
507
- "INSERT",
508
- "UPDATE",
509
- "DELETE"
510
- ]);
511
- var VALID_SEVERITIES2 = /* @__PURE__ */ new Set([
512
- "low",
513
- "medium",
514
- "high",
515
- "critical"
516
- ]);
517
- function isAuditOperation2(value) {
518
- return VALID_OPERATIONS2.has(value);
519
- }
520
- function isAuditSeverity2(value) {
521
- return VALID_SEVERITIES2.has(value);
522
- }
405
+ import { isAuditOperation as isAuditOperation2, isAuditSeverity as isAuditSeverity2 } from "@usebetterdev/audit-core";
523
406
  function sqliteAuditLogToRow(log) {
524
407
  const row = {
525
408
  id: log.id,
@@ -624,7 +507,10 @@ function sqliteRowToAuditLog(row) {
624
507
  }
625
508
 
626
509
  // src/sqlite-query.ts
627
- import { parseDuration as parseDuration2 } from "@usebetterdev/audit-core";
510
+ import {
511
+ decodeCursor as decodeCursor2,
512
+ interpretFilters as interpretFilters2
513
+ } from "@usebetterdev/audit-core";
628
514
  import {
629
515
  and as and3,
630
516
  or as or2,
@@ -639,87 +525,72 @@ import {
639
525
  desc as desc3,
640
526
  sql as sql3
641
527
  } from "drizzle-orm";
642
- function escapeLikePattern2(input) {
643
- return input.replace(/[%_\\]/g, "\\$&");
644
- }
645
- function resolveTimeFilter2(filter) {
646
- if ("date" in filter && filter.date !== void 0) {
647
- return filter.date;
648
- }
649
- if ("duration" in filter && filter.duration !== void 0) {
650
- return parseDuration2(filter.duration);
651
- }
652
- throw new Error("TimeFilter must have either date or duration");
653
- }
654
- function buildSqliteWhereConditions(filters) {
655
- const conditions = [];
656
- if (filters.resource !== void 0) {
657
- conditions.push(eq3(sqliteAuditLogs.tableName, filters.resource.tableName));
658
- if (filters.resource.recordId !== void 0) {
659
- conditions.push(eq3(sqliteAuditLogs.recordId, filters.resource.recordId));
528
+ var FIELD_COLUMNS2 = {
529
+ tableName: sqliteAuditLogs.tableName,
530
+ recordId: sqliteAuditLogs.recordId,
531
+ actorId: sqliteAuditLogs.actorId,
532
+ severity: sqliteAuditLogs.severity,
533
+ operation: sqliteAuditLogs.operation
534
+ };
535
+ function mapCondition2(condition) {
536
+ switch (condition.kind) {
537
+ case "eq": {
538
+ return eq3(FIELD_COLUMNS2[condition.field], condition.value);
660
539
  }
661
- }
662
- if (filters.actorIds !== void 0 && filters.actorIds.length > 0) {
663
- if (filters.actorIds.length === 1) {
664
- conditions.push(eq3(sqliteAuditLogs.actorId, filters.actorIds[0]));
665
- } else {
666
- conditions.push(inArray2(sqliteAuditLogs.actorId, filters.actorIds));
540
+ case "in": {
541
+ return inArray2(FIELD_COLUMNS2[condition.field], condition.values);
667
542
  }
668
- }
669
- if (filters.severities !== void 0 && filters.severities.length > 0) {
670
- if (filters.severities.length === 1) {
671
- conditions.push(eq3(sqliteAuditLogs.severity, filters.severities[0]));
672
- } else {
673
- conditions.push(inArray2(sqliteAuditLogs.severity, filters.severities));
543
+ case "timestampGte": {
544
+ return gte3(sqliteAuditLogs.timestamp, condition.value);
674
545
  }
675
- }
676
- if (filters.operations !== void 0 && filters.operations.length > 0) {
677
- if (filters.operations.length === 1) {
678
- conditions.push(eq3(sqliteAuditLogs.operation, filters.operations[0]));
679
- } else {
680
- conditions.push(inArray2(sqliteAuditLogs.operation, filters.operations));
546
+ case "timestampLte": {
547
+ return lte2(sqliteAuditLogs.timestamp, condition.value);
681
548
  }
682
- }
683
- if (filters.since !== void 0) {
684
- conditions.push(gte3(sqliteAuditLogs.timestamp, resolveTimeFilter2(filters.since)));
685
- }
686
- if (filters.until !== void 0) {
687
- conditions.push(lte2(sqliteAuditLogs.timestamp, resolveTimeFilter2(filters.until)));
688
- }
689
- if (filters.searchText !== void 0 && filters.searchText.length > 0) {
690
- const escaped = escapeLikePattern2(filters.searchText);
691
- const pattern = `%${escaped}%`;
692
- const searchCondition = or2(
693
- like(sqliteAuditLogs.label, pattern),
694
- like(sqliteAuditLogs.description, pattern)
695
- );
696
- if (searchCondition !== void 0) {
697
- conditions.push(searchCondition);
549
+ case "search": {
550
+ return or2(
551
+ like(sqliteAuditLogs.label, condition.pattern),
552
+ like(sqliteAuditLogs.description, condition.pattern)
553
+ );
698
554
  }
699
- }
700
- if (filters.compliance !== void 0 && filters.compliance.length > 0) {
701
- for (const tag of filters.compliance) {
702
- conditions.push(
703
- sql3`EXISTS (SELECT 1 FROM json_each(${sqliteAuditLogs.compliance}) WHERE value = ${tag})`
555
+ case "compliance": {
556
+ const tagConditions = [];
557
+ for (const tag of condition.tags) {
558
+ tagConditions.push(
559
+ sql3`EXISTS (SELECT 1 FROM json_each(${sqliteAuditLogs.compliance}) WHERE value = ${tag})`
560
+ );
561
+ }
562
+ return and3(...tagConditions);
563
+ }
564
+ case "cursor": {
565
+ const tsCompare = condition.sortOrder === "asc" ? gt2 : lt3;
566
+ const idCompare = condition.sortOrder === "asc" ? gt2 : lt3;
567
+ return or2(
568
+ tsCompare(sqliteAuditLogs.timestamp, condition.timestamp),
569
+ and3(
570
+ eq3(sqliteAuditLogs.timestamp, condition.timestamp),
571
+ idCompare(sqliteAuditLogs.id, condition.id)
572
+ )
704
573
  );
705
574
  }
706
575
  }
707
- if (conditions.length === 0) {
576
+ }
577
+ function buildSqliteWhereConditions(filters, cursor, sortOrder) {
578
+ const decoded = cursor !== void 0 ? decodeCursor2(cursor) : void 0;
579
+ const irConditions = interpretFilters2(filters, {
580
+ cursor: decoded,
581
+ sortOrder
582
+ });
583
+ const sqlConditions = [];
584
+ for (const c of irConditions) {
585
+ const mapped = mapCondition2(c);
586
+ if (mapped !== void 0) {
587
+ sqlConditions.push(mapped);
588
+ }
589
+ }
590
+ if (sqlConditions.length === 0) {
708
591
  return void 0;
709
592
  }
710
- return and3(...conditions);
711
- }
712
- function buildSqliteCursorCondition(cursor, sortOrder) {
713
- const decoded = decodeSqliteCursor(cursor);
714
- const tsCompare = sortOrder === "asc" ? gt2 : lt3;
715
- const idCompare = sortOrder === "asc" ? gt2 : lt3;
716
- return or2(
717
- tsCompare(sqliteAuditLogs.timestamp, decoded.timestamp),
718
- and3(
719
- eq3(sqliteAuditLogs.timestamp, decoded.timestamp),
720
- idCompare(sqliteAuditLogs.id, decoded.id)
721
- )
722
- );
593
+ return and3(...sqlConditions);
723
594
  }
724
595
  function buildSqliteOrderBy(sortOrder) {
725
596
  if (sortOrder === "asc") {
@@ -727,47 +598,10 @@ function buildSqliteOrderBy(sortOrder) {
727
598
  }
728
599
  return [desc3(sqliteAuditLogs.timestamp), desc3(sqliteAuditLogs.id)];
729
600
  }
730
- function encodeSqliteCursor(timestamp2, id) {
731
- const payload = JSON.stringify({ t: timestamp2.toISOString(), i: id });
732
- return btoa(payload);
733
- }
734
- var UUID_PATTERN2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
735
- function decodeSqliteCursor(cursor) {
736
- let parsed;
737
- try {
738
- parsed = JSON.parse(atob(cursor));
739
- } catch {
740
- throw new Error("Invalid cursor: failed to decode");
741
- }
742
- if (typeof parsed !== "object" || parsed === null || !("t" in parsed) || !("i" in parsed)) {
743
- throw new Error("Invalid cursor: missing required fields");
744
- }
745
- const { t, i } = parsed;
746
- if (typeof t !== "string" || typeof i !== "string") {
747
- throw new Error("Invalid cursor: fields must be strings");
748
- }
749
- const timestamp2 = new Date(t);
750
- if (isNaN(timestamp2.getTime())) {
751
- throw new Error("Invalid cursor: invalid timestamp");
752
- }
753
- if (!UUID_PATTERN2.test(i)) {
754
- throw new Error("Invalid cursor: id must be a valid UUID");
755
- }
756
- return { timestamp: timestamp2, id: i };
757
- }
758
601
 
759
602
  // src/sqlite-adapter.ts
760
603
  var DEFAULT_LIMIT2 = 50;
761
604
  var MAX_LIMIT2 = 250;
762
- function toCount2(value) {
763
- if (typeof value === "number") {
764
- return value;
765
- }
766
- if (typeof value === "string") {
767
- return Number(value);
768
- }
769
- return 0;
770
- }
771
605
  function drizzleSqliteAuditAdapter(db) {
772
606
  return {
773
607
  async writeLog(log) {
@@ -777,9 +611,7 @@ function drizzleSqliteAuditAdapter(db) {
777
611
  async queryLogs(spec) {
778
612
  const sortOrder = spec.sortOrder ?? "desc";
779
613
  const limit = Math.min(spec.limit ?? DEFAULT_LIMIT2, MAX_LIMIT2);
780
- const whereCondition = buildSqliteWhereConditions(spec.filters);
781
- const cursorCondition = spec.cursor !== void 0 ? buildSqliteCursorCondition(spec.cursor, sortOrder) : void 0;
782
- const combined = and4(whereCondition, cursorCondition);
614
+ const combined = buildSqliteWhereConditions(spec.filters, spec.cursor, sortOrder);
783
615
  const fetchLimit = limit + 1;
784
616
  const orderColumns = buildSqliteOrderBy(sortOrder);
785
617
  const query = db.select().from(sqliteAuditLogs).where(combined).orderBy(...orderColumns).limit(fetchLimit);
@@ -789,7 +621,7 @@ function drizzleSqliteAuditAdapter(db) {
789
621
  const entries = resultRows.map(sqliteRowToAuditLog);
790
622
  const lastRow = resultRows[resultRows.length - 1];
791
623
  if (hasNextPage && lastRow !== void 0) {
792
- return { entries, nextCursor: encodeSqliteCursor(lastRow.timestamp, lastRow.id) };
624
+ return { entries, nextCursor: encodeCursor2(lastRow.timestamp, lastRow.id) };
793
625
  }
794
626
  return { entries };
795
627
  },
@@ -812,35 +644,37 @@ function drizzleSqliteAuditAdapter(db) {
812
644
  }
813
645
  await db.delete(sqliteAuditLogs).where(and4(...conditions));
814
646
  const changesResult = await db.select({ count: sql4`changes()` }).from(sqliteAuditLogs);
815
- const deletedCount = changesResult[0] !== void 0 ? toCount2(changesResult[0].count) : 0;
647
+ const deletedCount = changesResult[0] !== void 0 ? toCount(changesResult[0].count) : 0;
816
648
  return { deletedCount };
817
649
  },
818
650
  async getStats(options) {
819
651
  const sinceCondition = options?.since !== void 0 ? gte4(sqliteAuditLogs.timestamp, options.since) : void 0;
652
+ const untilCondition = options?.until !== void 0 ? lt4(sqliteAuditLogs.timestamp, options.until) : void 0;
653
+ const timeCondition = and4(sinceCondition, untilCondition);
820
654
  const summaryQuery = db.select({
821
655
  totalLogs: sql4`count(*)`,
822
656
  tablesAudited: sql4`count(DISTINCT ${sqliteAuditLogs.tableName})`
823
- }).from(sqliteAuditLogs).where(sinceCondition);
657
+ }).from(sqliteAuditLogs).where(timeCondition);
824
658
  const eventsPerDayQuery = db.select({
825
659
  date: sql4`strftime('%Y-%m-%d', ${sqliteAuditLogs.timestamp}, 'unixepoch')`,
826
660
  count: sql4`count(*)`
827
- }).from(sqliteAuditLogs).where(sinceCondition).groupBy(sql4`strftime('%Y-%m-%d', ${sqliteAuditLogs.timestamp}, 'unixepoch')`).orderBy(sql4`strftime('%Y-%m-%d', ${sqliteAuditLogs.timestamp}, 'unixepoch')`).limit(365);
661
+ }).from(sqliteAuditLogs).where(timeCondition).groupBy(sql4`strftime('%Y-%m-%d', ${sqliteAuditLogs.timestamp}, 'unixepoch')`).orderBy(sql4`strftime('%Y-%m-%d', ${sqliteAuditLogs.timestamp}, 'unixepoch')`).limit(365);
828
662
  const topActorsQuery = db.select({
829
663
  actorId: sqliteAuditLogs.actorId,
830
664
  count: sql4`count(*)`
831
- }).from(sqliteAuditLogs).where(and4(sinceCondition, isNotNull2(sqliteAuditLogs.actorId))).groupBy(sqliteAuditLogs.actorId).orderBy(desc4(sql4`count(*)`)).limit(10);
665
+ }).from(sqliteAuditLogs).where(and4(timeCondition, isNotNull2(sqliteAuditLogs.actorId))).groupBy(sqliteAuditLogs.actorId).orderBy(desc4(sql4`count(*)`)).limit(10);
832
666
  const topTablesQuery = db.select({
833
667
  tableName: sqliteAuditLogs.tableName,
834
668
  count: sql4`count(*)`
835
- }).from(sqliteAuditLogs).where(sinceCondition).groupBy(sqliteAuditLogs.tableName).orderBy(desc4(sql4`count(*)`)).limit(10);
669
+ }).from(sqliteAuditLogs).where(timeCondition).groupBy(sqliteAuditLogs.tableName).orderBy(desc4(sql4`count(*)`)).limit(10);
836
670
  const operationQuery = db.select({
837
671
  operation: sqliteAuditLogs.operation,
838
672
  count: sql4`count(*)`
839
- }).from(sqliteAuditLogs).where(sinceCondition).groupBy(sqliteAuditLogs.operation);
673
+ }).from(sqliteAuditLogs).where(timeCondition).groupBy(sqliteAuditLogs.operation);
840
674
  const severityQuery = db.select({
841
675
  severity: sqliteAuditLogs.severity,
842
676
  count: sql4`count(*)`
843
- }).from(sqliteAuditLogs).where(and4(sinceCondition, isNotNull2(sqliteAuditLogs.severity))).groupBy(sqliteAuditLogs.severity);
677
+ }).from(sqliteAuditLogs).where(and4(timeCondition, isNotNull2(sqliteAuditLogs.severity))).groupBy(sqliteAuditLogs.severity);
844
678
  const results = await Promise.all([
845
679
  summaryQuery,
846
680
  eventsPerDayQuery,
@@ -868,40 +702,6 @@ function drizzleSqliteAuditAdapter(db) {
868
702
  }
869
703
  };
870
704
  }
871
- function assembleStats2(summaryRows, eventsPerDayRows, topActorsRows, topTablesRows, operationRows, severityRows) {
872
- const summary = summaryRows[0];
873
- const totalLogs = summary !== void 0 ? toCount2(summary.totalLogs) : 0;
874
- const tablesAudited = summary !== void 0 ? toCount2(summary.tablesAudited) : 0;
875
- const eventsPerDay = eventsPerDayRows.map((row) => ({
876
- date: String(row.date),
877
- count: toCount2(row.count)
878
- }));
879
- const topActors = topActorsRows.map((row) => ({
880
- actorId: String(row.actorId),
881
- count: toCount2(row.count)
882
- }));
883
- const topTables = topTablesRows.map((row) => ({
884
- tableName: String(row.tableName),
885
- count: toCount2(row.count)
886
- }));
887
- const operationBreakdown = {};
888
- for (const row of operationRows) {
889
- operationBreakdown[String(row.operation)] = toCount2(row.count);
890
- }
891
- const severityBreakdown = {};
892
- for (const row of severityRows) {
893
- severityBreakdown[String(row.severity)] = toCount2(row.count);
894
- }
895
- return {
896
- totalLogs,
897
- tablesAudited,
898
- eventsPerDay,
899
- topActors,
900
- topTables,
901
- operationBreakdown,
902
- severityBreakdown
903
- };
904
- }
905
705
 
906
706
  // src/proxy.ts
907
707
  import { getTableName } from "drizzle-orm";
@@ -1394,15 +1194,17 @@ async function fireCaptureLog(result, ctx, state) {
1394
1194
  }
1395
1195
  }
1396
1196
  }
1197
+
1198
+ // src/index.ts
1199
+ import { encodeCursor as encodeCursor3, decodeCursor as decodeCursor3 } from "@usebetterdev/audit-core";
1397
1200
  export {
1398
1201
  auditLogs,
1399
- buildCursorCondition,
1400
1202
  buildOrderBy,
1401
1203
  buildWhereConditions,
1402
- decodeCursor,
1204
+ decodeCursor3 as decodeCursor,
1403
1205
  drizzleAuditAdapter,
1404
1206
  drizzleSqliteAuditAdapter,
1405
- encodeCursor,
1207
+ encodeCursor3 as encodeCursor,
1406
1208
  sqliteAuditLogs,
1407
1209
  withAuditProxy
1408
1210
  };