odac 1.4.5 → 1.4.7

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.
@@ -1,32 +1,87 @@
1
1
  ---
2
2
  name: frontend-navigation-spa-skill
3
- description: Single-page navigation patterns in odac.js for smooth transitions, route control, and lifecycle-safe execution.
3
+ description: Single-page navigation patterns in odac.js including smart part diffing, smooth transitions, route control, and lifecycle-safe execution.
4
4
  metadata:
5
- tags: frontend, navigation, spa, ajax-navigation, page-lifecycle, transitions, view-transitions
5
+ tags: frontend, navigation, spa, ajax-navigation, page-lifecycle, transitions, view-transitions, part-diffing
6
6
  ---
7
7
 
8
8
  # Frontend Navigation & SPA Skill
9
9
 
10
10
  Smooth transitions and single-page application behavior using `odac.js`.
11
11
 
12
+ ## How AJAX Navigation Works
13
+
14
+ ODAC's navigation system is **zero-config** and **server-driven**. On first page load, the framework automatically:
15
+ 1. Detects all skeleton placeholder wrapper elements (those with `data-odac-navigate` attributes injected by the server).
16
+ 2. Registers click handlers on all internal links.
17
+ 3. Reads the initial parts state from `data-odac-parts` on the `<html>` element.
18
+
19
+ On every subsequent navigation:
20
+ 1. The client sends the current parts state to the server via `X-Odac-Parts`.
21
+ 2. The server returns only the parts that changed (`output`) plus the new parts manifest (`parts`).
22
+ 3. The client fades out and updates only the changed elements. Unchanged parts (e.g. a shared sidebar) are never touched.
23
+
12
24
  ## Rules
13
- 1. **Selection**: Enable via `Odac.action({ navigate: 'main' })`.
14
- 2. **Exclusion**: Use `data-navigate="false"` or `.no-navigate` class for full reloads.
15
- 3. **Lifecycle**: Use `load` and `page` events to run code after navigation.
16
- 4. **View Transitions**: Add `odac-transition` attribute to elements for native browser View Transition API animations. Zero-config — no JS setup required.
25
+ 1. **Zero-config auto-navigation**: Works automatically when the skeleton has `data-odac-navigate` elements. No `Odac.action()` call needed for basic navigation.
26
+ 2. **Manual setup**: Use `Odac.action({ navigate: ... })` to customize selectors, update targets, or add callbacks.
27
+ 3. **Exclusion**: Use `data-navigate="false"` attribute or `.no-navigate` class on links to force full page reloads.
28
+ 4. **Lifecycle**: Use `load` and `page` events to run code after navigation.
29
+ 5. **View Transitions**: Add `odac-transition` attribute to elements for native browser View Transition API animations.
17
30
 
18
31
  ## Patterns
32
+
33
+ ### Auto-navigation (zero-config)
34
+ No setup required. As long as the skeleton has properly wrapped placeholders, navigation is automatic.
35
+
36
+ ### Manual navigation setup
19
37
  ```javascript
20
38
  Odac.action({
21
39
  navigate: {
22
- update: 'main',
40
+ update: 'main', // CSS selector of the element to update
23
41
  on: function(page, vars) {
24
- console.log('Navigated to:', page);
42
+ console.log('Navigated to:', page)
25
43
  }
26
44
  }
27
- });
45
+ })
46
+ ```
47
+
48
+ ### Programmatic navigation
49
+ ```javascript
50
+ // Navigate to a URL programmatically
51
+ Odac.load('/docs/getting-started')
52
+
53
+ // Navigate without pushing to history
54
+ Odac.load('/docs/getting-started', null, false)
55
+
56
+ // Navigate with a callback
57
+ Odac.load('/docs/getting-started', function(page, vars) {
58
+ console.log('Loaded:', page)
59
+ })
60
+ ```
61
+
62
+ ### Excluding links from AJAX navigation
63
+ ```html
64
+ <!-- Full reload for this link -->
65
+ <a href="/logout" data-navigate="false">Logout</a>
66
+
67
+ <!-- Full reload via class -->
68
+ <a href="/external-page" class="no-navigate">External</a>
28
69
  ```
29
70
 
