@spfn/monitor 0.1.0-beta.23 → 0.1.0-beta.25

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
@@ -7,6 +7,7 @@ var monitorSchema = createSchema("@spfn/monitor");
7
7
 
8
8
  // src/server/entities/error-groups.ts
9
9
  import { text, integer, index } from "drizzle-orm/pg-core";
10
+ import { sql } from "drizzle-orm";
10
11
  import { id, timestamps, enumText, utcTimestamp } from "@spfn/core/db";
11
12
  var ERROR_GROUP_STATUSES = ["active", "resolved", "ignored"];
12
13
  var errorGroups = monitorSchema.table(
@@ -36,7 +37,15 @@ var errorGroups = monitorSchema.table(
36
37
  index("monitor_eg_fingerprint_idx").on(table.fingerprint),
37
38
  index("monitor_eg_status_idx").on(table.status),
38
39
  index("monitor_eg_last_seen_at_idx").on(table.lastSeenAt),
39
- index("monitor_eg_path_idx").on(table.path)
40
+ index("monitor_eg_path_idx").on(table.path),
41
+ // pg_trgm GIN indexes make the admin search's leading-wildcard ILIKE
42
+ // (%term%) on name/message/path sargable instead of a seq scan. Error
43
+ // groups are fingerprint-deduped, so insert volume is low — the write cost
44
+ // of these GIN indexes is acceptable here (NOT applied to the high-volume
45
+ // logs table). Requires the pg_trgm extension (see the migration).
46
+ index("monitor_eg_name_trgm_idx").using("gin", sql`${table.name} gin_trgm_ops`),
47
+ index("monitor_eg_message_trgm_idx").using("gin", sql`${table.message} gin_trgm_ops`),
48
+ index("monitor_eg_path_trgm_idx").using("gin", sql`${table.path} gin_trgm_ops`)
40
49
  ]
41
50
  );
