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 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'
@@ -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: {