@freshguard/freshguard-core 0.13.2 → 0.15.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 (196) hide show
  1. package/CHANGELOG.md +15 -1
  2. package/README.md +74 -1
  3. package/SKILL.md +229 -0
  4. package/dist/cli/index.d.ts +13 -0
  5. package/dist/cli/index.d.ts.map +1 -1
  6. package/dist/cli/index.js +74 -1
  7. package/dist/cli/index.js.map +1 -1
  8. package/dist/connectors/azure-sql.d.ts +121 -0
  9. package/dist/connectors/azure-sql.d.ts.map +1 -0
  10. package/dist/connectors/azure-sql.js +489 -0
  11. package/dist/connectors/azure-sql.js.map +1 -0
  12. package/dist/connectors/base-connector.d.ts +139 -0
  13. package/dist/connectors/base-connector.d.ts.map +1 -1
  14. package/dist/connectors/base-connector.js +160 -3
  15. package/dist/connectors/base-connector.js.map +1 -1
  16. package/dist/connectors/bigquery.d.ts +100 -0
  17. package/dist/connectors/bigquery.d.ts.map +1 -1
  18. package/dist/connectors/bigquery.js +143 -2
  19. package/dist/connectors/bigquery.js.map +1 -1
  20. package/dist/connectors/duckdb.d.ts +96 -0
  21. package/dist/connectors/duckdb.d.ts.map +1 -1
  22. package/dist/connectors/duckdb.js +144 -7
  23. package/dist/connectors/duckdb.js.map +1 -1
  24. package/dist/connectors/index.d.ts +28 -0
  25. package/dist/connectors/index.d.ts.map +1 -1
  26. package/dist/connectors/index.js +28 -0
  27. package/dist/connectors/index.js.map +1 -1
  28. package/dist/connectors/mssql.d.ts +119 -0
  29. package/dist/connectors/mssql.d.ts.map +1 -0
  30. package/dist/connectors/mssql.js +483 -0
  31. package/dist/connectors/mssql.js.map +1 -0
  32. package/dist/connectors/mysql.d.ts +85 -0
  33. package/dist/connectors/mysql.d.ts.map +1 -1
  34. package/dist/connectors/mysql.js +118 -3
  35. package/dist/connectors/mysql.js.map +1 -1
  36. package/dist/connectors/postgres.d.ts +85 -0
  37. package/dist/connectors/postgres.d.ts.map +1 -1
  38. package/dist/connectors/postgres.js +113 -6
  39. package/dist/connectors/postgres.js.map +1 -1
  40. package/dist/connectors/redshift.d.ts +90 -0
  41. package/dist/connectors/redshift.d.ts.map +1 -1
  42. package/dist/connectors/redshift.js +131 -7
  43. package/dist/connectors/redshift.js.map +1 -1
  44. package/dist/connectors/snowflake.d.ts +108 -0
  45. package/dist/connectors/snowflake.d.ts.map +1 -1
  46. package/dist/connectors/snowflake.js +137 -3
  47. package/dist/connectors/snowflake.js.map +1 -1
  48. package/dist/connectors/synapse.d.ts +123 -0
  49. package/dist/connectors/synapse.d.ts.map +1 -0
  50. package/dist/connectors/synapse.js +495 -0
  51. package/dist/connectors/synapse.js.map +1 -0
  52. package/dist/db/index.d.ts +25 -0
  53. package/dist/db/index.d.ts.map +1 -1
  54. package/dist/db/index.js +23 -0
  55. package/dist/db/index.js.map +1 -1
  56. package/dist/db/migrate.d.ts +23 -0
  57. package/dist/db/migrate.d.ts.map +1 -1
  58. package/dist/db/migrate.js +38 -0
  59. package/dist/db/migrate.js.map +1 -1
  60. package/dist/db/schema.d.ts +11 -0
  61. package/dist/db/schema.d.ts.map +1 -1
  62. package/dist/db/schema.js +70 -0
  63. package/dist/db/schema.js.map +1 -1
  64. package/dist/errors/debug-factory.d.ts +38 -0
  65. package/dist/errors/debug-factory.d.ts.map +1 -1
  66. package/dist/errors/debug-factory.js +40 -0
  67. package/dist/errors/debug-factory.js.map +1 -1
  68. package/dist/errors/index.d.ts +59 -0
  69. package/dist/errors/index.d.ts.map +1 -1
  70. package/dist/errors/index.js +110 -7
  71. package/dist/errors/index.js.map +1 -1
  72. package/dist/index.d.ts +32 -1
  73. package/dist/index.d.ts.map +1 -1
  74. package/dist/index.js +37 -1
  75. package/dist/index.js.map +1 -1
  76. package/dist/metadata/duckdb-storage.d.ts +3 -0
  77. package/dist/metadata/duckdb-storage.d.ts.map +1 -1
  78. package/dist/metadata/duckdb-storage.js +6 -0
  79. package/dist/metadata/duckdb-storage.js.map +1 -1
  80. package/dist/metadata/factory.d.ts +30 -0
  81. package/dist/metadata/factory.d.ts.map +1 -1
  82. package/dist/metadata/factory.js +31 -0
  83. package/dist/metadata/factory.js.map +1 -1
  84. package/dist/metadata/index.d.ts +26 -0
  85. package/dist/metadata/index.d.ts.map +1 -1
  86. package/dist/metadata/index.js +26 -0
  87. package/dist/metadata/index.js.map +1 -1
  88. package/dist/metadata/interface.d.ts +33 -0
  89. package/dist/metadata/interface.d.ts.map +1 -1
  90. package/dist/metadata/interface.js +3 -0
  91. package/dist/metadata/interface.js.map +1 -1
  92. package/dist/metadata/postgresql-storage.d.ts +3 -0
  93. package/dist/metadata/postgresql-storage.d.ts.map +1 -1
  94. package/dist/metadata/postgresql-storage.js +12 -2
  95. package/dist/metadata/postgresql-storage.js.map +1 -1
  96. package/dist/metadata/schema-config.d.ts +53 -0
  97. package/dist/metadata/schema-config.d.ts.map +1 -1
  98. package/dist/metadata/schema-config.js +64 -0
  99. package/dist/metadata/schema-config.js.map +1 -1
  100. package/dist/metadata/types.d.ts +3 -0
  101. package/dist/metadata/types.d.ts.map +1 -1
  102. package/dist/metadata/types.js +3 -0
  103. package/dist/metadata/types.js.map +1 -1
  104. package/dist/monitor/baseline-calculator.d.ts +56 -0
  105. package/dist/monitor/baseline-calculator.d.ts.map +1 -1
  106. package/dist/monitor/baseline-calculator.js +72 -0
  107. package/dist/monitor/baseline-calculator.js.map +1 -1
  108. package/dist/monitor/baseline-config.d.ts +77 -0
  109. package/dist/monitor/baseline-config.d.ts.map +1 -1
  110. package/dist/monitor/baseline-config.js +79 -1
  111. package/dist/monitor/baseline-config.js.map +1 -1
  112. package/dist/monitor/freshness.d.ts +40 -0
  113. package/dist/monitor/freshness.d.ts.map +1 -1
  114. package/dist/monitor/freshness.js +82 -3
  115. package/dist/monitor/freshness.js.map +1 -1
  116. package/dist/monitor/index.d.ts +29 -0
  117. package/dist/monitor/index.d.ts.map +1 -1
  118. package/dist/monitor/index.js +29 -0
  119. package/dist/monitor/index.js.map +1 -1
  120. package/dist/monitor/schema-baseline.d.ts +45 -0
  121. package/dist/monitor/schema-baseline.d.ts.map +1 -1
  122. package/dist/monitor/schema-baseline.js +63 -5
  123. package/dist/monitor/schema-baseline.js.map +1 -1
  124. package/dist/monitor/schema-changes.d.ts +45 -0
  125. package/dist/monitor/schema-changes.d.ts.map +1 -1
  126. package/dist/monitor/schema-changes.js +85 -0
  127. package/dist/monitor/schema-changes.js.map +1 -1
  128. package/dist/monitor/volume.d.ts +43 -0
  129. package/dist/monitor/volume.d.ts.map +1 -1
  130. package/dist/monitor/volume.js +89 -0
  131. package/dist/monitor/volume.js.map +1 -1
  132. package/dist/observability/logger.d.ts +91 -0
  133. package/dist/observability/logger.d.ts.map +1 -1
  134. package/dist/observability/logger.js +108 -0
  135. package/dist/observability/logger.js.map +1 -1
  136. package/dist/observability/metrics.d.ts +140 -0
  137. package/dist/observability/metrics.d.ts.map +1 -1
  138. package/dist/observability/metrics.js +184 -7
  139. package/dist/observability/metrics.js.map +1 -1
  140. package/dist/resilience/circuit-breaker.d.ts +112 -2
  141. package/dist/resilience/circuit-breaker.d.ts.map +1 -1
  142. package/dist/resilience/circuit-breaker.js +140 -6
  143. package/dist/resilience/circuit-breaker.js.map +1 -1
  144. package/dist/resilience/index.d.ts +9 -0
  145. package/dist/resilience/index.d.ts.map +1 -1
  146. package/dist/resilience/index.js +13 -0
  147. package/dist/resilience/index.js.map +1 -1
  148. package/dist/resilience/retry-policy.d.ts +105 -0
  149. package/dist/resilience/retry-policy.d.ts.map +1 -1
  150. package/dist/resilience/retry-policy.js +158 -7
  151. package/dist/resilience/retry-policy.js.map +1 -1
  152. package/dist/resilience/timeout-manager.d.ts +137 -0
  153. package/dist/resilience/timeout-manager.d.ts.map +1 -1
  154. package/dist/resilience/timeout-manager.js +151 -4
  155. package/dist/resilience/timeout-manager.js.map +1 -1
  156. package/dist/security/query-analyzer.d.ts +124 -0
  157. package/dist/security/query-analyzer.d.ts.map +1 -1
  158. package/dist/security/query-analyzer.js +150 -9
  159. package/dist/security/query-analyzer.js.map +1 -1
  160. package/dist/security/schema-cache.d.ts +152 -0
  161. package/dist/security/schema-cache.d.ts.map +1 -1
  162. package/dist/security/schema-cache.js +144 -12
  163. package/dist/security/schema-cache.js.map +1 -1
  164. package/dist/types/connector.d.ts +68 -1
  165. package/dist/types/connector.d.ts.map +1 -1
  166. package/dist/types/connector.js +38 -15
  167. package/dist/types/connector.js.map +1 -1
  168. package/dist/types/driver-results.d.ts +28 -0
  169. package/dist/types/driver-results.d.ts.map +1 -1
  170. package/dist/types/driver-results.js +12 -0
  171. package/dist/types/driver-results.js.map +1 -1
  172. package/dist/types.d.ts +113 -1
  173. package/dist/types.d.ts.map +1 -1
  174. package/dist/types.js +8 -0
  175. package/dist/types.js.map +1 -1
  176. package/dist/validation/index.d.ts +8 -0
  177. package/dist/validation/index.d.ts.map +1 -1
  178. package/dist/validation/index.js +12 -0
  179. package/dist/validation/index.js.map +1 -1
  180. package/dist/validation/runtime-validator.d.ts +98 -0
  181. package/dist/validation/runtime-validator.d.ts.map +1 -1
  182. package/dist/validation/runtime-validator.js +114 -1
  183. package/dist/validation/runtime-validator.js.map +1 -1
  184. package/dist/validation/sanitizers.d.ts +59 -0
  185. package/dist/validation/sanitizers.d.ts.map +1 -1
  186. package/dist/validation/sanitizers.js +104 -20
  187. package/dist/validation/sanitizers.js.map +1 -1
  188. package/dist/validation/schemas.d.ts +73 -0
  189. package/dist/validation/schemas.d.ts.map +1 -1
  190. package/dist/validation/schemas.js +132 -5
  191. package/dist/validation/schemas.js.map +1 -1
  192. package/dist/validators/index.d.ts +54 -0
  193. package/dist/validators/index.d.ts.map +1 -1
  194. package/dist/validators/index.js +93 -2
  195. package/dist/validators/index.js.map +1 -1
  196. package/package.json +6 -2
