Thin MongoDB ODM built for Standard Schema
mongodb
zod
deno
1import { type Db, type MongoClientOptions, MongoClient } from "mongodb";
2import { ConnectionError } from "./errors.ts";
3
4interface Connection {
5 client: MongoClient;
6 db: Db;
7}
8
9let connection: Connection | null = null;
10
11export interface ConnectOptions extends MongoClientOptions {};
12
13/**
14 * Health check details of the MongoDB connection
15 *
16 * @property healthy - Overall health status of the connection
17 * @property connected - Whether a connection is established
18 * @property responseTimeMs - Response time in milliseconds (if connection is healthy)
19 * @property error - Error message if health check failed
20 * @property timestamp - Timestamp when health check was performed
21 */
22export interface HealthCheckResult {
23 healthy: boolean;
24 connected: boolean;
25 responseTimeMs?: number;
26 error?: string;
27 timestamp: Date;
28}
29
30/**
31 * Connect to MongoDB with connection pooling, retry logic, and resilience options
32 *
33 * The MongoDB driver handles connection pooling and automatic retries.
34 * Retry logic is enabled by default for both reads and writes in MongoDB 4.2+.
35 *
36 * @param uri - MongoDB connection string
37 * @param dbName - Name of the database to connect to
38 * @param options - Connection options (pooling, retries, timeouts, etc.)
39 *
40 * @example
41 * Basic connection with pooling:
42 * ```ts
43 * await connect("mongodb://localhost:27017", "mydb", {
44 * maxPoolSize: 10,
45 * minPoolSize: 2,
46 * maxIdleTimeMS: 30000,
47 * connectTimeoutMS: 10000,
48 * socketTimeoutMS: 45000,
49 * });
50 * ```
51 *
52 * @example
53 * Production-ready connection with retry logic and resilience:
54 * ```ts
55 * await connect("mongodb://localhost:27017", "mydb", {
56 * // Connection pooling
57 * maxPoolSize: 10,
58 * minPoolSize: 2,
59 *
60 * // Automatic retry logic (enabled by default)
61 * retryReads: true, // Retry failed read operations
62 * retryWrites: true, // Retry failed write operations
63 *
64 * // Timeouts
65 * connectTimeoutMS: 10000, // Initial connection timeout
66 * socketTimeoutMS: 45000, // Socket operation timeout
67 * serverSelectionTimeoutMS: 10000, // Server selection timeout
68 *
69 * // Connection resilience
70 * maxIdleTimeMS: 30000, // Close idle connections
71 * heartbeatFrequencyMS: 10000, // Server health check interval
72 *
73 * // Optional: Compression for reduced bandwidth
74 * compressors: ['snappy', 'zlib'],
75 * });
76 * ```
77 */
78export async function connect(
79 uri: string,
80 dbName: string,
81 options?: ConnectOptions,
82): Promise<Connection> {
83 if (connection) {
84 return connection;
85 }
86
87 try {
88 const client = new MongoClient(uri, options);
89 await client.connect();
90 const db = client.db(dbName);
91
92 connection = { client, db };
93 return connection;
94 } catch (error) {
95 throw new ConnectionError(
96 `Failed to connect to MongoDB: ${error instanceof Error ? error.message : String(error)}`,
97 uri
98 );
99 }
100}
101
102export async function disconnect(): Promise<void> {
103 if (connection) {
104 await connection.client.close();
105 connection = null;
106 }
107}
108
109export function getDb(): Db {
110 if (!connection) {
111 throw new ConnectionError("MongoDB not connected. Call connect() first.");
112 }
113 return connection.db;
114}
115
116/**
117 * Check the health of the MongoDB connection
118 *
119 * Performs a ping operation to verify the database is responsive
120 * and returns detailed health information including response time.
121 *
122 * @example
123 * ```ts
124 * const health = await healthCheck();
125 * if (health.healthy) {
126 * console.log(`Database healthy (${health.responseTimeMs}ms)`);
127 * } else {
128 * console.error(`Database unhealthy: ${health.error}`);
129 * }
130 * ```
131 */
132export async function healthCheck(): Promise<HealthCheckResult> {
133 const timestamp = new Date();
134
135 // Check if connection exists
136 if (!connection) {
137 return {
138 healthy: false,
139 connected: false,
140 error: "No active connection. Call connect() first.",
141 timestamp,
142 };
143 }
144
145 try {
146 // Measure ping response time
147 const startTime = performance.now();
148 await connection.db.admin().ping();
149 const endTime = performance.now();
150 const responseTimeMs = Math.round(endTime - startTime);
151
152 return {
153 healthy: true,
154 connected: true,
155 responseTimeMs,
156 timestamp,
157 };
158 } catch (error) {
159 return {
160 healthy: false,
161 connected: true,
162 error: error instanceof Error ? error.message : String(error),
163 timestamp,
164 };
165 }
166}