odac 1.4.10 → 1.4.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,34 @@
1
+ ### 📚 Documentation
2
+
3
+ - refactor database configuration to a unified structure and document multi-connection support
4
+
5
+ ### 🛠️ Fixes & Improvements
6
+
7
+ - **mail:** add line wrapping for email content to comply with SMTP standards
8
+ - **mail:** use 990-char wrap for HTML, 76 for text; trim migration defaults
9
+ - **route:** hot-reload cron fix
10
+
11
+
12
+
13
+ ---
14
+
15
+ Powered by [⚡ ODAC](https://odac.run)
16
+
17
+ ### ✨ What's New
18
+
19
+ - add esbuild-powered JS/TS frontend pipeline
20
+ - **cache:** add proxy cache support with Odac.cache() method
21
+
22
+ ### 🛠️ Fixes & Improvements
23
+
24
+ - **frontend:** enforce pipeline .js output, handle watcher rejections and skip esbuild directory scans
25
+
26
+
27
+
28
+ ---
29
+
30
+ Powered by [⚡ ODAC](https://odac.run)
31
+
1
32
  ### 🛠️ Fixes & Improvements
2
33
 
3
34
  - **cache:** preserve Knex chain for insert operations during invalidation
package/bin/odac.js CHANGED
@@ -141,6 +141,195 @@ function getTailwindConfigs() {
141
141
  return configs
142
142
  }
143
143
 
144
+ /**
145
+ * Resolves JS/TS entry points from 'view/js' and returns esbuild-compatible configs.
146
+ * Supports multiple entry points: each .js/.ts/.mjs/.mts file in view/js/ becomes a separate bundle.
147
+ * Why: Mirrors the Tailwind CSS pipeline pattern for zero-config frontend asset compilation.
148
+ * @returns {Array<{ input: string, output: string, name: string }>}
149
+ */
150
+ function getJsConfigs() {
151
+ const jsDir = path.join(process.cwd(), 'view/js')
152
+ const outputDir = path.join(process.cwd(), 'public/assets/js')
153
+ const configs = []
154
+
155
+ if (!fs.existsSync(jsDir) || !fs.lstatSync(jsDir).isDirectory()) return configs
156
+
157
+ const validExtensions = ['.ts', '.js', '.mts', '.mjs']
158
+ const files = fs.readdirSync(jsDir).filter(file => {
159
+ const fullPath = path.join(jsDir, file)
160
+ if (!fs.lstatSync(fullPath).isFile()) return false
161
+ const ext = path.extname(file).toLowerCase()
162
+ return validExtensions.includes(ext) && !file.startsWith('_')
163
+ })
164
+
165
+ fs.mkdirSync(outputDir, {recursive: true})
166
+
167
+ for (const file of files) {
168
+ const input = path.join(jsDir, file)
169
+ const baseName = path.basename(file, path.extname(file))
170
+ const output = path.join(outputDir, `${baseName}.js`)
171
+
172
+ configs.push({input, output, name: file})
173
+ }
174
+
175
+ return configs
176
+ }
177
+
178
+ /**
179
+ * Reads the project's odac.json and returns the js pipeline configuration.
180
+ * Merges user config with sensible defaults for zero-config operation.
181
+ * @returns {{ target: string, minify: boolean, sourcemap: boolean, bundle: boolean, obfuscate: boolean|string }}
182
+ */
183
+ function getJsPipelineOptions() {
184
+ const configPath = path.join(process.cwd(), 'odac.json')
185
+ const defaults = {
186
+ target: 'es2020',
187
+ minify: true,
188
+ sourcemap: false,
189
+ bundle: true,
190
+ obfuscate: false
191
+ }
192
+
193
+ try {
194
+ if (fs.existsSync(configPath)) {
195
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'))
196
+ if (config.js && typeof config.js === 'object') {
197
+ return {...defaults, ...config.js}
198
+ }
199
+ }
200
+ } catch {
201
+ // Config read failure is non-fatal; use defaults
202
+ }
203
+
204
+ return defaults
205
+ }
206
+
207
+ /**
208
+ * Resolves esbuild mangle/obfuscation options based on the obfuscate config level.
209
+ * Why: Provides lightweight code obfuscation without external dependencies by leveraging
210
+ * esbuild's built-in property mangling and identifier minification capabilities.
211
+ *
212
+ * Levels:
213
+ * - false (default): No obfuscation, only standard minification.
214
+ * - true / "low": Mangles properties starting with _ (private-by-convention).
215
+ * - "medium": Mangles _ properties + top-level names + applies aggressive mangling.
216
+ * - "high": Maximum obfuscation — mangles all properties matching common patterns,
217
+ * drops console/debugger, and rewrites identifiers aggressively.
218
+ *
219
+ * @param {boolean|string} level Obfuscation level
220
+ * @returns {object} esbuild-compatible options to spread into the build config
221
+ */
222
+ function getObfuscationOptions(level) {
223
+ if (!level) return {}
224
+
225
+ const normalized = level === true ? 'low' : String(level).toLowerCase()
226
+
227
+ if (normalized === 'low') {
228
+ return {
229
+ mangleProps: /^_/
230
+ }
231
+ }
232
+
233
+ if (normalized === 'medium') {
234
+ return {
235
+ mangleProps: /^_/,
236
+ drop: ['debugger'],
237
+ pure: ['console.debug', 'console.trace']
238
+ }
239
+ }
240
+
241
+ if (normalized === 'high') {
242
+ return {
243
+ mangleProps: /^[_$]/,
244
+ drop: ['debugger', 'console'],
245
+ legalComments: 'none',
246
+ keepNames: false
247
+ }
248
+ }
249
+
250
+ return {}
251
+ }
252
+
253
+ /**
254
+ * Builds JS/TS entry points using esbuild.
255
+ * Why: Provides TypeScript transpilation, bundling, minification, and tree-shaking in a single pass.
256
+ * @param {Array<{ input: string, output: string, name: string }>} configs Entry point configurations
257
+ * @param {{ target: string, minify: boolean, sourcemap: boolean, bundle: boolean }} options Build options
258
+ * @param {boolean} [production=false] Whether to enable production optimizations
259
+ * @returns {Promise<boolean>} True if all builds succeeded
260
+ */
261
+ async function buildJs(configs, options, production = false) {
262
+ if (configs.length === 0) return true
263
+
264
+ const esbuild = require('esbuild')
265
+ const obfuscation = production ? getObfuscationOptions(options.obfuscate) : {}
266
+ let hasError = false
267
+
268
+ const buildPromises = configs.map(async ({input, output, name}) => {
269
+ try {
270
+ await esbuild.build({
271
+ entryPoints: [input],
272
+ outfile: output,
273
+ bundle: options.bundle,
274
+ minify: production ? true : options.minify,
275
+ sourcemap: options.sourcemap,
276
+ target: options.target,
277
+ platform: 'browser',
278
+ format: 'iife',
279
+ treeShaking: true,
280
+ legalComments: 'none',
281
+ logLevel: 'warning',
282
+ ...obfuscation
283
+ })
284
+ } catch (err) {
285
+ console.error(`❌ \x1b[31m[ODAC Script Error]\x1b[0m Build failed for ${name}:`, err.message)
286
+ hasError = true
287
+ }
288
+ })
289
+
290
+ await Promise.all(buildPromises)
291
+ return !hasError
292
+ }
293
+
294
+ /**
295
+ * Starts esbuild watch contexts for JS/TS entry points.
296
+ * Why: Provides instant rebuilds during development, matching the Tailwind CSS watcher pattern.
297
+ * @param {Array<{ input: string, output: string, name: string }>} configs Entry point configurations
298
+ * @param {{ target: string, minify: boolean, sourcemap: boolean, bundle: boolean }} options Build options
299
+ * @returns {Promise<Array<{ dispose: Function }>>} Array of esbuild watch contexts for cleanup
300
+ */
301
+ async function watchJs(configs, options) {
302
+ if (configs.length === 0) return []
303
+
304
+ const esbuild = require('esbuild')
305
+ const contexts = []
306
+
307
+ for (const {input, output, name} of configs) {
308
+ try {
309
+ const ctx = await esbuild.context({
310
+ entryPoints: [input],
311
+ outfile: output,
312
+ bundle: options.bundle,
313
+ minify: false,
314
+ sourcemap: true,
315
+ target: options.target,
316
+ platform: 'browser',
317
+ format: 'iife',
318
+ treeShaking: false,
319
+ legalComments: 'none',
320
+ logLevel: 'warning'
321
+ })
322
+
323
+ await ctx.watch()
324
+ contexts.push(ctx)
325
+ } catch (err) {
326
+ console.error(`❌ \x1b[31m[ODAC Script Error]\x1b[0m Watch failed for ${name}:`, err.message)
327
+ }
328
+ }
329
+
330
+ return contexts
331
+ }
332
+
144
333
  /**
145
334
  * Manages the AI Agent skills synchronization.
146
335
  * @param {string} targetDir The directory to sync skills into.
@@ -529,6 +718,31 @@ async function run() {
529
718
  process.on('SIGINT', cleanup)
530
719
  process.on('SIGTERM', cleanup)
531
720
  process.on('exit', cleanup)
721
+
722
+ // JS/TS Pipeline — esbuild watch mode
723
+ const jsConfigs = getJsConfigs()
724
+ if (jsConfigs.length > 0) {
725
+ const jsOptions = getJsPipelineOptions()
726
+ const jsNames = jsConfigs.map(c => c.name).join(', ')
727
+ console.log(`📦 \x1b[36mODAC Scripts:\x1b[0m Watching for changes (${jsNames})`)
728
+
729
+ watchJs(jsConfigs, jsOptions)
730
+ .then(contexts => {
731
+ const jsCleanup = () => {
732
+ contexts.forEach(ctx => {
733
+ try {
734
+ ctx.dispose()
735
+ } catch {}
736
+ })
737
+ }
738
+ process.on('SIGINT', jsCleanup)
739
+ process.on('SIGTERM', jsCleanup)
740
+ process.on('exit', jsCleanup)
741
+ })
742
+ .catch(err => {
743
+ console.error(`❌ \x1b[31m[ODAC Script Error]\x1b[0m Failed to initialize JS watcher:`, err.message)
744
+ })
745
+ }
532
746
  }
533
747
 
534
748
  require('../index.js')
@@ -554,7 +768,22 @@ async function run() {
554
768
  if (hasError) {
555
769
  process.exit(1)
556
770
  } else {
557
- console.log('✅ All builds completed successfully!')
771
+ console.log('✅ All CSS builds completed successfully!')
772
+ }
773
+
774
+ // JS/TS Pipeline — production build with minification and tree-shaking
775
+ const jsConfigs = getJsConfigs()
776
+ if (jsConfigs.length > 0) {
777
+ const jsOptions = getJsPipelineOptions()
778
+ const jsNames = jsConfigs.map(c => c.name).join(', ')
779
+ console.log(`📦 Compiling scripts (${jsNames})...`)
780
+
781
+ const jsSuccess = await buildJs(jsConfigs, jsOptions, true)
782
+ if (!jsSuccess) {
783
+ process.exit(1)
784
+ } else {
785
+ console.log('✅ All script builds completed successfully!')
786
+ }
558
787
  }
559
788
  } else if (command === 'start') {
560
789
  process.env.NODE_ENV = 'production'
@@ -20,16 +20,17 @@ Read the specific rule files based on whether you are working on the Backend or
20
20
  - [backend/cron.md](backend/cron.md) - Scheduled background tasks and automation
21
21
  - [backend/database.md](backend/database.md) - Query Builder, Write-Behind Cache, and DB best practices
22
22
  - [backend/forms.md](backend/forms.md) - Form processing and Validation logic
23
+ - [backend/image-processing.md](backend/image-processing.md) - High-performance server-side image manipulation
23
24
  - [backend/ipc.md](backend/ipc.md) - Inter-Process Communication and state sharing
24
25
  - [backend/mail.md](backend/mail.md) - Transactional email sending
25
26
  - [backend/migrations.md](backend/migrations.md) - Schema-first, auto-run, cluster-safe DB migrations
27
+ - [backend/odac-var.md](backend/odac-var.md) - Odac.Var (Fluent String Manipulation, Hashing, Encryption)
26
28
  - [backend/request_response.md](backend/request_response.md) - Handling Odac.Request and Odac.Response
27
29
  - [backend/routing.md](backend/routing.md) - Route definitions, Middlewares, and Error Pages
28
30
  - [backend/storage.md](backend/storage.md) - Persistent key-value storage (LMDB)
29
31
  - [backend/streaming.md](backend/streaming.md) - Server-Sent Events (SSE) and Streaming API
30
32
  - [backend/structure.md](backend/structure.md) - Project organization and Service Classes
31
33
  - [backend/translations.md](backend/translations.md) - Multi-language support (i18n)
32
- - [backend/odac-var.md](backend/odac-var.md) - Odac.Var (Fluent String Manipulation, Hashing, Encryption)
33
34
  - [backend/utilities.md](backend/utilities.md) - Flow Control (abort, direct, session)
34
35
  - [backend/validation.md](backend/validation.md) - Input sanitization and security checks
35
36
  - [backend/views.md](backend/views.md) - Template syntax, skeletons, and server-side JS
@@ -39,3 +40,4 @@ Read the specific rule files based on whether you are working on the Backend or
39
40
  - [frontend/forms.md](frontend/forms.md) - AJAX form handling and API requests
40
41
  - [frontend/navigation.md](frontend/navigation.md) - AJAX Navigation (SPA) behavior
41
42
  - [frontend/realtime.md](frontend/realtime.md) - WebSocket Hubs and EventSource usage
43
+ - [frontend/scripts.md](frontend/scripts.md) - esbuild-powered JS/TS frontend pipeline
@@ -30,10 +30,30 @@ const apiKey = Odac.env('API_KEY');
30
30
  ### 2. odac.json Structure
31
31
  ```json
32
32
  {
33
- "mysql": {
33
+ "database": {
34
+ "type": "mysql",
34
35
  "host": "${DB_HOST}",
35
36
  "password": "${DB_PASSWORD}"
36
37
  },
37
38
  "debug": true
38
39
  }
39
40
  ```
41
+
42
+ ### 3. Multiple Databases
43
+ ```json
44
+ {
45
+ "database": {
46
+ "default": {
47
+ "type": "mysql",
48
+ "database": "main_app"
49
+ },
50
+ "analytics": {
51
+ "type": "postgres",
52
+ "host": "analytics.local",
53
+ "database": "events"
54
+ }
55
+ }
56
+ }
57
+ ```
58
+
59
+ Access via: `Odac.DB.analytics.tableName`
@@ -16,6 +16,46 @@ High-performance database operations using the ODAC Query Builder, Read-Through
16
16
  4. **Read Caching**: Use `cache()` for frequently-read, rarely-changed data.
17
17
  5. **Write Coalescing**: Use `buffer` for high-frequency writes to avoid DB saturation.
18
18
 
19
+ ## Database Configuration (`odac.json`)
20
+
21
+ ODAC connects to databases automatically based on the `database` key in `odac.json`.
22
+
23
+ ### 1. Single Connection
24
+ ```json
25
+ {
26
+ "database": {
27
+ "type": "mysql",
28
+ "host": "${DB_HOST}",
29
+ "user": "${DB_USER}",
30
+ "password": "${DB_PASSWORD}",
31
+ "database": "myapp"
32
+ }
33
+ }
34
+ ```
35
+
36
+ ### 2. Multiple Connections
37
+ Define named objects. The one named `default` (or the first one) is used by default.
38
+ ```json
39
+ {
40
+ "database": {
41
+ "default": {
42
+ "type": "mysql",
43
+ "database": "app_db"
44
+ },
45
+ "analytics": {
46
+ "type": "postgres",
47
+ "host": "remote-stats.db",
48
+ "database": "events"
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ Access named connections via: `Odac.DB.analytics.tableName`
55
+
56
+ ### 3. Environment Variables
57
+ Always use `${VAR_NAME}` for sensitive credentials. Map them in `.env`.
58
+
19
59
  ## Query Builder Patterns
20
60
  ```javascript
21
61
  const user = await Odac.DB.users
@@ -2,7 +2,7 @@
2
2
  name: backend-request-response-skill
3
3
  description: ODAC request parsing and response composition patterns for consistent, secure, and predictable API behavior.
4
4
  metadata:
5
- tags: backend, request, response, headers, status-codes, json, api
5
+ tags: backend, request, response, headers, status-codes, json, api, cache, proxy
6
6
  ---
7
7
 
8
8
  # Backend Request & Response Skill
@@ -40,3 +40,23 @@ module.exports = function(Odac) {
40
40
  return "Raw text response";
41
41
  };
42
42
  ```
43
+
44
+ ### 3. Proxy Cache Control
45
+ Enable ODAC Proxy caching for static or semi-static page responses. Sets `X-ODAC-Cache` and `Cache-Control` headers automatically.
46
+
47
+ **ODAC Ecosystem Only** — requires the ODAC Proxy. Has no effect in standalone deployments.
48
+
49
+ ```javascript
50
+ module.exports = function(Odac) {
51
+ Odac.cache(3600) // Cache for 1 hour
52
+ Odac.View.skeleton('main').set('content', 'about')
53
+ }
54
+ ```
55
+
56
+ **Rules:**
57
+ - `Odac.cache(seconds)` — positive integer only. Throws `TypeError` otherwise.
58
+ - Overrides any previously set `Cache-Control` header for the request.
59
+ - **ODAC Proxy is smart:** automatically invalidates cache when content changes or dynamic content is detected.
60
+ - **Despite smart invalidation, never use on:** user-specific pages (dashboards, profiles, account pages), pages with session data, auth state, or per-user content. Do not rely on auto-detection as a safety net.
61
+
62
+ **Good candidates:** marketing pages, blog posts, docs, product listings (no personalization), static pages (about, FAQ, contact).
@@ -0,0 +1,36 @@
1
+ ---
2
+ name: frontend-scripts-typescript-skill
3
+ description: ODAC frontend JS/TS pipeline guidelines for writing, bundling, and optimizing client-side scripts using esbuild.
4
+ metadata:
5
+ tags: frontend, javascript, typescript, esbuild, bundling, minification, tree-shaking, scripts, assets
6
+ ---
7
+
8
+ # Frontend Scripts & TypeScript Skill
9
+
10
+ Zero-config frontend asset pipeline powered by esbuild for TypeScript transpilation, bundling, minification, and tree-shaking.
11
+
12
+ ## Core Rules
13
+ 1. **Entry Points**: Place `.ts`, `.js`, `.mts`, or `.mjs` files in `view/js/`. Each becomes a separate bundle.
14
+ 2. **Partials Convention**: Files starting with `_` (e.g., `_utils.ts`) are ignored as entry points — use them as shared imports only.
15
+ 3. **Output Path**: Compiled files go to `public/assets/js/{name}.js`.
16
+ 4. **No TypeScript Enforcement**: Both TypeScript and plain JavaScript are supported equally.
17
+ 5. **Import Resolution**: Use standard ES module `import`/`export` between files. esbuild bundles everything into a single output per entry point.
18
+ 6. **Configuration**: Optional `js` key in `odac.json` for `target`, `minify`, `sourcemap`, and `bundle` settings.
19
+
20
+ ## Development vs Production
21
+ - **`odac dev`**: Watch mode with source maps, no minification, instant rebuilds.
22
+ - **`odac build`**: Full minification, tree-shaking, and dead code elimination.
23
+
24
+ ## Example Structure
25
+ ```
26
+ view/js/
27
+ ├── app.ts → public/assets/js/app.js
28
+ ├── admin.ts → public/assets/js/admin.js
29
+ ├── _api.ts (shared module, not compiled)
30
+ └── _utils.ts (shared module, not compiled)
31
+ ```
32
+
33
+ ## HTML Integration
34
+ ```html
35
+ <script src="/assets/js/app.js"></script>
36
+ ```
@@ -12,7 +12,8 @@ The main configuration file located in your website's root directory. This file
12
12
  "request": {
13
13
  "timeout": 30000
14
14
  },
15
- "mysql": {
15
+ "database": {
16
+ "type": "mysql",
16
17
  "host": "localhost",
17
18
  "user": "root",
18
19
  "password": "secret123",
@@ -38,7 +39,8 @@ Perfect for development or non-sensitive settings:
38
39
 
39
40
  ```json
40
41
  {
41
- "mysql": {
42
+ "database": {
43
+ "type": "mysql",
42
44
  "host": "localhost",
43
45
  "password": "dev123"
44
46
  }
@@ -50,7 +52,8 @@ Use `${VARIABLE}` syntax in `odac.json` to reference `.env` values:
50
52
 
51
53
  ```json
52
54
  {
53
- "mysql": {
55
+ "database": {
56
+ "type": "mysql",
54
57
  "host": "${MYSQL_HOST}",
55
58
  "password": "${MYSQL_PASSWORD}"
56
59
  }
@@ -68,7 +71,8 @@ Combine both methods - use direct values for non-sensitive data and environment
68
71
 
69
72
  ```json
70
73
  {
71
- "mysql": {
74
+ "database": {
75
+ "type": "mysql",
72
76
  "host": "localhost",
73
77
  "user": "root",
74
78
  "password": "${MYSQL_PASSWORD}",
@@ -88,7 +92,7 @@ You can access configuration values in three ways:
88
92
 
89
93
  ```javascript
90
94
  // 1. From Odac.Config (recommended for structured config)
91
- const dbHost = Odac.Config.mysql.host
95
+ const dbHost = Odac.Config.database.host
92
96
 
93
97
  // 2. Using Odac.env() helper
94
98
  const apiKey = Odac.env('API_KEY')
@@ -130,6 +134,7 @@ const nodeEnv = process.env.NODE_ENV
130
134
  ```json
131
135
  {
132
136
  "database": {
137
+ "type": "mysql",
133
138
  "host": "localhost",
134
139
  "user": "root",
135
140
  "password": "${MYSQL_PASSWORD}",
@@ -161,6 +166,19 @@ const nodeEnv = process.env.NODE_ENV
161
166
  }
162
167
  ```
163
168
 
169
+ **Scripts (JS/TS Pipeline):**
170
+ ```json
171
+ {
172
+ "js": {
173
+ "target": "es2020",
174
+ "minify": true,
175
+ "sourcemap": false,
176
+ "bundle": true,
177
+ "obfuscate": false
178
+ }
179
+ }
180
+ ```
181
+
164
182
  **Debug Mode:**
165
183
  Enable verbose logging for development. This helps in troubleshooting by inspecting detailed logs for system actions like sending emails (e.g., SMTP connection details, server responses). Default is `false`.
166
184
 
@@ -182,7 +200,8 @@ See individual documentation sections for detailed configuration options.
182
200
  "request": {
183
201
  "timeout": 30000
184
202
  },
185
- "mysql": {
203
+ "database": {
204
+ "type": "mysql",
186
205
  "host": "${MYSQL_HOST}",
187
206
  "user": "${MYSQL_USER}",
188
207
  "password": "${MYSQL_PASSWORD}",
@@ -1,12 +1,13 @@
1
1
  ## 🔌 Database Connection
2
2
 
3
- When you add a `mysql` object to your `odac.json`, the system will automatically connect to your MySQL database. No separate connection setup is needed in your code.
3
+ When you add a `database` object to your `odac.json`, the system will automatically connect to your database. No separate connection setup is needed in your code.
4
4
 
5
5
  ### Basic Configuration
6
6
 
7
7
  ```json
8
8
  {
9
- "mysql": {
9
+ "database": {
10
+ "type": "mysql",
10
11
  "host": "localhost",
11
12
  "user": "your_user",
12
13
  "password": "your_password",
@@ -24,7 +25,8 @@ For better security, especially in production, you can use environment variables
24
25
  **odac.json:**
25
26
  ```json
26
27
  {
27
- "mysql": {
28
+ "database": {
29
+ "type": "mysql",
28
30
  "host": "${MYSQL_HOST}",
29
31
  "user": "${MYSQL_USER}",
30
32
  "password": "${MYSQL_PASSWORD}",
@@ -48,7 +50,8 @@ You can also mix direct values with environment variables:
48
50
 
49
51
  ```json
50
52
  {
51
- "mysql": {
53
+ "database": {
54
+ "type": "mysql",
52
55
  "host": "localhost",
53
56
  "user": "root",
54
57
  "password": "${MYSQL_PASSWORD}",
@@ -58,3 +61,37 @@ You can also mix direct values with environment variables:
58
61
  ```
59
62
 
60
63
  This way, non-sensitive values are directly in the config while passwords remain in the `.env` file.
64
+
65
+ ### Multiple Database Connections
66
+
67
+ ODAC supports multiple simultaneous database connections. You can define them as named objects within the `database` configuration:
68
+
69
+ ```json
70
+ {
71
+ "database": {
72
+ "default": {
73
+ "type": "mysql",
74
+ "host": "localhost",
75
+ "database": "app_db"
76
+ },
77
+ "analytics": {
78
+ "type": "postgres",
79
+ "host": "remote-stats.db",
80
+ "database": "events"
81
+ }
82
+ }
83
+ }
84
+ ```
85
+
86
+ #### Usage in Code
87
+
88
+ To use a specific connection, access it by its name via `Odac.DB`:
89
+
90
+ ```javascript
91
+ // Uses 'default' connection
92
+ const users = await Odac.DB.users.select('*')
93
+
94
+ // Uses 'analytics' connection
95
+ const events = await Odac.DB.analytics.pageviews.select('*')
96
+ ```
97
+
@@ -43,7 +43,8 @@ Reference environment variables using `${VARIABLE_NAME}` syntax:
43
43
 
44
44
  ```json
45
45
  {
46
- "mysql": {
46
+ "database": {
47
+ "type": "mysql",
47
48
  "host": "${MYSQL_HOST}",
48
49
  "user": "${MYSQL_USER}",
49
50
  "password": "${MYSQL_PASSWORD}",
@@ -95,7 +96,7 @@ module.exports = function() {
95
96
 
96
97
  ```javascript
97
98
  module.exports = function() {
98
- const dbHost = Odac.Config.mysql.host
99
+ const dbHost = Odac.Config.database.host
99
100
  const apiKey = Odac.Config.api.stripe.key
100
101
  }
101
102
  ```