71
+ ## Smart Part Diffing Behavior
72
+
73
+ The client automatically handles these scenarios without any configuration:
74
+
75
+ | Scenario | Client behavior |
76
+ |----------|----------------|
77
+ | Sidebar unchanged between pages | Sidebar DOM untouched, no flicker |
78
+ | Sidebar changes between pages | Sidebar fades out → new content → fades in |
79
+ | Sidebar removed on new page | Sidebar element content cleared |
80
+ | Skeleton changes | Full page reload |
81
+ | `content` part | Always updated |
82
+
83
+ The fade animation only runs on elements that actually receive new content. Unchanged parts stay fully visible throughout the navigation.
84
+
30
85
  ## View Transitions (Native Browser API)
31
86
 
32
87
  Elements with the `odac-transition` attribute automatically use the browser's View Transition API instead of the legacy fade animation. The attribute value becomes the `view-transition-name`, enabling per-element morphing between pages.
@@ -47,7 +102,6 @@ Elements with the `odac-transition` attribute automatically use the browser's Vi
47
102
 
48
103
  ### CSS Customization
49
104
  ```css
50
- /* Target a specific element's transition */
51
105
  ::view-transition-old(hero) {
52
106
  animation: fade-out 0.3s ease;
53
107
  }
@@ -55,7 +109,6 @@ Elements with the `odac-transition` attribute automatically use the browser's Vi
55
109
  animation: fade-in 0.3s ease;
56
110
  }
57
111
 
58
- /* Slide sidebar from left */
59
112
  ::view-transition-old(sidebar) {
60
113
  animation: slide-out-left 0.25s ease;
61
114
  }
@@ -66,6 +119,6 @@ Elements with the `odac-transition` attribute automatically use the browser's Vi
66
119
 
67
120
  ### Rules
68
121
  1. Each `odac-transition` value must be unique within the page.
69
- 2. Elements persist across navigations (e.g., shared header) for smooth morphing.
122
+ 2. Elements that persist across navigations (e.g. shared header) produce smooth morphing animations.
70
123
  3. No JavaScript configuration needed — attribute-only setup.
71
124
  4. Falls back to fade animation gracefully on unsupported browsers.
@@ -0,0 +1,77 @@
1
+ ## 🚀 Quick Start
2
+
3
+ ODAC is built for speed and zero-configuration. You can bootstrap a production-ready, high-performance web application in seconds.
4
+
5
+ ### 1. Requirements
6
+
7
+ - **Node.js:** 18.0.0 or higher.
8
+ - **npm:** 8.0.0 or higher.
9
+
10
+ ---
11
+
12
+ ### 2. Initialize Your Project
13
+
14
+ The standard way to start an ODAC project is using the interactive **init** command via `npx`. Run this in your terminal:
15
+
16
+ ```bash
17
+ npx odac init my-app
18
+ ```
19
+
20
+ **What this does:**
21
+ - Creates a new folder named `my-app`.
22
+ - Copies the ODAC enterprise skeleton (controllers, routes, views, etc.).
23
+ - Initializes `package.json` with the latest ODAC framework dependency.
24
+ - Runs `npm install` automatically.
25
+
26
+ ---
27
+
28
+ ### 3. Launch Development Mode
29
+
30
+ Navigate into your project directory and start the smart development server:
31
+
32
+ ```bash
33
+ cd my-app
34
+ npm run dev
35
+ ```
36
+
37
+ Your app is now live at `http://localhost:1071` (default port).
38
+
39
+ **Features enabled in Dev Mode:**
40
+ - **Hot-reloading:** The server and cluster workers restart instantly on backend changes.
41
+ - **Zero-Config Tailwind CSS v4:** Automatically watches and compiles your styles.
42
+ - **Detailed Stack Traces:** Helps you debug errors quickly in the browser and console.
43
+
44
+ ---
45
+
46
+ ### 4. Setup AI Agent Skills
47
+
48
+ ODAC is designed to be **AI-First**. It provides pre-built "skills" (knowledge files) that teach your AI coding assistant (like Antigravity, Claude, Cursor, or Windsurf) exactly how to write ODAC-compatible code.
49
+
50
+ Run this command inside your project root:
51
+
52
+ ```bash
53
+ npx odac skills
54
+ ```
55
+
56
+ Follow the interactive prompt to sync the documentation and patterns directly into your IDE's agent configuration folder.
57
+
58
+ ---
59
+
60
+ ### 5. Essential Commands
61
+
62
+ | Command | Description |
63
+ | :--- | :--- |
64
+ | `npm run dev` | Start development server with hot-reload & styles. |
65
+ | `npm run build` | Compile and minify styles for production. |
66
+ | `npm start` | Run the application in high-performance production mode. |
67
+ | `npx odac migrate` | Run pending database migrations. |
68
+ | `npx odac skills` | Sync/Update AI Agent knowledge files. |
69
+
70
+ ---
71
+
72
+ ### 6. Next Steps
73
+
74
+ - **Define Routes:** Open `route/web.js` to see how URLs are mapped to views.
75
+ - **Project Structure:** Learn how to organize your [file/folder layout](../02-structure/01-typical-project-layout.md).
76
+ - **Build Views:** Check the `view/` directory for your HTML templates.
77
+ - **Manage Database:** Update `odac.json` to connect to PostgreSQL, MySQL, or SQLite.
@@ -42,15 +42,16 @@ Example: `skeleton/main.html`
42
42
 
