@opentrust/db 7.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.
Files changed (83) hide show
  1. package/dist/client.d.ts +3 -0
  2. package/dist/client.d.ts.map +1 -0
  3. package/dist/client.js +51 -0
  4. package/dist/dialect.d.ts +3 -0
  5. package/dist/dialect.d.ts.map +1 -0
  6. package/dist/dialect.js +12 -0
  7. package/dist/generate.d.ts +2 -0
  8. package/dist/generate.d.ts.map +1 -0
  9. package/dist/generate.js +20 -0
  10. package/dist/helpers.d.ts +11 -0
  11. package/dist/helpers.d.ts.map +1 -0
  12. package/dist/helpers.js +32 -0
  13. package/dist/index.d.ts +13 -0
  14. package/dist/index.d.ts.map +1 -0
  15. package/dist/index.js +12 -0
  16. package/dist/migrate.d.ts +2 -0
  17. package/dist/migrate.d.ts.map +1 -0
  18. package/dist/migrate.js +61 -0
  19. package/dist/queries/agents.d.ts +25 -0
  20. package/dist/queries/agents.d.ts.map +1 -0
  21. package/dist/queries/agents.js +46 -0
  22. package/dist/queries/auth.d.ts +18 -0
  23. package/dist/queries/auth.d.ts.map +1 -0
  24. package/dist/queries/auth.js +77 -0
  25. package/dist/queries/detection-results.d.ts +24 -0
  26. package/dist/queries/detection-results.d.ts.map +1 -0
  27. package/dist/queries/detection-results.js +43 -0
  28. package/dist/queries/observations.d.ts +58 -0
  29. package/dist/queries/observations.d.ts.map +1 -0
  30. package/dist/queries/observations.js +212 -0
  31. package/dist/queries/policies.d.ts +25 -0
  32. package/dist/queries/policies.d.ts.map +1 -0
  33. package/dist/queries/policies.js +38 -0
  34. package/dist/queries/scanners.d.ts +25 -0
  35. package/dist/queries/scanners.d.ts.map +1 -0
  36. package/dist/queries/scanners.js +56 -0
  37. package/dist/queries/settings.d.ts +8 -0
  38. package/dist/queries/settings.d.ts.map +1 -0
  39. package/dist/queries/settings.js +30 -0
  40. package/dist/queries/usage.d.ts +18 -0
  41. package/dist/queries/usage.d.ts.map +1 -0
  42. package/dist/queries/usage.js +54 -0
  43. package/dist/schema/index.d.ts +4415 -0
  44. package/dist/schema/index.d.ts.map +1 -0
  45. package/dist/schema/index.js +19 -0
  46. package/dist/schema/mysql.d.ts +1479 -0
  47. package/dist/schema/mysql.d.ts.map +1 -0
  48. package/dist/schema/mysql.js +151 -0
  49. package/dist/schema/pg.d.ts +1479 -0
  50. package/dist/schema/pg.d.ts.map +1 -0
  51. package/dist/schema/pg.js +151 -0
  52. package/dist/schema/sqlite.d.ts +1479 -0
  53. package/dist/schema/sqlite.d.ts.map +1 -0
  54. package/dist/schema/sqlite.js +153 -0
  55. package/dist/seed.d.ts +2 -0
  56. package/dist/seed.d.ts.map +1 -0
  57. package/dist/seed.js +49 -0
  58. package/drizzle/sqlite/0000_serious_martin_li.sql +143 -0
  59. package/drizzle/sqlite/meta/0000_snapshot.json +945 -0
  60. package/drizzle/sqlite/meta/_journal.json +13 -0
  61. package/drizzle.config.mysql.ts +10 -0
  62. package/drizzle.config.pg.ts +10 -0
  63. package/drizzle.config.sqlite.ts +10 -0
  64. package/package.json +55 -0
  65. package/src/client.ts +66 -0
  66. package/src/dialect.ts +13 -0
  67. package/src/generate.ts +26 -0
  68. package/src/helpers.ts +47 -0
  69. package/src/index.ts +12 -0
  70. package/src/migrate.ts +74 -0
  71. package/src/queries/agents.ts +68 -0
  72. package/src/queries/auth.ts +94 -0
  73. package/src/queries/detection-results.ts +58 -0
  74. package/src/queries/observations.ts +275 -0
  75. package/src/queries/policies.ts +59 -0
  76. package/src/queries/scanners.ts +74 -0
  77. package/src/queries/settings.ts +34 -0
  78. package/src/queries/usage.ts +69 -0
  79. package/src/schema/index.ts +22 -0
  80. package/src/schema/mysql.ts +207 -0
  81. package/src/schema/pg.ts +208 -0
  82. package/src/schema/sqlite.ts +199 -0
  83. package/src/seed.ts +56 -0
