te.js 2.0.3 → 2.1.1
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/README.md +197 -187
- package/auto-docs/analysis/handler-analyzer.js +58 -58
- package/auto-docs/analysis/source-resolver.js +101 -101
- package/auto-docs/constants.js +37 -37
- package/auto-docs/docs-llm/index.js +7 -0
- package/auto-docs/{llm → docs-llm}/prompts.js +222 -222
- package/auto-docs/{llm → docs-llm}/provider.js +132 -187
- package/auto-docs/index.js +146 -146
- package/auto-docs/openapi/endpoint-processor.js +277 -277
- package/auto-docs/openapi/generator.js +107 -107
- package/auto-docs/openapi/level3.js +131 -131
- package/auto-docs/openapi/spec-builders.js +244 -244
- package/auto-docs/ui/docs-ui.js +186 -186
- package/auto-docs/utils/logger.js +17 -17
- package/auto-docs/utils/strip-usage.js +10 -10
- package/cli/docs-command.js +315 -315
- package/cli/fly-command.js +71 -71
- package/cli/index.js +56 -56
- package/database/index.js +165 -165
- package/database/mongodb.js +146 -146
- package/database/redis.js +201 -201
- package/docs/README.md +36 -36
- package/docs/ammo.md +362 -362
- package/docs/api-reference.md +490 -489
- package/docs/auto-docs.md +216 -215
- package/docs/cli.md +152 -152
- package/docs/configuration.md +275 -233
- package/docs/database.md +390 -391
- package/docs/error-handling.md +438 -417
- package/docs/file-uploads.md +333 -334
- package/docs/getting-started.md +214 -215
- package/docs/middleware.md +355 -356
- package/docs/rate-limiting.md +393 -394
- package/docs/routing.md +302 -302
- package/package.json +62 -62
- package/rate-limit/algorithms/fixed-window.js +141 -141
- package/rate-limit/algorithms/sliding-window.js +147 -147
- package/rate-limit/algorithms/token-bucket.js +115 -115
- package/rate-limit/base.js +165 -165
- package/rate-limit/index.js +147 -147
- package/rate-limit/storage/base.js +104 -104
- package/rate-limit/storage/memory.js +101 -101
- package/rate-limit/storage/redis.js +88 -88
- package/server/ammo/body-parser.js +220 -220
- package/server/ammo/dispatch-helper.js +103 -103
- package/server/ammo/enhancer.js +57 -57
- package/server/ammo.js +454 -356
- package/server/endpoint.js +97 -74
- package/server/error.js +9 -9
- package/server/errors/code-context.js +125 -0
- package/server/errors/llm-error-service.js +140 -0
- package/server/files/helper.js +33 -33
- package/server/files/uploader.js +143 -143
- package/server/handler.js +158 -113
- package/server/target.js +185 -175
- package/server/targets/middleware-validator.js +22 -22
- package/server/targets/path-validator.js +21 -21
- package/server/targets/registry.js +160 -160
- package/server/targets/shoot-validator.js +21 -21
- package/te.js +428 -363
- package/utils/auto-register.js +17 -17
- package/utils/configuration.js +64 -64
- package/utils/errors-llm-config.js +84 -0
- package/utils/request-logger.js +43 -43
- package/utils/status-codes.js +82 -82
- package/utils/tejas-entrypoint-html.js +18 -18
- package/auto-docs/llm/index.js +0 -6
- package/auto-docs/llm/parse.js +0 -88
package/cli/fly-command.js
CHANGED
|
@@ -1,71 +1,71 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `tejas fly` — start the Tejas server by running the user's entry point.
|
|
3
|
-
* Entry point is resolved in order: CLI arg → tejas.config.json "entry" → package.json "main" → index.js → app.js → server.js
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import path from 'node:path';
|
|
7
|
-
import fs from 'node:fs';
|
|
8
|
-
import { spawn } from 'node:child_process';
|
|
9
|
-
import { loadConfigFile } from '../utils/configuration.js';
|
|
10
|
-
|
|
11
|
-
const CONVENTION_FILES = ['index.js', 'app.js', 'server.js'];
|
|
12
|
-
|
|
13
|
-
function resolveEntryPoint(cliArg) {
|
|
14
|
-
const cwd = process.cwd();
|
|
15
|
-
|
|
16
|
-
if (cliArg) {
|
|
17
|
-
const candidate = path.isAbsolute(cliArg) ? cliArg : path.join(cwd, cliArg);
|
|
18
|
-
if (fs.existsSync(candidate)) return candidate;
|
|
19
|
-
throw new Error(`Entry file not found: ${cliArg}`);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
const config = loadConfigFile();
|
|
23
|
-
if (config.entry) {
|
|
24
|
-
const candidate = path.join(cwd, config.entry);
|
|
25
|
-
if (fs.existsSync(candidate)) return candidate;
|
|
26
|
-
throw new Error(`Entry file from tejas.config.json not found: ${config.entry}`);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
try {
|
|
30
|
-
const pkgPath = path.join(cwd, 'package.json');
|
|
31
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
32
|
-
if (pkg.main) {
|
|
33
|
-
const candidate = path.join(cwd, pkg.main);
|
|
34
|
-
if (fs.existsSync(candidate)) return candidate;
|
|
35
|
-
throw new Error(`Entry file from package.json "main" not found: ${pkg.main}`);
|
|
36
|
-
}
|
|
37
|
-
} catch (err) {
|
|
38
|
-
if (err.code === 'ENOENT') {
|
|
39
|
-
// no package.json, continue to convention
|
|
40
|
-
} else {
|
|
41
|
-
throw err;
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
for (const name of CONVENTION_FILES) {
|
|
46
|
-
const candidate = path.join(cwd, name);
|
|
47
|
-
if (fs.existsSync(candidate)) return candidate;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
throw new Error(
|
|
51
|
-
`Could not resolve entry point. Set "entry" in tejas.config.json, "main" in package.json, pass a file (tejas fly <file>), or add index.js, app.js, or server.js in ${cwd}`,
|
|
52
|
-
);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Resolves the entry point and spawns the server process. Exits with the child's exit code.
|
|
57
|
-
*/
|
|
58
|
-
export function runFlyCommand() {
|
|
59
|
-
const cliArg = process.argv[3]; // tejas fly [file]
|
|
60
|
-
const entryFile = resolveEntryPoint(cliArg);
|
|
61
|
-
|
|
62
|
-
const child = spawn(process.execPath, [entryFile], {
|
|
63
|
-
stdio: 'inherit',
|
|
64
|
-
cwd: process.cwd(),
|
|
65
|
-
env: process.env,
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
child.on('exit', (code, signal) => {
|
|
69
|
-
process.exit(code ?? (signal ? 1 : 0));
|
|
70
|
-
});
|
|
71
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* `tejas fly` — start the Tejas server by running the user's entry point.
|
|
3
|
+
* Entry point is resolved in order: CLI arg → tejas.config.json "entry" → package.json "main" → index.js → app.js → server.js
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import path from 'node:path';
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import { spawn } from 'node:child_process';
|
|
9
|
+
import { loadConfigFile } from '../utils/configuration.js';
|
|
10
|
+
|
|
11
|
+
const CONVENTION_FILES = ['index.js', 'app.js', 'server.js'];
|
|
12
|
+
|
|
13
|
+
function resolveEntryPoint(cliArg) {
|
|
14
|
+
const cwd = process.cwd();
|
|
15
|
+
|
|
16
|
+
if (cliArg) {
|
|
17
|
+
const candidate = path.isAbsolute(cliArg) ? cliArg : path.join(cwd, cliArg);
|
|
18
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
19
|
+
throw new Error(`Entry file not found: ${cliArg}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const config = loadConfigFile();
|
|
23
|
+
if (config.entry) {
|
|
24
|
+
const candidate = path.join(cwd, config.entry);
|
|
25
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
26
|
+
throw new Error(`Entry file from tejas.config.json not found: ${config.entry}`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
31
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
32
|
+
if (pkg.main) {
|
|
33
|
+
const candidate = path.join(cwd, pkg.main);
|
|
34
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
35
|
+
throw new Error(`Entry file from package.json "main" not found: ${pkg.main}`);
|
|
36
|
+
}
|
|
37
|
+
} catch (err) {
|
|
38
|
+
if (err.code === 'ENOENT') {
|
|
39
|
+
// no package.json, continue to convention
|
|
40
|
+
} else {
|
|
41
|
+
throw err;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (const name of CONVENTION_FILES) {
|
|
46
|
+
const candidate = path.join(cwd, name);
|
|
47
|
+
if (fs.existsSync(candidate)) return candidate;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
throw new Error(
|
|
51
|
+
`Could not resolve entry point. Set "entry" in tejas.config.json, "main" in package.json, pass a file (tejas fly <file>), or add index.js, app.js, or server.js in ${cwd}`,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Resolves the entry point and spawns the server process. Exits with the child's exit code.
|
|
57
|
+
*/
|
|
58
|
+
export function runFlyCommand() {
|
|
59
|
+
const cliArg = process.argv[3]; // tejas fly [file]
|
|
60
|
+
const entryFile = resolveEntryPoint(cliArg);
|
|
61
|
+
|
|
62
|
+
const child = spawn(process.execPath, [entryFile], {
|
|
63
|
+
stdio: 'inherit',
|
|
64
|
+
cwd: process.cwd(),
|
|
65
|
+
env: process.env,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
child.on('exit', (code, signal) => {
|
|
69
|
+
process.exit(code ?? (signal ? 1 : 0));
|
|
70
|
+
});
|
|
71
|
+
}
|
package/cli/index.js
CHANGED
|
@@ -1,57 +1,57 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* CLI entry point for te.js (tejas).
|
|
5
|
-
* Usage: tejas fly [file] | tejas generate:docs [--ci] | tejas docs:on-push
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { runDocsCommand, runDocsCommandCI, runDocsOnPush } from './docs-command.js';
|
|
9
|
-
import { runFlyCommand } from './fly-command.js';
|
|
10
|
-
|
|
11
|
-
const command = process.argv[2];
|
|
12
|
-
const ciFlag = process.argv.includes('--ci');
|
|
13
|
-
|
|
14
|
-
if (command === 'fly') {
|
|
15
|
-
try {
|
|
16
|
-
runFlyCommand();
|
|
17
|
-
} catch (err) {
|
|
18
|
-
console.error(err?.message ?? err);
|
|
19
|
-
process.exit(1);
|
|
20
|
-
}
|
|
21
|
-
} else if (command === 'docs:on-push') {
|
|
22
|
-
runDocsOnPush().catch((err) => {
|
|
23
|
-
console.error(err?.message ?? err);
|
|
24
|
-
process.exit(1);
|
|
25
|
-
});
|
|
26
|
-
} else if (command === 'generate:docs') {
|
|
27
|
-
if (ciFlag) {
|
|
28
|
-
runDocsCommandCI().catch((err) => {
|
|
29
|
-
console.error(err?.message ?? err);
|
|
30
|
-
process.exit(1);
|
|
31
|
-
});
|
|
32
|
-
} else {
|
|
33
|
-
runDocsCommand().catch((err) => {
|
|
34
|
-
console.error(err?.message ?? err);
|
|
35
|
-
process.exit(1);
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
} else {
|
|
39
|
-
console.log(`
|
|
40
|
-
tejas - te.js framework CLI
|
|
41
|
-
|
|
42
|
-
Usage: tejas <command> [options]
|
|
43
|
-
|
|
44
|
-
Commands:
|
|
45
|
-
fly [file] Start the Tejas server
|
|
46
|
-
generate:docs [--ci] OpenAPI documentation generator (interactive or CI mode)
|
|
47
|
-
docs:on-push Generate docs when pushing to production branch (use in pre-push hook)
|
|
48
|
-
|
|
49
|
-
Examples:
|
|
50
|
-
tejas fly
|
|
51
|
-
tejas fly index.js
|
|
52
|
-
tejas generate:docs
|
|
53
|
-
tejas generate:docs --ci
|
|
54
|
-
tejas docs:on-push
|
|
55
|
-
`);
|
|
56
|
-
process.exit(command ? 1 : 0);
|
|
57
|
-
}
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CLI entry point for te.js (tejas).
|
|
5
|
+
* Usage: tejas fly [file] | tejas generate:docs [--ci] | tejas docs:on-push
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { runDocsCommand, runDocsCommandCI, runDocsOnPush } from './docs-command.js';
|
|
9
|
+
import { runFlyCommand } from './fly-command.js';
|
|
10
|
+
|
|
11
|
+
const command = process.argv[2];
|
|
12
|
+
const ciFlag = process.argv.includes('--ci');
|
|
13
|
+
|
|
14
|
+
if (command === 'fly') {
|
|
15
|
+
try {
|
|
16
|
+
runFlyCommand();
|
|
17
|
+
} catch (err) {
|
|
18
|
+
console.error(err?.message ?? err);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
} else if (command === 'docs:on-push') {
|
|
22
|
+
runDocsOnPush().catch((err) => {
|
|
23
|
+
console.error(err?.message ?? err);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
});
|
|
26
|
+
} else if (command === 'generate:docs') {
|
|
27
|
+
if (ciFlag) {
|
|
28
|
+
runDocsCommandCI().catch((err) => {
|
|
29
|
+
console.error(err?.message ?? err);
|
|
30
|
+
process.exit(1);
|
|
31
|
+
});
|
|
32
|
+
} else {
|
|
33
|
+
runDocsCommand().catch((err) => {
|
|
34
|
+
console.error(err?.message ?? err);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
} else {
|
|
39
|
+
console.log(`
|
|
40
|
+
tejas - te.js framework CLI
|
|
41
|
+
|
|
42
|
+
Usage: tejas <command> [options]
|
|
43
|
+
|
|
44
|
+
Commands:
|
|
45
|
+
fly [file] Start the Tejas server
|
|
46
|
+
generate:docs [--ci] OpenAPI documentation generator (interactive or CI mode)
|
|
47
|
+
docs:on-push Generate docs when pushing to production branch (use in pre-push hook)
|
|
48
|
+
|
|
49
|
+
Examples:
|
|
50
|
+
tejas fly
|
|
51
|
+
tejas fly index.js
|
|
52
|
+
tejas generate:docs
|
|
53
|
+
tejas generate:docs --ci
|
|
54
|
+
tejas docs:on-push
|
|
55
|
+
`);
|
|
56
|
+
process.exit(command ? 1 : 0);
|
|
57
|
+
}
|
package/database/index.js
CHANGED
|
@@ -1,165 +1,165 @@
|
|
|
1
|
-
import redis from './redis.js';
|
|
2
|
-
import mongodb from './mongodb.js';
|
|
3
|
-
import TejError from '../server/error.js';
|
|
4
|
-
import TejLogger from 'tej-logger';
|
|
5
|
-
|
|
6
|
-
const logger = new TejLogger('DatabaseManager');
|
|
7
|
-
|
|
8
|
-
class DatabaseManager {
|
|
9
|
-
static #instance = null;
|
|
10
|
-
static #isInitializing = false;
|
|
11
|
-
|
|
12
|
-
// Enhanced connection tracking with metadata
|
|
13
|
-
#connections = new Map();
|
|
14
|
-
#initializingConnections = new Map();
|
|
15
|
-
|
|
16
|
-
// Helper method for sleeping
|
|
17
|
-
async #sleep(ms) {
|
|
18
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
constructor() {
|
|
22
|
-
if (DatabaseManager.#instance) {
|
|
23
|
-
return DatabaseManager.#instance;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (!DatabaseManager.#isInitializing) {
|
|
27
|
-
throw new TejError(
|
|
28
|
-
500,
|
|
29
|
-
'Use DatabaseManager.getInstance() to get the instance',
|
|
30
|
-
);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
DatabaseManager.#isInitializing = false;
|
|
34
|
-
DatabaseManager.#instance = this;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
static getInstance() {
|
|
38
|
-
if (!DatabaseManager.#instance) {
|
|
39
|
-
DatabaseManager.#isInitializing = true;
|
|
40
|
-
DatabaseManager.#instance = new DatabaseManager();
|
|
41
|
-
}
|
|
42
|
-
return DatabaseManager.#instance;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async initializeConnection(dbType, config) {
|
|
46
|
-
const key = dbType.toLowerCase();
|
|
47
|
-
|
|
48
|
-
// If a connection already exists for this config, return it
|
|
49
|
-
if (this.#connections.has(key)) {
|
|
50
|
-
return this.#connections.get(key).client;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Set initializing flag
|
|
54
|
-
this.#initializingConnections.set(key, true);
|
|
55
|
-
|
|
56
|
-
let client;
|
|
57
|
-
try {
|
|
58
|
-
switch (key) {
|
|
59
|
-
case 'redis':
|
|
60
|
-
client = await redis.createConnection({
|
|
61
|
-
isCluster: config.isCluster || false,
|
|
62
|
-
options: config || {},
|
|
63
|
-
});
|
|
64
|
-
break;
|
|
65
|
-
case 'mongodb':
|
|
66
|
-
client = await mongodb.createConnection(config);
|
|
67
|
-
break;
|
|
68
|
-
default:
|
|
69
|
-
throw new TejError(400, `Unsupported database type: ${dbType}`);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
this.#connections.set(key, {
|
|
73
|
-
type: dbType,
|
|
74
|
-
client,
|
|
75
|
-
config,
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
// Clear initializing flag
|
|
79
|
-
this.#initializingConnections.delete(key);
|
|
80
|
-
|
|
81
|
-
return client;
|
|
82
|
-
} catch (error) {
|
|
83
|
-
// Clear initializing flag on error
|
|
84
|
-
this.#initializingConnections.delete(key);
|
|
85
|
-
logger.error(`Failed to initialize ${dbType} connection:`, error);
|
|
86
|
-
throw error;
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
getConnection(dbType) {
|
|
91
|
-
const key = dbType.toLowerCase();
|
|
92
|
-
const connection = this.#connections.get(key);
|
|
93
|
-
if (!connection) {
|
|
94
|
-
throw new TejError(
|
|
95
|
-
404,
|
|
96
|
-
`No connection found for ${dbType} with given config`,
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
return connection.client;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
async closeConnection(dbType, config) {
|
|
103
|
-
const key = dbType.toLowerCase();
|
|
104
|
-
if (!this.#connections.has(key)) {
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
try {
|
|
109
|
-
const connection = this.#connections.get(key);
|
|
110
|
-
switch (key) {
|
|
111
|
-
case 'redis':
|
|
112
|
-
await redis.closeConnection(connection.client);
|
|
113
|
-
break;
|
|
114
|
-
case 'mongodb':
|
|
115
|
-
await mongodb.closeConnection(connection.client);
|
|
116
|
-
break;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
this.#connections.delete(key);
|
|
120
|
-
} catch (error) {
|
|
121
|
-
logger.error(`Error closing ${dbType} connection:`, error);
|
|
122
|
-
throw error;
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Close all database connections
|
|
128
|
-
* @returns {Promise<void>}
|
|
129
|
-
*/
|
|
130
|
-
async closeAllConnections() {
|
|
131
|
-
const closePromises = [];
|
|
132
|
-
for (const [key, connection] of this.#connections) {
|
|
133
|
-
closePromises.push(
|
|
134
|
-
this.closeConnection(connection.type, connection.config),
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
await Promise.all(closePromises);
|
|
138
|
-
this.#connections.clear();
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Get all active connections
|
|
143
|
-
* @returns {Map<string, {type: string, client: any, config: Object}>}
|
|
144
|
-
*/
|
|
145
|
-
getActiveConnections() {
|
|
146
|
-
return new Map(this.#connections);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Check if a connection exists or is being initialized
|
|
151
|
-
* @param {string} dbType - Type of database
|
|
152
|
-
* @param {Object} config - Database configuration
|
|
153
|
-
* @returns {{ exists: boolean, initializing: boolean }}
|
|
154
|
-
*/
|
|
155
|
-
hasConnection(dbType, config) {
|
|
156
|
-
const key = dbType.toLowerCase();
|
|
157
|
-
return {
|
|
158
|
-
exists: this.#connections.has(key),
|
|
159
|
-
initializing: this.#initializingConnections.has(key),
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
const dbManager = DatabaseManager.getInstance();
|
|
165
|
-
export default dbManager;
|
|
1
|
+
import redis from './redis.js';
|
|
2
|
+
import mongodb from './mongodb.js';
|
|
3
|
+
import TejError from '../server/error.js';
|
|
4
|
+
import TejLogger from 'tej-logger';
|
|
5
|
+
|
|
6
|
+
const logger = new TejLogger('DatabaseManager');
|
|
7
|
+
|
|
8
|
+
class DatabaseManager {
|
|
9
|
+
static #instance = null;
|
|
10
|
+
static #isInitializing = false;
|
|
11
|
+
|
|
12
|
+
// Enhanced connection tracking with metadata
|
|
13
|
+
#connections = new Map();
|
|
14
|
+
#initializingConnections = new Map();
|
|
15
|
+
|
|
16
|
+
// Helper method for sleeping
|
|
17
|
+
async #sleep(ms) {
|
|
18
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
constructor() {
|
|
22
|
+
if (DatabaseManager.#instance) {
|
|
23
|
+
return DatabaseManager.#instance;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!DatabaseManager.#isInitializing) {
|
|
27
|
+
throw new TejError(
|
|
28
|
+
500,
|
|
29
|
+
'Use DatabaseManager.getInstance() to get the instance',
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
DatabaseManager.#isInitializing = false;
|
|
34
|
+
DatabaseManager.#instance = this;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
static getInstance() {
|
|
38
|
+
if (!DatabaseManager.#instance) {
|
|
39
|
+
DatabaseManager.#isInitializing = true;
|
|
40
|
+
DatabaseManager.#instance = new DatabaseManager();
|
|
41
|
+
}
|
|
42
|
+
return DatabaseManager.#instance;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async initializeConnection(dbType, config) {
|
|
46
|
+
const key = dbType.toLowerCase();
|
|
47
|
+
|
|
48
|
+
// If a connection already exists for this config, return it
|
|
49
|
+
if (this.#connections.has(key)) {
|
|
50
|
+
return this.#connections.get(key).client;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Set initializing flag
|
|
54
|
+
this.#initializingConnections.set(key, true);
|
|
55
|
+
|
|
56
|
+
let client;
|
|
57
|
+
try {
|
|
58
|
+
switch (key) {
|
|
59
|
+
case 'redis':
|
|
60
|
+
client = await redis.createConnection({
|
|
61
|
+
isCluster: config.isCluster || false,
|
|
62
|
+
options: config || {},
|
|
63
|
+
});
|
|
64
|
+
break;
|
|
65
|
+
case 'mongodb':
|
|
66
|
+
client = await mongodb.createConnection(config);
|
|
67
|
+
break;
|
|
68
|
+
default:
|
|
69
|
+
throw new TejError(400, `Unsupported database type: ${dbType}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this.#connections.set(key, {
|
|
73
|
+
type: dbType,
|
|
74
|
+
client,
|
|
75
|
+
config,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Clear initializing flag
|
|
79
|
+
this.#initializingConnections.delete(key);
|
|
80
|
+
|
|
81
|
+
return client;
|
|
82
|
+
} catch (error) {
|
|
83
|
+
// Clear initializing flag on error
|
|
84
|
+
this.#initializingConnections.delete(key);
|
|
85
|
+
logger.error(`Failed to initialize ${dbType} connection:`, error);
|
|
86
|
+
throw error;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
getConnection(dbType) {
|
|
91
|
+
const key = dbType.toLowerCase();
|
|
92
|
+
const connection = this.#connections.get(key);
|
|
93
|
+
if (!connection) {
|
|
94
|
+
throw new TejError(
|
|
95
|
+
404,
|
|
96
|
+
`No connection found for ${dbType} with given config`,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
return connection.client;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async closeConnection(dbType, config) {
|
|
103
|
+
const key = dbType.toLowerCase();
|
|
104
|
+
if (!this.#connections.has(key)) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
const connection = this.#connections.get(key);
|
|
110
|
+
switch (key) {
|
|
111
|
+
case 'redis':
|
|
112
|
+
await redis.closeConnection(connection.client);
|
|
113
|
+
break;
|
|
114
|
+
case 'mongodb':
|
|
115
|
+
await mongodb.closeConnection(connection.client);
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
this.#connections.delete(key);
|
|
120
|
+
} catch (error) {
|
|
121
|
+
logger.error(`Error closing ${dbType} connection:`, error);
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Close all database connections
|
|
128
|
+
* @returns {Promise<void>}
|
|
129
|
+
*/
|
|
130
|
+
async closeAllConnections() {
|
|
131
|
+
const closePromises = [];
|
|
132
|
+
for (const [key, connection] of this.#connections) {
|
|
133
|
+
closePromises.push(
|
|
134
|
+
this.closeConnection(connection.type, connection.config),
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
await Promise.all(closePromises);
|
|
138
|
+
this.#connections.clear();
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Get all active connections
|
|
143
|
+
* @returns {Map<string, {type: string, client: any, config: Object}>}
|
|
144
|
+
*/
|
|
145
|
+
getActiveConnections() {
|
|
146
|
+
return new Map(this.#connections);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Check if a connection exists or is being initialized
|
|
151
|
+
* @param {string} dbType - Type of database
|
|
152
|
+
* @param {Object} config - Database configuration
|
|
153
|
+
* @returns {{ exists: boolean, initializing: boolean }}
|
|
154
|
+
*/
|
|
155
|
+
hasConnection(dbType, config) {
|
|
156
|
+
const key = dbType.toLowerCase();
|
|
157
|
+
return {
|
|
158
|
+
exists: this.#connections.has(key),
|
|
159
|
+
initializing: this.#initializingConnections.has(key),
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const dbManager = DatabaseManager.getInstance();
|
|
165
|
+
export default dbManager;
|