43
43
  **Important Rules for Placeholders:**
44
44
 
45
- 1. **Each placeholder must be wrapped in HTML tags** - This allows AJAX to identify and update specific sections
46
- 2. **Never place placeholders directly next to each other** - Bad: `{{ HEADER }}{{ CONTENT }}`, Good: `<header>{{ HEADER }}</header><main>{{ CONTENT }}</main>`
47
- 3. **Placeholders are uppercase** - `{{ HEADER }}`, `{{ CONTENT }}`, `{{ FOOTER }}`
48
- 4. **Use semantic HTML tags** - `<header>`, `<main>`, `<footer>`, `<aside>`, `<nav>`, etc.
45
+ 1. **Each placeholder must be wrapped in its own HTML tag** This allows the AJAX navigation system to identify and independently update each section.
46
+ 2. **Never place placeholders directly next to each other** Bad: `{{ HEADER }}{{ CONTENT }}`, Good: `<header>{{ HEADER }}</header><main>{{ CONTENT }}</main>`
47
+ 3. **Placeholders are uppercase** `{{ HEADER }}`, `{{ CONTENT }}`, `{{ FOOTER }}`
48
+ 4. **Use semantic HTML tags** `<header>`, `<main>`, `<footer>`, `<aside>`, `<nav>`, etc.
49
+ 5. **Unset placeholders are automatically removed** — If a controller does not call `set('sidebar', ...)`, the `{{ SIDEBAR }}` placeholder is silently removed from the output. No stale text leaks into the HTML.
49
50
 
50
51
  **Why wrap in tags?**
51
- When using AJAX navigation, the system needs to identify which part of the page to update. HTML tags provide clear boundaries for each section.
52
+ When using AJAX navigation, the system automatically injects `data-odac-navigate` attributes onto the wrapper elements of each placeholder. This enables the smart part-diffing engine to update only the sections that actually changed between navigations.
52
53
 
53
- **Note:** Skeleton files currently support only view part placeholders (uppercase). For dynamic content like page titles, use a view part for the `<head>` section or set them in individual view files.
54
+ **Note:** Skeleton files support only view part placeholders (uppercase). For dynamic content like page titles, use a view part for the `<head>` section or place a `<title>` tag inside the content view.
54
55
 
55
56
  ### View Files
56
57
 
@@ -66,8 +67,29 @@ view/
66
67
  ├── content/
67
68
  │ ├── home.html
68
69
  │ └── about.html
70
+ ├── sidebar/
71
+ │ └── docs.html
69
72
  └── footer/
70
73
  └── main.html
