odac 1.4.9 → 1.4.11
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 +30 -0
- package/bin/odac.js +230 -1
- package/docs/ai/skills/SKILL.md +3 -1
- 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 +13 -0
- 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/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 +102 -7
- package/src/Database/WriteBuffer.js +15 -1
- package/src/Database.js +17 -3
- package/src/Odac.js +3 -0
- package/src/Request.js +21 -0
- package/template/public/.gitkeep +0 -0
- package/template/{public/assets → view}/js/app.js +8 -2
- package/test/Database/Migration/migrate_column.test.js +143 -0
- package/test/Database/WriteBuffer/insert.test.js +118 -0
- package/test/Odac/cache.test.js +54 -0
- package/test/Request/cache.test.js +95 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,33 @@
|
|
|
1
|
+
### ✨ What's New
|
|
2
|
+
|
|
3
|
+
- add esbuild-powered JS/TS frontend pipeline
|
|
4
|
+
- **cache:** add proxy cache support with Odac.cache() method
|
|
5
|
+
|
|
6
|
+
### 🛠️ Fixes & Improvements
|
|
7
|
+
|
|
8
|
+
- **frontend:** enforce pipeline .js output, handle watcher rejections and skip esbuild directory scans
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
Powered by [⚡ ODAC](https://odac.run)
|
|
15
|
+
|
|
16
|
+
### 🛠️ Fixes & Improvements
|
|
17
|
+
|
|
18
|
+
- **cache:** preserve Knex chain for insert operations during invalidation
|
|
19
|
+
- **database:** resolve duplicate error handler bug in insert cache invalidation
|
|
20
|
+
- **migration:** add column type comparison to detect schema changes
|
|
21
|
+
- **migration:** handle PostgreSQL primary key type changes safely
|
|
22
|
+
- **migration:** preserve column nullable state when altering without explicit schema
|
|
23
|
+
- **write-buffer:** auto-generate nanoid values for buffered inserts
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
Powered by [⚡ ODAC](https://odac.run)
|
|
30
|
+
|
|
1
31
|
### ⚙️ Engine Tuning
|
|
2
32
|
|
|
3
33
|
- migrate hashing from BCrypt to scrypt and update Odac.Var documentation and validation logic
|
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
|
|
@@ -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
|
+
```
|
|
@@ -161,6 +161,19 @@ const nodeEnv = process.env.NODE_ENV
|
|
|
161
161
|
}
|
|
162
162
|
```
|
|
163
163
|
|
|
164
|
+
**Scripts (JS/TS Pipeline):**
|
|
165
|
+
```json
|
|
166
|
+
{
|
|
167
|
+
"js": {
|
|
168
|
+
"target": "es2020",
|
|
169
|
+
"minify": true,
|
|
170
|
+
"sourcemap": false,
|
|
171
|
+
"bundle": true,
|
|
172
|
+
"obfuscate": false
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
164
177
|
**Debug Mode:**
|
|
165
178
|
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
179
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
## 🚀 Proxy Cache
|
|
2
|
+
|
|
3
|
+
`Odac.cache()` lets you tell the ODAC Proxy to cache the current page's HTML response, so repeat visitors get a near-instant response without hitting your application server at all.
|
|
4
|
+
|
|
5
|
+
> **ODAC Ecosystem Only:** This feature works exclusively within the ODAC ecosystem. It relies on the `X-ODAC-Cache` header that only the ODAC Proxy understands and acts upon.
|
|
6
|
+
|
|
7
|
+
### Basic Usage
|
|
8
|
+
|
|
9
|
+
Call `Odac.cache(seconds)` at the top of your controller with a TTL (time-to-live) in seconds:
|
|
10
|
+
|
|
11
|
+
```javascript
|
|
12
|
+
module.exports = function (Odac) {
|
|
13
|
+
Odac.cache(3600) // Cache this page for 1 hour
|
|
14
|
+
|
|
15
|
+
Odac.set('title', 'About Us')
|
|
16
|
+
Odac.View.skeleton('main').set('content', 'about')
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
That's it. The ODAC Proxy handles the rest.
|
|
21
|
+
|
|
22
|
+
### How It Works
|
|
23
|
+
|
|
24
|
+
When `Odac.cache(seconds)` is called, ODAC sets two response headers:
|
|
25
|
+
|
|
26
|
+
| Header | Value | Purpose |
|
|
27
|
+
|--------|-------|---------|
|
|
28
|
+
| `X-ODAC-Cache` | `3600` | Tells the ODAC Proxy to cache this response for the given TTL |
|
|
29
|
+
| `Cache-Control` | `public, max-age=3600` | Standard browser/CDN cache directive |
|
|
30
|
+
|
|
31
|
+
The ODAC Proxy intercepts the response, stores it, and serves it directly on subsequent requests — bypassing your application entirely until the TTL expires.
|
|
32
|
+
|
|
33
|
+
### Smart Cache Invalidation
|
|
34
|
+
|
|
35
|
+
The ODAC Proxy is intelligent about cache invalidation. You don't need to manually clear the cache in most cases:
|
|
36
|
+
|
|
37
|
+
- **Content changes:** If the underlying page content changes (e.g. a file is updated or a deployment happens), the Proxy detects this and automatically invalidates the cache on the next request.
|
|
38
|
+
- **Dynamic content detection:** If the Proxy detects that a response contains dynamic or user-specific content, it cancels the cache for that response automatically.
|
|
39
|
+
|
|
40
|
+
### When to Use It
|
|
41
|
+
|
|
42
|
+
`Odac.cache()` is designed for pages where the HTML output is **identical for all visitors**:
|
|
43
|
+
|
|
44
|
+
✅ **Good candidates:**
|
|
45
|
+
- Marketing and landing pages
|
|
46
|
+
- Blog posts and articles
|
|
47
|
+
- Documentation pages
|
|
48
|
+
- Product listing pages (without personalization)
|
|
49
|
+
- Static "About", "Contact", "FAQ" pages
|
|
50
|
+
|
|
51
|
+
❌ **Do not use on:**
|
|
52
|
+
- Pages with user-specific content (dashboards, profiles, account pages)
|
|
53
|
+
- Pages that display session data or authentication state
|
|
54
|
+
- Pages with per-user pricing, recommendations, or notifications
|
|
55
|
+
- Any page where the HTML output differs between users
|
|
56
|
+
|
|
57
|
+
> Even though the ODAC Proxy can detect dynamic content and cancel caching, you should not rely on this as a safety net. If a page is user-specific, simply don't call `Odac.cache()`.
|
|
58
|
+
|
|
59
|
+
### TTL Reference
|
|
60
|
+
|
|
61
|
+
| Scenario | Recommended TTL |
|
|
62
|
+
|----------|----------------|
|
|
63
|
+
| Frequently updated content (news, blog) | `300` – `900` (5–15 min) |
|
|
64
|
+
| Semi-static content (docs, product pages) | `3600` – `86400` (1–24 hrs) |
|
|
65
|
+
| Fully static pages (about, landing) | `86400` – `604800` (1–7 days) |
|
|
66
|
+
|
|
67
|
+
### Error Handling
|
|
68
|
+
|
|
69
|
+
`Odac.cache()` throws a `TypeError` if the argument is not a positive integer:
|
|
70
|
+
|
|
71
|
+
```javascript
|
|
72
|
+
Odac.cache(3600) // ✅ Valid
|
|
73
|
+
Odac.cache(0) // ❌ TypeError
|
|
74
|
+
Odac.cache(-1) // ❌ TypeError
|
|
75
|
+
Odac.cache('3600') // ❌ TypeError
|
|
76
|
+
Odac.cache(3.5) // ❌ TypeError
|
|
77
|
+
```
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# 📦 Scripts & TypeScript
|
|
2
|
+
|
|
3
|
+
ODAC comes with built-in, **Zero-Config** support for frontend JavaScript and TypeScript. Write your scripts in `view/js/`, and ODAC handles transpilation, bundling, minification, and tree-shaking automatically — just like the Tailwind CSS pipeline.
|
|
4
|
+
|
|
5
|
+
## How it Works
|
|
6
|
+
|
|
7
|
+
The framework uses [esbuild](https://esbuild.github.io/) under the hood for blazing-fast builds:
|
|
8
|
+
|
|
9
|
+
1. **Development (`npm run dev`)**:
|
|
10
|
+
* ODAC watches all `.ts`, `.js`, `.mts`, and `.mjs` files in `view/js/`.
|
|
11
|
+
* Changes trigger instant rebuilds (sub-millisecond).
|
|
12
|
+
* Source maps are enabled for easy debugging.
|
|
13
|
+
|
|
14
|
+
2. **Production (`npm run build`)**:
|
|
15
|
+
* All entry points are bundled, minified, and tree-shaken.
|
|
16
|
+
* Output goes to `public/assets/js/{name}.js`.
|
|
17
|
+
|
|
18
|
+
3. **Serving (`npm start`)**:
|
|
19
|
+
* The compiled JS files are served statically. No runtime overhead.
|
|
20
|
+
|
|
21
|
+
## Quick Start
|
|
22
|
+
|
|
23
|
+
Create a file at **`view/js/app.ts`** (or `app.js` for plain JavaScript):
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
// view/js/app.ts
|
|
27
|
+
interface User {
|
|
28
|
+
id: number
|
|
29
|
+
name: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const greet = (user: User): string => {
|
|
33
|
+
return `Hello, ${user.name}!`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
37
|
+
console.log(greet({ id: 1, name: 'World' }))
|
|
38
|
+
})
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
That's it. Run `npm run dev` and ODAC compiles it to `public/assets/js/app.js`.
|
|
42
|
+
|
|
43
|
+
## Entry Points & Imports
|
|
44
|
+
|
|
45
|
+
Each file in `view/js/` becomes a separate entry point (bundle). You can use standard ES module imports between files:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
view/js/
|
|
49
|
+
├── app.ts → public/assets/js/app.js
|
|
50
|
+
├── admin.ts → public/assets/js/admin.js
|
|
51
|
+
├── _utils.ts (ignored — partial/import only)
|
|
52
|
+
└── _api.ts (ignored — partial/import only)
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
> **Convention:** Files starting with `_` (underscore) are **not** compiled as entry points. Use them as shared modules that get imported by your entry points.
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// view/js/_api.ts (shared module — not compiled on its own)
|
|
59
|
+
export const fetchUsers = async (): Promise<unknown> => {
|
|
60
|
+
const res = await fetch('/api/users')
|
|
61
|
+
return res.json()
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
// view/js/admin.ts (entry point — compiled to admin.js)
|
|
67
|
+
import { fetchUsers } from './_api'
|
|
68
|
+
|
|
69
|
+
document.addEventListener('DOMContentLoaded', async () => {
|
|
70
|
+
const users = await fetchUsers()
|
|
71
|
+
console.log(users)
|
|
72
|
+
})
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
esbuild bundles the imported code into the final output — no extra network requests.
|
|
76
|
+
|
|
77
|
+
## TypeScript or JavaScript — Your Choice
|
|
78
|
+
|
|
79
|
+
ODAC doesn't force TypeScript on you. Both work equally well:
|
|
80
|
+
|
|
81
|
+
| Extension | Behavior |
|
|
82
|
+
|-----------|----------|
|
|
83
|
+
| `.ts` | TypeScript with full type-checking support |
|
|
84
|
+
| `.js` | Plain JavaScript, passed through as-is |
|
|
85
|
+
| `.mts` | TypeScript with ES module syntax |
|
|
86
|
+
| `.mjs` | JavaScript with ES module syntax |
|
|
87
|
+
|
|
88
|
+
## HTML Integration
|
|
89
|
+
|
|
90
|
+
In your skeleton or layout files, reference the compiled output:
|
|
91
|
+
|
|
92
|
+
```html
|
|
93
|
+
<script src="/assets/js/app.js"></script>
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The default project template already includes this in `skeleton/main.html`.
|
|
97
|
+
|
|
98
|
+
## Configuration (Optional)
|
|
99
|
+
|
|
100
|
+
ODAC works with zero configuration, but you can customize the JS pipeline in `odac.json`:
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"js": {
|
|
105
|
+
"target": "es2020",
|
|
106
|
+
"minify": true,
|
|
107
|
+
"sourcemap": false,
|
|
108
|
+
"bundle": true,
|
|
109
|
+
"obfuscate": false
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
| Option | Default | Description |
|
|
115
|
+
|-------------|------------|-------------|
|
|
116
|
+
| `target` | `"es2020"` | JavaScript target version (`es2015`, `es2020`, `esnext`, etc.) |
|
|
117
|
+
| `minify` | `true` | Enable minification in production builds |
|
|
118
|
+
| `sourcemap` | `false` | Generate source maps in production (always enabled in dev) |
|
|
119
|
+
| `bundle` | `true` | Bundle imported modules into a single file |
|
|
120
|
+
| `obfuscate` | `false` | Code obfuscation level (`false`, `true`/`"low"`, `"medium"`, `"high"`) |
|
|
121
|
+
|
|
122
|
+
## Obfuscation
|
|
123
|
+
|
|
124
|
+
ODAC supports three levels of code obfuscation for production builds. Obfuscation is disabled by default and only applied during `odac build` — development mode is never obfuscated.
|
|
125
|
+
|
|
126
|
+
### Levels
|
|
127
|
+
|
|
128
|
+
| Level | What it does |
|
|
129
|
+
|----------|-------------|
|
|
130
|
+
| `false` | No obfuscation. Standard minification only. |
|
|
131
|
+
| `true` / `"low"` | Mangles properties starting with `_` (private-by-convention). |
|
|
132
|
+
| `"medium"` | Low + drops `debugger` statements + removes `console.debug` and `console.trace`. |
|
|
133
|
+
| `"high"` | Maximum — mangles `_` and `$` prefixed properties, drops all `console.*` calls and `debugger` statements. |
|
|
134
|
+
|
|
135
|
+
### Example
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
{
|
|
139
|
+
"js": {
|
|
140
|
+
"obfuscate": "medium"
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
> **Tip:** Start with `"low"` or `"medium"`. The `"high"` level mangles `$`-prefixed properties which may break code that interacts with external libraries using `$` conventions (e.g., jQuery, some frameworks). Test thoroughly before deploying with `"high"`.
|
|
146
|
+
|
|
147
|
+
## What You Get
|
|
148
|
+
|
|
149
|
+
- **TypeScript Support** — Write type-safe frontend code without any setup
|
|
150
|
+
- **Bundling** — `import`/`export` between files, everything merged into one output
|
|
151
|
+
- **Minification** — Whitespace removal, variable shortening, dead code elimination
|
|
152
|
+
- **Tree-Shaking** — Unused exports are automatically removed
|
|
153
|
+
- **Obfuscation** — Optional property mangling and console stripping (3 levels)
|
|
154
|
+
- **Source Maps** — Enabled in development for easy debugging
|
|
155
|
+
- **Multiple Entry Points** — Separate bundles for different pages/sections
|
|
156
|
+
- **Sub-millisecond Rebuilds** — esbuild's native speed keeps your dev loop instant
|
package/docs/index.json
CHANGED
|
@@ -165,6 +165,10 @@
|
|
|
165
165
|
{
|
|
166
166
|
"file": "02-sending-a-response-replying-to-the-user.md",
|
|
167
167
|
"title": "Response Object"
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"file": "03-proxy-cache.md",
|
|
171
|
+
"title": "Proxy Cache"
|
|
168
172
|
}
|
|
169
173
|
]
|
|
170
174
|
},
|
|
@@ -215,6 +219,10 @@
|
|
|
215
219
|
{
|
|
216
220
|
"file": "10-styling-and-tailwind.md",
|
|
217
221
|
"title": "Styling & Tailwind CSS"
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
"file": "12-scripts-and-typescript.md",
|
|
225
|
+
"title": "Scripts & TypeScript"
|
|
218
226
|
}
|
|
219
227
|
]
|
|
220
228
|
},
|
package/eslint.config.mjs
CHANGED
|
@@ -38,7 +38,7 @@ export default defineConfig([
|
|
|
38
38
|
},
|
|
39
39
|
{
|
|
40
40
|
files: ['template/**/*.js'],
|
|
41
|
-
ignores: ['template/public/**/*.js'],
|
|
41
|
+
ignores: ['template/public/**/*.js', 'template/view/js/**/*.js'],
|
|
42
42
|
languageOptions: {
|
|
43
43
|
globals: {
|
|
44
44
|
...globals.node,
|
|
@@ -56,6 +56,25 @@ export default defineConfig([
|
|
|
56
56
|
'prettier/prettier': 'error'
|
|
57
57
|
}
|
|
58
58
|
},
|
|
59
|
+
{
|
|
60
|
+
files: ['template/view/js/**/*.js'],
|
|
61
|
+
languageOptions: {
|
|
62
|
+
globals: {
|
|
63
|
+
...globals.browser,
|
|
64
|
+
Odac: 'readonly'
|
|
65
|
+
},
|
|
66
|
+
sourceType: 'script'
|
|
67
|
+
},
|
|
68
|
+
plugins: {
|
|
69
|
+
js,
|
|
70
|
+
prettier: prettierPlugin
|
|
71
|
+
},
|
|
72
|
+
rules: {
|
|
73
|
+
...js.configs.recommended.rules,
|
|
74
|
+
...prettierConfig.rules,
|
|
75
|
+
'prettier/prettier': 'error'
|
|
76
|
+
}
|
|
77
|
+
},
|
|
59
78
|
{
|
|
60
79
|
files: ['template/public/**/*.js'],
|
|
61
80
|
languageOptions: {
|