42
51
 
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/server/entities/schema.ts","../src/server/entities/error-groups.ts","../src/server/entities/error-events.ts","../src/server/entities/logs.ts"],"sourcesContent":["/**\n * @spfn/monitor\n *\n * Error tracking, log management, and monitoring dashboard for SPFN\n *\n * @example\n * ```typescript\n * // Server-side\n * import { monitorRouter, createMonitorErrorHandler } from '@spfn/monitor/server';\n *\n * // Client-side (API calls)\n * import { monitorApi } from '@spfn/monitor';\n * const stats = await monitorApi.getStats.call({});\n * ```\n */\n\n// ============================================================================\n// API Client\n// ============================================================================\nimport { createApi } from '@spfn/core/nextjs';\nimport { monitorRouter } from './server/routes';\n\n/**\n * Type-safe API client for monitor routes\n *\n * @example\n * ```typescript\n * import { monitorApi } from '@spfn/monitor';\n *\n * // Get dashboard stats\n * const stats = await monitorApi.getStats.call({});\n *\n * // List errors\n * const errors = await monitorApi.listErrors.call({\n * query: { status: 'active', limit: 20 }\n * });\n * ```\n */\nexport const monitorApi = createApi<typeof monitorRouter>({});\n\n// Router type for external use\nexport type MonitorRouter = typeof monitorRouter;\n\n// ============================================================================\n// Shared Types (client-safe)\n// ============================================================================\nexport type {\n ErrorGroupStatus,\n LogLevel,\n} from './server/entities';\n\nexport type { MonitorStats } from './server/services/stats.service';\n\nexport {\n ERROR_GROUP_STATUSES,\n LOG_LEVELS,\n} from './server/entities';\n","/**\n * @spfn/monitor - Database Schema Definition\n *\n * Defines the 'spfn_monitor' PostgreSQL schema for all monitor-related tables\n */\n\nimport { createSchema } from '@spfn/core/db';\n\n/**\n * Monitor schema for all monitoring tables\n * Tables: error_groups, error_events, logs\n */\nexport const monitorSchema = createSchema('@spfn/monitor');\n","/**\n * @spfn/monitor - Error Groups Entity\n *\n * Groups errors by fingerprint (name + message + path) to avoid\n * duplicate tracking. Tracks count, status, and first/last seen times.\n */\n\nimport { text, integer, index } from 'drizzle-orm/pg-core';\nimport { id, timestamps, enumText, utcTimestamp } from '@spfn/core/db';\nimport { monitorSchema } from './schema';\n\n/**\n * Error group status types\n */\nexport const ERROR_GROUP_STATUSES = ['active', 'resolved', 'ignored'] as const;\nexport type ErrorGroupStatus = typeof ERROR_GROUP_STATUSES[number];\n\n/**\n * Error groups table — groups errors by fingerprint\n */\nexport const errorGroups = monitorSchema.table('error_groups',\n {\n // Primary Key\n id: id(),\n\n // Business Key — SHA-256 first 16 hex chars of (name:message:path)\n fingerprint: text('fingerprint').notNull().unique(),\n\n // Error identification\n name: text('name').notNull(),\n message: text('message').notNull(),\n path: text('path').notNull(),\n method: text('method').notNull(),\n statusCode: integer('status_code').notNull(),\n\n // Status\n status: enumText('status', ERROR_GROUP_STATUSES).default('active').notNull(),\n\n // Counters\n count: integer('count').notNull().default(1),\n\n // Timeline\n firstSeenAt: utcTimestamp('first_seen_at').notNull(),\n lastSeenAt: utcTimestamp('last_seen_at').notNull(),\n resolvedAt: utcTimestamp('resolved_at'),\n\n ...timestamps(),\n },\n (table) => [\n index('monitor_eg_fingerprint_idx').on(table.fingerprint),\n index('monitor_eg_status_idx').on(table.status),\n index('monitor_eg_last_seen_at_idx').on(table.lastSeenAt),\n index('monitor_eg_path_idx').on(table.path),\n ],\n);\n\nexport type ErrorGroup = typeof errorGroups.$inferSelect;\nexport type NewErrorGroup = typeof errorGroups.$inferInsert;\n","/**\n * @spfn/monitor - Error Events Entity\n *\n * Individual error occurrences linked to an error group.\n * Stores request-specific context (headers, query, stack trace).\n */\n\nimport { text, integer, jsonb, index } from 'drizzle-orm/pg-core';\nimport { id, timestamps, foreignKey } from '@spfn/core/db';\nimport { monitorSchema } from './schema';\nimport { errorGroups } from './error-groups';\n\n/**\n * Error events table — individual error occurrences\n */\nexport const errorEvents = monitorSchema.table('error_events',\n {\n // Primary Key\n id: id(),\n\n // Foreign Key\n groupId: foreignKey('group', () => errorGroups.id).notNull(),\n\n // Request context\n requestId: text('request_id'),\n userId: text('user_id'),\n statusCode: integer('status_code').notNull(),\n\n // Request details\n headers: jsonb('headers').$type<Record<string, string>>(),\n query: jsonb('query').$type<Record<string, string>>(),\n stackTrace: text('stack_trace'),\n metadata: jsonb('metadata').$type<Record<string, unknown>>(),\n\n ...timestamps(),\n },\n (table) => [\n index('monitor_ee_group_id_idx').on(table.groupId),\n index('monitor_ee_created_at_idx').on(table.createdAt),\n index('monitor_ee_user_id_idx').on(table.userId),\n ],\n);\n\nexport type ErrorEvent = typeof errorEvents.$inferSelect;\nexport type NewErrorEvent = typeof errorEvents.$inferInsert;\n","/**\n * @spfn/monitor - Logs Entity\n *\n * Developer logs stored in DB for retrieval via admin dashboard.\n * Supports level-based filtering, source tracking, and metadata.\n */\n\nimport { text, jsonb, index } from 'drizzle-orm/pg-core';\nimport { id, timestamps, enumText } from '@spfn/core/db';\nimport { monitorSchema } from './schema';\n\n/**\n * Log level types\n */\nexport const LOG_LEVELS = ['debug', 'info', 'warn', 'error', 'fatal'] as const;\nexport type LogLevel = typeof LOG_LEVELS[number];\n\n/**\n * Logs table — developer log entries\n */\nexport const logs = monitorSchema.table('logs',\n {\n // Primary Key\n id: id(),\n\n // Log data\n level: enumText('level', LOG_LEVELS).notNull(),\n message: text('message').notNull(),\n source: text('source'),\n\n // Request context\n requestId: text('request_id'),\n userId: text('user_id'),\n\n // Extra data\n metadata: jsonb('metadata').$type<Record<string, unknown>>(),\n\n ...timestamps(),\n },\n (table) => [\n index('monitor_log_level_idx').on(table.level),\n index('monitor_log_source_idx').on(table.source),\n index('monitor_log_created_at_idx').on(table.createdAt),\n ],\n);\n\nexport type Log = typeof logs.$inferSelect;\nexport type NewLog = typeof logs.$inferInsert;\n"],"mappings":"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","names":["text","integer","index","id","timestamps","id","text","integer","timestamps","index","text","jsonb","index","id","timestamps","enumText","id","enumText","text","jsonb","timestamps","index"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/server/entities/schema.ts","../src/server/entities/error-groups.ts","../src/server/entities/error-events.ts","../src/server/entities/logs.ts"],"sourcesContent":["/**\n * @spfn/monitor\n *\n * Error tracking, log management, and monitoring dashboard for SPFN\n *\n * @example\n * ```typescript\n * // Server-side\n * import { monitorRouter, createMonitorErrorHandler } from '@spfn/monitor/server';\n *\n * // Client-side (API calls)\n * import { monitorApi } from '@spfn/monitor';\n * const stats = await monitorApi.getStats.call({});\n * ```\n */\n\n// ============================================================================\n// API Client\n// ============================================================================\nimport { createApi } from '@spfn/core/nextjs';\nimport { monitorRouter } from './server/routes';\n\n/**\n * Type-safe API client for monitor routes\n *\n * @example\n * ```typescript\n * import { monitorApi } from '@spfn/monitor';\n *\n * // Get dashboard stats\n * const stats = await monitorApi.getStats.call({});\n *\n * // List errors\n * const errors = await monitorApi.listErrors.call({\n * query: { status: 'active', limit: 20 }\n * });\n * ```\n */\nexport const monitorApi = createApi<typeof monitorRouter>({});\n\n// Router type for external use\nexport type MonitorRouter = typeof monitorRouter;\n\n// ============================================================================\n// Shared Types (client-safe)\n// ============================================================================\nexport type {\n ErrorGroupStatus,\n LogLevel,\n} from './server/entities';\n\nexport type { MonitorStats } from './server/services/stats.service';\n\nexport {\n ERROR_GROUP_STATUSES,\n LOG_LEVELS,\n} from './server/entities';\n","/**\n * @spfn/monitor - Database Schema Definition\n *\n * Defines the 'spfn_monitor' PostgreSQL schema for all monitor-related tables\n */\n\nimport { createSchema } from '@spfn/core/db';\n\n/**\n * Monitor schema for all monitoring tables\n * Tables: error_groups, error_events, logs\n */\nexport const monitorSchema = createSchema('@spfn/monitor');\n","/**\n * @spfn/monitor - Error Groups Entity\n *\n * Groups errors by fingerprint (name + message + path) to avoid\n * duplicate tracking. Tracks count, status, and first/last seen times.\n */\n\nimport { text, integer, index } from 'drizzle-orm/pg-core';\nimport { sql } from 'drizzle-orm';\nimport { id, timestamps, enumText, utcTimestamp } from '@spfn/core/db';\nimport { monitorSchema } from './schema';\n\n/**\n * Error group status types\n */\nexport const ERROR_GROUP_STATUSES = ['active', 'resolved', 'ignored'] as const;\nexport type ErrorGroupStatus = typeof ERROR_GROUP_STATUSES[number];\n\n/**\n * Error groups table — groups errors by fingerprint\n */\nexport const errorGroups = monitorSchema.table('error_groups',\n {\n // Primary Key\n id: id(),\n\n // Business Key — SHA-256 first 16 hex chars of (name:message:path)\n fingerprint: text('fingerprint').notNull().unique(),\n\n // Error identification\n name: text('name').notNull(),\n message: text('message').notNull(),\n path: text('path').notNull(),\n method: text('method').notNull(),\n statusCode: integer('status_code').notNull(),\n\n // Status\n status: enumText('status', ERROR_GROUP_STATUSES).default('active').notNull(),\n\n // Counters\n count: integer('count').notNull().default(1),\n\n // Timeline\n firstSeenAt: utcTimestamp('first_seen_at').notNull(),\n lastSeenAt: utcTimestamp('last_seen_at').notNull(),\n resolvedAt: utcTimestamp('resolved_at'),\n\n ...timestamps(),\n },\n (table) => [\n index('monitor_eg_fingerprint_idx').on(table.fingerprint),\n index('monitor_eg_status_idx').on(table.status),\n index('monitor_eg_last_seen_at_idx').on(table.lastSeenAt),\n index('monitor_eg_path_idx').on(table.path),\n // pg_trgm GIN indexes make the admin search's leading-wildcard ILIKE\n // (%term%) on name/message/path sargable instead of a seq scan. Error\n // groups are fingerprint-deduped, so insert volume is low — the write cost\n // of these GIN indexes is acceptable here (NOT applied to the high-volume\n // logs table). Requires the pg_trgm extension (see the migration).\n index('monitor_eg_name_trgm_idx').using('gin', sql`${table.name} gin_trgm_ops`),\n index('monitor_eg_message_trgm_idx').using('gin', sql`${table.message} gin_trgm_ops`),\n index('monitor_eg_path_trgm_idx').using('gin', sql`${table.path} gin_trgm_ops`),\n ],\n);\n\nexport type ErrorGroup = typeof errorGroups.$inferSelect;\nexport type NewErrorGroup = typeof errorGroups.$inferInsert;\n","/**\n * @spfn/monitor - Error Events Entity\n *\n * Individual error occurrences linked to an error group.\n * Stores request-specific context (headers, query, stack trace).\n */\n\nimport { text, integer, jsonb, index } from 'drizzle-orm/pg-core';\nimport { id, timestamps, foreignKey } from '@spfn/core/db';\nimport { monitorSchema } from './schema';\nimport { errorGroups } from './error-groups';\n\n/**\n * Error events table — individual error occurrences\n */\nexport const errorEvents = monitorSchema.table('error_events',\n {\n // Primary Key\n id: id(),\n\n // Foreign Key\n groupId: foreignKey('group', () => errorGroups.id).notNull(),\n\n // Request context\n requestId: text('request_id'),\n userId: text('user_id'),\n statusCode: integer('status_code').notNull(),\n\n // Request details\n headers: jsonb('headers').$type<Record<string, string>>(),\n query: jsonb('query').$type<Record<string, string>>(),\n stackTrace: text('stack_trace'),\n metadata: jsonb('metadata').$type<Record<string, unknown>>(),\n\n ...timestamps(),\n },\n (table) => [\n index('monitor_ee_group_id_idx').on(table.groupId),\n index('monitor_ee_created_at_idx').on(table.createdAt),\n index('monitor_ee_user_id_idx').on(table.userId),\n ],\n);\n\nexport type ErrorEvent = typeof errorEvents.$inferSelect;\nexport type NewErrorEvent = typeof errorEvents.$inferInsert;\n","/**\n * @spfn/monitor - Logs Entity\n *\n * Developer logs stored in DB for retrieval via admin dashboard.\n * Supports level-based filtering, source tracking, and metadata.\n */\n\nimport { text, jsonb, index } from 'drizzle-orm/pg-core';\nimport { id, timestamps, enumText } from '@spfn/core/db';\nimport { monitorSchema } from './schema';\n\n/**\n * Log level types\n */\nexport const LOG_LEVELS = ['debug', 'info', 'warn', 'error', 'fatal'] as const;\nexport type LogLevel = typeof LOG_LEVELS[number];\n\n/**\n * Logs table — developer log entries\n */\nexport const logs = monitorSchema.table('logs',\n {\n // Primary Key\n id: id(),\n\n // Log data\n level: enumText('level', LOG_LEVELS).notNull(),\n message: text('message').notNull(),\n source: text('source'),\n\n // Request context\n requestId: text('request_id'),\n userId: text('user_id'),\n\n // Extra data\n metadata: jsonb('metadata').$type<Record<string, unknown>>(),\n\n ...timestamps(),\n },\n (table) => [\n index('monitor_log_level_idx').on(table.level),\n index('monitor_log_source_idx').on(table.source),\n index('monitor_log_created_at_idx').on(table.createdAt),\n ],\n);\n\nexport type Log = typeof logs.$inferSelect;\nexport type NewLog = typeof logs.$inferInsert;\n"],"mappings":"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","names":["text","integer","index","id","timestamps","id","text","integer","timestamps","index","text","jsonb","index","id","timestamps","enumText","id","enumText","text","jsonb","timestamps","index"]}
package/dist/server.js CHANGED
@@ -2628,7 +2628,7 @@ var Type = type_exports2;
2628
2628
  import { route } from "@spfn/core/route";