71
74
  ```
72
75
 
76
+ ### Smart AJAX Navigation & Part Diffing
73
77
 
78
+ When navigating between pages via AJAX, ODAC uses a **server-driven part diffing** system to minimize unnecessary work:
79
+
80
+ - **Unchanged parts are skipped** — If `sidebar` points to the same view file on both the current and next page, the server does not re-render it and the client does not update its DOM. The sidebar stays visible and untouched.
81
+ - **Changed parts are updated** — Only parts whose view path changed are rendered and sent to the client.
82
+ - **Removed parts are cleared** — If the next page does not set a part that the current page had (e.g. navigating away from a page with a sidebar), that element's content is emptied.
83
+ - **`content` is always refreshed** — Because content views are typically URL-dependent (e.g. `/{id}`), the `content` part is always re-rendered regardless of view path.
84
+ - **Skeleton change triggers full reload** — If the next page uses a different skeleton, a full page navigation is performed automatically.
85
+
86
+ This means a shared sidebar, header, or footer that does not change between pages will never flicker or reload during AJAX navigation.
87
+
88
+ #### Force-refresh a part
89
+
90
+ If a part's view path stays the same but its rendered output changes per request (e.g. a sidebar with an active menu state), mark it with `{ refresh: true }`:
91
+
92
+ ```javascript
93
+ // This sidebar will re-render on every navigation even if the view path is unchanged
94
+ Odac.View.set('sidebar', 'docs.nav', { refresh: true })
95
+ ```
@@ -70,6 +70,17 @@ Odac.View
70
70
 
71
71
  In this case, placeholders like `{{ HEADER }}`, `{{ CONTENT }}`, `{{ FOOTER }}` in the skeleton are automatically matched with `view/home/header.html`, `view/home/content.html`, `view/home/footer.html` files.
72
72
 
73
+ ### 6. Force-Refreshing a Part
74
+
75
+ By default, ODAC's smart diffing skips re-rendering a part if its view path hasn't changed between navigations. If a part's output is request-dependent (e.g. a sidebar that highlights the active menu item), use `{ refresh: true }` to always re-render it:
76
+
77
+ ```javascript
78
+ // Sidebar re-renders on every AJAX navigation regardless of view path
79
+ Odac.View.set('sidebar', 'docs.nav', { refresh: true })
80
+ ```
81
+
82
+ This option is only relevant for AJAX navigations. Full page loads always render all parts.
83
+
73
84
  ### Setting Dynamic Page Titles and Meta Tags
74
85
 
75
86
  Since skeleton files only support view part placeholders, you have two approaches for dynamic titles:
@@ -101,8 +112,6 @@ Create a separate view part for the `<head>` section:
101
112
  </html>
102
113
  ```
103
114
 
104
- **Note:** Each placeholder is wrapped in an HTML tag so AJAX can identify and update specific sections.
105
-
106
115
  **Head View (view/head/main.html):**
107
116
  ```html
108
117
  <head>
@@ -122,15 +131,13 @@ module.exports = async function (Odac) {
122
131
  .where('id', productId)
123
132
  .first()
124
133
 
125
- // Set dynamic title and description
126
134
  Odac.pageTitle = product ? `${product.name} - My Store` : 'Product Not Found'
127
135
  Odac.pageDescription = product ? product.short_description : ''
128
-
129
136
  Odac.product = product
130
137
 
131
138
  Odac.View.set({
132
139
  skeleton: 'main',
133
- head: 'main', // Include dynamic head
140
+ head: 'main',
134
141
  header: 'main',
135
142
  content: 'product.detail',
136
143
  footer: 'main'
@@ -142,21 +149,6 @@ module.exports = async function (Odac) {
142
149
 
143
150
  Include the title tag in your content view:
144
151
 
145
- **Skeleton (skeleton/simple.html):**
146
- ```html
147
- <!DOCTYPE html>
148
- <html lang="en">
149
- <head>
150
- <meta charset="UTF-8">
151
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
152
- <link rel="stylesheet" href="/assets/css/style.css">
153
- </head>
154
- <body>
155
- {{ CONTENT }}
156
- </body>
157
- </html>
158
- ```
159
-
160
152
  **Content View (view/content/product.html):**
161
153
  ```html
162
154
  <title>{{ Odac.product.name }} - My Store</title>
@@ -167,7 +159,7 @@ Include the title tag in your content view:
167
159
  </div>
