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

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
 
@@ -2780,7 +2789,7 @@ var ErrorGroupsRepository = class extends BaseRepository {
2780
2789
  }
2781
2790
  async incrementCount(id4) {
2782
2791
  const result = await this.db.update(errorGroups).set({
2783
- count: sql`${errorGroups.count} + 1`,
2792
+ count: sql2`${errorGroups.count} + 1`,
2784
2793
  lastSeenAt: /* @__PURE__ */ new Date(),
2785
2794
  updatedAt: /* @__PURE__ */ new Date()
2786
2795
  }).where(eq(errorGroups.id, id4)).returning();
@@ -2797,7 +2806,7 @@ var ErrorGroupsRepository = class extends BaseRepository {
2797
2806
  const result = await this.db.update(errorGroups).set({
2798
2807
  status: "active",
2799
2808
  resolvedAt: null,
2800
- count: sql`${errorGroups.count} + 1`,
2809
+ count: sql2`${errorGroups.count} + 1`,
2801
2810
  lastSeenAt: now,
2802
2811
  updatedAt: now
2803
2812
  }).where(eq(errorGroups.id, id4)).returning();
@@ -2815,7 +2824,7 @@ var ErrorGroupsRepository = class extends BaseRepository {
2815
2824
  async countByStatus() {
2816
2825
  const result = await this.readDb.select({
2817
2826
  status: errorGroups.status,
2818
- count: sql`count(*)::int`
2827
+ count: sql2`count(*)::int`
2819
2828
  }).from(errorGroups).groupBy(errorGroups.status);
2820
2829
  const counts = {
2821
2830
  active: 0,
@@ -2853,7 +2862,7 @@ var ErrorEventsRepository = class extends BaseRepository2 {
2853
2862
  var errorEventsRepository = new ErrorEventsRepository();
2854
2863
 
2855
2864
  // 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";
2865
+ 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
2866
  import { BaseRepository as BaseRepository3 } from "@spfn/core/db";
2858
2867
  var LogsRepository = class extends BaseRepository3 {
2859
2868
  async create(data) {
@@ -2907,7 +2916,7 @@ var LogsRepository = class extends BaseRepository3 {
2907
2916
  async countByLevel() {
2908
2917
  const result = await this.readDb.select({
2909
2918
  level: logs.level,
2910
- count: sql2`count(*)::int`
2919
+ count: sql3`count(*)::int`
2911
2920
  }).from(logs).groupBy(logs.level);
2912
2921
  const counts = {
2913
2922
  debug: 0,
@@ -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
  }