2629
2629
 
2630
2630
  // src/server/repositories/error-groups.repository.ts
2631
- import { eq, and, desc, sql, ilike, or, gte, lte } from "drizzle-orm";
2631
+ import { eq, and, desc, sql as sql2, ilike, or, gte, lte } from "drizzle-orm";
2632
2632
  import { BaseRepository } from "@spfn/core/db";
2633
2633
 
2634
2634
  // src/server/entities/schema.ts
@@ -2637,6 +2637,7 @@ var monitorSchema = createSchema("@spfn/monitor");
2637
2637
 
2638
2638
  // src/server/entities/error-groups.ts
2639
2639
  import { text, integer, index } from "drizzle-orm/pg-core";
2640
+ import { sql } from "drizzle-orm";
2640
2641
  import { id, timestamps, enumText, utcTimestamp } from "@spfn/core/db";
2641
2642
  var ERROR_GROUP_STATUSES = ["active", "resolved", "ignored"];
2642
2643
  var errorGroups = monitorSchema.table(
@@ -2666,7 +2667,15 @@ var errorGroups = monitorSchema.table(
2666
2667
  index("monitor_eg_fingerprint_idx").on(table.fingerprint),
2667
2668
  index("monitor_eg_status_idx").on(table.status),
2668
2669
  index("monitor_eg_last_seen_at_idx").on(table.lastSeenAt),
2669
- index("monitor_eg_path_idx").on(table.path)
2670
+ index("monitor_eg_path_idx").on(table.path),
2671
+ // pg_trgm GIN indexes make the admin search's leading-wildcard ILIKE
2672
+ // (%term%) on name/message/path sargable instead of a seq scan. Error
2673
+ // groups are fingerprint-deduped, so insert volume is low — the write cost
2674
+ // of these GIN indexes is acceptable here (NOT applied to the high-volume
2675
+ // logs table). Requires the pg_trgm extension (see the migration).
2676
+ index("monitor_eg_name_trgm_idx").using("gin", sql`${table.name} gin_trgm_ops`),
2677
+ index("monitor_eg_message_trgm_idx").using("gin", sql`${table.message} gin_trgm_ops`),
2678
+ index("monitor_eg_path_trgm_idx").using("gin", sql`${table.path} gin_trgm_ops`)
2670
2679
  ]
2671
2680
  );