168
160
  ```
169
161
 
170
- **Note:** This approach is less clean but works for simple cases.
162
+ The AJAX navigation system automatically extracts the `<title>` tag from the rendered content and updates `document.title`.
171
163
 
172
164
  ### Important Notes
173
165
 
@@ -175,5 +167,6 @@ Include the title tag in your content view:
175
167
  - Skeleton files should be in the `skeleton/` directory, view files in the `view/` directory
176
168
  - Placeholders for view parts are written in uppercase: `{{ HEADER }}`, `{{ CONTENT }}`, etc.
177
169
  - View part names are specified in lowercase: `header`, `content`, etc.
178
- - Variables in skeleton/views are accessed via `Odac` object: `{{ Odac.variableName }}`
179
- - You don't need to use `return` from the controller, `Odac.View.set()` automatically initiates the rendering process
170
+ - Variables in views are accessed via the `Odac` object: `{{ Odac.variableName }}`
171
+ - Unset placeholders are silently removed from the final HTML output
172
+ - You don't need to call `return` from the controller — `Odac.View.set()` automatically initiates rendering
@@ -50,6 +50,10 @@ Access URL query parameters directly:
50
50
  <!-- URL: /search?q=laptop -->
51
51
  <odac get="q" />
52
52
  <!-- Output: laptop -->
53
+
54
+ <!-- Get raw query parameter (unescaped) -->
55
+ <odac get="htmlContent" raw />
56
+ <!-- Output: <b>Trusted HTML</b> -->
53
57
  ```
54
58
 
55
59
  **Note:** `<odac get>` is for URL parameters. For controller data, use `<odac var>`.
@@ -185,6 +189,7 @@ Full access to the Odac object in templates:
185
189
  | Raw HTML (inline) | `{!! x !!}` | [Variables](./03-variables.md) |
186
190
  | String | `<odac>text</odac>` | [Variables](./03-variables.md) |
187
191
  | Query Parameter | `<odac get="key" />` | [Request Data](./04-request-data.md) |
192
+ | Query Parameter Raw | `<odac get="key" raw />` | [Request Data](./04-request-data.md) |
188
193
  | Translation | `<odac translate>key</odac>` | [Translations](./07-translations.md) |
189
194
  | Translation Raw | `<odac translate raw>key</odac>` | [Translations](./07-translations.md) |
190
195
  | If | `<odac:if condition="x">` | [Conditionals](./05-conditionals.md) |
@@ -33,6 +33,19 @@ If a parameter doesn't exist, it safely returns an empty string:
33
33
 
34
34
  This prevents errors when parameters are optional.
35
35
 
36
+ ### Raw HTML Output
37
+
38
+ By default, `<odac get>` automatically escapes HTML special characters to prevent XSS attacks. If you need to output raw HTML from a query parameter (only from trusted sources), use the `raw` attribute:
39
+
40
+ ```html
41
+ <!-- URL: /page?content=%3Cb%3EHello%3C/b%3E -->
42
+
43
+ <odac get="content" raw />
44
+ <!-- Output: <b>Hello</b> -->
45
+ ```
46
+
47
+ **Security Warning:** Never use `raw` with query parameters if they can contain user-generated content. Query parameters are easily manipulated by users. Only use `raw` if you are certain the value is safe (e.g., predefined tokens).
48
+
36
49
  ### Difference: get vs var
37
50
 
38
51
  **`<odac get>` - Query Parameters (from URL):**
