odac 1.4.2 → 1.4.4

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.
Files changed (48) hide show
  1. package/.agent/rules/coding.md +2 -2
  2. package/.github/workflows/release.yml +2 -0
  3. package/.husky/pre-push +0 -1
  4. package/.kiro/steering/coding.md +27 -0
  5. package/.kiro/steering/memory.md +56 -0
  6. package/.kiro/steering/project.md +30 -0
  7. package/.kiro/steering/workflow.md +16 -0
  8. package/CHANGELOG.md +99 -0
  9. package/README.md +1 -1
  10. package/client/odac.js +92 -15
  11. package/docs/ai/skills/backend/authentication.md +7 -5
  12. package/docs/ai/skills/backend/controllers.md +24 -3
  13. package/docs/ai/skills/backend/forms.md +8 -6
  14. package/docs/ai/skills/backend/image-processing.md +93 -0
  15. package/docs/ai/skills/backend/request_response.md +2 -2
  16. package/docs/ai/skills/backend/routing.md +11 -0
  17. package/docs/ai/skills/backend/structure.md +1 -1
  18. package/docs/ai/skills/frontend/realtime.md +18 -2
  19. package/docs/backend/05-controllers/02-your-trusty-odac-assistant.md +24 -0
  20. package/docs/backend/07-views/03-template-syntax.md +18 -2
  21. package/docs/backend/07-views/11-image-optimization.md +197 -0
  22. package/package.json +5 -2
  23. package/src/Auth.js +8 -4
  24. package/src/Config.js +5 -0
  25. package/src/Database/ConnectionFactory.js +16 -0
  26. package/src/Ipc.js +3 -2
  27. package/src/Lang.js +17 -10
  28. package/src/Odac.js +1 -0
  29. package/src/Request.js +20 -20
  30. package/src/Route.js +80 -33
  31. package/src/Validator.js +5 -5
  32. package/src/View/Image.js +495 -0
  33. package/src/View.js +4 -0
  34. package/test/Auth/verifyMagicLink.test.js +281 -0
  35. package/test/Client/ws.test.js +32 -0
  36. package/test/Lang/get.test.js +37 -11
  37. package/test/Odac/image.test.js +61 -0
  38. package/test/Route/check.test.js +101 -0
  39. package/test/Route/set.test.js +102 -0
  40. package/test/View/Image/buildFilename.test.js +62 -0
  41. package/test/View/Image/hash.test.js +59 -0
  42. package/test/View/Image/isAvailable.test.js +15 -0
  43. package/test/View/Image/parse.test.js +83 -0
  44. package/test/View/Image/process.test.js +38 -0
  45. package/test/View/Image/render.test.js +117 -0
  46. package/test/View/Image/serve.test.js +56 -0
  47. package/test/View/Image/url.test.js +53 -0
  48. package/test/View/constructor.test.js +10 -0
@@ -21,7 +21,7 @@ trigger: always_on
21
21
 
22
22
  ## 4. Modern JavaScript
23
23
  - **Standard:** Use ES6+ features (Async/Await, Arrow functions, Destructuring).
24
- - **Modules:** Strict adherence to ES Modules (import/export).
24
+ - **Modules:** The current codebase uses CommonJS (`require` / `module.exports`). Follow CommonJS for existing modules to keep the codebase consistent. A gradual migration to ES Modules is planned; prefer ESM only for new, isolated packages or tools with clear interop boundaries.
25
25
 
26
26
  ## 5. Route & Session Logic
27
- - **Session Initialization:** `Odac.Request.setSession()` MUST be called before any logic that attempts to access `Odac.Request.session()`. This includes global middleware or form processing logic in `Route.js` that runs before the specific controller is resolved.
27
+ - **Session Initialization:** `Odac.Request.setSession()` MUST be called before any logic that attempts to access `Odac.Request.session()`. This includes global middleware or form processing logic in `Route.js` that runs before the specific controller is resolved.
@@ -30,6 +30,7 @@ jobs:
30
30
  with:
31
31
  node-version: '24'
32
32
  registry-url: 'https://registry.npmjs.org'
33
+ always-auth: false
33
34
 
34
35
  - name: Update npm
35
36
  run: npm install -g npm@latest
@@ -40,6 +41,7 @@ jobs:
40
41
  - name: Release
41
42
  env:
42
43
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
44
+ HUSKY: 0
43
45
  run: npx semantic-release
44
46
  - name: Get version
45
47
  id: version
package/.husky/pre-push CHANGED
@@ -1,5 +1,4 @@
1
1
  #!/usr/bin/env sh
