noormme 1.0.0 → 1.0.2
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/LICENSE +89 -21
- package/README.md +195 -578
- package/dist/cjs/cli/commands/analyze.js +4 -1
- package/dist/cjs/cli/commands/generate.js +48 -8
- package/dist/cjs/cli/commands/init.js +54 -14
- package/dist/cjs/cli/commands/inspect.js +10 -3
- package/dist/cjs/cli/commands/migrate.js +38 -2
- package/dist/cjs/cli/commands/optimize.js +4 -1
- package/dist/cjs/cli/commands/status.js +41 -5
- package/dist/cjs/cli/commands/watch.js +4 -1
- package/dist/cjs/cli/index.js +4 -1
- package/dist/cjs/dialect/database-introspector.js +16 -21
- package/dist/cjs/dialect/sqlite/sqlite-introspector.js +13 -24
- package/dist/cjs/dynamic/dynamic.d.ts +20 -0
- package/dist/cjs/dynamic/dynamic.js +25 -0
- package/dist/cjs/edge-runtime/edge-config.d.ts +125 -0
- package/dist/cjs/edge-runtime/edge-config.js +323 -0
- package/dist/cjs/errors/NoormError.d.ts +27 -4
- package/dist/cjs/errors/NoormError.js +142 -21
- package/dist/cjs/logging/logger.d.ts +7 -2
- package/dist/cjs/logging/logger.js +21 -7
- package/dist/cjs/noormme.d.ts +12 -8
- package/dist/cjs/noormme.js +133 -61
- package/dist/cjs/operation-node/column-node.js +4 -0
- package/dist/cjs/operation-node/identifier-node.js +4 -0
- package/dist/cjs/operation-node/table-node.js +7 -0
- package/dist/cjs/parser/reference-parser.js +5 -0
- package/dist/cjs/parser/table-parser.js +2 -0
- package/dist/cjs/performance/index.d.ts +44 -0
- package/dist/cjs/performance/index.js +64 -0
- package/dist/cjs/performance/query-optimizer.d.ts +134 -0
- package/dist/cjs/performance/query-optimizer.js +391 -0
- package/dist/cjs/performance/services/cache-service.d.ts +177 -0
- package/dist/cjs/performance/services/cache-service.js +415 -0
- package/dist/cjs/performance/services/connection-factory.d.ts +198 -0
- package/dist/cjs/performance/services/connection-factory.js +498 -0
- package/dist/cjs/performance/services/metrics-collector.d.ts +162 -0
- package/dist/cjs/performance/services/metrics-collector.js +406 -0
- package/dist/cjs/performance/utils/query-parser.d.ts +123 -0
- package/dist/cjs/performance/utils/query-parser.js +295 -0
- package/dist/cjs/raw-builder/sql.d.ts +73 -26
- package/dist/cjs/raw-builder/sql.js +9 -0
- package/dist/cjs/repository/repository-factory.d.ts +10 -42
- package/dist/cjs/repository/repository-factory.js +276 -394
- package/dist/cjs/schema/core/coordinators/schema-discovery.coordinator.js +6 -4
- package/dist/cjs/schema/core/factories/discovery-factory.js +5 -5
- package/dist/cjs/schema/core/utils/name-generator.js +34 -2
- package/dist/cjs/schema/core/utils/type-mapper.d.ts +19 -14
- package/dist/cjs/schema/core/utils/type-mapper.js +4 -7
- package/dist/cjs/schema/dialects/sqlite/discovery/sqlite-constraint-discovery.js +3 -2
- package/dist/cjs/schema/dialects/sqlite/sqlite-discovery.coordinator.d.ts +2 -0
- package/dist/cjs/schema/dialects/sqlite/sqlite-discovery.coordinator.js +19 -5
- package/dist/cjs/schema/test/dialect-capabilities.test.js +6 -0
- package/dist/cjs/schema/test/error-handling.test.js +52 -33
- package/dist/cjs/schema/test/integration.test.js +51 -5
- package/dist/cjs/schema/test/sqlite-discovery-coordinator.test.js +8 -4
- package/dist/cjs/sqlite-migration/index.js +35 -2
- package/dist/cjs/testing/test-utils.d.ts +5 -0
- package/dist/cjs/testing/test-utils.js +66 -6
- package/dist/cjs/types/index.d.ts +78 -13
- package/dist/cjs/types/index.js +23 -0
- package/dist/cjs/types/type-generator.d.ts +8 -0
- package/dist/cjs/types/type-generator.js +86 -17
- package/dist/cjs/util/safe-sql-helpers.d.ts +124 -0
- package/dist/cjs/util/safe-sql-helpers.js +221 -0
- package/dist/cjs/util/security-validator.d.ts +44 -0
- package/dist/cjs/util/security-validator.js +256 -0
- package/dist/cjs/util/security.d.ts +60 -0
- package/dist/cjs/util/security.js +137 -0
- package/dist/cjs/watch/schema-watcher.js +26 -7
- package/dist/esm/cli/commands/generate.js +10 -6
- package/dist/esm/cli/commands/init.js +15 -11
- package/dist/esm/cli/commands/inspect.js +6 -2
- package/dist/esm/cli/commands/status.js +3 -3
- package/dist/esm/dialect/database-introspector.js +16 -21
- package/dist/esm/dialect/sqlite/sqlite-introspector.js +13 -24
- package/dist/esm/dynamic/dynamic.d.ts +20 -0
- package/dist/esm/dynamic/dynamic.js +25 -0
- package/dist/esm/edge-runtime/edge-config.d.ts +125 -0
- package/dist/esm/edge-runtime/edge-config.js +281 -0
- package/dist/esm/errors/NoormError.d.ts +27 -4
- package/dist/esm/errors/NoormError.js +134 -18
- package/dist/esm/logging/logger.d.ts +7 -2
- package/dist/esm/logging/logger.js +21 -7
- package/dist/esm/noormme.d.ts +12 -8
- package/dist/esm/noormme.js +130 -61
- package/dist/esm/operation-node/column-node.js +4 -0
- package/dist/esm/operation-node/identifier-node.js +4 -0
- package/dist/esm/operation-node/table-node.js +7 -0
- package/dist/esm/parser/reference-parser.js +5 -0
- package/dist/esm/parser/table-parser.js +2 -0
- package/dist/esm/performance/index.d.ts +44 -0
- package/dist/esm/performance/index.js +48 -0
- package/dist/esm/performance/query-optimizer.d.ts +134 -0
- package/dist/esm/performance/query-optimizer.js +387 -0
- package/dist/esm/performance/services/cache-service.d.ts +177 -0
- package/dist/esm/performance/services/cache-service.js +410 -0
- package/dist/esm/performance/services/connection-factory.d.ts +198 -0
- package/dist/esm/performance/services/connection-factory.js +493 -0
- package/dist/esm/performance/services/metrics-collector.d.ts +162 -0
- package/dist/esm/performance/services/metrics-collector.js +402 -0
- package/dist/esm/performance/utils/query-parser.d.ts +123 -0
- package/dist/esm/performance/utils/query-parser.js +292 -0
- package/dist/esm/raw-builder/sql.d.ts +73 -26
- package/dist/esm/raw-builder/sql.js +9 -0
- package/dist/esm/repository/repository-factory.d.ts +10 -42
- package/dist/esm/repository/repository-factory.js +277 -395
- package/dist/esm/schema/core/coordinators/schema-discovery.coordinator.js +6 -4
- package/dist/esm/schema/core/factories/discovery-factory.js +5 -5
- package/dist/esm/schema/core/utils/name-generator.js +34 -2
- package/dist/esm/schema/core/utils/type-mapper.d.ts +19 -14
- package/dist/esm/schema/core/utils/type-mapper.js +4 -7
- package/dist/esm/schema/dialects/sqlite/discovery/sqlite-constraint-discovery.js +3 -2
- package/dist/esm/schema/dialects/sqlite/sqlite-discovery.coordinator.d.ts +2 -0
- package/dist/esm/schema/dialects/sqlite/sqlite-discovery.coordinator.js +19 -5
- package/dist/esm/schema/test/dialect-capabilities.test.js +6 -0
- package/dist/esm/schema/test/error-handling.test.js +52 -33
- package/dist/esm/schema/test/integration.test.js +18 -5
- package/dist/esm/schema/test/sqlite-discovery-coordinator.test.js +8 -4
- package/dist/esm/testing/test-utils.d.ts +5 -0
- package/dist/esm/testing/test-utils.js +66 -6
- package/dist/esm/types/index.d.ts +78 -13
- package/dist/esm/types/index.js +22 -1
- package/dist/esm/types/type-generator.d.ts +8 -0
- package/dist/esm/types/type-generator.js +86 -17
- package/dist/esm/util/safe-sql-helpers.d.ts +124 -0
- package/dist/esm/util/safe-sql-helpers.js +209 -0
- package/dist/esm/util/security-validator.d.ts +44 -0
- package/dist/esm/util/security-validator.js +246 -0
- package/dist/esm/util/security.d.ts +60 -0
- package/dist/esm/util/security.js +114 -0
- package/dist/esm/watch/schema-watcher.js +26 -7
- package/package.json +1 -1
- package/dist/cjs/performance/query-analyzer.d.ts +0 -89
- package/dist/cjs/performance/query-analyzer.js +0 -263
- package/dist/esm/performance/query-analyzer.d.ts +0 -89
- package/dist/esm/performance/query-analyzer.js +0 -260
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Security utilities for NOORMME
|
|
4
|
+
* Export all security-related functions for easy import
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.SecurityPatterns = exports.SecurityGuidelines = exports.validateNumericRange = exports.validateEnum = exports.SafeSQLExamples = exports.SafeSQLKeywords = exports.safeCaseStatement = exports.safeBoolean = exports.safeOrderBy = exports.safeLockMode = exports.safeKeyword = exports.safeOffset = exports.safeLimit = exports.safeOrderDirection = exports.RateLimiter = exports.validateMigrationName = exports.validateOutputDirectory = exports.sanitizeDatabasePath = exports.validateFilePath = exports.validateColumnReference = exports.validateTableReference = exports.validateIdentifier = void 0;
|
|
8
|
+
exports.securityChecklist = securityChecklist;
|
|
9
|
+
// Input validation and sanitization
|
|
10
|
+
var security_validator_js_1 = require("./security-validator.js");
|
|
11
|
+
Object.defineProperty(exports, "validateIdentifier", { enumerable: true, get: function () { return security_validator_js_1.validateIdentifier; } });
|
|
12
|
+
Object.defineProperty(exports, "validateTableReference", { enumerable: true, get: function () { return security_validator_js_1.validateTableReference; } });
|
|
13
|
+
Object.defineProperty(exports, "validateColumnReference", { enumerable: true, get: function () { return security_validator_js_1.validateColumnReference; } });
|
|
14
|
+
Object.defineProperty(exports, "validateFilePath", { enumerable: true, get: function () { return security_validator_js_1.validateFilePath; } });
|
|
15
|
+
Object.defineProperty(exports, "sanitizeDatabasePath", { enumerable: true, get: function () { return security_validator_js_1.sanitizeDatabasePath; } });
|
|
16
|
+
Object.defineProperty(exports, "validateOutputDirectory", { enumerable: true, get: function () { return security_validator_js_1.validateOutputDirectory; } });
|
|
17
|
+
Object.defineProperty(exports, "validateMigrationName", { enumerable: true, get: function () { return security_validator_js_1.validateMigrationName; } });
|
|
18
|
+
Object.defineProperty(exports, "RateLimiter", { enumerable: true, get: function () { return security_validator_js_1.RateLimiter; } });
|
|
19
|
+
// Safe SQL helpers
|
|
20
|
+
var safe_sql_helpers_js_1 = require("./safe-sql-helpers.js");
|
|
21
|
+
Object.defineProperty(exports, "safeOrderDirection", { enumerable: true, get: function () { return safe_sql_helpers_js_1.safeOrderDirection; } });
|
|
22
|
+
Object.defineProperty(exports, "safeLimit", { enumerable: true, get: function () { return safe_sql_helpers_js_1.safeLimit; } });
|
|
23
|
+
Object.defineProperty(exports, "safeOffset", { enumerable: true, get: function () { return safe_sql_helpers_js_1.safeOffset; } });
|
|
24
|
+
Object.defineProperty(exports, "safeKeyword", { enumerable: true, get: function () { return safe_sql_helpers_js_1.safeKeyword; } });
|
|
25
|
+
Object.defineProperty(exports, "safeLockMode", { enumerable: true, get: function () { return safe_sql_helpers_js_1.safeLockMode; } });
|
|
26
|
+
Object.defineProperty(exports, "safeOrderBy", { enumerable: true, get: function () { return safe_sql_helpers_js_1.safeOrderBy; } });
|
|
27
|
+
Object.defineProperty(exports, "safeBoolean", { enumerable: true, get: function () { return safe_sql_helpers_js_1.safeBoolean; } });
|
|
28
|
+
Object.defineProperty(exports, "safeCaseStatement", { enumerable: true, get: function () { return safe_sql_helpers_js_1.safeCaseStatement; } });
|
|
29
|
+
Object.defineProperty(exports, "SafeSQLKeywords", { enumerable: true, get: function () { return safe_sql_helpers_js_1.SafeSQLKeywords; } });
|
|
30
|
+
Object.defineProperty(exports, "SafeSQLExamples", { enumerable: true, get: function () { return safe_sql_helpers_js_1.SafeSQLExamples; } });
|
|
31
|
+
Object.defineProperty(exports, "validateEnum", { enumerable: true, get: function () { return safe_sql_helpers_js_1.validateEnum; } });
|
|
32
|
+
Object.defineProperty(exports, "validateNumericRange", { enumerable: true, get: function () { return safe_sql_helpers_js_1.validateNumericRange; } });
|
|
33
|
+
/**
|
|
34
|
+
* Security best practices and guidelines
|
|
35
|
+
*/
|
|
36
|
+
exports.SecurityGuidelines = {
|
|
37
|
+
/**
|
|
38
|
+
* NEVER use sql.raw() or sql.lit() with user input
|
|
39
|
+
*/
|
|
40
|
+
NEVER_USE_RAW_WITH_USER_INPUT: 'Use safe alternatives or parameterized queries',
|
|
41
|
+
/**
|
|
42
|
+
* ALWAYS validate dynamic identifiers
|
|
43
|
+
*/
|
|
44
|
+
ALWAYS_VALIDATE_IDENTIFIERS: 'Use validateColumnReference() or validateTableReference()',
|
|
45
|
+
/**
|
|
46
|
+
* ALWAYS use whitelists for user-controlled values
|
|
47
|
+
*/
|
|
48
|
+
ALWAYS_USE_WHITELISTS: 'Define allowed values and validate against them',
|
|
49
|
+
/**
|
|
50
|
+
* ALWAYS sanitize file paths
|
|
51
|
+
*/
|
|
52
|
+
ALWAYS_SANITIZE_PATHS: 'Use validateFilePath() or sanitizeDatabasePath()',
|
|
53
|
+
/**
|
|
54
|
+
* PREFER parameterized queries
|
|
55
|
+
*/
|
|
56
|
+
PREFER_PARAMETERIZED_QUERIES: 'Use sql`${value}` instead of sql.raw()',
|
|
57
|
+
/**
|
|
58
|
+
* PREFER safe helpers over raw SQL
|
|
59
|
+
*/
|
|
60
|
+
PREFER_SAFE_HELPERS: 'Use safeOrderDirection(), safeLimit(), etc.',
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Quick security checklist for developers
|
|
64
|
+
*/
|
|
65
|
+
function securityChecklist() {
|
|
66
|
+
return `
|
|
67
|
+
NOORMME Security Checklist:
|
|
68
|
+
|
|
69
|
+
✅ Input Validation
|
|
70
|
+
□ All user inputs validated with whitelists
|
|
71
|
+
□ Dynamic columns/tables use validateColumnReference()/validateTableReference()
|
|
72
|
+
□ File paths use validateFilePath() or sanitizeDatabasePath()
|
|
73
|
+
|
|
74
|
+
✅ SQL Injection Prevention
|
|
75
|
+
□ No sql.raw() with user input
|
|
76
|
+
□ No sql.lit() with user input
|
|
77
|
+
□ Use safe alternatives: safeOrderDirection(), safeLimit(), etc.
|
|
78
|
+
□ Parameterized queries used for all values
|
|
79
|
+
|
|
80
|
+
✅ Path Traversal Prevention
|
|
81
|
+
□ All file operations validated
|
|
82
|
+
□ No parent directory references (..) allowed
|
|
83
|
+
□ No absolute paths accepted
|
|
84
|
+
|
|
85
|
+
✅ Production Readiness
|
|
86
|
+
□ Environment variables for sensitive config
|
|
87
|
+
□ Error messages don't leak sensitive info
|
|
88
|
+
□ Rate limiting enabled for auth endpoints
|
|
89
|
+
□ Database file permissions set (600/640)
|
|
90
|
+
□ Dependencies updated regularly
|
|
91
|
+
□ Backups encrypted and secure
|
|
92
|
+
|
|
93
|
+
Use this checklist before deploying to production!
|
|
94
|
+
`;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Common security patterns
|
|
98
|
+
*/
|
|
99
|
+
exports.SecurityPatterns = {
|
|
100
|
+
/**
|
|
101
|
+
* Safe column selection from user input
|
|
102
|
+
*/
|
|
103
|
+
safeColumnSelection: (userColumn, allowedColumns) => {
|
|
104
|
+
if (!allowedColumns.includes(userColumn)) {
|
|
105
|
+
throw new Error(`Invalid column: ${userColumn}`);
|
|
106
|
+
}
|
|
107
|
+
return userColumn;
|
|
108
|
+
},
|
|
109
|
+
/**
|
|
110
|
+
* Safe table selection from user input
|
|
111
|
+
*/
|
|
112
|
+
safeTableSelection: (userTable, allowedTables) => {
|
|
113
|
+
if (!allowedTables.includes(userTable)) {
|
|
114
|
+
throw new Error(`Invalid table: ${userTable}`);
|
|
115
|
+
}
|
|
116
|
+
return userTable;
|
|
117
|
+
},
|
|
118
|
+
/**
|
|
119
|
+
* Safe enum validation
|
|
120
|
+
*/
|
|
121
|
+
safeEnumValue: (value, allowedValues) => {
|
|
122
|
+
if (!allowedValues.includes(value)) {
|
|
123
|
+
throw new Error(`Invalid value: ${value}. Allowed: ${allowedValues.join(', ')}`);
|
|
124
|
+
}
|
|
125
|
+
return value;
|
|
126
|
+
},
|
|
127
|
+
/**
|
|
128
|
+
* Safe numeric range validation
|
|
129
|
+
*/
|
|
130
|
+
safeNumericValue: (value, min, max) => {
|
|
131
|
+
const num = typeof value === 'string' ? parseFloat(value) : value;
|
|
132
|
+
if (isNaN(num) || num < min || num > max) {
|
|
133
|
+
throw new Error(`Invalid number: ${value}. Must be between ${min} and ${max}`);
|
|
134
|
+
}
|
|
135
|
+
return num;
|
|
136
|
+
},
|
|
137
|
+
};
|
|
@@ -18,11 +18,13 @@ class SchemaWatcher {
|
|
|
18
18
|
this.schemaDiscovery = schemaDiscovery;
|
|
19
19
|
this.logger = logger;
|
|
20
20
|
this.options = options;
|
|
21
|
+
// Merge options, giving priority to explicitly passed values
|
|
22
|
+
const defaultEnabled = process.env.NODE_ENV === 'development';
|
|
21
23
|
this.options = {
|
|
22
24
|
pollInterval: 5000,
|
|
23
25
|
ignoreViews: true,
|
|
24
26
|
ignoredTables: [],
|
|
25
|
-
enabled:
|
|
27
|
+
enabled: options.enabled !== undefined ? options.enabled : defaultEnabled,
|
|
26
28
|
...options
|
|
27
29
|
};
|
|
28
30
|
}
|
|
@@ -40,29 +42,40 @@ class SchemaWatcher {
|
|
|
40
42
|
}
|
|
41
43
|
this.logger.info('Starting schema change monitoring...');
|
|
42
44
|
// Get initial schema snapshot
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
try {
|
|
46
|
+
const initialSchema = await this.getCurrentSchema();
|
|
47
|
+
this.lastSchemaHash = this.hashSchema(initialSchema);
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
this.logger.error('Failed to get initial schema snapshot:', error);
|
|
51
|
+
// Set a default hash to allow watching to continue
|
|
52
|
+
this.lastSchemaHash = '0';
|
|
53
|
+
}
|
|
45
54
|
this.isWatching = true;
|
|
46
55
|
this.intervalId = setInterval(() => {
|
|
47
56
|
this.checkForChanges().catch(error => {
|
|
48
57
|
this.logger.error('Error checking for schema changes:', error);
|
|
49
58
|
});
|
|
50
59
|
}, this.options.pollInterval);
|
|
60
|
+
// Unref the interval to allow the process to exit
|
|
61
|
+
if (this.intervalId && typeof this.intervalId.unref === 'function') {
|
|
62
|
+
this.intervalId.unref();
|
|
63
|
+
}
|
|
51
64
|
this.logger.info(`Schema watcher started (polling every ${this.options.pollInterval}ms)`);
|
|
52
65
|
}
|
|
53
66
|
/**
|
|
54
67
|
* Stop watching for schema changes
|
|
55
68
|
*/
|
|
56
69
|
stopWatching() {
|
|
57
|
-
if (!this.isWatching) {
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
70
|
if (this.intervalId) {
|
|
61
71
|
clearInterval(this.intervalId);
|
|
62
72
|
this.intervalId = null;
|
|
63
73
|
}
|
|
64
74
|
this.isWatching = false;
|
|
65
|
-
this.
|
|
75
|
+
this.callbacks = []; // Clear callbacks on stop
|
|
76
|
+
if (this.logger) {
|
|
77
|
+
this.logger.info('Schema watcher stopped');
|
|
78
|
+
}
|
|
66
79
|
}
|
|
67
80
|
/**
|
|
68
81
|
* Register callback for schema changes
|
|
@@ -86,6 +99,8 @@ class SchemaWatcher {
|
|
|
86
99
|
try {
|
|
87
100
|
const currentSchema = await this.getCurrentSchema();
|
|
88
101
|
const currentHash = this.hashSchema(currentSchema);
|
|
102
|
+
// Debug logging
|
|
103
|
+
this.logger.debug(`Schema check - Last hash: ${this.lastSchemaHash}, Current hash: ${currentHash}, Tables: ${currentSchema.tables.length}`);
|
|
89
104
|
if (this.lastSchemaHash && currentHash !== this.lastSchemaHash) {
|
|
90
105
|
this.logger.info('Schema changes detected, analyzing...');
|
|
91
106
|
// Since we only have the hash, we'll need to do a full schema comparison
|
|
@@ -98,6 +113,10 @@ class SchemaWatcher {
|
|
|
98
113
|
}
|
|
99
114
|
return changes;
|
|
100
115
|
}
|
|
116
|
+
// Update hash even if no changes detected yet (for first check after initialization)
|
|
117
|
+
if (!this.lastSchemaHash) {
|
|
118
|
+
this.lastSchemaHash = currentHash;
|
|
119
|
+
}
|
|
101
120
|
return [];
|
|
102
121
|
}
|
|
103
122
|
catch (error) {
|
|
@@ -3,11 +3,13 @@ import { promises as fs } from 'fs';
|
|
|
3
3
|
import * as path from 'path';
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import { NOORMME } from '../../noormme.js';
|
|
6
|
+
import { sanitizeDatabasePath, validateOutputDirectory } from '../../util/security-validator.js';
|
|
6
7
|
export async function generate(options = {}) {
|
|
7
8
|
console.log(chalk.blue.bold('\n🔧 NOORMME Code Generation - Automating TypeScript & Repositories\n'));
|
|
8
9
|
try {
|
|
9
|
-
//
|
|
10
|
-
const
|
|
10
|
+
// SECURITY: Validate and sanitize database path to prevent path traversal attacks
|
|
11
|
+
const databasePathInput = options.database || process.env.DATABASE_PATH || './database.sqlite';
|
|
12
|
+
const databasePath = sanitizeDatabasePath(databasePathInput);
|
|
11
13
|
const db = new NOORMME({
|
|
12
14
|
dialect: 'sqlite',
|
|
13
15
|
connection: {
|
|
@@ -20,7 +22,9 @@ export async function generate(options = {}) {
|
|
|
20
22
|
});
|
|
21
23
|
await db.initialize();
|
|
22
24
|
const schemaInfo = await db.getSchemaInfo();
|
|
25
|
+
// SECURITY: Validate output directory to prevent path traversal attacks
|
|
23
26
|
const outputDir = options.output || './generated';
|
|
27
|
+
validateOutputDirectory(outputDir);
|
|
24
28
|
const format = options.format || 'dts';
|
|
25
29
|
console.log(chalk.gray(`📁 Output directory: ${outputDir}`));
|
|
26
30
|
console.log(chalk.gray(`📊 Discovered ${schemaInfo.tables.length} tables\n`));
|
|
@@ -184,11 +188,11 @@ function mapColumnToTsType(column) {
|
|
|
184
188
|
}
|
|
185
189
|
// JSON types
|
|
186
190
|
if (type.includes('json')) {
|
|
187
|
-
return 'Record<string,
|
|
191
|
+
return 'Record<string, unknown>';
|
|
188
192
|
}
|
|
189
193
|
// Array types
|
|
190
194
|
if (type.includes('array') || type.includes('[]')) {
|
|
191
|
-
return '
|
|
195
|
+
return 'unknown[]';
|
|
192
196
|
}
|
|
193
197
|
// UUID types
|
|
194
198
|
if (type.includes('uuid')) {
|
|
@@ -199,11 +203,11 @@ function mapColumnToTsType(column) {
|
|
|
199
203
|
}
|
|
200
204
|
function getPrimaryKeyType(table) {
|
|
201
205
|
if (!table.primaryKey || table.primaryKey.length === 0) {
|
|
202
|
-
return '
|
|
206
|
+
return 'unknown';
|
|
203
207
|
}
|
|
204
208
|
if (table.primaryKey.length === 1) {
|
|
205
209
|
const pkColumn = table.columns.find(col => col.name === table.primaryKey[0]);
|
|
206
|
-
return pkColumn ? mapColumnToTsType(pkColumn) : '
|
|
210
|
+
return pkColumn ? mapColumnToTsType(pkColumn) : 'unknown';
|
|
207
211
|
}
|
|
208
212
|
// Composite primary key
|
|
209
213
|
const types = table.primaryKey.map(pkCol => {
|
|
@@ -3,12 +3,17 @@ import inquirer from 'inquirer';
|
|
|
3
3
|
import { promises as fs } from 'fs';
|
|
4
4
|
import * as path from 'path';
|
|
5
5
|
import chalk from 'chalk';
|
|
6
|
+
import { sanitizeDatabasePath, validateOutputDirectory } from '../../util/security-validator.js';
|
|
6
7
|
export async function init(options) {
|
|
7
8
|
console.log(chalk.blue.bold('\n🎯 NOORMME Zero-Configuration Setup\n'));
|
|
8
9
|
console.log(chalk.gray('Setting up NOORMME with complete SQLite automation...\n'));
|
|
9
10
|
try {
|
|
10
|
-
//
|
|
11
|
-
const
|
|
11
|
+
// SECURITY: Validate and sanitize database path to prevent path traversal attacks
|
|
12
|
+
const databasePathInput = options.database || './database.sqlite';
|
|
13
|
+
const databasePath = sanitizeDatabasePath(databasePathInput);
|
|
14
|
+
// SECURITY: Validate output directory to prevent path traversal attacks
|
|
15
|
+
const outputDir = options.output || 'lib';
|
|
16
|
+
validateOutputDirectory(outputDir);
|
|
12
17
|
console.log(chalk.blue('🔍 Detecting existing SQLite database...'));
|
|
13
18
|
// Check if database exists
|
|
14
19
|
const dbExists = await checkDatabaseExists(databasePath);
|
|
@@ -55,7 +60,6 @@ export async function init(options) {
|
|
|
55
60
|
return;
|
|
56
61
|
}
|
|
57
62
|
// Generate files with automation focus
|
|
58
|
-
const outputDir = options.output || 'lib';
|
|
59
63
|
await generateDbFile(databasePath, outputDir, options.force, autoOptimize, autoIndex);
|
|
60
64
|
await generateEnvExample(databasePath);
|
|
61
65
|
await generateAutomationConfig(databasePath, autoOptimize, autoIndex);
|
|
@@ -120,7 +124,7 @@ async function generateDbFile(databasePath, outputDir, force, autoOptimize, auto
|
|
|
120
124
|
|
|
121
125
|
// NOORMME with complete SQLite automation
|
|
122
126
|
// Automatically discovers schema, generates types, and optimizes performance
|
|
123
|
-
|
|
127
|
+
const database = new NOORMME({
|
|
124
128
|
dialect: 'sqlite',
|
|
125
129
|
connection: { database: '${databasePath}' },
|
|
126
130
|
|
|
@@ -154,7 +158,7 @@ export const db = new NOORMME({
|
|
|
154
158
|
|
|
155
159
|
// Initialize with complete automation
|
|
156
160
|
export async function initializeDatabase() {
|
|
157
|
-
await
|
|
161
|
+
await database.initialize()
|
|
158
162
|
console.log('🎉 NOORMME initialized with complete SQLite automation!')
|
|
159
163
|
console.log('✅ Schema discovered automatically')
|
|
160
164
|
console.log('✅ Types generated automatically')
|
|
@@ -163,32 +167,32 @@ export async function initializeDatabase() {
|
|
|
163
167
|
|
|
164
168
|
// Get a repository for any table (auto-generated)
|
|
165
169
|
export function getRepository<T>(tableName: string) {
|
|
166
|
-
return
|
|
170
|
+
return database.getRepository<T>(tableName)
|
|
167
171
|
}
|
|
168
172
|
|
|
169
173
|
// Get Kysely instance for complex queries
|
|
170
174
|
export function getKysely() {
|
|
171
|
-
return
|
|
175
|
+
return database.getKysely()
|
|
172
176
|
}
|
|
173
177
|
|
|
174
178
|
// Performance monitoring
|
|
175
179
|
export async function getPerformanceMetrics() {
|
|
176
|
-
return await
|
|
180
|
+
return await database.getSQLitePerformanceMetrics()
|
|
177
181
|
}
|
|
178
182
|
|
|
179
183
|
// Get optimization recommendations
|
|
180
184
|
export async function getOptimizationRecommendations() {
|
|
181
|
-
return await
|
|
185
|
+
return await database.getSQLiteIndexRecommendations()
|
|
182
186
|
}
|
|
183
187
|
|
|
184
188
|
// Apply optimizations
|
|
185
189
|
export async function applyOptimizations() {
|
|
186
|
-
return await
|
|
190
|
+
return await database.applySQLiteOptimizations()
|
|
187
191
|
}
|
|
188
192
|
|
|
189
193
|
// Close database connections
|
|
190
194
|
export async function closeDatabase() {
|
|
191
|
-
await
|
|
195
|
+
await database.close()
|
|
192
196
|
}
|
|
193
197
|
`;
|
|
194
198
|
// Ensure directory exists
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/// <reference types="./inspect.d.ts" />
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
import { NOORMME } from '../../noormme.js';
|
|
4
|
+
import { sanitizeDatabasePath, validateIdentifier } from '../../util/security-validator.js';
|
|
4
5
|
export async function inspect(tableName, options = {}) {
|
|
5
6
|
console.log(chalk.blue.bold('\n🔍 NOORMME Schema Inspection - Intelligent Database Discovery\n'));
|
|
6
7
|
try {
|
|
7
|
-
//
|
|
8
|
-
const
|
|
8
|
+
// SECURITY: Validate and sanitize database path to prevent path traversal attacks
|
|
9
|
+
const databasePathInput = options.database || process.env.DATABASE_PATH || './database.sqlite';
|
|
10
|
+
const databasePath = sanitizeDatabasePath(databasePathInput);
|
|
9
11
|
const db = new NOORMME({
|
|
10
12
|
dialect: 'sqlite',
|
|
11
13
|
connection: {
|
|
@@ -20,6 +22,8 @@ export async function inspect(tableName, options = {}) {
|
|
|
20
22
|
console.log(chalk.gray(`📁 Database: ${databasePath}\n`));
|
|
21
23
|
const schemaInfo = await db.getSchemaInfo();
|
|
22
24
|
if (tableName) {
|
|
25
|
+
// SECURITY: Validate table name to prevent SQL injection
|
|
26
|
+
validateIdentifier(tableName, 'table name');
|
|
23
27
|
// Show specific table with automation insights
|
|
24
28
|
const table = schemaInfo.tables.find(t => t.name === tableName);
|
|
25
29
|
if (!table) {
|
|
@@ -210,10 +210,10 @@ async function getAutomationStatus(db) {
|
|
|
210
210
|
try {
|
|
211
211
|
const config = db.config;
|
|
212
212
|
return {
|
|
213
|
-
autoOptimization: config.
|
|
213
|
+
autoOptimization: config.automation?.enableAutoOptimization ?? true,
|
|
214
214
|
queryAnalysis: config.performance?.enableQueryOptimization ?? true,
|
|
215
|
-
autoIndexing: config.
|
|
216
|
-
schemaMonitoring: config.
|
|
215
|
+
autoIndexing: config.automation?.enableIndexRecommendations ?? true,
|
|
216
|
+
schemaMonitoring: config.automation?.enableSchemaWatcher ?? false
|
|
217
217
|
};
|
|
218
218
|
}
|
|
219
219
|
catch {
|
|
@@ -12,30 +12,24 @@ export class DatabaseIntrospector {
|
|
|
12
12
|
* Get all tables in the database
|
|
13
13
|
*/
|
|
14
14
|
async getTables() {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}));
|
|
26
|
-
}
|
|
27
|
-
catch (error) {
|
|
28
|
-
console.warn('SQLite table discovery failed:', error);
|
|
29
|
-
throw new Error('Unable to introspect database tables. SQLite database required.');
|
|
30
|
-
}
|
|
15
|
+
// SQLite
|
|
16
|
+
const sqliteTables = await this.db
|
|
17
|
+
.selectFrom('sqlite_master')
|
|
18
|
+
.select('name')
|
|
19
|
+
.where('type', '=', 'table')
|
|
20
|
+
.where('name', 'not like', 'sqlite_%')
|
|
21
|
+
.execute();
|
|
22
|
+
return sqliteTables.map(t => ({
|
|
23
|
+
name: t.name
|
|
24
|
+
}));
|
|
31
25
|
}
|
|
32
26
|
/**
|
|
33
27
|
* Get columns for a specific table
|
|
34
28
|
*/
|
|
35
29
|
async getColumns(tableName) {
|
|
36
30
|
try {
|
|
37
|
-
// SQLite - use raw SQL for
|
|
38
|
-
const result = await sql `
|
|
31
|
+
// SQLite - use raw SQL for PRAGMA table_info
|
|
32
|
+
const result = await sql `PRAGMA table_info(${sql.lit(tableName)})`.execute(this.db);
|
|
39
33
|
const sqliteColumns = result.rows;
|
|
40
34
|
return sqliteColumns.map((col) => ({
|
|
41
35
|
name: col.name,
|
|
@@ -48,7 +42,8 @@ export class DatabaseIntrospector {
|
|
|
48
42
|
}
|
|
49
43
|
catch (error) {
|
|
50
44
|
console.warn('SQLite column discovery failed:', error);
|
|
51
|
-
|
|
45
|
+
// Return empty array instead of throwing for non-existent tables
|
|
46
|
+
return [];
|
|
52
47
|
}
|
|
53
48
|
}
|
|
54
49
|
/**
|
|
@@ -63,8 +58,8 @@ export class DatabaseIntrospector {
|
|
|
63
58
|
*/
|
|
64
59
|
async getForeignKeys(tableName) {
|
|
65
60
|
try {
|
|
66
|
-
// SQLite - use raw SQL for
|
|
67
|
-
const result = await sql `
|
|
61
|
+
// SQLite - use raw SQL for PRAGMA foreign_key_list
|
|
62
|
+
const result = await sql `PRAGMA foreign_key_list(${sql.lit(tableName)})`.execute(this.db);
|
|
68
63
|
const sqliteFks = result.rows;
|
|
69
64
|
return sqliteFks.map((fk) => ({
|
|
70
65
|
name: `fk_${tableName}_${fk.from}`,
|
|
@@ -38,28 +38,17 @@ export class SqliteIntrospector extends DatabaseIntrospector {
|
|
|
38
38
|
}
|
|
39
39
|
async #getTableMetadata(options) {
|
|
40
40
|
const tablesResult = await this.#tablesQuery(this.#db, options).execute();
|
|
41
|
-
|
|
42
|
-
.with('table_list', (qb) => this.#tablesQuery(qb, options))
|
|
43
|
-
.selectFrom([
|
|
44
|
-
'table_list as tl',
|
|
45
|
-
sql `pragma_table_info(tl.name)`.as('p'),
|
|
46
|
-
])
|
|
47
|
-
.select([
|
|
48
|
-
'tl.name as table',
|
|
49
|
-
'p.cid',
|
|
50
|
-
'p.name',
|
|
51
|
-
'p.type',
|
|
52
|
-
'p.notnull',
|
|
53
|
-
'p.dflt_value',
|
|
54
|
-
'p.pk',
|
|
55
|
-
])
|
|
56
|
-
.orderBy('tl.name')
|
|
57
|
-
.orderBy('p.cid')
|
|
58
|
-
.execute();
|
|
41
|
+
// Get column metadata for each table separately since PRAGMA doesn't work in joins
|
|
59
42
|
const columnsByTable = {};
|
|
60
|
-
for (const
|
|
61
|
-
|
|
62
|
-
|
|
43
|
+
for (const table of tablesResult) {
|
|
44
|
+
try {
|
|
45
|
+
const columns = await sql `PRAGMA table_info(${sql.lit(table.name)})`.execute(this.#db);
|
|
46
|
+
columnsByTable[table.name] = columns.rows;
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
console.warn(`Failed to get columns for table ${table.name}:`, error);
|
|
50
|
+
columnsByTable[table.name] = [];
|
|
51
|
+
}
|
|
63
52
|
}
|
|
64
53
|
return tablesResult.map(({ name, sql, type }) => {
|
|
65
54
|
// Enhanced auto-increment detection
|
|
@@ -180,7 +169,7 @@ export class SqliteIntrospector extends DatabaseIntrospector {
|
|
|
180
169
|
}
|
|
181
170
|
async getColumns(tableName) {
|
|
182
171
|
try {
|
|
183
|
-
// SQLite - use raw SQL for
|
|
172
|
+
// SQLite - use raw SQL for PRAGMA table_info
|
|
184
173
|
const result = await sql `PRAGMA table_info(${sql.lit(tableName)})`.execute(this.#db);
|
|
185
174
|
const sqliteColumns = result.rows;
|
|
186
175
|
return sqliteColumns.map((col) => ({
|
|
@@ -199,7 +188,7 @@ export class SqliteIntrospector extends DatabaseIntrospector {
|
|
|
199
188
|
}
|
|
200
189
|
async getIndexes(tableName) {
|
|
201
190
|
try {
|
|
202
|
-
// SQLite - use raw SQL for
|
|
191
|
+
// SQLite - use raw SQL for PRAGMA index_list
|
|
203
192
|
const result = await sql `PRAGMA index_list(${sql.lit(tableName)})`.execute(this.#db);
|
|
204
193
|
const sqliteIndexes = result.rows;
|
|
205
194
|
return sqliteIndexes.map((idx) => ({
|
|
@@ -215,7 +204,7 @@ export class SqliteIntrospector extends DatabaseIntrospector {
|
|
|
215
204
|
}
|
|
216
205
|
async getForeignKeys(tableName) {
|
|
217
206
|
try {
|
|
218
|
-
// SQLite - use raw SQL for
|
|
207
|
+
// SQLite - use raw SQL for PRAGMA foreign_key_list
|
|
219
208
|
const result = await sql `PRAGMA foreign_key_list(${sql.lit(tableName)})`.execute(this.#db);
|
|
220
209
|
const sqliteFks = result.rows;
|
|
221
210
|
return sqliteFks.map((fk) => ({
|
|
@@ -17,6 +17,10 @@ export declare class DynamicModule<DB> {
|
|
|
17
17
|
* create an SQL injection vulnerability. Always __always__ validate the user
|
|
18
18
|
* input before passing it to this method.
|
|
19
19
|
*
|
|
20
|
+
* SECURITY: This method now includes built-in validation to prevent SQL injection
|
|
21
|
+
* through malicious column references. However, you should still validate that
|
|
22
|
+
* the column reference is from a trusted source or whitelist of allowed columns.
|
|
23
|
+
*
|
|
20
24
|
* There are couple of examples below for some use cases, but you can pass
|
|
21
25
|
* `ref` to other methods as well. If the types allow you to pass a `ref`
|
|
22
26
|
* value to some place, it should work.
|
|
@@ -29,6 +33,12 @@ export declare class DynamicModule<DB> {
|
|
|
29
33
|
* async function someQuery(filterColumn: string, filterValue: string) {
|
|
30
34
|
* const { ref } = db.dynamic
|
|
31
35
|
*
|
|
36
|
+
* // Validate input against a whitelist of allowed columns
|
|
37
|
+
* const allowedColumns = ['first_name', 'last_name', 'email']
|
|
38
|
+
* if (!allowedColumns.includes(filterColumn)) {
|
|
39
|
+
* throw new Error('Invalid column')
|
|
40
|
+
* }
|
|
41
|
+
*
|
|
32
42
|
* return await db
|
|
33
43
|
* .selectFrom('person')
|
|
34
44
|
* .selectAll()
|
|
@@ -46,6 +56,12 @@ export declare class DynamicModule<DB> {
|
|
|
46
56
|
* async function someQuery(orderBy: string) {
|
|
47
57
|
* const { ref } = db.dynamic
|
|
48
58
|
*
|
|
59
|
+
* // Validate against allowed columns
|
|
60
|
+
* const allowedColumns = ['first_name', 'last_name', 'created_at']
|
|
61
|
+
* if (!allowedColumns.includes(orderBy)) {
|
|
62
|
+
* throw new Error('Invalid order column')
|
|
63
|
+
* }
|
|
64
|
+
*
|
|
49
65
|
* return await db
|
|
50
66
|
* .selectFrom('person')
|
|
51
67
|
* .select('person.first_name as fn')
|
|
@@ -92,6 +108,10 @@ export declare class DynamicModule<DB> {
|
|
|
92
108
|
*
|
|
93
109
|
* The type `T` is allowed to be a union of multiple tables.
|
|
94
110
|
*
|
|
111
|
+
* SECURITY: This method now includes built-in validation to prevent SQL injection
|
|
112
|
+
* through malicious table references. However, you should still validate that
|
|
113
|
+
* the table reference is from a trusted source or whitelist of allowed tables.
|
|
114
|
+
*
|
|
95
115
|
* <!-- siteExample("select", "Generic find query", 130) -->
|
|
96
116
|
*
|
|
97
117
|
* A generic type-safe helper function for finding a row by a column value:
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/// <reference types="./dynamic.d.ts" />
|
|
2
2
|
import { DynamicReferenceBuilder } from './dynamic-reference-builder.js';
|
|
3
3
|
import { DynamicTableBuilder } from './dynamic-table-builder.js';
|
|
4
|
+
import { validateColumnReference, validateTableReference } from '../util/security-validator.js';
|
|
4
5
|
export class DynamicModule {
|
|
5
6
|
/**
|
|
6
7
|
* Creates a dynamic reference to a column that is not know at compile time.
|
|
@@ -18,6 +19,10 @@ export class DynamicModule {
|
|
|
18
19
|
* create an SQL injection vulnerability. Always __always__ validate the user
|
|
19
20
|
* input before passing it to this method.
|
|
20
21
|
*
|
|
22
|
+
* SECURITY: This method now includes built-in validation to prevent SQL injection
|
|
23
|
+
* through malicious column references. However, you should still validate that
|
|
24
|
+
* the column reference is from a trusted source or whitelist of allowed columns.
|
|
25
|
+
*
|
|
21
26
|
* There are couple of examples below for some use cases, but you can pass
|
|
22
27
|
* `ref` to other methods as well. If the types allow you to pass a `ref`
|
|
23
28
|
* value to some place, it should work.
|
|
@@ -30,6 +35,12 @@ export class DynamicModule {
|
|
|
30
35
|
* async function someQuery(filterColumn: string, filterValue: string) {
|
|
31
36
|
* const { ref } = db.dynamic
|
|
32
37
|
*
|
|
38
|
+
* // Validate input against a whitelist of allowed columns
|
|
39
|
+
* const allowedColumns = ['first_name', 'last_name', 'email']
|
|
40
|
+
* if (!allowedColumns.includes(filterColumn)) {
|
|
41
|
+
* throw new Error('Invalid column')
|
|
42
|
+
* }
|
|
43
|
+
*
|
|
33
44
|
* return await db
|
|
34
45
|
* .selectFrom('person')
|
|
35
46
|
* .selectAll()
|
|
@@ -47,6 +58,12 @@ export class DynamicModule {
|
|
|
47
58
|
* async function someQuery(orderBy: string) {
|
|
48
59
|
* const { ref } = db.dynamic
|
|
49
60
|
*
|
|
61
|
+
* // Validate against allowed columns
|
|
62
|
+
* const allowedColumns = ['first_name', 'last_name', 'created_at']
|
|
63
|
+
* if (!allowedColumns.includes(orderBy)) {
|
|
64
|
+
* throw new Error('Invalid order column')
|
|
65
|
+
* }
|
|
66
|
+
*
|
|
50
67
|
* return await db
|
|
51
68
|
* .selectFrom('person')
|
|
52
69
|
* .select('person.first_name as fn')
|
|
@@ -88,6 +105,8 @@ export class DynamicModule {
|
|
|
88
105
|
* ```
|
|
89
106
|
*/
|
|
90
107
|
ref(reference) {
|
|
108
|
+
// Security validation to prevent SQL injection
|
|
109
|
+
validateColumnReference(reference);
|
|
91
110
|
return new DynamicReferenceBuilder(reference);
|
|
92
111
|
}
|
|
93
112
|
/**
|
|
@@ -95,6 +114,10 @@ export class DynamicModule {
|
|
|
95
114
|
*
|
|
96
115
|
* The type `T` is allowed to be a union of multiple tables.
|
|
97
116
|
*
|
|
117
|
+
* SECURITY: This method now includes built-in validation to prevent SQL injection
|
|
118
|
+
* through malicious table references. However, you should still validate that
|
|
119
|
+
* the table reference is from a trusted source or whitelist of allowed tables.
|
|
120
|
+
*
|
|
98
121
|
* <!-- siteExample("select", "Generic find query", 130) -->
|
|
99
122
|
*
|
|
100
123
|
* A generic type-safe helper function for finding a row by a column value:
|
|
@@ -124,6 +147,8 @@ export class DynamicModule {
|
|
|
124
147
|
* ```
|
|
125
148
|
*/
|
|
126
149
|
table(table) {
|
|
150
|
+
// Security validation to prevent SQL injection
|
|
151
|
+
validateTableReference(table);
|
|
127
152
|
return new DynamicTableBuilder(table);
|
|
128
153
|
}
|
|
129
154
|
}
|