package/docs/index.json CHANGED
@@ -1,5 +1,15 @@
1
1
  {
2
2
  "backend": [
3
+ {
4
+ "file": "00-getting-started",
5
+ "title": "Getting Started",
6
+ "children": [
7
+ {
8
+ "file": "01-quick-start.md",
9
+ "title": "Quick Start"
10
+ }
11
+ ]
12
+ },
3
13
  {
4
14
  "file": "01-overview",
5
15
  "title": "Backend Overview",
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "odac",
3
- "description": " Next-Gen Server & Framework: Web, DNS, Mail, SSL & Monitoring in one CLI.",
3
+ "description": "Lightweight, high-performance Node.js framework for building modern web applications with built-in routing, auth, database, templating, WebSocket, i18n and zero-config Tailwind CSS.",
4
4
  "homepage": "https://odac.run",
5
5
  "author": {
6
6
  "name": "emre.red",
7
7
  "email": "mail@emre.red",
8
8
  "url": "https://emre.red"
9
9
  },
10
- "version": "1.4.5",
10
+ "version": "1.4.7",
11
11
  "license": "MIT",
12
12
  "engines": {
13
13
  "node": ">=18.0.0"
@@ -27,14 +27,38 @@
27
27
  "tailwindcss": "^4.1.18"
28
28
  },
29
29
  "optionalDependencies": {
30
+ "sharp": "^0.33.0"
31
+ },
32
+ "peerDependencies": {
30
33
  "mysql2": "^3.16.0",
31
34
  "pg": "^8.16.3",
32
35
  "redis": "^5.10.0",
33
- "sharp": "^0.33.0"
36
+ "sqlite3": "^6.0.0"
37
+ },
38
+ "peerDependenciesMeta": {
39
+ "mysql2": {
40
+ "optional": true
41
+ },
42
+ "pg": {
43
+ "optional": true
44
+ },
45
+ "redis": {
46
+ "optional": true
47
+ },
48
+ "sqlite3": {
49
+ "optional": true
50
+ }
34
51
  },
35
52
  "overrides": {
36
- "tar": "7.5.9",
37
- "cross-spawn": "7.0.6"
53
+ "brace-expansion": "5.0.5",
54
+ "cross-spawn": "7.0.6",
55
+ "handlebars": "4.7.9",
56
+ "minimatch": "10.2.4",
57
+ "test-exclude": {
58
+ "minimatch": "3.1.5"
59
+ },
60
+ "picomatch": "4.0.4",
61
+ "tar": "7.5.13"
38
62
  },
39
63
  "devDependencies": {
40
64
  "@eslint/js": "^9.39.2",
@@ -54,7 +78,7 @@
54
78
  "lint-staged": "^16.2.7",
55
79
  "prettier": "^3.8.1",
56
80
  "semantic-release": "^25.0.3",
57
- "sqlite3": "^5.1.7"
81
+ "sqlite3": "^6.0.1"
58
82
  },
59
83
  "scripts": {
60
84
  "lint": "eslint .",
package/src/Request.js CHANGED
@@ -16,6 +16,7 @@ class OdacRequest {
16
16
  isAjaxLoad = false
17
17
  ajaxLoad = null
18
18
  clientSkeleton = null
19
+ clientParts = null
19
20
  page = null
20
21
 
21
22
  constructor(id, req, res, odac) {
@@ -50,11 +51,11 @@ class OdacRequest {
50
51
  this.status(code)
51
52
  let result = {401: 'Unauthorized', 404: 'Not Found', 408: 'Request Timeout'}[code] ?? null
52
53
  if (
53
- global.Odac.Route.routes[this.route].error &&
54
- global.Odac.Route.routes[this.route].error[code] &&
55
- typeof global.Odac.Route.routes[this.route].error[code].cache === 'function'
54
+ this.#odac.Route?.routes?.[this.route]?.error &&
55
+ this.#odac.Route.routes[this.route].error[code] &&
56
+ typeof this.#odac.Route.routes[this.route].error[code].cache === 'function'
56
57
  )
57
- result = await global.Odac.Route.routes[this.route].error[code].cache(this.#odac)
58
+ result = await this.#odac.Route.routes[this.route].error[code].cache(this.#odac)
58
59
  this.end(result)
59
60
  }
60
61
 
package/src/Route.js CHANGED
@@ -150,6 +150,17 @@ class Route {
150
150
  }
151
151
  Odac.Request.isAjaxLoad = true
152
152
  Odac.Request.clientSkeleton = Odac.Request.header('X-Odac-Skeleton')
153
+
154
+ // Parse client's current part values for smart diffing
155
+ const partsHeader = Odac.Request.header('X-Odac-Parts')
156
+ if (partsHeader) {
157
+ const parts = {}
158
+ for (const entry of partsHeader.split(',')) {
159
+ const idx = entry.indexOf('=')
160
+ if (idx > 0) parts[entry.substring(0, idx)] = decodeURIComponent(entry.substring(idx + 1))
161
+ }
162
+ Odac.Request.clientParts = parts
163
+ }
153
164
  }
154
165
  if (Odac.Config?.route?.[url]) {
155
166
  // PROD CACHE HIT