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 +31 -0
- package/bin/odac.js +230 -1
- package/docs/ai/skills/SKILL.md +3 -1
- package/docs/ai/skills/backend/config.md +21 -1
- package/docs/ai/skills/backend/database.md +40 -0
- package/docs/ai/skills/backend/request_response.md +21 -1
- package/docs/ai/skills/frontend/scripts.md +36 -0
- package/docs/backend/03-config/00-configuration-overview.md +25 -6
- package/docs/backend/03-config/01-database-connection.md +41 -4
- package/docs/backend/03-config/04-environment-variables.md +3 -2
- package/docs/backend/06-request-and-response/03-proxy-cache.md +77 -0
- package/docs/backend/07-views/12-scripts-and-typescript.md +156 -0
- package/docs/backend/08-database/01-getting-started.md +11 -0
- package/docs/backend/10-authentication/04-odac-register-forms.md +7 -4
- package/docs/backend/10-authentication/06-odac-login-forms.md +7 -4
- package/docs/index.json +8 -0
- package/eslint.config.mjs +20 -1
- package/package.json +2 -1
- package/src/Config.js +7 -0
- package/src/Database/Migration.js +32 -11
- package/src/Mail.js +61 -0
- package/src/Odac.js +3 -0
- package/src/Request.js +21 -0
- package/src/Route/Cron.js +10 -0
- package/src/Route.js +1 -0
- package/template/public/.gitkeep +0 -0
- package/template/{public/assets → view}/js/app.js +8 -2
- package/test/Odac/cache.test.js +54 -0
- package/test/Request/cache.test.js +95 -0
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'
|
package/docs/ai/skills/SKILL.md
CHANGED
|
@@ -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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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.
|
|
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
|
-
"
|
|
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 `
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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.
|
|
99
|
+
const dbHost = Odac.Config.database.host
|
|
99
100
|
const apiKey = Odac.Config.api.stripe.key
|
|
100
101
|
}
|
|
101
102
|
```
|