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.
Files changed (137) hide show
  1. package/LICENSE +89 -21
  2. package/README.md +195 -578
  3. package/dist/cjs/cli/commands/analyze.js +4 -1
  4. package/dist/cjs/cli/commands/generate.js +48 -8
  5. package/dist/cjs/cli/commands/init.js +54 -14
  6. package/dist/cjs/cli/commands/inspect.js +10 -3
  7. package/dist/cjs/cli/commands/migrate.js +38 -2
  8. package/dist/cjs/cli/commands/optimize.js +4 -1
  9. package/dist/cjs/cli/commands/status.js +41 -5
  10. package/dist/cjs/cli/commands/watch.js +4 -1
  11. package/dist/cjs/cli/index.js +4 -1
  12. package/dist/cjs/dialect/database-introspector.js +16 -21
  13. package/dist/cjs/dialect/sqlite/sqlite-introspector.js +13 -24
  14. package/dist/cjs/dynamic/dynamic.d.ts +20 -0
  15. package/dist/cjs/dynamic/dynamic.js +25 -0
  16. package/dist/cjs/edge-runtime/edge-config.d.ts +125 -0
  17. package/dist/cjs/edge-runtime/edge-config.js +323 -0
  18. package/dist/cjs/errors/NoormError.d.ts +27 -4
  19. package/dist/cjs/errors/NoormError.js +142 -21
  20. package/dist/cjs/logging/logger.d.ts +7 -2
  21. package/dist/cjs/logging/logger.js +21 -7
  22. package/dist/cjs/noormme.d.ts +12 -8
  23. package/dist/cjs/noormme.js +133 -61
  24. package/dist/cjs/operation-node/column-node.js +4 -0
  25. package/dist/cjs/operation-node/identifier-node.js +4 -0
  26. package/dist/cjs/operation-node/table-node.js +7 -0
  27. package/dist/cjs/parser/reference-parser.js +5 -0
  28. package/dist/cjs/parser/table-parser.js +2 -0
  29. package/dist/cjs/performance/index.d.ts +44 -0
  30. package/dist/cjs/performance/index.js +64 -0
  31. package/dist/cjs/performance/query-optimizer.d.ts +134 -0
  32. package/dist/cjs/performance/query-optimizer.js +391 -0
  33. package/dist/cjs/performance/services/cache-service.d.ts +177 -0
  34. package/dist/cjs/performance/services/cache-service.js +415 -0
  35. package/dist/cjs/performance/services/connection-factory.d.ts +198 -0
  36. package/dist/cjs/performance/services/connection-factory.js +498 -0
  37. package/dist/cjs/performance/services/metrics-collector.d.ts +162 -0
  38. package/dist/cjs/performance/services/metrics-collector.js +406 -0
  39. package/dist/cjs/performance/utils/query-parser.d.ts +123 -0
  40. package/dist/cjs/performance/utils/query-parser.js +295 -0
  41. package/dist/cjs/raw-builder/sql.d.ts +73 -26
  42. package/dist/cjs/raw-builder/sql.js +9 -0
  43. package/dist/cjs/repository/repository-factory.d.ts +10 -42
  44. package/dist/cjs/repository/repository-factory.js +276 -394
  45. package/dist/cjs/schema/core/coordinators/schema-discovery.coordinator.js +6 -4
  46. package/dist/cjs/schema/core/factories/discovery-factory.js +5 -5
  47. package/dist/cjs/schema/core/utils/name-generator.js +34 -2
  48. package/dist/cjs/schema/core/utils/type-mapper.d.ts +19 -14
  49. package/dist/cjs/schema/core/utils/type-mapper.js +4 -7
  50. package/dist/cjs/schema/dialects/sqlite/discovery/sqlite-constraint-discovery.js +3 -2
  51. package/dist/cjs/schema/dialects/sqlite/sqlite-discovery.coordinator.d.ts +2 -0
  52. package/dist/cjs/schema/dialects/sqlite/sqlite-discovery.coordinator.js +19 -5
  53. package/dist/cjs/schema/test/dialect-capabilities.test.js +6 -0
  54. package/dist/cjs/schema/test/error-handling.test.js +52 -33
  55. package/dist/cjs/schema/test/integration.test.js +51 -5
  56. package/dist/cjs/schema/test/sqlite-discovery-coordinator.test.js +8 -4
  57. package/dist/cjs/sqlite-migration/index.js +35 -2
  58. package/dist/cjs/testing/test-utils.d.ts +5 -0
  59. package/dist/cjs/testing/test-utils.js +66 -6
  60. package/dist/cjs/types/index.d.ts +78 -13
  61. package/dist/cjs/types/index.js +23 -0
  62. package/dist/cjs/types/type-generator.d.ts +8 -0
  63. package/dist/cjs/types/type-generator.js +86 -17
  64. package/dist/cjs/util/safe-sql-helpers.d.ts +124 -0
  65. package/dist/cjs/util/safe-sql-helpers.js +221 -0
  66. package/dist/cjs/util/security-validator.d.ts +44 -0
  67. package/dist/cjs/util/security-validator.js +256 -0
  68. package/dist/cjs/util/security.d.ts +60 -0
  69. package/dist/cjs/util/security.js +137 -0
  70. package/dist/cjs/watch/schema-watcher.js +26 -7
  71. package/dist/esm/cli/commands/generate.js +10 -6
  72. package/dist/esm/cli/commands/init.js +15 -11
  73. package/dist/esm/cli/commands/inspect.js +6 -2
  74. package/dist/esm/cli/commands/status.js +3 -3
  75. package/dist/esm/dialect/database-introspector.js +16 -21
  76. package/dist/esm/dialect/sqlite/sqlite-introspector.js +13 -24
  77. package/dist/esm/dynamic/dynamic.d.ts +20 -0
  78. package/dist/esm/dynamic/dynamic.js +25 -0
  79. package/dist/esm/edge-runtime/edge-config.d.ts +125 -0
  80. package/dist/esm/edge-runtime/edge-config.js +281 -0
  81. package/dist/esm/errors/NoormError.d.ts +27 -4
  82. package/dist/esm/errors/NoormError.js +134 -18
  83. package/dist/esm/logging/logger.d.ts +7 -2
  84. package/dist/esm/logging/logger.js +21 -7
  85. package/dist/esm/noormme.d.ts +12 -8
  86. package/dist/esm/noormme.js +130 -61
  87. package/dist/esm/operation-node/column-node.js +4 -0
  88. package/dist/esm/operation-node/identifier-node.js +4 -0
  89. package/dist/esm/operation-node/table-node.js +7 -0
  90. package/dist/esm/parser/reference-parser.js +5 -0
  91. package/dist/esm/parser/table-parser.js +2 -0
  92. package/dist/esm/performance/index.d.ts +44 -0
  93. package/dist/esm/performance/index.js +48 -0
  94. package/dist/esm/performance/query-optimizer.d.ts +134 -0
  95. package/dist/esm/performance/query-optimizer.js +387 -0
  96. package/dist/esm/performance/services/cache-service.d.ts +177 -0
  97. package/dist/esm/performance/services/cache-service.js +410 -0
  98. package/dist/esm/performance/services/connection-factory.d.ts +198 -0
  99. package/dist/esm/performance/services/connection-factory.js +493 -0
  100. package/dist/esm/performance/services/metrics-collector.d.ts +162 -0
  101. package/dist/esm/performance/services/metrics-collector.js +402 -0
  102. package/dist/esm/performance/utils/query-parser.d.ts +123 -0
  103. package/dist/esm/performance/utils/query-parser.js +292 -0
  104. package/dist/esm/raw-builder/sql.d.ts +73 -26
  105. package/dist/esm/raw-builder/sql.js +9 -0
  106. package/dist/esm/repository/repository-factory.d.ts +10 -42
  107. package/dist/esm/repository/repository-factory.js +277 -395
  108. package/dist/esm/schema/core/coordinators/schema-discovery.coordinator.js +6 -4
  109. package/dist/esm/schema/core/factories/discovery-factory.js +5 -5
  110. package/dist/esm/schema/core/utils/name-generator.js +34 -2
  111. package/dist/esm/schema/core/utils/type-mapper.d.ts +19 -14
  112. package/dist/esm/schema/core/utils/type-mapper.js +4 -7
  113. package/dist/esm/schema/dialects/sqlite/discovery/sqlite-constraint-discovery.js +3 -2
  114. package/dist/esm/schema/dialects/sqlite/sqlite-discovery.coordinator.d.ts +2 -0
  115. package/dist/esm/schema/dialects/sqlite/sqlite-discovery.coordinator.js +19 -5
  116. package/dist/esm/schema/test/dialect-capabilities.test.js +6 -0
  117. package/dist/esm/schema/test/error-handling.test.js +52 -33
  118. package/dist/esm/schema/test/integration.test.js +18 -5
  119. package/dist/esm/schema/test/sqlite-discovery-coordinator.test.js +8 -4
  120. package/dist/esm/testing/test-utils.d.ts +5 -0
  121. package/dist/esm/testing/test-utils.js +66 -6
  122. package/dist/esm/types/index.d.ts +78 -13
  123. package/dist/esm/types/index.js +22 -1
  124. package/dist/esm/types/type-generator.d.ts +8 -0
  125. package/dist/esm/types/type-generator.js +86 -17
  126. package/dist/esm/util/safe-sql-helpers.d.ts +124 -0
  127. package/dist/esm/util/safe-sql-helpers.js +209 -0
  128. package/dist/esm/util/security-validator.d.ts +44 -0
  129. package/dist/esm/util/security-validator.js +246 -0
  130. package/dist/esm/util/security.d.ts +60 -0
  131. package/dist/esm/util/security.js +114 -0
  132. package/dist/esm/watch/schema-watcher.js +26 -7
  133. package/package.json +1 -1
  134. package/dist/cjs/performance/query-analyzer.d.ts +0 -89
  135. package/dist/cjs/performance/query-analyzer.js +0 -263
  136. package/dist/esm/performance/query-analyzer.d.ts +0 -89
  137. 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: process.env.NODE_ENV === 'development',
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
- const initialSchema = await this.getCurrentSchema();
44
- this.lastSchemaHash = this.hashSchema(initialSchema);
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.logger.info('Schema watcher stopped');
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
- // Initialize NOORMME with database path
10
- const databasePath = options.database || process.env.DATABASE_PATH || './database.sqlite';
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, any>';
191
+ return 'Record<string, unknown>';
188
192
  }