package/CHANGELOG.md CHANGED
@@ -7,7 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.15.0] - 2026-02-12
11
+
10
12
  ### Added
13
+ - **SKILL.md agent skills directory** — instruction-style documentation for AI coding agents, wired via `agentskills` field in package.json
14
+ - **Per-export API reference** in README — tables for monitoring functions, connectors, metadata storage, errors, and key types
15
+ - **"When to use this"** paragraph in README for quick orientation
16
+ - **Compatibility section** in README documenting Node, TypeScript, and ESM requirements
17
+
18
+ ### Changed
19
+ - JSDoc comments now preserved in shipped `.d.ts` files (`removeComments: false` in tsconfig)
20
+ - Enhanced JSDoc on all 9 connector constructors with `@param`, `@example` tags
21
+ - Added module-level JSDoc to `connectors/index`, `monitor/index`, `metadata/index`, `db/index`
22
+ - Added `@example` to `MonitoringRule`, `CheckResult`, and `DataSource` type interfaces
23
+ - Improved `createDatabase()` JSDoc with usage example
11
24
  - Documentation site with versioned API reference
12
25
 
13
26
  ## [0.12.0] - 2026-02-10
@@ -148,7 +161,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
148
161
  - Drizzle ORM database schema and migrations
149
162
  - MIT license
