@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.
- package/CHANGELOG.md +15 -1
- package/README.md +74 -1
- package/SKILL.md +229 -0
- package/dist/cli/index.d.ts +13 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +74 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/connectors/azure-sql.d.ts +121 -0
- package/dist/connectors/azure-sql.d.ts.map +1 -0
- package/dist/connectors/azure-sql.js +489 -0
- package/dist/connectors/azure-sql.js.map +1 -0
- package/dist/connectors/base-connector.d.ts +139 -0
- package/dist/connectors/base-connector.d.ts.map +1 -1
- package/dist/connectors/base-connector.js +160 -3
- package/dist/connectors/base-connector.js.map +1 -1
- package/dist/connectors/bigquery.d.ts +100 -0
- package/dist/connectors/bigquery.d.ts.map +1 -1
- package/dist/connectors/bigquery.js +143 -2
- package/dist/connectors/bigquery.js.map +1 -1
- package/dist/connectors/duckdb.d.ts +96 -0
- package/dist/connectors/duckdb.d.ts.map +1 -1
- package/dist/connectors/duckdb.js +144 -7
- package/dist/connectors/duckdb.js.map +1 -1
- package/dist/connectors/index.d.ts +28 -0
- package/dist/connectors/index.d.ts.map +1 -1
- package/dist/connectors/index.js +28 -0
- package/dist/connectors/index.js.map +1 -1
- package/dist/connectors/mssql.d.ts +119 -0
- package/dist/connectors/mssql.d.ts.map +1 -0
- package/dist/connectors/mssql.js +483 -0
- package/dist/connectors/mssql.js.map +1 -0
- package/dist/connectors/mysql.d.ts +85 -0
- package/dist/connectors/mysql.d.ts.map +1 -1
- package/dist/connectors/mysql.js +118 -3
- package/dist/connectors/mysql.js.map +1 -1
- package/dist/connectors/postgres.d.ts +85 -0
- package/dist/connectors/postgres.d.ts.map +1 -1
- package/dist/connectors/postgres.js +113 -6
- package/dist/connectors/postgres.js.map +1 -1
- package/dist/connectors/redshift.d.ts +90 -0
- package/dist/connectors/redshift.d.ts.map +1 -1
- package/dist/connectors/redshift.js +131 -7
- package/dist/connectors/redshift.js.map +1 -1
- package/dist/connectors/snowflake.d.ts +108 -0
- package/dist/connectors/snowflake.d.ts.map +1 -1
- package/dist/connectors/snowflake.js +137 -3
- package/dist/connectors/snowflake.js.map +1 -1
- package/dist/connectors/synapse.d.ts +123 -0
- package/dist/connectors/synapse.d.ts.map +1 -0
- package/dist/connectors/synapse.js +495 -0
- package/dist/connectors/synapse.js.map +1 -0
- package/dist/db/index.d.ts +25 -0
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +23 -0
- package/dist/db/index.js.map +1 -1
- package/dist/db/migrate.d.ts +23 -0
- package/dist/db/migrate.d.ts.map +1 -1
- package/dist/db/migrate.js +38 -0
- package/dist/db/migrate.js.map +1 -1
- package/dist/db/schema.d.ts +11 -0
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +70 -0
- package/dist/db/schema.js.map +1 -1
- package/dist/errors/debug-factory.d.ts +38 -0
- package/dist/errors/debug-factory.d.ts.map +1 -1
- package/dist/errors/debug-factory.js +40 -0
- package/dist/errors/debug-factory.js.map +1 -1
- package/dist/errors/index.d.ts +59 -0
- package/dist/errors/index.d.ts.map +1 -1
- package/dist/errors/index.js +110 -7
- package/dist/errors/index.js.map +1 -1
- package/dist/index.d.ts +32 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +37 -1
- package/dist/index.js.map +1 -1
- package/dist/metadata/duckdb-storage.d.ts +3 -0
- package/dist/metadata/duckdb-storage.d.ts.map +1 -1
- package/dist/metadata/duckdb-storage.js +6 -0
- package/dist/metadata/duckdb-storage.js.map +1 -1
- package/dist/metadata/factory.d.ts +30 -0
- package/dist/metadata/factory.d.ts.map +1 -1
- package/dist/metadata/factory.js +31 -0
- package/dist/metadata/factory.js.map +1 -1
- package/dist/metadata/index.d.ts +26 -0
- package/dist/metadata/index.d.ts.map +1 -1
- package/dist/metadata/index.js +26 -0
- package/dist/metadata/index.js.map +1 -1
- package/dist/metadata/interface.d.ts +33 -0
- package/dist/metadata/interface.d.ts.map +1 -1
- package/dist/metadata/interface.js +3 -0
- package/dist/metadata/interface.js.map +1 -1
- package/dist/metadata/postgresql-storage.d.ts +3 -0
- package/dist/metadata/postgresql-storage.d.ts.map +1 -1
- package/dist/metadata/postgresql-storage.js +12 -2
- package/dist/metadata/postgresql-storage.js.map +1 -1
- package/dist/metadata/schema-config.d.ts +53 -0
- package/dist/metadata/schema-config.d.ts.map +1 -1
- package/dist/metadata/schema-config.js +64 -0
- package/dist/metadata/schema-config.js.map +1 -1
- package/dist/metadata/types.d.ts +3 -0
- package/dist/metadata/types.d.ts.map +1 -1
- package/dist/metadata/types.js +3 -0
- package/dist/metadata/types.js.map +1 -1
- package/dist/monitor/baseline-calculator.d.ts +56 -0
- package/dist/monitor/baseline-calculator.d.ts.map +1 -1
- package/dist/monitor/baseline-calculator.js +72 -0
- package/dist/monitor/baseline-calculator.js.map +1 -1
- package/dist/monitor/baseline-config.d.ts +77 -0
- package/dist/monitor/baseline-config.d.ts.map +1 -1
- package/dist/monitor/baseline-config.js +79 -1
- package/dist/monitor/baseline-config.js.map +1 -1
- package/dist/monitor/freshness.d.ts +40 -0
- package/dist/monitor/freshness.d.ts.map +1 -1
- package/dist/monitor/freshness.js +82 -3
- package/dist/monitor/freshness.js.map +1 -1
- package/dist/monitor/index.d.ts +29 -0
- package/dist/monitor/index.d.ts.map +1 -1
- package/dist/monitor/index.js +29 -0
- package/dist/monitor/index.js.map +1 -1
- package/dist/monitor/schema-baseline.d.ts +45 -0
- package/dist/monitor/schema-baseline.d.ts.map +1 -1
- package/dist/monitor/schema-baseline.js +63 -5
- package/dist/monitor/schema-baseline.js.map +1 -1
- package/dist/monitor/schema-changes.d.ts +45 -0
- package/dist/monitor/schema-changes.d.ts.map +1 -1
- package/dist/monitor/schema-changes.js +85 -0
- package/dist/monitor/schema-changes.js.map +1 -1
- package/dist/monitor/volume.d.ts +43 -0
- package/dist/monitor/volume.d.ts.map +1 -1
- package/dist/monitor/volume.js +89 -0
- package/dist/monitor/volume.js.map +1 -1
- package/dist/observability/logger.d.ts +91 -0
- package/dist/observability/logger.d.ts.map +1 -1
- package/dist/observability/logger.js +108 -0
- package/dist/observability/logger.js.map +1 -1
- package/dist/observability/metrics.d.ts +140 -0
- package/dist/observability/metrics.d.ts.map +1 -1
- package/dist/observability/metrics.js +184 -7
- package/dist/observability/metrics.js.map +1 -1
- package/dist/resilience/circuit-breaker.d.ts +112 -2
- package/dist/resilience/circuit-breaker.d.ts.map +1 -1
- package/dist/resilience/circuit-breaker.js +140 -6
- package/dist/resilience/circuit-breaker.js.map +1 -1
- package/dist/resilience/index.d.ts +9 -0
- package/dist/resilience/index.d.ts.map +1 -1
- package/dist/resilience/index.js +13 -0
- package/dist/resilience/index.js.map +1 -1
- package/dist/resilience/retry-policy.d.ts +105 -0
- package/dist/resilience/retry-policy.d.ts.map +1 -1
- package/dist/resilience/retry-policy.js +158 -7
- package/dist/resilience/retry-policy.js.map +1 -1
- package/dist/resilience/timeout-manager.d.ts +137 -0
- package/dist/resilience/timeout-manager.d.ts.map +1 -1
- package/dist/resilience/timeout-manager.js +151 -4
- package/dist/resilience/timeout-manager.js.map +1 -1
- package/dist/security/query-analyzer.d.ts +124 -0
- package/dist/security/query-analyzer.d.ts.map +1 -1
- package/dist/security/query-analyzer.js +150 -9
- package/dist/security/query-analyzer.js.map +1 -1
- package/dist/security/schema-cache.d.ts +152 -0
- package/dist/security/schema-cache.d.ts.map +1 -1
- package/dist/security/schema-cache.js +144 -12
- package/dist/security/schema-cache.js.map +1 -1
- package/dist/types/connector.d.ts +68 -1
- package/dist/types/connector.d.ts.map +1 -1
- package/dist/types/connector.js +38 -15
- package/dist/types/connector.js.map +1 -1
- package/dist/types/driver-results.d.ts +28 -0
- package/dist/types/driver-results.d.ts.map +1 -1
- package/dist/types/driver-results.js +12 -0
- package/dist/types/driver-results.js.map +1 -1
- package/dist/types.d.ts +113 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -1
- package/dist/validation/index.d.ts +8 -0
- package/dist/validation/index.d.ts.map +1 -1
- package/dist/validation/index.js +12 -0
- package/dist/validation/index.js.map +1 -1
- package/dist/validation/runtime-validator.d.ts +98 -0
- package/dist/validation/runtime-validator.d.ts.map +1 -1
- package/dist/validation/runtime-validator.js +114 -1
- package/dist/validation/runtime-validator.js.map +1 -1
- package/dist/validation/sanitizers.d.ts +59 -0
- package/dist/validation/sanitizers.d.ts.map +1 -1
- package/dist/validation/sanitizers.js +104 -20
- package/dist/validation/sanitizers.js.map +1 -1
- package/dist/validation/schemas.d.ts +73 -0
- package/dist/validation/schemas.d.ts.map +1 -1
- package/dist/validation/schemas.js +132 -5
- package/dist/validation/schemas.js.map +1 -1
- package/dist/validators/index.d.ts +54 -0
- package/dist/validators/index.d.ts.map +1 -1
- package/dist/validators/index.js +93 -2
- package/dist/validators/index.js.map +1 -1
- package/package.json +6 -2
|
@@ -1,22 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema baseline management for schema change monitoring
|
|
3
|
+
* Handles storage, retrieval and comparison of database schema baselines
|
|
4
|
+
*
|
|
5
|
+
* Security features:
|
|
6
|
+
* - Input validation to prevent SQL injection
|
|
7
|
+
* - Safe schema comparison algorithms
|
|
8
|
+
* - Sanitized error messages
|
|
9
|
+
*
|
|
10
|
+
* @module @freshguard/freshguard-core/monitor/schema-baseline
|
|
11
|
+
* @license MIT
|
|
12
|
+
*/
|
|
1
13
|
import type { SchemaBaseline, SchemaChanges } from '../types.js';
|
|
2
14
|
import type { TableSchema } from '../types/connector.js';
|
|
3
15
|
import type { MetadataStorage } from '../metadata/interface.js';
|
|
16
|
+
/**
|
|
17
|
+
* Manager for schema baseline operations
|
|
18
|
+
*/
|
|
4
19
|
export declare class SchemaBaselineManager {
|
|
20
|
+
/**
|
|
21
|
+
* Store a new schema baseline
|
|
22
|
+
*/
|
|
5
23
|
storeBaseline(metadataStorage: MetadataStorage, ruleId: string, tableName: string, schema: TableSchema, adaptationReason?: string): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Get existing schema baseline for a rule
|
|
26
|
+
*/
|
|
6
27
|
getBaseline(metadataStorage: MetadataStorage, ruleId: string): Promise<SchemaBaseline | null>;
|
|
28
|
+
/**
|
|
29
|
+
* Update existing baseline with new schema
|
|
30
|
+
*/
|
|
7
31
|
updateBaseline(metadataStorage: MetadataStorage, ruleId: string, newSchema: TableSchema, adaptationReason: string): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Generate consistent hash for schema comparison
|
|
34
|
+
*/
|
|
8
35
|
private generateSchemaHash;
|
|
9
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Schema comparison utility
|
|
39
|
+
*/
|
|
10
40
|
export declare class SchemaComparer {
|
|
41
|
+
/**
|
|
42
|
+
* Compare two schemas and detect changes
|
|
43
|
+
*/
|
|
11
44
|
compareSchemas(baseline: TableSchema, current: TableSchema, config?: {
|
|
12
45
|
trackTypes?: boolean;
|
|
13
46
|
trackNullability?: boolean;
|
|
14
47
|
trackedColumns?: string[];
|
|
15
48
|
monitoringMode?: 'full' | 'partial';
|
|
16
49
|
}): SchemaChanges;
|
|
50
|
+
/**
|
|
51
|
+
* Normalize database type names for comparison
|
|
52
|
+
*/
|
|
17
53
|
private normalizeType;
|
|
54
|
+
/**
|
|
55
|
+
* Determine impact level for type changes
|
|
56
|
+
*/
|
|
18
57
|
private determineTypeChangeImpact;
|
|
58
|
+
/**
|
|
59
|
+
* Calculate overall severity based on changes
|
|
60
|
+
*/
|
|
19
61
|
private calculateSeverity;
|
|
62
|
+
/**
|
|
63
|
+
* Generate human-readable summary
|
|
64
|
+
*/
|
|
20
65
|
private generateSummary;
|
|
21
66
|
}
|
|
22
67
|
//# sourceMappingURL=schema-baseline.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-baseline.d.ts","sourceRoot":"","sources":["../../src/monitor/schema-baseline.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"schema-baseline.d.ts","sourceRoot":"","sources":["../../src/monitor/schema-baseline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAgB,MAAM,aAAa,CAAC;AAC/E,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAIhE;;GAEG;AACH,qBAAa,qBAAqB;IAChC;;OAEG;IACG,aAAa,CACjB,eAAe,EAAE,eAAe,EAChC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,WAAW,EACnB,gBAAgB,CAAC,EAAE,MAAM,GACxB,OAAO,CAAC,IAAI,CAAC;IAsChB;;OAEG;IACG,WAAW,CAAC,eAAe,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAqBnG;;OAEG;IACG,cAAc,CAClB,eAAe,EAAE,eAAe,EAChC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,WAAW,EACtB,gBAAgB,EAAE,MAAM,GACvB,OAAO,CAAC,IAAI,CAAC;IAYhB;;OAEG;IACH,OAAO,CAAC,kBAAkB;CAW3B;AAED;;GAEG;AACH,qBAAa,cAAc;IACzB;;OAEG;IACH,cAAc,CACZ,QAAQ,EAAE,WAAW,EACrB,OAAO,EAAE,WAAW,EACpB,MAAM,GAAE;QACN,UAAU,CAAC,EAAE,OAAO,CAAC;QACrB,gBAAgB,CAAC,EAAE,OAAO,CAAC;QAC3B,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;QAC1B,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;KAChC,GACL,aAAa;IAmGhB;;OAEG;IACH,OAAO,CAAC,aAAa;IASrB;;OAEG;IACH,OAAO,CAAC,yBAAyB;IAuCjC;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAkBzB;;OAEG;IACH,OAAO,CAAC,eAAe;CA6BxB"}
|
|
@@ -1,6 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema baseline management for schema change monitoring
|
|
3
|
+
* Handles storage, retrieval and comparison of database schema baselines
|
|
4
|
+
*
|
|
5
|
+
* Security features:
|
|
6
|
+
* - Input validation to prevent SQL injection
|
|
7
|
+
* - Safe schema comparison algorithms
|
|
8
|
+
* - Sanitized error messages
|
|
9
|
+
*
|
|
10
|
+
* @module @freshguard/freshguard-core/monitor/schema-baseline
|
|
11
|
+
* @license MIT
|
|
12
|
+
*/
|
|
1
13
|
import { ConfigurationError, MetadataStorageError } from '../errors/index.js';
|
|
2
14
|
import { createHash } from 'crypto';
|
|
15
|
+
/**
|
|
16
|
+
* Manager for schema baseline operations
|
|
17
|
+
*/
|
|
3
18
|
export class SchemaBaselineManager {
|
|
19
|
+
/**
|
|
20
|
+
* Store a new schema baseline
|
|
21
|
+
*/
|
|
4
22
|
async storeBaseline(metadataStorage, ruleId, tableName, schema, adaptationReason) {
|
|
5
23
|
if (!metadataStorage) {
|
|
6
24
|
throw new ConfigurationError('Metadata storage is required for schema baseline management');
|
|
@@ -29,6 +47,9 @@ export class SchemaBaselineManager {
|
|
|
29
47
|
throw new MetadataStorageError('Failed to store schema baseline', 'schema_baseline_storage', undefined, error instanceof Error ? error : undefined);
|
|
30
48
|
}
|
|
31
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Get existing schema baseline for a rule
|
|
52
|
+
*/
|
|
32
53
|
async getBaseline(metadataStorage, ruleId) {
|
|
33
54
|
if (!metadataStorage) {
|
|
34
55
|
throw new ConfigurationError('Metadata storage is required for schema baseline management');
|
|
@@ -43,6 +64,9 @@ export class SchemaBaselineManager {
|
|
|
43
64
|
throw new MetadataStorageError('Failed to retrieve schema baseline', 'schema_baseline_retrieval', undefined, error instanceof Error ? error : undefined);
|
|
44
65
|
}
|
|
45
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Update existing baseline with new schema
|
|
69
|
+
*/
|
|
46
70
|
async updateBaseline(metadataStorage, ruleId, newSchema, adaptationReason) {
|
|
47
71
|
if (!metadataStorage) {
|
|
48
72
|
throw new ConfigurationError('Metadata storage is required for schema baseline management');
|
|
@@ -52,32 +76,45 @@ export class SchemaBaselineManager {
|
|
|
52
76
|
}
|
|
53
77
|
await this.storeBaseline(metadataStorage, ruleId, newSchema.table, newSchema, adaptationReason);
|
|
54
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* Generate consistent hash for schema comparison
|
|
81
|
+
*/
|
|
55
82
|
generateSchemaHash(schema) {
|
|
83
|
+
// Create deterministic string representation of schema
|
|
56
84
|
const normalizedColumns = schema.columns
|
|
57
|
-
.slice()
|
|
58
|
-
.sort((a, b) => a.name.localeCompare(b.name))
|
|
85
|
+
.slice() // Create copy to avoid mutation
|
|
86
|
+
.sort((a, b) => a.name.localeCompare(b.name)) // Sort by name for consistency
|
|
59
87
|
.map(col => `${col.name}:${col.type.toLowerCase()}:${col.nullable ? 'null' : 'notnull'}`)
|
|
60
88
|
.join('|');
|
|
61
89
|
const schemaString = `${schema.table}:${normalizedColumns}`;
|
|
62
90
|
return createHash('sha256').update(schemaString).digest('hex');
|
|
63
91
|
}
|
|
64
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Schema comparison utility
|
|
95
|
+
*/
|
|
65
96
|
export class SchemaComparer {
|
|
97
|
+
/**
|
|
98
|
+
* Compare two schemas and detect changes
|
|
99
|
+
*/
|
|
66
100
|
compareSchemas(baseline, current, config = {}) {
|
|
67
101
|
if (!baseline || !current) {
|
|
68
102
|
throw new ConfigurationError('Both baseline and current schemas are required for comparison');
|
|
69
103
|
}
|
|
70
|
-
const trackTypes = config.trackTypes !== false;
|
|
71
|
-
const trackNullability = config.trackNullability === true;
|
|
104
|
+
const trackTypes = config.trackTypes !== false; // Default: true
|
|
105
|
+
const trackNullability = config.trackNullability === true; // Default: false
|
|
72
106
|
const monitoringMode = config.monitoringMode ?? 'full';
|
|
107
|
+
// Create column maps for efficient lookup
|
|
73
108
|
const baselineColumns = new Map(baseline.columns.map(col => [col.name, col]));
|
|
74
109
|
const currentColumns = new Map(current.columns.map(col => [col.name, col]));
|
|
75
110
|
const addedColumns = [];
|
|
76
111
|
const removedColumns = [];
|
|
77
112
|
const modifiedColumns = [];
|
|
113
|
+
// If partial mode, filter to only tracked columns
|
|
78
114
|
const columnsToCheck = monitoringMode === 'partial' && config.trackedColumns
|
|
79
115
|
? config.trackedColumns
|
|
80
116
|
: Array.from(new Set([...baselineColumns.keys(), ...currentColumns.keys()]));
|
|
117
|
+
// Check for added columns
|
|
81
118
|
for (const [columnName, column] of currentColumns) {
|
|
82
119
|
if (!columnsToCheck.includes(columnName))
|
|
83
120
|
continue;
|
|
@@ -90,11 +127,13 @@ export class SchemaComparer {
|
|
|
90
127
|
});
|
|
91
128
|
}
|
|
92
129
|
}
|
|
130
|
+
// Check for removed columns and modifications
|
|
93
131
|
for (const [columnName, baselineColumn] of baselineColumns) {
|
|
94
132
|
if (!columnsToCheck.includes(columnName))
|
|
95
133
|
continue;
|
|
96
134
|
const currentColumn = currentColumns.get(columnName);
|
|
97
135
|
if (!currentColumn) {
|
|
136
|
+
// Column was removed
|
|
98
137
|
removedColumns.push({
|
|
99
138
|
columnName,
|
|
100
139
|
changeType: 'removed',
|
|
@@ -103,6 +142,7 @@ export class SchemaComparer {
|
|
|
103
142
|
});
|
|
104
143
|
}
|
|
105
144
|
else if (trackTypes || trackNullability) {
|
|
145
|
+
// Check for type or nullability changes
|
|
106
146
|
const changes = [];
|
|
107
147
|
if (trackTypes && this.normalizeType(baselineColumn.type) !== this.normalizeType(currentColumn.type)) {
|
|
108
148
|
changes.push({
|
|
@@ -119,15 +159,18 @@ export class SchemaComparer {
|
|
|
119
159
|
changeType: 'nullability_changed',
|
|
120
160
|
oldValue: baselineColumn.nullable ? 'NULL' : 'NOT NULL',
|
|
121
161
|
newValue: currentColumn.nullable ? 'NULL' : 'NOT NULL',
|
|
122
|
-
impact: currentColumn.nullable ? 'safe' : 'breaking'
|
|
162
|
+
impact: currentColumn.nullable ? 'safe' : 'breaking' // Adding NOT NULL is breaking
|
|
123
163
|
});
|
|
124
164
|
}
|
|
125
165
|
modifiedColumns.push(...changes);
|
|
126
166
|
}
|
|
127
167
|
}
|
|
168
|
+
// Calculate summary
|
|
128
169
|
const changeCount = addedColumns.length + removedColumns.length + modifiedColumns.length;
|
|
129
170
|
const hasChanges = changeCount > 0;
|
|
171
|
+
// Determine severity based on impact
|
|
130
172
|
const severity = this.calculateSeverity(addedColumns, removedColumns, modifiedColumns);
|
|
173
|
+
// Generate summary message
|
|
131
174
|
const summary = this.generateSummary(addedColumns, removedColumns, modifiedColumns);
|
|
132
175
|
return {
|
|
133
176
|
hasChanges,
|
|
@@ -139,6 +182,9 @@ export class SchemaComparer {
|
|
|
139
182
|
severity
|
|
140
183
|
};
|
|
141
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* Normalize database type names for comparison
|
|
187
|
+
*/
|
|
142
188
|
normalizeType(type) {
|
|
143
189
|
return type.toLowerCase()
|
|
144
190
|
.replace(/varchar\(\d+\)/g, 'varchar')
|
|
@@ -147,9 +193,13 @@ export class SchemaComparer {
|
|
|
147
193
|
.replace(/numeric\(\d+,?\d*\)/g, 'numeric')
|
|
148
194
|
.trim();
|
|
149
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* Determine impact level for type changes
|
|
198
|
+
*/
|
|
150
199
|
determineTypeChangeImpact(oldType, newType) {
|
|
151
200
|
const normalizedOld = this.normalizeType(oldType);
|
|
152
201
|
const normalizedNew = this.normalizeType(newType);
|
|
202
|
+
// Safe expansions (usually backwards compatible)
|
|
153
203
|
const safeChanges = [
|
|
154
204
|
['varchar', 'text'],
|
|
155
205
|
['int', 'bigint'],
|
|
@@ -165,6 +215,7 @@ export class SchemaComparer {
|
|
|
165
215
|
return 'safe';
|
|
166
216
|
}
|
|
167
217
|
}
|
|
218
|
+
// Warning changes (might be compatible but risky)
|
|
168
219
|
const warningChanges = [
|
|
169
220
|
['bigint', 'int'],
|
|
170
221
|
['numeric', 'int'],
|
|
@@ -175,8 +226,12 @@ export class SchemaComparer {
|
|
|
175
226
|
return 'warning';
|
|
176
227
|
}
|
|
177
228
|
}
|
|
229
|
+
// Everything else is potentially breaking
|
|
178
230
|
return 'breaking';
|
|
179
231
|
}
|
|
232
|
+
/**
|
|
233
|
+
* Calculate overall severity based on changes
|
|
234
|
+
*/
|
|
180
235
|
calculateSeverity(addedColumns, removedColumns, modifiedColumns) {
|
|
181
236
|
const allChanges = [...addedColumns, ...removedColumns, ...modifiedColumns];
|
|
182
237
|
if (allChanges.some(change => change.impact === 'breaking')) {
|
|
@@ -187,6 +242,9 @@ export class SchemaComparer {
|
|
|
187
242
|
}
|
|
188
243
|
return 'low';
|
|
189
244
|
}
|
|
245
|
+
/**
|
|
246
|
+
* Generate human-readable summary
|
|
247
|
+
*/
|
|
190
248
|
generateSummary(addedColumns, removedColumns, modifiedColumns) {
|
|
191
249
|
const parts = [];
|
|
192
250
|
if (addedColumns.length > 0) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-baseline.js","sourceRoot":"","sources":["../../src/monitor/schema-baseline.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"schema-baseline.js","sourceRoot":"","sources":["../../src/monitor/schema-baseline.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema change monitoring algorithm
|
|
3
|
+
* Detects database schema changes and alerts based on configuration
|
|
4
|
+
*
|
|
5
|
+
* Security features:
|
|
6
|
+
* - Input validation to prevent SQL injection
|
|
7
|
+
* - Error sanitization to prevent information disclosure
|
|
8
|
+
* - Timeout protection against long-running queries
|
|
9
|
+
* - Safe parameter validation
|
|
10
|
+
*
|
|
11
|
+
* @module @freshguard/freshguard-core/monitor/schema-changes
|
|
12
|
+
* @license MIT
|
|
13
|
+
*/
|
|
1
14
|
import type { CheckResult, MonitoringRule, FreshGuardConfig } from '../types.js';
|
|
2
15
|
import type { Connector } from '../types/connector.js';
|
|
3
16
|
import type { MetadataStorage } from '../metadata/interface.js';
|
|
17
|
+
/**
|
|
18
|
+
* Detect schema changes by comparing the current table schema against a stored
|
|
19
|
+
* baseline. Returns `'alert'` when columns are added, removed, or modified.
|
|
20
|
+
*
|
|
21
|
+
* On the first run (no baseline), stores the current schema and returns `'ok'`.
|
|
22
|
+
* Subsequent runs compare against that baseline. The `adaptationMode` in
|
|
23
|
+
* `rule.schemaChangeConfig` controls whether safe changes auto-update the baseline.
|
|
24
|
+
*
|
|
25
|
+
* Requires metadata storage to persist schema baselines.
|
|
26
|
+
*
|
|
27
|
+
* @param connector - Database connector instance
|
|
28
|
+
* @param rule - Monitoring rule with `ruleType: 'schema_change'` and optional `schemaChangeConfig`
|
|
29
|
+
* @param metadataStorage - Metadata storage for schema baseline persistence
|
|
30
|
+
* @param config - Optional configuration including debug settings and timeouts
|
|
31
|
+
* @returns CheckResult with `status` and `schemaChanges` containing added/removed/modified columns
|
|
32
|
+
* @throws {ConfigurationError} If the rule is missing required schema change fields
|
|
33
|
+
* @throws {QueryError} If the schema query fails
|
|
34
|
+
* @throws {TimeoutError} If the query exceeds the configured timeout
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```typescript
|
|
38
|
+
* import { checkSchemaChanges, createMetadataStorage, PostgresConnector } from '@freshguard/freshguard-core';
|
|
39
|
+
*
|
|
40
|
+
* const connector = new PostgresConnector({ host: 'localhost', database: 'mydb', username: 'user', password: 'pass', ssl: true });
|
|
41
|
+
* const storage = await createMetadataStorage();
|
|
42
|
+
* const rule = { id: 'r1', sourceId: 's1', name: 'Users Schema', tableName: 'users', ruleType: 'schema_change' as const, checkIntervalMinutes: 60, isActive: true, schemaChangeConfig: { adaptationMode: 'manual' as const, monitoringMode: 'full' as const }, createdAt: new Date(), updatedAt: new Date() };
|
|
43
|
+
* const result = await checkSchemaChanges(connector, rule, storage);
|
|
44
|
+
* if (result.schemaChanges?.hasChanges) { console.log(result.schemaChanges.summary); }
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* @since 0.10.0
|
|
48
|
+
*/
|
|
4
49
|
export declare function checkSchemaChanges(connector: Connector, rule: MonitoringRule, metadataStorage?: MetadataStorage, config?: FreshGuardConfig): Promise<CheckResult>;
|
|
5
50
|
//# sourceMappingURL=schema-changes.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema-changes.d.ts","sourceRoot":"","sources":["../../src/monitor/schema-changes.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"schema-changes.d.ts","sourceRoot":"","sources":["../../src/monitor/schema-changes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,gBAAgB,EAA8B,MAAM,aAAa,CAAC;AAC7G,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAYhE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,SAAS,EACpB,IAAI,EAAE,cAAc,EACpB,eAAe,CAAC,EAAE,eAAe,EACjC,MAAM,CAAC,EAAE,gBAAgB,GACxB,OAAO,CAAC,WAAW,CAAC,CAqOtB"}
|
|
@@ -1,13 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema change monitoring algorithm
|
|
3
|
+
* Detects database schema changes and alerts based on configuration
|
|
4
|
+
*
|
|
5
|
+
* Security features:
|
|
6
|
+
* - Input validation to prevent SQL injection
|
|
7
|
+
* - Error sanitization to prevent information disclosure
|
|
8
|
+
* - Timeout protection against long-running queries
|
|
9
|
+
* - Safe parameter validation
|
|
10
|
+
*
|
|
11
|
+
* @module @freshguard/freshguard-core/monitor/schema-changes
|
|
12
|
+
* @license MIT
|
|
13
|
+
*/
|
|
1
14
|
import { validateTableName } from '../validators/index.js';
|
|
2
15
|
import { QueryError, TimeoutError, ConfigurationError, ErrorHandler } from '../errors/index.js';
|
|
3
16
|
import { DebugErrorFactory, mergeDebugConfig } from '../errors/debug-factory.js';
|
|
4
17
|
import { SchemaBaselineManager, SchemaComparer } from './schema-baseline.js';
|
|
18
|
+
/**
|
|
19
|
+
* Detect schema changes by comparing the current table schema against a stored
|
|
20
|
+
* baseline. Returns `'alert'` when columns are added, removed, or modified.
|
|
21
|
+
*
|
|
22
|
+
* On the first run (no baseline), stores the current schema and returns `'ok'`.
|
|
23
|
+
* Subsequent runs compare against that baseline. The `adaptationMode` in
|
|
24
|
+
* `rule.schemaChangeConfig` controls whether safe changes auto-update the baseline.
|
|
25
|
+
*
|
|
26
|
+
* Requires metadata storage to persist schema baselines.
|
|
27
|
+
*
|
|
28
|
+
* @param connector - Database connector instance
|
|
29
|
+
* @param rule - Monitoring rule with `ruleType: 'schema_change'` and optional `schemaChangeConfig`
|
|
30
|
+
* @param metadataStorage - Metadata storage for schema baseline persistence
|
|
31
|
+
* @param config - Optional configuration including debug settings and timeouts
|
|
32
|
+
* @returns CheckResult with `status` and `schemaChanges` containing added/removed/modified columns
|
|
33
|
+
* @throws {ConfigurationError} If the rule is missing required schema change fields
|
|
34
|
+
* @throws {QueryError} If the schema query fails
|
|
35
|
+
* @throws {TimeoutError} If the query exceeds the configured timeout
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```typescript
|
|
39
|
+
* import { checkSchemaChanges, createMetadataStorage, PostgresConnector } from '@freshguard/freshguard-core';
|
|
40
|
+
*
|
|
41
|
+
* const connector = new PostgresConnector({ host: 'localhost', database: 'mydb', username: 'user', password: 'pass', ssl: true });
|
|
42
|
+
* const storage = await createMetadataStorage();
|
|
43
|
+
* const rule = { id: 'r1', sourceId: 's1', name: 'Users Schema', tableName: 'users', ruleType: 'schema_change' as const, checkIntervalMinutes: 60, isActive: true, schemaChangeConfig: { adaptationMode: 'manual' as const, monitoringMode: 'full' as const }, createdAt: new Date(), updatedAt: new Date() };
|
|
44
|
+
* const result = await checkSchemaChanges(connector, rule, storage);
|
|
45
|
+
* if (result.schemaChanges?.hasChanges) { console.log(result.schemaChanges.summary); }
|
|
46
|
+
* ```
|
|
47
|
+
*
|
|
48
|
+
* @since 0.10.0
|
|
49
|
+
*/
|
|
5
50
|
export async function checkSchemaChanges(connector, rule, metadataStorage, config) {
|
|
6
51
|
const startTime = Date.now();
|
|
7
52
|
const debugConfig = mergeDebugConfig(config?.debug);
|
|
8
53
|
const debugFactory = new DebugErrorFactory(debugConfig);
|
|
9
54
|
const debugId = `fg-schema-${Date.now().toString(36)}-${Math.random().toString(36).substr(2, 5)}`;
|
|
10
55
|
try {
|
|
56
|
+
// Log debug info at start
|
|
11
57
|
if (debugConfig.enabled) {
|
|
12
58
|
console.log(`[DEBUG-${debugId}] Starting schema change check:`, {
|
|
13
59
|
table: rule.tableName,
|
|
@@ -15,23 +61,29 @@ export async function checkSchemaChanges(connector, rule, metadataStorage, confi
|
|
|
15
61
|
timestamp: new Date().toISOString()
|
|
16
62
|
});
|
|
17
63
|
}
|
|
64
|
+
// Validate input parameters for security
|
|
18
65
|
validateSchemaChangeRule(rule);
|
|
19
66
|
validateTableName(rule.tableName);
|
|
67
|
+
// Initialize managers
|
|
20
68
|
const baselineManager = new SchemaBaselineManager();
|
|
21
69
|
const schemaComparer = new SchemaComparer();
|
|
70
|
+
// Get configuration with defaults
|
|
22
71
|
const schemaConfig = rule.schemaChangeConfig ?? {};
|
|
23
72
|
const adaptationMode = schemaConfig.adaptationMode ?? 'manual';
|
|
24
73
|
const monitoringMode = schemaConfig.monitoringMode ?? 'full';
|
|
25
74
|
const trackTypes = schemaConfig.trackedColumns?.trackTypes !== false;
|
|
26
75
|
const trackNullability = schemaConfig.trackedColumns?.trackNullability === true;
|
|
76
|
+
// Execute schema introspection with timeout protection
|
|
27
77
|
const currentSchema = await executeWithTimeout(() => executeSchemaQuery(connector, rule.tableName, debugConfig, debugFactory), config?.timeoutMs ?? 30000, 'Schema introspection query timeout');
|
|
28
78
|
if (!currentSchema) {
|
|
29
79
|
throw new QueryError('Failed to retrieve current schema', 'schema_query', rule.tableName);
|
|
30
80
|
}
|
|
81
|
+
// Get existing baseline if available
|
|
31
82
|
let baseline = null;
|
|
32
83
|
if (metadataStorage) {
|
|
33
84
|
baseline = await baselineManager.getBaseline(metadataStorage, rule.id);
|
|
34
85
|
}
|
|
86
|
+
// Handle first run - capture baseline and return OK
|
|
35
87
|
if (!baseline) {
|
|
36
88
|
if (debugConfig.enabled) {
|
|
37
89
|
console.log(`[DEBUG-${debugId}] No baseline found, capturing initial schema`);
|
|
@@ -70,18 +122,21 @@ export async function checkSchemaChanges(connector, rule, metadataStorage, confi
|
|
|
70
122
|
}
|
|
71
123
|
});
|
|
72
124
|
}
|
|
125
|
+
// Compare current schema with baseline
|
|
73
126
|
const schemaChanges = schemaComparer.compareSchemas(baseline.schema, currentSchema, {
|
|
74
127
|
trackTypes,
|
|
75
128
|
trackNullability,
|
|
76
129
|
trackedColumns: schemaConfig.trackedColumns?.columns,
|
|
77
130
|
monitoringMode
|
|
78
131
|
});
|
|
132
|
+
// Determine if we should alert or adapt
|
|
79
133
|
let shouldAlert = schemaChanges.hasChanges;
|
|
80
134
|
let shouldUpdateBaseline = false;
|
|
81
135
|
let adaptationReason = '';
|
|
82
136
|
if (schemaChanges.hasChanges) {
|
|
83
137
|
switch (adaptationMode) {
|
|
84
138
|
case 'auto': {
|
|
139
|
+
// Auto-adapt to safe changes only
|
|
85
140
|
const hasSafeChangesOnly = [...schemaChanges.addedColumns, ...schemaChanges.modifiedColumns]
|
|
86
141
|
.every(change => change.impact === 'safe') && schemaChanges.removedColumns.length === 0;
|
|
87
142
|
if (hasSafeChangesOnly) {
|
|
@@ -92,22 +147,27 @@ export async function checkSchemaChanges(connector, rule, metadataStorage, confi
|
|
|
92
147
|
break;
|
|
93
148
|
}
|
|
94
149
|
case 'alert_only':
|
|
150
|
+
// Always alert, never update baseline
|
|
95
151
|
shouldAlert = true;
|
|
96
152
|
shouldUpdateBaseline = false;
|
|
97
153
|
break;
|
|
98
154
|
case 'manual':
|
|
99
155
|
default:
|
|
156
|
+
// Alert on changes, require manual baseline update
|
|
100
157
|
shouldAlert = true;
|
|
101
158
|
shouldUpdateBaseline = false;
|
|
102
159
|
break;
|
|
103
160
|
}
|
|
104
161
|
}
|
|
162
|
+
// Update baseline if needed
|
|
105
163
|
if (shouldUpdateBaseline && metadataStorage) {
|
|
106
164
|
await baselineManager.updateBaseline(metadataStorage, rule.id, currentSchema, adaptationReason);
|
|
107
165
|
}
|
|
166
|
+
// Determine status
|
|
108
167
|
const status = shouldAlert ? 'alert' : 'ok';
|
|
109
168
|
const executedAt = new Date();
|
|
110
169
|
const executionDurationMs = Date.now() - startTime;
|
|
170
|
+
// Save execution result
|
|
111
171
|
await saveExecutionResult(metadataStorage, {
|
|
112
172
|
ruleId: rule.id,
|
|
113
173
|
status,
|
|
@@ -133,9 +193,11 @@ export async function checkSchemaChanges(connector, rule, metadataStorage, confi
|
|
|
133
193
|
});
|
|
134
194
|
}
|
|
135
195
|
catch (error) {
|
|
196
|
+
// Use secure error handling to prevent information disclosure
|
|
136
197
|
const userMessage = ErrorHandler.getUserMessage(error);
|
|
137
198
|
const executedAt = new Date();
|
|
138
199
|
const executionDurationMs = Date.now() - startTime;
|
|
200
|
+
// Log debug error information
|
|
139
201
|
if (debugConfig.enabled) {
|
|
140
202
|
console.error(`[DEBUG-${debugId}] Schema change check failed:`, {
|
|
141
203
|
table: rule.tableName,
|
|
@@ -154,18 +216,23 @@ export async function checkSchemaChanges(connector, rule, metadataStorage, confi
|
|
|
154
216
|
error: userMessage
|
|
155
217
|
}, debugConfig);
|
|
156
218
|
}
|
|
219
|
+
// Create result with debug information
|
|
157
220
|
const result = createSecureCheckResult('failed', {
|
|
158
221
|
error: userMessage,
|
|
159
222
|
executionDurationMs,
|
|
160
223
|
executedAt,
|
|
161
224
|
debugId
|
|
162
225
|
});
|
|
226
|
+
// Add debug information if available
|
|
163
227
|
if (debugConfig.enabled && error instanceof Error && 'debug' in error) {
|
|
164
228
|
result.debug = error.debug;
|
|
165
229
|
}
|
|
166
230
|
return result;
|
|
167
231
|
}
|
|
168
232
|
}
|
|
233
|
+
/**
|
|
234
|
+
* Save execution result to metadata storage with error handling
|
|
235
|
+
*/
|
|
169
236
|
async function saveExecutionResult(metadataStorage, execution, debugConfig) {
|
|
170
237
|
if (!metadataStorage)
|
|
171
238
|
return;
|
|
@@ -193,6 +260,9 @@ async function saveExecutionResult(metadataStorage, execution, debugConfig) {
|
|
|
193
260
|
}
|
|
194
261
|
}
|
|
195
262
|
}
|
|
263
|
+
/**
|
|
264
|
+
* Validate monitoring rule parameters for security
|
|
265
|
+
*/
|
|
196
266
|
function validateSchemaChangeRule(rule) {
|
|
197
267
|
if (!rule) {
|
|
198
268
|
throw new ConfigurationError('Monitoring rule is required');
|
|
@@ -203,9 +273,11 @@ function validateSchemaChangeRule(rule) {
|
|
|
203
273
|
if (rule.tableName.length > 256) {
|
|
204
274
|
throw new ConfigurationError('Table name too long (max 256 characters)');
|
|
205
275
|
}
|
|
276
|
+
// Validate rule type matches
|
|
206
277
|
if (rule.ruleType !== 'schema_change') {
|
|
207
278
|
throw new ConfigurationError('Rule type must be "schema_change" for schema change checks');
|
|
208
279
|
}
|
|
280
|
+
// Validate schema change configuration if provided
|
|
209
281
|
const config = rule.schemaChangeConfig;
|
|
210
282
|
if (config) {
|
|
211
283
|
if (config.adaptationMode && !['auto', 'manual', 'alert_only'].includes(config.adaptationMode)) {
|
|
@@ -222,6 +294,9 @@ function validateSchemaChangeRule(rule) {
|
|
|
222
294
|
}
|
|
223
295
|
}
|
|
224
296
|
}
|
|
297
|
+
/**
|
|
298
|
+
* Execute schema introspection using connector methods with proper error handling
|
|
299
|
+
*/
|
|
225
300
|
async function executeSchemaQuery(connector, tableName, debugConfig, debugFactory) {
|
|
226
301
|
const startTime = performance.now();
|
|
227
302
|
const queryContext = {
|
|
@@ -231,6 +306,7 @@ async function executeSchemaQuery(connector, tableName, debugConfig, debugFactor
|
|
|
231
306
|
operation: 'schema_query'
|
|
232
307
|
};
|
|
233
308
|
try {
|
|
309
|
+
// Log connector operation in debug mode
|
|
234
310
|
if (debugConfig.enabled) {
|
|
235
311
|
console.log(`[DEBUG] Executing schema introspection via connector:`, {
|
|
236
312
|
table: tableName,
|
|
@@ -241,6 +317,7 @@ async function executeSchemaQuery(connector, tableName, debugConfig, debugFactor
|
|
|
241
317
|
}
|
|
242
318
|
const schema = await connector.getTableSchema(tableName);
|
|
243
319
|
queryContext.duration = performance.now() - startTime;
|
|
320
|
+
// Validate schema result
|
|
244
321
|
if (!schema || typeof schema !== 'object') {
|
|
245
322
|
throw debugFactory.createQueryError('Invalid schema returned from connector', undefined, queryContext);
|
|
246
323
|
}
|
|
@@ -250,6 +327,7 @@ async function executeSchemaQuery(connector, tableName, debugConfig, debugFactor
|
|
|
250
327
|
if (!Array.isArray(schema.columns) || schema.columns.length === 0) {
|
|
251
328
|
throw debugFactory.createQueryError('Schema missing or empty columns array', undefined, queryContext);
|
|
252
329
|
}
|
|
330
|
+
// Validate each column
|
|
253
331
|
for (const column of schema.columns) {
|
|
254
332
|
if (!column.name || typeof column.name !== 'string') {
|
|
255
333
|
throw debugFactory.createQueryError('Invalid column name in schema', undefined, queryContext);
|
|
@@ -265,9 +343,13 @@ async function executeSchemaQuery(connector, tableName, debugConfig, debugFactor
|
|
|
265
343
|
}
|
|
266
344
|
catch (error) {
|
|
267
345
|
queryContext.duration = performance.now() - startTime;
|
|
346
|
+
// Create enhanced error with debug context
|
|
268
347
|
throw debugFactory.createQueryError('Failed to execute schema introspection via connector', error instanceof Error ? error : undefined, queryContext);
|
|
269
348
|
}
|
|
270
349
|
}
|
|
350
|
+
/**
|
|
351
|
+
* Execute operation with timeout protection
|
|
352
|
+
*/
|
|
271
353
|
async function executeWithTimeout(operation, timeoutMs, timeoutMessage) {
|
|
272
354
|
return new Promise((resolve, reject) => {
|
|
273
355
|
const timer = setTimeout(() => {
|
|
@@ -279,6 +361,9 @@ async function executeWithTimeout(operation, timeoutMs, timeoutMessage) {
|
|
|
279
361
|
.finally(() => clearTimeout(timer));
|
|
280
362
|
});
|
|
281
363
|
}
|
|
364
|
+
/**
|
|
365
|
+
* Create secure check result with consistent structure
|
|
366
|
+
*/
|
|
282
367
|
function createSecureCheckResult(status, data) {
|
|
283
368
|
return {
|
|
284
369
|
status,
|