189
193
  // Array types
190
194
  if (type.includes('array') || type.includes('[]')) {
191
- return 'any[]';
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 'any';
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) : 'any';
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
- // Get database path
11
- const databasePath = options.database || './database.sqlite';
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
- export const db = new NOORMME({
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 db.initialize()
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 db.getRepository<T>(tableName)
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 db.getKysely()
175
+ return database.getKysely()
172
176
  }
173
177
 
174
178
  // Performance monitoring
175
179
  export async function getPerformanceMetrics() {
176
- return await db.getSQLitePerformanceMetrics()
180
+ return await database.getSQLitePerformanceMetrics()
177
181
  }
178
182
 
179
183
  // Get optimization recommendations
180
184
  export async function getOptimizationRecommendations() {
181
- return await db.getSQLiteIndexRecommendations()
185
+ return await database.getSQLiteIndexRecommendations()
182
186
  }
183
187
 
184
188
  // Apply optimizations
185
189
  export async function applyOptimizations() {
186
- return await db.applySQLiteOptimizations()
190
+ return await database.applySQLiteOptimizations()
187
191
  }
188
192
 
189
193
  // Close database connections
190
194
  export async function closeDatabase() {
191
- await db.close()
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
- // Initialize NOORMME with database path
8
- const databasePath = options.database || process.env.DATABASE_PATH || './database.sqlite';
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.performance?.enableAutoOptimization ?? true,
213
+ autoOptimization: config.automation?.enableAutoOptimization ?? true,
214
214
  queryAnalysis: config.performance?.enableQueryOptimization ?? true,
215
- autoIndexing: config.performance?.enableAutoIndexing ?? true,
216
- schemaMonitoring: config.watch?.enabled ?? false
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
- try {
16
- // SQLite
17
- const sqliteTables = await this.db
18
- .selectFrom('sqlite_master')
19
- .select('name')
20
- .where('type', '=', 'table')
21
- .where('name', 'not like', 'sqlite_%')
22
- .execute();
23
- return sqliteTables.map(t => ({
24
- name: t.name
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 pragma_table_info
38
- const result = await sql `pragma_table_info(${sql.lit(tableName)})`.execute(this.db);
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
- throw new Error(`Unable to introspect columns for table '${tableName}'. SQLite database required.`);
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 pragma_foreign_key_list
67
- const result = await sql `pragma_foreign_key_list(${sql.lit(tableName)})`.execute(this.db);
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
- const tableMetadata = await this.#db
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 row of tableMetadata) {
61
- columnsByTable[row.table] ??= [];
62
- columnsByTable[row.table].push(row);
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 pragma_table_info
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 pragma_index_list
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 pragma_foreign_key_list
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
  }