@@ -0,0 +1,275 @@
1
+ import { eq, and, desc, count, sql } from "drizzle-orm";
2
+ import type { Database } from "../client.js";
3
+ import { toolCallObservations, agentPermissions } from "../schema/index.js";
4
+ import { DEFAULT_TENANT_ID } from "@opentrust/shared";
5
+
6
+ // ─── Tool name → category / access pattern inference ────────────
7
+
8
+ const ACCESS_READ_PREFIXES = ["list", "get", "search", "read", "fetch", "find", "query", "check", "view"];
9
+ const ACCESS_WRITE_PREFIXES = ["create", "send", "write", "update", "edit", "post", "put", "add", "set"];
10
+ const ACCESS_ADMIN_PREFIXES = ["delete", "remove", "execute", "run", "admin", "destroy", "revoke", "drop"];
11
+
12
+ export function inferCategory(toolName: string): string {
13
+ // "github_create_issue" → "github"
14
+ // "slack_send_message" → "slack"
15
+ // "read_file" → "filesystem"
16
+ const lower = toolName.toLowerCase();
17
+
18
+ if (lower.startsWith("read_file") || lower.startsWith("write_file") || lower.startsWith("list_dir")) {
19
+ return "filesystem";
20
+ }
21
+ if (lower.startsWith("execute_command") || lower.startsWith("run_command") || lower === "bash") {
22
+ return "shell";
23
+ }
24
+
25
+ const underscoreIdx = lower.indexOf("_");
26
+ if (underscoreIdx > 0) {
27
+ return lower.slice(0, underscoreIdx);
28
+ }
29
+ return lower;
30
+ }
31
+
32
+ export function inferAccessPattern(toolName: string): "read" | "write" | "admin" | "unknown" {
33
+ const lower = toolName.toLowerCase();
34
+
35
+ // Strip category prefix: "github_create_issue" → "create_issue"
36
+ const underscoreIdx = lower.indexOf("_");
37
+ const action = underscoreIdx > 0 ? lower.slice(underscoreIdx + 1) : lower;
38
+
39
+ for (const prefix of ACCESS_ADMIN_PREFIXES) {
40
+ if (action.startsWith(prefix)) return "admin";
41
+ }
42
+ for (const prefix of ACCESS_WRITE_PREFIXES) {
43
+ if (action.startsWith(prefix)) return "write";
44
+ }
45
+ for (const prefix of ACCESS_READ_PREFIXES) {
46
+ if (action.startsWith(prefix)) return "read";
47
+ }
48
+ return "unknown";
49
+ }
50
+
51
+ // ─── Query Functions ────────────────────────────────────────────
52
+
53
+ export function observationQueries(db: Database) {
54
+ return {
55
+ /**
56
+ * Record a tool call observation.
57
+ */
58
+ async record(data: {
59
+ agentId: string;
60
+ sessionKey?: string;
61
+ toolName: string;
62
+ params?: Record<string, unknown>;
63
+ phase: "before" | "after";
64
+ result?: unknown;
65
+ error?: string;
66
+ durationMs?: number;
67
+ blocked?: boolean;
68
+ blockReason?: string;
69
+ tenantId?: string;
70
+ }) {
71
+ const category = inferCategory(data.toolName);
72
+ const accessPattern = inferAccessPattern(data.toolName);
73
+ const tenantId = data.tenantId ?? DEFAULT_TENANT_ID;
74
+
75
+ await db.insert(toolCallObservations).values({
76
+ agentId: data.agentId,
77
+ sessionKey: data.sessionKey ?? null,
78
+ toolName: data.toolName,
79
+ category,
80
+ accessPattern,
81
+ paramsJson: data.params ?? null,
82
+ phase: data.phase,
83
+ resultJson: data.result ?? null,
84
+ error: data.error ?? null,
85
+ durationMs: data.durationMs ?? null,
86
+ blocked: data.blocked ?? false,
87
+ blockReason: data.blockReason ?? null,
88
+ tenantId,
89
+ });
90
+
91
+ // Upsert permission on "after" phase (or "before" if blocked)
92
+ if (data.phase === "after" || data.blocked) {
93
+ await this.upsertPermission({
94
+ agentId: data.agentId,
95
+ toolName: data.toolName,
96
+ category,
97
+ accessPattern,
98
+ params: data.params,
99
+ hasError: !!data.error,
100
+ tenantId,
101
+ });
102
+ }
103
+ },
104
+
105
+ /**
106
+ * Upsert an agent permission entry based on observed tool call.
107
+ */
108
+ async upsertPermission(data: {
109
+ agentId: string;
110
+ toolName: string;
111
+ category: string;
112
+ accessPattern: string;
113
+ params?: Record<string, unknown>;
114
+ hasError: boolean;
115
+ tenantId: string;
116
+ }) {
117
+ const now = new Date().toISOString();
118
+
119
+ // Check if permission already exists
120
+ const existing = await db
121
+ .select()
122
+ .from(agentPermissions)
123
+ .where(
124
+ and(
125
+ eq(agentPermissions.tenantId, data.tenantId),
126
+ eq(agentPermissions.agentId, data.agentId),
127
+ eq(agentPermissions.toolName, data.toolName),
128
+ ),
129
+ )
130
+ .limit(1);
131
+
132
+ if (existing.length > 0) {
133
+ const perm = existing[0]!;
134
+ const targets = (perm.targetsJson as string[]) || [];
135
+ const newTargets = extractTargets(data.params);
136
+ const mergedTargets = mergeTargets(targets, newTargets);
137
+
138
+ await db
139
+ .update(agentPermissions)
140
+ .set({
141
+ callCount: (perm.callCount ?? 0) + 1,
142
+ errorCount: (perm.errorCount ?? 0) + (data.hasError ? 1 : 0),
143
+ lastSeen: now,
144
+ targetsJson: mergedTargets,
145
+ })
146
+ .where(eq(agentPermissions.id, perm.id));
147
+ } else {
148
+ const targets = extractTargets(data.params);
149
+ await db.insert(agentPermissions).values({
150
+ tenantId: data.tenantId,
151
+ agentId: data.agentId,
152
+ toolName: data.toolName,
153
+ category: data.category,
154
+ accessPattern: data.accessPattern,
155
+ targetsJson: targets,
156
+ callCount: 1,
157
+ errorCount: data.hasError ? 1 : 0,
158
+ firstSeen: now,
159
+ lastSeen: now,
160
+ });
161
+ }
162
+ },
163
+
164
+ /**
165
+ * Get recent observations, optionally filtered by agentId.
166
+ */
167
+ async findRecent(opts: {
168
+ agentId?: string;
169
+ limit?: number;
170
+ tenantId?: string;
171
+ } = {}) {
172
+ const tenantId = opts.tenantId ?? DEFAULT_TENANT_ID;
173
+ const limit = opts.limit ?? 50;
174
+
175
+ const conditions = [eq(toolCallObservations.tenantId, tenantId)];
176
+ if (opts.agentId) {
177
+ conditions.push(eq(toolCallObservations.agentId, opts.agentId));
178
+ }
179
+
180
+ return db
181
+ .select()
182
+ .from(toolCallObservations)
183
+ .where(and(...conditions))
184
+ .orderBy(desc(toolCallObservations.timestamp))
185
+ .limit(limit);
186
+ },
187
+
188
+ /**
189
+ * Get the aggregated permission profile for an agent.
190
+ */
191
+ async getPermissions(agentId: string, tenantId: string = DEFAULT_TENANT_ID) {
192
+ return db
193
+ .select()
194
+ .from(agentPermissions)
195
+ .where(
196
+ and(
197
+ eq(agentPermissions.tenantId, tenantId),
198
+ eq(agentPermissions.agentId, agentId),
199
+ ),
200
+ )
201
+ .orderBy(desc(agentPermissions.callCount));
202
+ },
203
+
204
+ /**
205
+ * Get permissions for all agents (overview).
206
+ */
207
+ async getAllPermissions(tenantId: string = DEFAULT_TENANT_ID) {
208
+ return db
209
+ .select()
210
+ .from(agentPermissions)
211
+ .where(eq(agentPermissions.tenantId, tenantId))
212
+ .orderBy(agentPermissions.agentId, desc(agentPermissions.callCount));
213
+ },
214
+
215
+ /**
216
+ * Find first-seen tool calls (anomalies) — permissions with callCount = 1.
217
+ */
218
+ async findAnomalies(tenantId: string = DEFAULT_TENANT_ID, limit: number = 20) {
219
+ return db
220
+ .select()
221
+ .from(agentPermissions)
222
+ .where(
223
+ and(
224
+ eq(agentPermissions.tenantId, tenantId),
225
+ eq(agentPermissions.callCount, 1),
226
+ ),
227
+ )
228
+ .orderBy(desc(agentPermissions.firstSeen))
229
+ .limit(limit);
230
+ },
231
+
232
+ /**
233
+ * Get observation count summary per agent.
234
+ */
235
+ async summary(tenantId: string = DEFAULT_TENANT_ID) {
236
+ return db
237
+ .select({
238
+ agentId: toolCallObservations.agentId,
239
+ totalCalls: count(),
240
+ blockedCalls: sql<number>`sum(case when ${toolCallObservations.blocked} = true then 1 else 0 end)`,
241
+ uniqueTools: sql<number>`count(distinct ${toolCallObservations.toolName})`,
242
+ })
243
+ .from(toolCallObservations)
244
+ .where(eq(toolCallObservations.tenantId, tenantId))
245
+ .groupBy(toolCallObservations.agentId);
246
+ },
247
+ };
248
+ }
249
+
250
+ // ─── Helpers ────────────────────────────────────────────────────
251
+
252
+ /** Extract likely target identifiers from tool call params. */
253
+ function extractTargets(params?: Record<string, unknown>): string[] {
254
+ if (!params) return [];
255
+ const targets: string[] = [];
256
+
257
+ const targetKeys = ["repo", "repository", "channel", "to", "email", "path", "file", "url", "owner", "user", "org"];
258
+ for (const key of targetKeys) {
259
+ const val = params[key];
260
+ if (typeof val === "string" && val.length > 0 && val.length < 200) {
261
+ targets.push(val);
262
+ }
263
+ }
264
+ return targets;
265
+ }
266
+
267
+ /** Merge new targets into existing list, capped at 50 entries. */
268
+ function mergeTargets(existing: string[], incoming: string[]): string[] {
269
+ const set = new Set(existing);
270
+ for (const t of incoming) {
271
+ set.add(t);
272
+ }
273
+ const merged = [...set];
274
+ return merged.length > 50 ? merged.slice(-50) : merged;
275
+ }
@@ -0,0 +1,59 @@
1
+ import { eq, and } from "drizzle-orm";
2
+ import type { Database } from "../client.js";
3
+ import { policies } from "../schema/index.js";
4
+ import { insertReturning, updateReturning } from "../helpers.js";
5
+ import { DEFAULT_TENANT_ID } from "@opentrust/shared";
6
+
7
+ export function policyQueries(db: Database) {
8
+ return {
9
+ async findAll(tenantId: string = DEFAULT_TENANT_ID) {
10
+ return db.select().from(policies).where(eq(policies.tenantId, tenantId)).orderBy(policies.createdAt);
11
+ },
12
+
13
+ async findById(id: string, tenantId: string = DEFAULT_TENANT_ID) {
14
+ const result = await db.select().from(policies).where(and(eq(policies.id, id), eq(policies.tenantId, tenantId))).limit(1);
15
+ return result[0] ?? null;
16
+ },
17
+
18
+ async create(data: {
19
+ name: string;
20
+ description?: string | null;
21
+ scannerIds: string[];
22
+ action: string;
23
+ sensitivityThreshold?: number;
24
+ tenantId?: string;
25
+ }) {
26
+ return insertReturning(db, policies, {
27
+ ...data,
28
+ sensitivityThreshold: data.sensitivityThreshold ?? 0.5,
29
+ tenantId: data.tenantId ?? DEFAULT_TENANT_ID,
30
+ });
31
+ },
32
+
33
+ async update(id: string, data: Partial<{
34
+ name: string;
35
+ description: string | null;
36
+ scannerIds: string[];
37
+ action: string;
38
+ sensitivityThreshold: number;
39
+ isEnabled: boolean;
40
+ }>, tenantId: string = DEFAULT_TENANT_ID) {
41
+ return updateReturning(db, policies, and(eq(policies.id, id), eq(policies.tenantId, tenantId)), {
42
+ ...data,
43
+ updatedAt: new Date().toISOString(),
44
+ });
45
+ },
46
+
47
+ async delete(id: string, tenantId: string = DEFAULT_TENANT_ID) {
48
+ await db.delete(policies).where(and(eq(policies.id, id), eq(policies.tenantId, tenantId)));
49
+ },
50
+
51
+ /** Get all enabled policies for detection flow */
52
+ async getEnabled(tenantId: string = DEFAULT_TENANT_ID) {
53
+ return db
54
+ .select()
55
+ .from(policies)
56
+ .where(and(eq(policies.isEnabled, true), eq(policies.tenantId, tenantId)));
57
+ },
58
+ };
59
+ }
@@ -0,0 +1,74 @@
1
+ import { eq, and } from "drizzle-orm";
2
+ import type { Database } from "../client.js";
3
+ import { scannerDefinitions } from "../schema/index.js";
4
+ import { insertReturning } from "../helpers.js";
5
+ import { DEFAULT_TENANT_ID } from "@opentrust/shared";
6
+
7
+ export function scannerQueries(db: Database) {
8
+ return {
9
+ /** Get all scanners (defaults + overrides) */
10
+ async getAll(tenantId: string = DEFAULT_TENANT_ID) {
11
+ return db
12
+ .select()
13
+ .from(scannerDefinitions)
14
+ .where(eq(scannerDefinitions.tenantId, tenantId))
15
+ .orderBy(scannerDefinitions.scannerId);
16
+ },
17
+
18
+ /** Get all system default scanners */
19
+ async getDefaults(tenantId: string = DEFAULT_TENANT_ID) {
20
+ return db
21
+ .select()
22
+ .from(scannerDefinitions)
23
+ .where(and(eq(scannerDefinitions.isDefault, true), eq(scannerDefinitions.tenantId, tenantId)))
24
+ .orderBy(scannerDefinitions.scannerId);
25
+ },
26
+
27
+ /** Get enabled scanners for detection */
28
+ async getEnabled(tenantId: string = DEFAULT_TENANT_ID) {
29
+ return db
30
+ .select()
31
+ .from(scannerDefinitions)
32
+ .where(and(eq(scannerDefinitions.isEnabled, true), eq(scannerDefinitions.tenantId, tenantId)))
33
+ .orderBy(scannerDefinitions.scannerId);
34
+ },
35
+
36
+ /** Upsert a scanner override */
37
+ async upsert(data: {
38
+ scannerId: string;
39
+ name: string;
40
+ description: string;
41
+ isEnabled: boolean;
42
+ tenantId?: string;
43
+ }) {
44
+ const tid = data.tenantId ?? DEFAULT_TENANT_ID;
45
+ // Delete existing non-default with same scannerId for this tenant
46
+ await db
47
+ .delete(scannerDefinitions)
48
+ .where(
49
+ and(
50
+ eq(scannerDefinitions.scannerId, data.scannerId),
51
+ eq(scannerDefinitions.isDefault, false),
52
+ eq(scannerDefinitions.tenantId, tid)
53
+ )
54
+ );
55
+ return insertReturning(db, scannerDefinitions, {
56
+ scannerId: data.scannerId,
57
+ name: data.name,
58
+ description: data.description,
59
+ isEnabled: data.isEnabled,
60
+ isDefault: false,
61
+ tenantId: tid,
62
+ });
63
+ },
64
+
65
+ /** Create a system default scanner */
66
+ async createDefault(data: { scannerId: string; name: string; description: string; tenantId?: string }) {
67
+ return insertReturning(db, scannerDefinitions, {
68
+ ...data,
69
+ isDefault: true,
70
+ tenantId: data.tenantId ?? DEFAULT_TENANT_ID,
71
+ });
72
+ },
73
+ };
74
+ }
@@ -0,0 +1,34 @@
1
+ import { eq } from "drizzle-orm";
2
+ import type { Database } from "../client.js";
3
+ import { settings } from "../schema/index.js";
4
+
5
+ export function settingsQueries(db: Database) {
6
+ return {
7
+ async get(key: string): Promise<string | null> {
8
+ const result = await db.select().from(settings).where(eq(settings.key, key)).limit(1);
9
+ return result[0]?.value ?? null;
10
+ },
11
+
12
+ async set(key: string, value: string) {
13
+ const existing = await this.get(key);
14
+ if (existing !== null) {
15
+ await db.update(settings).set({ value, updatedAt: new Date().toISOString() }).where(eq(settings.key, key));
16
+ } else {
17
+ await db.insert(settings).values({ key, value });
18
+ }
19
+ },
20
+
21
+ async getAll(): Promise<Record<string, string>> {
22
+ const rows = await db.select().from(settings);
23
+ const result: Record<string, string> = {};
24
+ for (const row of rows) {
25
+ result[row.key] = row.value;
26
+ }
27
+ return result;
28
+ },
29
+
30
+ async delete(key: string) {
31
+ await db.delete(settings).where(eq(settings.key, key));
32
+ },
33
+ };
34
+ }
@@ -0,0 +1,69 @@
1
+ import { eq, gte, lte, sql, count, and } from "drizzle-orm";
2
+ import type { Database } from "../client.js";
3
+ import { usageLogs } from "../schema/index.js";
4
+ import { DEFAULT_TENANT_ID } from "@opentrust/shared";
5
+
6
+ export function usageQueries(db: Database) {
7
+ return {
8
+ async log(data: {
9
+ agentId?: string | null;
10
+ endpoint: string;
11
+ statusCode: number;
12
+ responseSafe: boolean | null;
13
+ categories?: string[];
14
+ latencyMs: number;
15
+ requestId: string;
16
+ tenantId?: string;
17
+ }) {
18
+ await db.insert(usageLogs).values({
19
+ ...data,
20
+ categories: data.categories ?? [],
21
+ tenantId: data.tenantId ?? DEFAULT_TENANT_ID,
22
+ });
23
+ },
24
+
25
+ async countInPeriod(start: Date | string, end: Date | string, tenantId: string = DEFAULT_TENANT_ID) {
26
+ const result = await db
27
+ .select({ count: count() })
28
+ .from(usageLogs)
29
+ .where(and(gte(usageLogs.createdAt, start), lte(usageLogs.createdAt, end), eq(usageLogs.tenantId, tenantId)));
30
+ return result[0]?.count ?? 0;
31
+ },
32
+
33
+ async summary(start: Date | string, end: Date | string, tenantId: string = DEFAULT_TENANT_ID) {
34
+ const result = await db
35
+ .select({
36
+ totalCalls: count(),
37
+ safeCount: sql<number>`sum(case when ${usageLogs.responseSafe} = true then 1 else 0 end)`,
38
+ unsafeCount: sql<number>`sum(case when ${usageLogs.responseSafe} = false then 1 else 0 end)`,
39
+ })
40
+ .from(usageLogs)
41
+ .where(and(gte(usageLogs.createdAt, start), lte(usageLogs.createdAt, end), eq(usageLogs.tenantId, tenantId)));
42
+ return result[0] ?? { totalCalls: 0, safeCount: 0, unsafeCount: 0 };
43
+ },
44
+
45
+ async daily(start: Date | string, end: Date | string, tenantId: string = DEFAULT_TENANT_ID) {
46
+ const result = await db
47
+ .select({
48
+ date: sql<string>`date(${usageLogs.createdAt})`,
49
+ count: count(),
50
+ safeCount: sql<number>`sum(case when ${usageLogs.responseSafe} = true then 1 else 0 end)`,
51
+ unsafeCount: sql<number>`sum(case when ${usageLogs.responseSafe} = false then 1 else 0 end)`,
52
+ })
53
+ .from(usageLogs)
54
+ .where(and(gte(usageLogs.createdAt, start), lte(usageLogs.createdAt, end), eq(usageLogs.tenantId, tenantId)))
55
+ .groupBy(sql`date(${usageLogs.createdAt})`)
56
+ .orderBy(sql`date(${usageLogs.createdAt})`);
57
+ return result;
58
+ },
59
+
60
+ async countRecent(minutes: number = 1, tenantId: string = DEFAULT_TENANT_ID) {
61
+ const since = new Date(Date.now() - minutes * 60_000).toISOString();
62
+ const result = await db
63
+ .select({ count: count() })
64
+ .from(usageLogs)
65
+ .where(and(gte(usageLogs.createdAt, since), eq(usageLogs.tenantId, tenantId)));
66
+ return result[0]?.count ?? 0;
67
+ },
68
+ };
69
+ }
@@ -0,0 +1,22 @@
1
+ import { getDialect } from "../dialect.js";
2
+
3
+ const dialect = getDialect();
4
+
5
+ // Dynamic schema loading based on dialect
6
+ // Using top-level await (Node 22+)
7
+ const mod = dialect === "sqlite"
8
+ ? await import("./sqlite.js")
9
+ : dialect === "mysql"
10
+ ? await import("./mysql.js")
11
+ : await import("./pg.js");
12
+
13
+ export const settings = mod.settings;
14
+ export const agents = mod.agents;
15
+ export const scannerDefinitions = mod.scannerDefinitions;
16
+ export const policies = mod.policies;
17
+ export const usageLogs = mod.usageLogs;
18
+ export const detectionResults = mod.detectionResults;
19
+ export const toolCallObservations = mod.toolCallObservations;
20
+ export const agentPermissions = mod.agentPermissions;
21
+ export const magicLinks = mod.magicLinks;
22
+ export const userSessions = mod.userSessions;