2672
2681
 
@@ -2726,6 +2735,7 @@ var logs = monitorSchema.table(
2726
2735
  );
2727
2736
 
2728
2737
  // src/server/repositories/error-groups.repository.ts
2738
+ var DEFAULT_FIND_LIMIT = 100;
2729
2739
  var ErrorGroupsRepository = class extends BaseRepository {
2730
2740
  async findById(id4) {
2731
2741
  const result = await this.readDb.select().from(errorGroups).where(eq(errorGroups.id, id4)).limit(1);
@@ -2763,9 +2773,7 @@ var ErrorGroupsRepository = class extends BaseRepository {
2763
2773
  if (where) {
2764
2774
  query = query.where(where);
2765
2775
  }
2766
- if (filters.limit) {
2767
- query = query.limit(filters.limit);
2768
- }
2776
+ query = query.limit(filters.limit ?? DEFAULT_FIND_LIMIT);
2769
2777
  if (filters.offset) {
2770
2778
  query = query.offset(filters.offset);
2771
2779
  }
@@ -2780,7 +2788,7 @@ var ErrorGroupsRepository = class extends BaseRepository {
2780
2788
  }
2781
2789
  async incrementCount(id4) {
2782
2790
  const result = await this.db.update(errorGroups).set({
2783
- count: sql`${errorGroups.count} + 1`,
2791
+ count: sql2`${errorGroups.count} + 1`,
2784
2792
  lastSeenAt: /* @__PURE__ */ new Date(),
2785
2793
  updatedAt: /* @__PURE__ */ new Date()
2786
2794
  }).where(eq(errorGroups.id, id4)).returning();
@@ -2797,7 +2805,7 @@ var ErrorGroupsRepository = class extends BaseRepository {
2797
2805
  const result = await this.db.update(errorGroups).set({
2798
2806
  status: "active",
2799
2807
  resolvedAt: null,
2800
- count: sql`${errorGroups.count} + 1`,
2808
+ count: sql2`${errorGroups.count} + 1`,
2801
2809
  lastSeenAt: now,
2802
2810
  updatedAt: now
2803
2811
  }).where(eq(errorGroups.id, id4)).returning();
@@ -2815,7 +2823,7 @@ var ErrorGroupsRepository = class extends BaseRepository {
2815
2823
  async countByStatus() {
2816
2824
  const result = await this.readDb.select({
2817
2825
  status: errorGroups.status,
2818
- count: sql`count(*)::int`
2826
+ count: sql2`count(*)::int`
2819
2827
  }).from(errorGroups).groupBy(errorGroups.status);
2820
2828
  const counts = {
2821
2829
  active: 0,
@@ -2853,8 +2861,9 @@ var ErrorEventsRepository = class extends BaseRepository2 {
2853
2861
  var errorEventsRepository = new ErrorEventsRepository();
2854
2862
 
2855
2863
  // src/server/repositories/logs.repository.ts
2856
- import { eq as eq3, and as and2, desc as desc3, lt as lt2, ilike as ilike2, or as or2, gte as gte2, lte as lte2, sql as sql2 } from "drizzle-orm";
2864
+ import { eq as eq3, and as and2, desc as desc3, lt as lt2, ilike as ilike2, or as or2, gte as gte2, lte as lte2, sql as sql3 } from "drizzle-orm";
2857
2865
  import { BaseRepository as BaseRepository3 } from "@spfn/core/db";
2866
+ var DEFAULT_FIND_LIMIT2 = 100;
2858
2867
  var LogsRepository = class extends BaseRepository3 {
2859
2868
  async create(data) {
2860
2869
  return await this._create(logs, {
@@ -2896,9 +2905,7 @@ var LogsRepository = class extends BaseRepository3 {
2896
2905
  if (where) {
2897
2906
  query = query.where(where);
2898
2907
  }
2899
- if (filters.limit) {
2900
- query = query.limit(filters.limit);
2901
- }
2908
+ query = query.limit(filters.limit ?? DEFAULT_FIND_LIMIT2);
2902
2909
  if (filters.offset) {
2903
2910
  query = query.offset(filters.offset);
2904
2911
  }
@@ -2907,7 +2914,7 @@ var LogsRepository = class extends BaseRepository3 {
2907
2914
  async countByLevel() {
2908
2915
  const result = await this.readDb.select({
2909
2916
  level: logs.level,
2910
- count: sql2`count(*)::int`
2917
+ count: sql3`count(*)::int`
2911
2918
  }).from(logs).groupBy(logs.level);
2912
2919
  const counts = {
2913
2920
  debug: 0,
@@ -3083,8 +3090,10 @@ async function trackError(err, ctx, metadata) {
3083
3090
  }
3084
3091
  return;
3085
3092
  }
3086
- await errorGroupsRepository.incrementCount(existing.id);
3087
- await safeCreateEvent(existing.id, err, ctx, metadata);
3093
+ await Promise.all([
3094
+ errorGroupsRepository.incrementCount(existing.id),
3095
+ safeCreateEvent(existing.id, err, ctx, metadata)
3096
+ ]);
3088
3097
  }
3089
3098
  async function updateErrorGroupStatus(groupId, newStatus) {
3090
3099
  const group = await errorGroupsRepository.findById(groupId);
@@ -3157,7 +3166,7 @@ async function queryLogs(filters) {
3157
3166
  }
3158
3167
 
3159
3168
  // src/server/services/stats.service.ts
3160
- import { sql as sql3, gte as gte3 } from "drizzle-orm";
3169
+ import { sql as sql4, gte as gte3 } from "drizzle-orm";
3161
3170
  import { getDatabase } from "@spfn/core/db";
3162
3171
  async function getMonitorStats() {
3163
3172
  const [statusCounts, recentErrors, levelCounts, trends] = await Promise.all([
@@ -3187,9 +3196,9 @@ async function getTrends() {
3187
3196
  const last24h = new Date(now.getTime() - 24 * 60 * 60 * 1e3);
3188
3197
  const last7d = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1e3);
3189
3198
  const [errorsLast24h, errorsLast7d, logsLast24h] = await Promise.all([
3190
- db.select({ count: sql3`count(*)::int` }).from(errorEvents).where(gte3(errorEvents.createdAt, last24h)).then((r) => r[0]?.count ?? 0),
3191
- db.select({ count: sql3`count(*)::int` }).from(errorEvents).where(gte3(errorEvents.createdAt, last7d)).then((r) => r[0]?.count ?? 0),
3192
- db.select({ count: sql3`count(*)::int` }).from(logs).where(gte3(logs.createdAt, last24h)).then((r) => r[0]?.count ?? 0)
3199
+ db.select({ count: sql4`count(*)::int` }).from(errorEvents).where(gte3(errorEvents.createdAt, last24h)).then((r) => r[0]?.count ?? 0),
3200
+ db.select({ count: sql4`count(*)::int` }).from(errorEvents).where(gte3(errorEvents.createdAt, last7d)).then((r) => r[0]?.count ?? 0),
3201
+ db.select({ count: sql4`count(*)::int` }).from(logs).where(gte3(logs.createdAt, last24h)).then((r) => r[0]?.count ?? 0)
3193
3202
  ]);
3194
3203
  return { errorsLast24h, errorsLast7d, logsLast24h };
3195
3204
  }