150
163
 
151
- [Unreleased]: https://github.com/freshguard-dev/freshguard-core/compare/v0.12.0...HEAD
164
+ [Unreleased]: https://github.com/freshguard-dev/freshguard-core/compare/v0.15.0...HEAD
165
+ [0.15.0]: https://github.com/freshguard-dev/freshguard-core/compare/v0.12.0...v0.15.0
152
166
  [0.12.0]: https://github.com/freshguard-dev/freshguard-core/compare/v0.11.3...v0.12.0
153
167
  [0.11.3]: https://github.com/freshguard-dev/freshguard-core/compare/v0.11.2...v0.11.3
154
168
  [0.11.2]: https://github.com/freshguard-dev/freshguard-core/compare/v0.11.1...v0.11.2
package/README.md CHANGED
@@ -6,7 +6,9 @@
6
6
  [![npm version](https://img.shields.io/npm/v/@freshguard/freshguard-core.svg)](https://www.npmjs.com/package/@freshguard/freshguard-core)
7
7
  [![Docs](https://img.shields.io/badge/docs-website-blue)](https://freshguard-dev.github.io/freshguard-core)
8
8
 
9
- Monitor when your data pipelines go stale. Supports PostgreSQL, DuckDB, BigQuery, Snowflake, MySQL, and Redshift. Self-hosted. Free forever.
9
+ Monitor when your data pipelines go stale. Supports PostgreSQL, DuckDB, BigQuery, Snowflake, MySQL, Redshift, SQL Server, Azure SQL, and Azure Synapse. Self-hosted. Free forever.
10
+
11
+ **When to use this:** You run data pipelines (ETL/ELT) that land data into a warehouse or database and you need to know — programmatically — when a table stops updating, row counts look wrong, or the schema drifts. Install this package in a Node.js script, a cron job, or a long-running process and it will check your tables and tell you what's stale. No dashboard, no vendor lock-in, no account required.
10
12
 
11
13
  ## Install
12
14
 
@@ -52,6 +54,63 @@ if (result.status === 'alert') {
52
54
  - **Volume anomaly detection** — Alert when row counts deviate from a calculated baseline
53
55
  - **Schema change detection** — Alert when columns are added, removed, or modified
54
56
 
57
+ ## API at a glance
58
+
59
+ ### Monitoring functions
60
+
61
+ | Export | Description |
62
+ |---|---|
63
+ | `checkFreshness(connector, rule)` | Returns `'alert'` when `MAX(timestampColumn)` exceeds `toleranceMinutes` |
64
+ | `checkVolumeAnomaly(connector, rule)` | Returns `'alert'` when current row count deviates from the historical baseline |
65
+ | `checkSchemaChanges(connector, rule)` | Returns `'alert'` when columns are added, removed, or modified |
66
+
67
+ ### Connectors
68
+
69
+ | Export | Database |
70
+ |---|---|
71
+ | `PostgresConnector` | PostgreSQL |
72
+ | `DuckDBConnector` | DuckDB (local file / `:memory:`) |
73
+ | `BigQueryConnector` | Google BigQuery |
74
+ | `SnowflakeConnector` | Snowflake |
75
+ | `MySQLConnector` | MySQL |
76
+ | `RedshiftConnector` | Amazon Redshift |
77
+ | `MSSQLConnector` | SQL Server |
78
+ | `AzureSQLConnector` | Azure SQL Database |
79
+ | `SynapseConnector` | Azure Synapse Analytics |
80
+
81
+ All connectors accept a `ConnectorConfig` (host, port, database, username, password, ssl) and an optional `SecurityConfig` override.
82
+
83
+ ### Metadata storage
84
+
85
+ | Export | Description |
86
+ |---|---|
87
+ | `createMetadataStorage(config?)` | Factory — returns DuckDB (default) or PostgreSQL metadata store |
88
+ | `DuckDBMetadataStorage` | Embedded DuckDB storage (zero-setup) |
89
+ | `PostgreSQLMetadataStorage` | PostgreSQL-backed shared storage |
90
+
91
+ ### Database utilities
92
+
93
+ | Export | Description |
94
+ |---|---|
95
+ | `createDatabase(connectionString)` | Create a Drizzle ORM connection to the FreshGuard PostgreSQL schema |
96
+ | `schema` | Drizzle table definitions |
97
+
98
+ ### Error classes
99
+
100
+ | Export | Thrown when |
101
+ |---|---|
102
+ | `FreshGuardError` | Base class for all FreshGuard errors |
103
+ | `SecurityError` | SQL injection attempt or blocked query |
104
+ | `ConnectionError` | Database connection failure |
105
+ | `TimeoutError` | Query or connection timeout exceeded |
106
+ | `QueryError` | Query execution failure |
107
+ | `ConfigurationError` | Invalid configuration |
108
+ | `MonitoringError` | Monitoring logic failure |
109
+
110
+ ### Key types
111
+
112
+ `MonitoringRule`, `CheckResult`, `DataSource`, `SourceCredentials`, `ConnectorConfig`, `CheckStatus`, `RuleType`, `DataSourceType`, `SchemaChanges`, `ColumnChange`, `FreshGuardConfig`
113
+
55
114
  ## Documentation
56
115
 
57
116
  **[Read the full docs](https://freshguard-dev.github.io/freshguard-core)** — guides, API reference, examples, and self-hosting instructions.
@@ -75,6 +134,9 @@ if (result.status === 'alert') {
75
134
  | Snowflake | `SnowflakeConnector` | 0.2.0 |
76
135
  | MySQL | `MySQLConnector` | 0.11.0 |
77
136
  | Redshift | `RedshiftConnector` | 0.11.0 |
137
+ | SQL Server | `MSSQLConnector` | 0.14.0 |
138
+ | Azure SQL Database | `AzureSQLConnector` | 0.14.0 |
139
+ | Azure Synapse Analytics | `SynapseConnector` | 0.14.0 |
78
140
 
79
141
  ## CLI
80
142
 
@@ -85,6 +147,17 @@ pnpm exec freshguard test
85
147
  pnpm exec freshguard run
86
148
  ```
87
149
 
150
+ ## Compatibility
151
+
152
+ | Requirement | Version |
153
+ |---|---|
154
+ | Node.js | >=20.0.0 |
155
+ | TypeScript | >=5.3 (ships `.d.ts` + declaration maps) |
156
+ | Module system | ESM only (`"type": "module"`) |
157
+ | Package manager | pnpm >=10 (npm/yarn work for consumers) |
158
+
159
+ This package follows **semver**. Breaking changes only land in major version bumps. The current version is **0.x** (pre-1.0), so minor versions may contain breaking changes — check [CHANGELOG.md](CHANGELOG.md) before upgrading.
160
+
88
161
  ## Architecture
89
162
 
90
163
  This is the **open-source core** (MIT licensed). It contains all monitoring logic, connectors, and CLI tooling. It does not include multi-tenant features, authentication, or dashboards — those are part of [FreshGuard Cloud](https://freshguard.dev).
package/SKILL.md ADDED
@@ -0,0 +1,229 @@
1
+ # FreshGuard Core — Agent Skill
2
+
3
+ ## When to use this library
4
+
5
+ Use `@freshguard/freshguard-core` when you need to **programmatically check whether data in a database table is fresh, whether row counts are normal, or whether the schema has changed**. It is the right tool when:
6
+
7
+ - A user asks you to monitor a data pipeline for staleness.
8
+ - A user wants alerts when a table stops updating or row counts look wrong.
9
+ - A user needs to detect schema drift (added/removed/modified columns).
10
+ - You need to connect to PostgreSQL, DuckDB, BigQuery, Snowflake, MySQL, Redshift, SQL Server, Azure SQL, or Azure Synapse and run read-only health checks.
11
+
12
+ Do **not** use this library for writing data, running arbitrary queries, or building dashboards.
13
+
14
+ ---
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ pnpm install @freshguard/freshguard-core
20
+ ```
21
+
22
+ All imports come from the single entry point:
23
+
24
+ ```typescript
25
+ import { checkFreshness, PostgresConnector, ... } from '@freshguard/freshguard-core';
26
+ ```
27
+
28
+ ---
29
+
30
+ ## Core operations
31
+
32
+ ### 1. Check data freshness
33
+
34
+ Detect when a table hasn't been updated within an expected window.
35
+
36
+ ```typescript
37
+ import { checkFreshness, PostgresConnector } from '@freshguard/freshguard-core';
38
+
39
+ // 1. Create a connector
40
+ const connector = new PostgresConnector({
41
+ host: 'localhost', port: 5432,
42
+ database: 'analytics',
43
+ username: 'readonly',
44
+ password: process.env.DB_PASSWORD!,
45
+ ssl: true,
46
+ });
47
+
48
+ // 2. Define a rule
49
+ const rule = {
50
+ id: 'orders-freshness',
51
+ sourceId: 'prod_pg',
52
+ name: 'Orders Freshness',
53
+ tableName: 'orders',
54
+ ruleType: 'freshness' as const,
55
+ toleranceMinutes: 60, // alert if data is older than 60 min
56
+ timestampColumn: 'updated_at', // column to check
57
+ checkIntervalMinutes: 5,
58
+ isActive: true,
59
+ createdAt: new Date(),
60
+ updatedAt: new Date(),
61
+ };
62
+
63
+ // 3. Run the check
64
+ const result = await checkFreshness(connector, rule);
65
+
66
+ // 4. Inspect the result
67
+ if (result.status === 'alert') {
68
+ console.log(`Data is ${result.lagMinutes} minutes stale`);
69
+ }
70
+ // result.status is 'ok' | 'alert' | 'failed'
71
+ ```
72
+
73
+ ### 2. Detect volume anomalies
74
+
75
+ Alert when a table's row count deviates from its historical baseline.
76
+
77
+ ```typescript
78
+ import { checkVolumeAnomaly, PostgresConnector } from '@freshguard/freshguard-core';
79
+
80
+ const connector = new PostgresConnector({ /* ... */ });
81
+
82
+ const rule = {
83
+ id: 'orders-volume',
84
+ sourceId: 'prod_pg',
85
+ name: 'Orders Volume',
86
+ tableName: 'orders',
87
+ ruleType: 'volume_anomaly' as const,
88
+ baselineWindowDays: 14,
89
+ deviationThresholdPercent: 30, // alert if >30% deviation
90
+ checkIntervalMinutes: 60,
91
+ isActive: true,
92
+ createdAt: new Date(),
93
+ updatedAt: new Date(),
94
+ };
95
+
96
+ // Provide metadata storage so the baseline can be calculated from history
97
+ import { createMetadataStorage } from '@freshguard/freshguard-core';
98
+ const storage = await createMetadataStorage(); // embedded DuckDB
99
+
100
+ const result = await checkVolumeAnomaly(connector, rule, storage);
101
+
102
+ if (result.status === 'alert') {
103
+ console.log(`Row count deviation: ${result.deviation}% from baseline ${result.baselineAverage}`);
104
+ }
105
+ ```
106
+
107
+ ### 3. Detect schema changes
108
+
109
+ Alert when columns are added, removed, or modified.
110
+
111
+ ```typescript
112
+ import { checkSchemaChanges, PostgresConnector } from '@freshguard/freshguard-core';
113
+
114
+ const connector = new PostgresConnector({ /* ... */ });
115
+
116
+ const rule = {
117
+ id: 'orders-schema',
118
+ sourceId: 'prod_pg',
119
+ name: 'Orders Schema',
120
+ tableName: 'orders',
121
+ ruleType: 'schema_change' as const,
122
+ trackColumnChanges: true,
123
+ checkIntervalMinutes: 60,
124
+ isActive: true,
125
+ createdAt: new Date(),
126
+ updatedAt: new Date(),
127
+ };
128
+
129
+ const storage = await createMetadataStorage();
130
+ const result = await checkSchemaChanges(connector, rule, storage);
131
+
132
+ if (result.status === 'alert' && result.schemaChanges) {
133
+ console.log(result.schemaChanges.summary);
134
+ // e.g. "2 columns added, 1 column removed"
135
+ }
136
+ ```
137
+
138
+ ---
139
+
140
+ ## Connector setup
141
+
142
+ Every connector takes a `ConnectorConfig` object:
143
+
144
+ ```typescript
145
+ interface ConnectorConfig {
146
+ host: string;
147
+ port: number;
148
+ database: string;
149
+ username: string;
150
+ password: string;
151
+ ssl?: boolean; // default: true
152
+ timeout?: number; // connection timeout ms (default: 30000)
153
+ queryTimeout?: number; // query timeout ms (default: 10000)
154
+ maxRows?: number; // max rows returned (default: 1000)
155
+ }
156
+ ```
157
+
158
+ | Connector | Notes |
159
+ |---|---|
160
+ | `PostgresConnector` | Standard PostgreSQL |
161
+ | `DuckDBConnector` | `database` = file path or `':memory:'` |
162
+ | `BigQueryConnector` | `database` = GCP project ID |
163
+ | `SnowflakeConnector` | `host` = `<account>.snowflakecomputing.com` |
164
+ | `MySQLConnector` | Standard MySQL, port 3306 |
165
+ | `RedshiftConnector` | PostgreSQL wire protocol, port 5439 |
166
+ | `MSSQLConnector` | SQL Server, port 1433 |
167
+ | `AzureSQLConnector` | `host` = `<server>.database.windows.net` |
168
+ | `SynapseConnector` | `host` = `<workspace>.sql.azuresynapse.net` |
169
+
170
+ ---
171
+
172
+ ## Metadata storage
173
+
174
+ Volume anomaly and schema change checks need historical data. Pass a
175
+ `MetadataStorage` instance as the third argument:
176
+
177
+ ```typescript
178
+ import { createMetadataStorage } from '@freshguard/freshguard-core';
179
+
180
+ // Embedded DuckDB (zero-setup, default)
181
+ const storage = await createMetadataStorage();
182
+
183
+ // Custom DuckDB path
184
+ const storage = await createMetadataStorage({ type: 'duckdb', path: './meta.db' });
185
+
186
+ // PostgreSQL (shared across workers)
187
+ const storage = await createMetadataStorage({
188
+ type: 'postgresql',
189
+ url: 'postgresql://user:pass@host:5432/freshguard_meta',
190
+ });
191
+ ```
192
+
193
+ ---
194
+
195
+ ## Error handling
196
+
197
+ All errors extend `FreshGuardError`. Catch specific subclasses:
198
+
199
+ ```typescript
200
+ import {
201
+ SecurityError,
202
+ ConnectionError,
203
+ TimeoutError,
204
+ QueryError,
205
+ ConfigurationError,
206
+ } from '@freshguard/freshguard-core';
207
+
208
+ try {
209
+ const result = await checkFreshness(connector, rule);
210
+ } catch (err) {
211
+ if (err instanceof ConnectionError) {
212
+ // database unreachable
213
+ } else if (err instanceof TimeoutError) {
214
+ // query or connection timed out
215
+ } else if (err instanceof SecurityError) {
216
+ // blocked query (SQL injection prevention)
217
+ }
218
+ }
219
+ ```
220
+
221
+ ---
222
+
223
+ ## Key types
224
+
225
+ - **`MonitoringRule`** — defines what table to check, which algorithm, and the thresholds.
226
+ - **`CheckResult`** — the outcome: `status` (`'ok'` | `'alert'` | `'failed'`), plus `lagMinutes`, `deviation`, `schemaChanges`, etc.
227
+ - **`ConnectorConfig`** — database connection credentials.
228
+ - **`DataSource`** — a registered database with metadata.
229
+ - **`FreshGuardConfig`** — top-level config for self-hosted deployments.
@@ -1,3 +1,16 @@
1
1
  #!/usr/bin/env node
2
+ /**
3
+ * FreshGuard CLI - Secure Command-Line Interface
4
+ * Simple command-line interface for self-hosters with built-in security measures
5
+ *
6
+ * Security features:
7
+ * - Secure credential handling with environment variables
8
+ * - Input validation to prevent command injection
9
+ * - No sensitive data logging
10
+ * - Safe file path handling
11
+ * - Connection validation with timeouts
12
+ *
13
+ * @module @freshguard/freshguard-core/cli
14
+ */
2
15
  export {};
3
16
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;GAYG"}
package/dist/cli/index.js CHANGED
@@ -1,4 +1,17 @@
1
1
  #!/usr/bin/env node
2
+ /**
3
+ * FreshGuard CLI - Secure Command-Line Interface
4
+ * Simple command-line interface for self-hosters with built-in security measures
5
+ *
6
+ * Security features:
7
+ * - Secure credential handling with environment variables
8
+ * - Input validation to prevent command injection
9
+ * - No sensitive data logging
10
+ * - Safe file path handling
11
+ * - Connection validation with timeouts
12
+ *
13
+ * @module @freshguard/freshguard-core/cli
14
+ */
2
15
  import { readFile, writeFile, existsSync, mkdirSync } from 'fs';
3
16
  import { join, resolve, dirname } from 'path';
4
17
  import { promisify } from 'util';
@@ -7,8 +20,11 @@ import { DuckDBConnector } from '../connectors/duckdb.js';
7
20
  import { BigQueryConnector } from '../connectors/bigquery.js';
8
21
  import { SnowflakeConnector } from '../connectors/snowflake.js';
9
22
  import { ConfigurationError, ConnectionError, ErrorHandler } from '../errors/index.js';
23
+ // Import validators if needed for future CLI validation
24
+ // import { validateTableName } from '../validators/index.js';
10
25
  const readFileAsync = promisify(readFile);
11
26
  const writeFileAsync = promisify(writeFile);
27
+ // Security: Define allowed configuration file paths to prevent path traversal
12
28
  const ALLOWED_CONFIG_DIRS = [
13
29
  process.cwd(),
14
30
  join(process.cwd(), '.freshguard'),
@@ -23,6 +39,7 @@ async function main() {
23
39
  printHelp();
24
40
  process.exit(0);
25
41
  }
42
+ // Security: Validate command to prevent injection
26
43
  if (!isValidCommand(command)) {
27
44
  console.error(`āŒ Invalid command: ${command}`);
28
45
  process.exit(1);
@@ -55,21 +72,30 @@ async function main() {
55
72
  }
56
73
  }
57
74
  catch (error) {
75
+ // Security: Use error sanitization to prevent information disclosure
58
76
  const userMessage = ErrorHandler.getUserMessage(error);
59
77
  console.error(`āŒ Error: ${userMessage}`);
78
+ // Only show stack trace in development mode
60
79
  if (process.env.NODE_ENV === 'development' && error instanceof Error) {
61
80
  console.error('Stack trace:', error.stack);
62
81
  }
63
82
  process.exit(1);
64
83
  }
65
84
  }
85
+ /**
86
+ * Security: Validate command to prevent injection attacks
87
+ */
66
88
  function isValidCommand(cmd) {
67
89
  const validCommands = ['init', 'test', 'run', 'version', '-v', '--version', 'help', '-h', '--help'];
68
90
  return validCommands.includes(cmd) && /^[a-zA-Z-]+$/.test(cmd);
69
91
  }
92
+ /**
93
+ * Security: Validate file path to prevent directory traversal
94
+ */
70
95
  function validateConfigPath(filePath) {
71
96
  try {
72
97
  const resolvedPath = resolve(filePath);
98
+ // Check if path is within allowed directories
73
99
  const isAllowed = ALLOWED_CONFIG_DIRS.some(allowedDir => {
74
100
  const resolvedAllowedDir = resolve(allowedDir);
75
101
  return resolvedPath.startsWith(resolvedAllowedDir);
@@ -83,8 +109,12 @@ function validateConfigPath(filePath) {
83
109
  throw new ConfigurationError('Invalid configuration file path');
84
110
  }
85
111
  }
112
+ /**
113
+ * Initialize FreshGuard configuration with secure credential handling
114
+ */
86
115
  async function handleInit() {
87
116
  console.log('šŸš€ FreshGuard Init\n');
117
+ // Security: Get database URL from environment or prompt securely
88
118
  const dbUrl = process.env.FRESHGUARD_DATABASE_URL;
89
119
  let dbType = 'postgres';
90
120
  if (!dbUrl) {
@@ -92,6 +122,7 @@ async function handleInit() {
92
122
  console.log('Please set your database connection string as an environment variable:');
93
123
  console.log('');
94
124
  console.log('For PostgreSQL:');
125
+ // eslint-disable-next-line no-secrets/no-secrets
95
126
  console.log(' export FRESHGUARD_DATABASE_URL="postgresql://user:password@localhost:5432/database?sslmode=require"');
96
127
  console.log('');
97
128
  console.log('For DuckDB:');
@@ -100,16 +131,21 @@ async function handleInit() {
100
131
  console.log('Then run: freshguard init');
101
132
  return;
102
133
  }
134
+ // Security: Parse URL safely without exposing credentials in logs
103
135
  try {
104
136
  const config = parseSecureConnectionString(dbUrl);
105
137
  dbType = config.type;
106
138
  console.log('āœ… Database URL detected');
107
139
  console.log(`šŸ“Š Database type: ${dbType}`);
108
140
  console.log(`šŸ”’ SSL required: ${config.ssl ? 'Yes' : 'No'}`);
141
+ // Create configuration directory if it doesn't exist
109
142
  const configDir = dirname(validateConfigPath(DEFAULT_CONFIG_FILE));
143
+ // eslint-disable-next-line security/detect-non-literal-fs-filename
110
144
  if (!existsSync(configDir)) {
145
+ // eslint-disable-next-line security/detect-non-literal-fs-filename
111
146
  mkdirSync(configDir, { recursive: true });
112
147
  }
148
+ // Create initial configuration
113
149
  const initialConfig = {
114
150
  connections: {
115
151
  default: {
@@ -133,6 +169,9 @@ async function handleInit() {
133
169
  throw new ConfigurationError('Failed to parse database connection string. Please check the format.');
134
170
  }
135
171
  }
172
+ /**
173
+ * Test database connection with security validation
174
+ */
136
175
  async function handleTest() {
137
176
  console.log('šŸ” Testing database connection...\n');
138
177
  const config = await loadConfig();
@@ -143,11 +182,14 @@ async function handleTest() {
143
182
  const connConfig = config.connections[connectionName];
144
183
  let connector;
145
184
  try {
185
+ // Create connector with security validation
146
186
  connector = createSecureConnector(connConfig.type, connConfig);
147
187
  console.log(`šŸ“Š Testing ${connConfig.type} connection...`);
188
+ // Test connection with timeout
148
189
  const testResult = await connector.testConnection();
149
190
  if (testResult) {
150
191
  console.log('āœ… Connection successful!');
192
+ // Test basic operations
151
193
  try {
152
194
  const tables = await connector.listTables();
153
195
  console.log(`šŸ“‹ Found ${tables.length} tables`);
@@ -165,6 +207,7 @@ async function handleTest() {
165
207
  }
166
208
  }
167
209
  catch (error) {
210
+ // Security: Don't expose detailed connection errors
168
211
  console.log('āŒ Connection failed');
169
212
  if (process.env.NODE_ENV === 'development') {
170
213
  console.log('Debug info:', ErrorHandler.getUserMessage(error));
@@ -175,6 +218,9 @@ async function handleTest() {
175
218
  process.exit(1);
176
219
  }
177
220
  }
221
+ /**
222
+ * Run monitoring scheduler with secure operations
223
+ */
178
224
  async function handleRun() {
179
225
  console.log('ā±ļø Starting FreshGuard monitoring scheduler...\n');
180
226
  const config = await loadConfig();
@@ -188,6 +234,7 @@ async function handleRun() {
188
234
  const connector = createSecureConnector(connConfig.type, connConfig);
189
235
  console.log(`šŸ“Š Using ${connConfig.type} connection`);
190
236
  console.log(`šŸ”’ Security mode: ${config.securityMode ?? 'strict'}`);
237
+ // Verify connection before starting
191
238
  const isConnected = await connector.testConnection();
192
239
  if (!isConnected) {
193
240
  throw new ConnectionError('Cannot connect to database');
@@ -201,26 +248,36 @@ async function handleRun() {
201
248
  console.log(' - Setting up alerting');
202
249
  console.log('');
203
250
  console.log('Press Ctrl+C to stop...');
251
+ // Simple monitoring loop (production would use proper scheduler)
204
252
  process.on('SIGINT', () => {
205
253
  console.log('\nšŸ›‘ Shutting down gracefully...');
206
254
  process.exit(0);
207
255
  });
256
+ // Basic monitoring loop - this would be more sophisticated in production
208
257
  while (true) {
209
258
  try {
210
259
  console.log('šŸ” Running health checks...');
260
+ // This is where real monitoring logic would go
261
+ // For now, just test the connection periodically
211
262
  await connector.testConnection();
212
263
  console.log('āœ… Health check completed');
264
+ // Wait 1 minute between checks
213
265
  await new Promise(resolve => setTimeout(resolve, 60000));
214
266
  }
215
267
  catch (error) {
216
268
  console.error('āŒ Monitoring check failed:', ErrorHandler.getUserMessage(error));
269
+ // Continue monitoring even if one check fails
217
270
  await new Promise(resolve => setTimeout(resolve, 60000));
218
271
  }
219
272
  }
220
273
  }
274
+ /**
275
+ * Parse connection string securely without exposing credentials
276
+ */
221
277
  function parseSecureConnectionString(connectionString) {
222
278
  try {
223
279
  const url = new URL(connectionString);
280
+ // Determine database type from protocol
224
281
  let type;
225
282
  switch (url.protocol) {
226
283
  case 'postgresql:':
@@ -239,11 +296,12 @@ function parseSecureConnectionString(connectionString) {
239
296
  default:
240
297
  throw new ConfigurationError(`Unsupported database protocol: ${url.protocol}`);
241
298
  }
299
+ // Security: Extract credentials from environment variables instead of URL when possible
242
300
  const config = {
243
301
  type,
244
302
  host: url.hostname ?? 'localhost',
245
303
  port: url.port ? parseInt(url.port, 10) : getDefaultPort(type),
246
- database: url.pathname.slice(1) ?? '',
304
+ database: url.pathname.slice(1) ?? '', // Remove leading slash
247
305
  username: url.username ?? process.env.FRESHGUARD_DB_USER ?? '',
248
306
  password: url.password ?? process.env.FRESHGUARD_DB_PASSWORD ?? '',
249
307
  ssl: url.searchParams.get('sslmode') !== 'disable' && url.searchParams.get('ssl') !== 'false',
@@ -254,6 +312,9 @@ function parseSecureConnectionString(connectionString) {
254
312
  throw new ConfigurationError('Invalid database connection string format');
255
313
  }
256
314
  }
315
+ /**
316
+ * Get default port for database type
317
+ */
257
318
  function getDefaultPort(type) {
258
319
  switch (type) {
259
320
  case 'postgres': return 5432;
@@ -263,6 +324,9 @@ function getDefaultPort(type) {
263
324
  default: return 0;
264
325
  }
265
326
  }
327
+ /**
328
+ * Create secure database connector
329
+ */
266
330
  function createSecureConnector(type, config) {
267
331
  switch (type) {
268
332
  case 'postgres':
@@ -277,14 +341,19 @@ function createSecureConnector(type, config) {
277
341
  throw new ConfigurationError(`Unsupported connector type: ${type}`);
278
342
  }
279
343
  }
344
+ /**
345
+ * Load configuration from secure file path
346
+ */
280
347
  async function loadConfig() {
281
348
  try {
282
349
  const configPath = validateConfigPath(DEFAULT_CONFIG_FILE);
350
+ // eslint-disable-next-line security/detect-non-literal-fs-filename
283
351
  if (!existsSync(configPath)) {
284
352
  throw new ConfigurationError('Configuration file not found. Run: freshguard init');
285
353
  }
286
354
  const configData = await readFileAsync(configPath, 'utf8');
287
355
  const config = JSON.parse(configData);
356
+ // Validate configuration structure
288
357
  if (!config.connections || Object.keys(config.connections).length === 0) {
289
358
  throw new ConfigurationError('No connections configured');
290
359
  }
@@ -297,6 +366,9 @@ async function loadConfig() {
297
366
  throw new ConfigurationError('Failed to load configuration file');
298
367
  }
299
368
  }
369
+ /**
370
+ * Get command line flag value
371
+ */
300
372
  function getFlag(flag) {
301
373
  const flagIndex = args.indexOf(flag);
302
374
  if (flagIndex !== -1 && flagIndex + 1 < args.length) {
@@ -319,6 +391,7 @@ function printHelp() {
319
391
  console.log('');
320
392
  console.log('Examples:');
321
393
  console.log(' # Set database URL and initialize');
394
+ // eslint-disable-next-line no-secrets/no-secrets
322
395
  console.log(' export FRESHGUARD_DATABASE_URL="postgresql://user:pass@localhost:5432/db"');
323
396
  console.log(' freshguard init');
324
397
  console.log('');