2
- . "$(dirname -- "$0")/_/husky.sh"
3
2
 
4
3
  # Prevent pushing code with High/Critical vulnerabilities in production dependencies
5
4
  echo "🛡️ Running Production Security Gate (npm audit)..."
@@ -0,0 +1,27 @@
1
+ ---
2
+ inclusion: always
3
+ ---
4
+
5
+ # Coding Standards & Best Practices
6
+
7
+ ## 1. Testing Strategy
8
+ - **Rule:** No feature is complete without tests.
9
+ - **Goal:** Maintain stability and prevent regressions.
10
+ - **Action:** Write unit tests for logic and integration tests for API endpoints.
11
+
12
+ ## 2. Dependency Management
13
+ - **Philosophy:** "Less is more."
14
+ - **Rule:** Avoid external dependencies unless absolutely necessary.
15
+ - **Preference:** Prioritize native Node.js modules (`fs`, `http`, `crypto`, etc.) to reduce bundle size and security attack surface.
16
+
17
+ ## 3. Error Handling
18
+ - **Rule:** Fail loudly and clearly.
19
+ - **Practice:** Use custom Error classes where possible.
20
+ - **Message:** Error messages should guide the developer on how to fix the issue, not just say "Error".
21
+
22
+ ## 4. Modern JavaScript
23
+ - **Standard:** Use ES6+ features (Async/Await, Arrow functions, Destructuring).
24
+ - **Modules:** The current codebase uses CommonJS (`require` / `module.exports`). Follow CommonJS for existing modules to keep the codebase consistent. A gradual migration to ES Modules is planned; prefer ESM only for new, isolated packages or tools with clear interop boundaries.
25
+
26
+ ## 5. Route & Session Logic
27
+ - **Session Initialization:** `Odac.Request.setSession()` MUST be called before any logic that attempts to access `Odac.Request.session()`. This includes global middleware or form processing logic in `Route.js` that runs before the specific controller is resolved.
@@ -0,0 +1,56 @@
1
+ ---
2
+ inclusion: always
3
+ ---
4
+
5
+ # Project Memory & Rules
6
+
7
+ ## Configuration & Environment
8
+ - **Debug Mode Logic:** The `debug` configuration in `src/Config.js` defaults to `process.env.NODE_ENV !== 'production'`. This ensures that `odac dev` (undefined NODE_ENV) enables debug/hot-reload, while `odac start` (NODE_ENV=production) disables it to use caching.
9
+ - **Logging Strategy:**
10
+ - **Development (`debug: true`):** Enable verbose logging, hot-reloading notifications, and detailed stack traces for easier debugging.
11
+ - **Production (`debug: false`):** Minimize logging to essential operational events (Start/Stop) and Fatal Errors only. Avoid `console.log` for per-request information to preserve performance and disk space. Sensitive error details must not be exposed to the user.
12
+
13
+ ## Development Standards & Integrity
14
+ - **NO QUICK/LAZY FIXES:** Explicitly prohibited.
15
+ - Never implement truncated solutions (e.g., `substring(0, 32)` on a hash) or temporary workarounds just to make code run.
16
+ - Always implement the mathematically and architecturally correct "Enterprise-Grade" solution (e.g., using raw `Buffer` for crypto keys instead of hex strings).
17
+ - If a proper solution requires refactoring, do the refactoring. Do not patch holes.
18
+ - **Prioritize Correctness over Speed:** It is better to verify documentation or think for a minute than to output a sub-par patch.
19
+
20
+ ## Code Quality & Modern Standards
21
+ - **No Legacy Syntax:**
22
+ - **Strictly Prohibited:** The use of `var` is forbidden. Use `const` (preferred) or `let` (only if mutation is needed).
23
+ - **Variable Scope:** Ensure variables are block-scoped to prevent leakage.
24
+ - **Anti-Spaghetti Code:**
25
+ - **Fail-Fast Pattern:** Avoid deeply nested `if/else` logic. Use early returns (`return`, `break`, `continue`) to handle negative cases immediately.
26
+ - **Promise Handling:** Resolve Promises upfront (e.g., `Promise.all` or strict `await` before loops) rather than mixing `await` inside deep logic or mutating input objects.
27
+ - **Strict Equality:** Always use strict equality checks (`===`) instead of loose ones.
28
+ - **Loop Optimization:** Use labeled loops (`label: for`) for efficient control flow in nested structures. Eliminate intermediate "flag" variables (`isMatch`, `found`) by using direct `return` or `continue label`.
29
+ - **Direct Returns:** Return a value as soon as it is determined. Avoid assigning to a temporary variable (e.g. `matchedUser`) and breaking the loop, unless post-loop processing is strictly necessary.
30
+ - **Async State Safety:** When an async function depends on mutable class state (like `pendingMiddlewares`), capture that state into a local `const` *synchronously* before triggering any async operations. This prevents race conditions where the state changes before the async task consumes it.
31
+ - **Async I/O Preference:** Prefer asynchronous file system operations (`fs.promises` or `await fs.promises.*`) over synchronous methods (`fs.readFileSync`, `fs.writeFileSync`) to prevent blocking the event loop and ensure high concurrency, especially in request handling paths.
32
+
33
+ ## Dependency Management
34
+ - **Prefer Native Fetch:** Use the native `fetch` API for network requests in both Node.js (18+) and browser environments to reduce dependencies and bundle size.
35
+
36
+ ## Naming & Text Conventions
37
+ - **ODAC Casing:** Always write "ODAC" in uppercase letters when referring to the framework name in strings, comments, log messages, or user-facing text. **EXCEPTION:** The class name itself (`class Odac`) and variable references to it should remain `Odac` (PascalCase) as per code conventions.
38
+
39
+ ## Documentation Standards
40
+ - **AI Skill Front Matter:** Every file under `docs/ai/skills/**/*.md` must start with YAML front matter containing `name`, `description`, and `metadata.tags`; values must be specific to that document's topic (never copied from generic examples).
41
+
42
+ ## Testing & Validation
43
+ - **Mandatory Test Coverage:** Every new feature, method, or significant logic change MUST be accompanied by a corresponding unit or integration test.
44
+ - **Verify Correctness:** do not assume code works; prove it with a test that covers both success and failure scenarios (e.g., edge cases, error conditions).
45
+ - **Update Existing Tests:** If a feature modifies existing behavior, update the relevant tests to reflect the new logic and ensure they pass.
46
+ - **Atomic Test Structure:**
47
+ - **Directory Mapping:** Each source class/module must have its own directory under `test/` (e.g., `src/Auth.js` -> `test/Auth/`).
48
+ - **Method-Level Files:** Every public method should have its own test file within the class directory (e.g., `test/Auth/check.test.js`).
49
+ - **Sub-module Context:** Nested modules should follow the same pattern (e.g., `src/View/Form.js` -> `test/View/Form/generateFieldHtml.test.js`).
50
+ - **Isolation & Parallelism:** This structure is mandatory to leverage Jest's multi-threaded execution and ensure strict isolation between test cases.
51
+
52
+ ## Client Library (odac.js)
53
+ - **Automatic JSON Parsing:** The `#ajax` method (and by extension `odac.get`) must automatically parse the response if the `Content-Type` header contains `application/json`, even if `dataType` is not explicitly set to `json`.
54
+
55
+ ## Security Logic & Authentication
56
+ - **Enterprise Token Rotation:** The `Auth.js` system utilizes a non-blocking refresh token rotation mechanism for cookies (`odac_x`/`odac_y`). To prevent race conditions during concurrent requests in high-throughput SPAs, rotated tokens are **not** immediately deleted. Instead, their `active` timestamp is set to naturally expire in 60 seconds (Grace Period), and their `date` timestamp is set to the Unix Epoch (`new Date(0)`) as an identifier mark. Never delete rotated tokens immediately.
@@ -0,0 +1,30 @@
1
+ ---
2
+ inclusion: always
3
+ ---
4
+
5
+ # Project Context & Design Philosophy
6
+
7
+ ## Project Identity
8
+ - **Type:** Node.js Framework
9
+ - **Goal:** To provide a robust, enterprise-ready backbone for web applications.
10
+
11
+ ## Core Priorities (The "Big 3")
12
+ 1. **Enterprise-Level Security:**
13
+ - Security is not an afterthought; it is foundational.
14
+ - Default to secure settings.
15
+ - Validate all inputs.
16
+ - Sanitize outputs.
17
+ - Use established cryptographic standards.
18
+ 2. **Zero-Config:**
19
+ - The framework should work "out of the box" with sensible defaults.
20
+ - Configuration should be optional, not mandatory for getting started.
21
+ - "Convention over Configuration" is key.
22
+ 3. **High Performance:**
23
+ - Code must be optimized for throughput and low latency.
24
+ - Avoid unnecessary overhead.
25
+ - Profile and benchmark critical paths.
26
+ - Memory management is crucial.
27
+
28
+ ## Interaction Guidelines for AI
29
+ - Always assume the user wants the most efficient and secure solution unless specified otherwise.
30
+ - When suggesting architecture, prioritize scalability and maintainability.
@@ -0,0 +1,16 @@
1
+ ---
2
+ inclusion: always
3
+ ---
4
+
5
+ # Development Workflow Rules
6
+
7
+ ## 1. Quality Assurance (Linting)
8
+ - **Rule:** **ALWAYS** runs lint checks after writing or modifying code.
9
+ - **Action:** Execute the project's linting command (e.g., `npm run lint` or `eslint .`) to verify code compliance.
10
+ - **Strictness:** Do not mark a task as complete if lint errors persist. Fix them immediately.
11
+
12
+ ## 2. Documentation Hygiene
13
+ - **Rule:** Documentation must be kept in sync with code changes.
14
+ - **Trigger:** Adding a new feature, modifying an API, or changing configuration behavior.
15
+ - **Action:** Update the relevant `.md` files (README, API docs, etc.) or JSDoc comments.
16
+ - **Goal:** Ensure that the documentation is never stale and accurately reflects the current state of the codebase.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,104 @@
1
1
  ### doc
2
2
 
3
+ - Introduce WebSocket routing and controllers, update request handling, and refactor language and validator modules to use async operations.
4
+
5
+ ### ⚙️ Engine Tuning
6
+
7
+ - **view:** extract <odac:img> parsing to Image.parse() method
8
+
9
+ ### ✨ What's New
10
+
11
+ - **image:** add Odac.image() API for programmatic image URL generation
12
+ - **view:** add <odac:img> tag with on-demand image processing
13
+ - **view:** add warning message when sharp dependency is unavailable
14
+ - **view:** implement human-readable cache filenames and mtime-based cache busting
15
+
16
+ ### 📚 Documentation
17
+
18
+ - **steering:** correct module system standard to explicitly dictate CommonJS preserving architectural consistency
19
+ - **View/Image:** correct cache eviction jsdoc from LRU to FIFO reflecting O(1) performance trait
20
+
21
+ ### 🛠️ Fixes & Improvements
22
+
23
+ - **auth:** prevent duplicate login tokens during magic link verification
24
+ - Refactor `fs` module usage to `node:fs` and `fs.promises`, and update string manipulation from `substr` to `slice`.
25
+ - **request:** use global.Odac namespace for consistent access
26
+ - **route:** improve controller loading error handling with specific error messages
27
+ - **route:** update inline function reference on hot reload
28
+ - **View/Image:** clamp unrequested output formats to supported whitelist to prevent processing crashes
29
+
30
+
31
+
32
+ ---
33
+
34
+ Powered by [⚡ ODAC](https://odac.run)
35
+
36
+ ### doc
37
+
38
+ - Introduce WebSocket routing and controllers, update request handling, and refactor language and validator modules to use async operations.
39
+
40
+ ### ⚙️ Engine Tuning
41
+
42
+ - **view:** extract <odac:img> parsing to Image.parse() method
43
+
44
+ ### ✨ What's New
45
+
46
+ - **image:** add Odac.image() API for programmatic image URL generation
47
+ - **view:** add <odac:img> tag with on-demand image processing
48
+ - **view:** add warning message when sharp dependency is unavailable
49
+ - **view:** implement human-readable cache filenames and mtime-based cache busting
50
+
51
+ ### 📚 Documentation
52
+
53
+ - **steering:** correct module system standard to explicitly dictate CommonJS preserving architectural consistency
54
+ - **View/Image:** correct cache eviction jsdoc from LRU to FIFO reflecting O(1) performance trait
55
+
56
+ ### 🛠️ Fixes & Improvements
57
+
58
+ - **auth:** prevent duplicate login tokens during magic link verification
59
+ - Refactor `fs` module usage to `node:fs` and `fs.promises`, and update string manipulation from `substr` to `slice`.
60
+ - **request:** use global.Odac namespace for consistent access
61
+ - **route:** improve controller loading error handling with specific error messages
62
+ - **route:** update inline function reference on hot reload
63
+ - **View/Image:** clamp unrequested output formats to supported whitelist to prevent processing crashes
64
+
65
+
66
+
67
+ ---
68
+
69
+ Powered by [⚡ ODAC](https://odac.run)
70
+
71
+ ### ⚙️ Engine Tuning
72
+
73
+ - **client:** extract ws connection logic and fix recursive sharedworker reconnects reconnect bug
74
+ - **client:** remove redundant truthy check for websocket token
75
+ - **route:** remove useless variable assignment for decodedUrl
76
+
77
+ ### ⚡️ Performance Upgrades
78
+
79
+ - **client:** remove redundant token consumption during websocket initialization
80
+
81
+ ### 📚 Documentation
82
+
83
+ - **README:** enhance security section with detailed CSRF protection features
84
+
85
+ ### 🛠️ Fixes & Improvements
86
+
87
+ - **client:** enhance websocket reconnection logic with attempt tracking and timer management
88
+ - **client:** preserve existing websocket subprotocols & add try/catch layer to token provider
89
+ - **route:** improve URL decoding and public path handling for file requests
90
+ - **route:** sanitize decoded URL and improve public path validation
91
+ - **route:** use robust path.extname instead of string splitting for mime type resolution
92
+ - **websocket:** implement token provider for dynamic token handling on reconnect
93
+
94
+
95
+
96
+ ---
97
+
98
+ Powered by [⚡ ODAC](https://odac.run)
99
+
100
+ ### doc
101
+
3
102
  - **forms:** update backend and frontend forms documentation with practical usage patterns and improved descriptions
4
103
  - **validation:** enhance backend validation documentation with detailed usage patterns and examples
5
104
 
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  * 🎨 **Built-in Tailwind CSS:** Zero-config integration with Tailwind CSS v4. Automatic compilation and optimization out of the box.
10
10
  * 🔗 **Powerful Routing:** Create clean, custom URLs and manage infinite pages with a flexible routing system.
11
11
  * ✨ **Seamless SPA Experience:** Automatic AJAX handling for forms and page transitions eliminates the need for complex client-side code.
12
- * 🛡️ **Built-in Security:** Automatic CSRF protection and secure default headers keep your application safe.
12
+ * 🛡️ **Built-in Security:** Enterprise-grade security out of the box. Includes secure default headers and a **Multi-tab Safe, Single-Use CSRF Protection (Nonce)**. Tokens self-replenish in the background, ensuring maximum defense without ever interrupting the user experience.
13
13
  * 🔐 **Authentication:** Ready-to-use session management with enterprise-grade **Refresh Token Rotation**, secure password hashing, and authentication helpers.
14
14
  * 🗄️ **Database Agnostic:** Integrated support for major databases (PostgreSQL, MySQL, SQLite) and Redis via Knex.js.
15
15
  * 🌍 **i18n Support:** Native multi-language support to help you reach a global audience.
package/client/odac.js CHANGED
@@ -7,10 +7,12 @@ class OdacWebSocket {
7
7
  #reconnectAttempts = 0
8
8
  #handlers = {}
9
9
  #isClosed = false
10
+ #tokenProvider = null
10
11
 
11
12
  constructor(url, protocols = [], options = {}) {
12
13
  this.#url = url
13
14
  this.#protocols = protocols
15
+ this.#tokenProvider = options.tokenProvider || null
14
16
  this.#options = {
15
17
  autoReconnect: true,
16
18
  reconnectDelay: 3000,
@@ -23,6 +25,19 @@ class OdacWebSocket {
23
25
  connect() {
24
26
  if (this.#isClosed) return
25
27
 
28
+ if (this.#tokenProvider) {
29
+ try {
30
+ const freshToken = this.#tokenProvider()
31
+ if (freshToken) {
32
+ let current = Array.isArray(this.#protocols) ? this.#protocols : this.#protocols ? [this.#protocols] : []
33
+ this.#protocols = current.filter(p => !p.startsWith('odac-token-'))
34
+ this.#protocols.push(`odac-token-${freshToken}`)
35
+ }
36
+ } catch (e) {
37
+ console.error('Odac WebSocket tokenProvider error:', e)
38
+ }
39
+ }
40
+
26
41
  this.#socket = this.#protocols.length > 0 ? new WebSocket(this.#url, this.#protocols) : new WebSocket(this.#url)
27
42
 
28
43
  this.#socket.onopen = () => {
@@ -924,12 +939,9 @@ if (typeof window !== 'undefined') {
924
939
  const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
925
940
  const wsUrl = `${protocol}//${window.location.host}${path}`
926
941
  const protocols = []
927
- if (token) {
928
- const csrfToken = this.token()
929
- if (csrfToken) protocols.push(`odac-token-${csrfToken}`)
930
- }
942
+ const tokenProvider = token ? () => this.token() : null
931
943
 
932
- return new OdacWebSocket(wsUrl, protocols, options)
944
+ return new OdacWebSocket(wsUrl, protocols, {...options, tokenProvider})
933
945
  }
934
946
 
935
947
  #createSharedWebSocket(path, options) {
@@ -966,6 +978,9 @@ if (typeof window !== 'undefined') {
966
978
  case 'error':
967
979
  emit('error', data)
968
980
  break
981
+ case 'requestToken':
982
+ worker.port.postMessage({type: 'provideToken', token: this.token()})
983
+ break
969
984
  }
970
985
  }
971
986
 
@@ -1031,6 +1046,67 @@ if (typeof window !== 'undefined') {
1031
1046
 
1032
1047
  const broadcast = (type, data) => ports.forEach(port => port.postMessage({type, data}))
1033
1048
 
1049
+ let wsConfig = null
1050
+ let reconnectAttempts = 0
1051
+ let reconnectTimer = null
1052
+
1053
+ const requestTokenFromPort = () => {
1054
+ return new Promise(resolve => {
1055
+ const firstPort = ports.values().next().value
1056
+ if (!firstPort) return resolve(null)
1057
+
1058
+ let timeoutTimer = null
1059
+
1060
+ const handler = event => {
1061
+ if (event.data.type === 'provideToken') {
1062
+ clearTimeout(timeoutTimer)
1063
+ firstPort.removeEventListener('message', handler)
1064
+ resolve(event.data.token)
1065
+ }
1066
+ }
1067
+
1068
+ timeoutTimer = setTimeout(() => {
1069
+ firstPort.removeEventListener('message', handler)
1070
+ resolve(null)
1071
+ }, 5000)
1072
+
1073
+ firstPort.addEventListener('message', handler)
1074
+ firstPort.postMessage({type: 'requestToken'})
1075
+ })
1076
+ }
1077
+
1078
+ const connectSocket = protocols => {
1079
+ if (!wsConfig || ports.size === 0) return
1080
+ const wsUrl = wsConfig.protocol + '//' + wsConfig.host + wsConfig.path
1081
+ socket = new OdacWebSocket(wsUrl, protocols, {
1082
+ ...wsConfig.options,
1083
+ tokenProvider: null
1084
+ })
1085
+ socket.on('open', () => {
1086
+ reconnectAttempts = 0
1087
+ broadcast('open')
1088
+ })
1089
+ socket.on('message', data => broadcast('message', data))
1090
+ socket.on('close', e => {
1091
+ broadcast('close', {code: e?.code, reason: e?.reason, wasClean: e?.wasClean})
1092
+ const maxAttempts = wsConfig.options.maxReconnectAttempts || 10
1093
+ if (wsConfig && wsConfig.options.autoReconnect !== false && ports.size > 0 && reconnectAttempts < maxAttempts) {
1094
+ if (socket) {
1095
+ socket.close()
1096
+ socket = null
1097
+ }
1098
+ reconnectAttempts++
1099
+ reconnectTimer = setTimeout(() => {
1100
+ requestTokenFromPort().then(freshToken => {
1101
+ if (!freshToken || ports.size === 0) return
1102
+ connectSocket(['odac-token-' + freshToken])
1103
+ })
1104
+ }, wsConfig.options.reconnectDelay || 1000)
1105
+ }
1106
+ })
1107
+ socket.on('error', e => broadcast('error', {message: e?.message || 'WebSocket error'}))
1108
+ }
1109
+
1034
1110
  self.onconnect = e => {
1035
1111
  const port = e.ports[0]
1036
1112
  ports.add(port)
@@ -1041,15 +1117,10 @@ if (typeof window !== 'undefined') {
1041
1117
  switch (type) {
1042
1118
  case 'connect':
1043
1119
  if (!socket) {
1044
- const wsUrl = protocol + '//' + host + path
1120
+ wsConfig = {host, path, protocol, options}
1045
1121
  const protocols = token ? ['odac-token-' + token] : []
1046
- socket = new OdacWebSocket(wsUrl, protocols, options)
1047
- socket.on('open', () => broadcast('open'))
1048
- socket.on('message', data => broadcast('message', data))
1049
- socket.on('close', e => broadcast('close', {code: e?.code, reason: e?.reason, wasClean: e?.wasClean}))
1050
- socket.on('error', e => broadcast('error', {message: e?.message || 'WebSocket error'}))
1122
+ connectSocket(protocols)
1051
1123
  } else if (socket.connected) {
1052
- // If already connected, notify the new port immediately
1053
1124
  port.postMessage({type: 'open'})
1054
1125
  }
1055
1126
  break
@@ -1058,9 +1129,15 @@ if (typeof window !== 'undefined') {
1058
1129
  break
1059
1130
  case 'close':
1060
1131
  ports.delete(port)
1061
- if (ports.size === 0 && socket) {
1062
- socket.close()
1063
- socket = null
1132
+ if (ports.size === 0) {
1133
+ if (reconnectTimer) {
1134
+ clearTimeout(reconnectTimer)
1135
+ reconnectTimer = null
1136
+ }
1137
+ if (socket) {
1138
+ socket.close()
1139
+ socket = null
1140
+ }
1064
1141
  }
1065
1142
  break
1066
1143
  }
@@ -59,13 +59,15 @@ const val = Odac.session('key');
59
59
  Odac.session('key', null);
60
60
  ```
61
61
 
62
- ### 4. Realtime Hubs (WebSockets)
62
+ ### 4. Realtime Broadcasting (Ipc)
63
63
  ```javascript
64
- // Broadcast to everyone in a room
65
- Odac.Hub.to('lobby').send('chat_message', { text: 'Hello!' });
64
+ // Broadcast to everyone subscibed in this worker cluster
65
+ await Odac.Ipc.publish('lobby', { type: 'chat', text: 'Hello!' });
66
66
 
67
- // Targeted user broadcast
68
- Odac.Hub.user(userId).send('notification', { text: 'New follower' });
67
+ // In your WS handler, listen for Ipc messages
68
+ Odac.Ipc.subscribe('lobby', (msg) => {
69
+ Odac.ws.send(msg);
70
+ });
69
71
  ```
70
72
 
71
73
  ## Security Best Practices
@@ -32,7 +32,7 @@ class User {
32
32
 
33
33
  // GET /users/{id}
34
34
  async show(Odac) {
35
- const id = Odac.Request.input('id');
35
+ const id = await Odac.request('id');
36
36
  const user = await Odac.Service.get('User').find(id);
37
37
  return Odac.return(user);
38
38
  }
@@ -48,7 +48,28 @@ class User {
48
48
  module.exports = User;
49
49
  ```
50
50
 
51
- ### 2. Simple Function-Based Controller
51
+ ### 2. WebSocket Controller
52
+ ```javascript
53
+ // controller/ws/Chat.js
54
+ module.exports = function(Odac) {
55
+ const ws = Odac.ws; // The WebSocket client instance
56
+
57
+ ws.on('message', (data) => {
58
+ console.log('Received:', data);
59
+ // Broadcast to everyone else
60
+ ws.broadcast({ sender: ws.id, text: data.text });
61
+ });
62
+
63
+ ws.on('close', () => {
64
+ console.log('Client disconnected:', ws.id);
65
+ });
66
+
67
+ // Optional: send welcome message
68
+ ws.send({ type: 'info', message: 'Connected to Chat!' });
69
+ };
70
+ ```
71
+
72
+ ### 3. Simple Function-Based Controller
52
73
  ```javascript
53
74
  // controller/Home.js
54
75
  module.exports = function(Odac) {
@@ -56,7 +77,7 @@ module.exports = function(Odac) {
56
77
  };
57
78
  ```
58
79
 
59
- ### 3. Usage in Routes
80
+ ### 4. Usage in Routes
60
81
  ```javascript
61
82
  // route/web.js
62
83
  Odac.Route.get('/users', 'User@index');
@@ -109,12 +109,14 @@ module.exports = class Contact {
109
109
 
110
110
  ## Patterns
111
111
  ```javascript
112
- // Access validated data in action-driven custom form
113
- Odac.Route.class.post('/contact', class Contact {
112
+ // Custom form action in class/Contact.js
113
+ module.exports = class Contact {
114
114
  async submit(form) {
115
- const {email, message} = form.data
116
- if (!email || !message) return form.error('_odac_form', 'Missing input')
117
- return form.success('Saved successfully')
115
+ const { email, message } = form.data;
116
+ if (!email || !message) return form.error('email', 'Required fields missing');
117
+
118
+ // Process data...
119
+ return form.success('Message received!');
118
120
  }
119
- })
121
+ }
120
122
  ```
@@ -0,0 +1,93 @@
1
+ ---
2
+ name: backend-image-processing-skill
3
+ description: ODAC on-demand image optimization via the odac:img template tag with automatic resize, format conversion, and aggressive caching.
4
+ metadata:
5
+ tags: backend, image, optimization, sharp, webp, resize, template, view
6
+ ---
7
+
8
+ # Backend Image Processing Skill
9
+
10
+ ODAC provides on-demand image processing through the `<odac:img>` template tag. Images are automatically resized, converted to modern formats (WebP, AVIF), and cached to disk for sub-millisecond subsequent responses.
11
+
12
+ ## Architectural Approach
13
+
14
+ Processing happens at render time via `src/View/Image.js`. The first request triggers sharp-based transformation; all subsequent requests serve from `storage/.cache/img/`. Sharp is an optional dependency — when absent, `<odac:img>` gracefully degrades to a standard `<img>` tag.
15
+
16
+ ## Core Rules
17
+
18
+ 1. **Optional Dependency**: Sharp must be installed separately (`npm install sharp`). The framework functions without it.
19
+ 2. **Security**: Path traversal is blocked. Only files under `public/` are processable.
20
+ 3. **Cache**: Processed images are stored in `storage/.cache/img/` with human-readable filenames (`{name}-{dimension}-{hash}.{ext}`) for easy debugging and CDN log analysis.
21
+ 4. **Max Dimension**: 4096px cap prevents resource exhaustion from oversized requests.
22
+
23
+ ## Reference Patterns
24
+
25
+ ### 1. Basic Resize + Format Conversion
26
+ ```html
27
+ <odac:img src="/images/hero.jpg" width="800" height="600" format="webp"/>
28
+ ```
29
+
30
+ ### 2. Format Conversion Only (No Resize)
31
+ ```html
32
+ <odac:img src="/images/logo.png" format="webp"/>
33
+ ```
34
+
35
+ ### 3. Custom Quality
36
+ ```html
37
+ <odac:img src="/images/banner.jpg" width="1200" format="webp" quality="90"/>
38
+ ```
39
+
40
+ ### 4. Dynamic Source from Controller
41
+ ```html
42
+ <odac:img src="{{ product.image }}" width="200" height="200" format="webp" alt="Product"/>
43
+ ```
44
+
45
+ ### 5. With Standard HTML Attributes
46
+ ```html
47
+ <odac:img src="/images/avatar.jpg" width="64" height="64" format="webp"
48
+ alt="User avatar" class="rounded-full" loading="lazy" decoding="async"/>
49
+ ```
50
+
51
+ ## Supported Attributes
52
+
53
+ | Attribute | Type | Description |
54
+ |-----------|------|-------------|
55
+ | `src` | string | Source path relative to `public/` (required) |
56
+ | `width` | number | Target width in pixels |
57
+ | `height` | number | Target height in pixels |
58
+ | `format` | string | Output format: `webp`, `avif`, `png`, `jpeg`, `tiff` |
59
+ | `quality` | number | Compression quality 1-100 (default: 80) |
60
+ | All others | — | Passed through to the `<img>` tag as-is |
61
+
62
+ ## Configuration
63
+
64
+ Default settings in `odac.json`:
65
+ ```json
66
+ {
67
+ "image": {
68
+ "quality": 80,
69
+ "maxDimension": 4096,
70
+ "format": "webp"
71
+ }
72
+ }
73
+ ```
74
+
75
+ ## Best Practices
76
+
77
+ - Prefer `webp` format for the best size/quality ratio across modern browsers.
78
+ - Set explicit `width` and `height` to prevent layout shift (CLS).
79
+ - Use `loading="lazy"` for below-the-fold images.
80
+ - Keep source images at high resolution in `public/`; let ODAC handle the downsizing.
81
+
82
+ ## Programmatic API (`Odac.image()`)
83
+
84
+ For cases where a raw URL is needed instead of an `<img>` tag (div backgrounds, JSON APIs, mail templates, cron jobs):
85
+
86
+ ```js
87
+ const url = await Odac.image('/images/hero.jpg', { width: 800, format: 'webp' })
88
+ // → "/_odac/img/hero-800-a1b2c3d4.webp"
89
+ ```
90
+
91
+ - Available on every `Odac` instance (controller, cron, middleware).
92
+ - Same processing pipeline and cache as `<odac:img>`.
93
+ - Returns original `src` as fallback when sharp is unavailable.
@@ -18,7 +18,7 @@ Handling incoming data and sending structured responses.
18
18
  3. **Returning Data**:
19
19
  - `return { ... }`: Returns JSON.
20
20
  - `return Odac.return({ ... })`: Explicit JSON return.
21
- - `Odac.Response.header('Key', 'Value')`: Set custom headers.
21
+ - `Odac.Request.header('Key', 'Value')`: Set custom headers.
22
22
 
23
23
  ## Reference Patterns
24
24
  ### 1. Unified Request Handling
@@ -36,7 +36,7 @@ module.exports = async function(Odac) {
36
36
  ### 2. Header and Status Management
37
37
  ```javascript
38
38
  module.exports = function(Odac) {
39
- Odac.Response.header('Content-Type', 'text/plain');
39
+ Odac.Request.header('Content-Type', 'text/plain');
40
40
  return "Raw text response";
41
41
  };
42
42
  ```