odac 1.4.6 → 1.4.8
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 +37 -0
- package/client/odac.js +1 -1
- package/docs/ai/README.md +1 -1
- package/docs/ai/skills/SKILL.md +1 -1
- package/docs/ai/skills/backend/database.md +103 -12
- package/docs/ai/skills/backend/ipc.md +71 -12
- package/docs/ai/skills/backend/views.md +6 -1
- package/docs/backend/00-getting-started/01-quick-start.md +77 -0
- package/docs/backend/07-views/03-template-syntax.md +5 -0
- package/docs/backend/07-views/04-request-data.md +13 -0
- package/docs/backend/08-database/05-write-behind-cache.md +230 -0
- package/docs/backend/13-utilities/02-ipc.md +117 -0
- package/docs/index.json +10 -0
- package/package.json +1 -1
- package/src/Database/WriteBuffer.js +605 -0
- package/src/Database.js +32 -1
- package/src/Ipc.js +343 -81
- package/src/Odac.js +2 -1
- package/src/Storage.js +4 -2
- package/src/View.js +33 -18
- package/test/Database/WriteBuffer/_recoverFromCheckpoint.test.js +207 -0
- package/test/Database/WriteBuffer/buffer.test.js +143 -0
- package/test/Database/WriteBuffer/flush.test.js +192 -0
- package/test/Database/WriteBuffer/get.test.js +72 -0
- package/test/Database/WriteBuffer/increment.test.js +118 -0
- package/test/Database/WriteBuffer/update.test.js +178 -0
- package/test/Ipc/hset.test.js +59 -0
- package/test/Ipc/incrBy.test.js +65 -0
- package/test/Ipc/lock.test.js +62 -0
- package/test/Ipc/rpush.test.js +68 -0
- package/test/Ipc/sadd.test.js +68 -0
- package/test/View/addNavigateAttribute.test.js +53 -0
- package/test/View/print.test.js +45 -1
- package/test/View/tags.test.js +132 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,40 @@
|
|
|
1
|
+
### ✨ What's New
|
|
2
|
+
|
|
3
|
+
- **database:** add Write-Behind Cache with counter, update, and batch insert buffering
|
|
4
|
+
- **ipc,database:** extend Ipc with atomic ops and delegate WriteBuffer state to Ipc layer
|
|
5
|
+
|
|
6
|
+
### 🛠️ Fixes & Improvements
|
|
7
|
+
|
|
8
|
+
- atomic queue drain, transaction-safe flush, hgetall clone & unhandled rejection
|
|
9
|
+
- **odac:** ensure proper token query parameter handling in get method
|
|
10
|
+
- **storage:** ensure synchronous operations for put and remove methods
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
Powered by [⚡ ODAC](https://odac.run)
|
|
17
|
+
|
|
18
|
+
### ⚙️ Engine Tuning
|
|
19
|
+
|
|
20
|
+
- **test:** remove unused fsPromises and standardize async I/O in View tests
|
|
21
|
+
|
|
22
|
+
### 📚 Documentation
|
|
23
|
+
|
|
24
|
+
- add quick start guide and register in documentation index
|
|
25
|
+
|
|
26
|
+
### 🛠️ Fixes & Improvements
|
|
27
|
+
|
|
28
|
+
- add raw attribute support to `<odac get>` tag for unescaped HTML output
|
|
29
|
+
- improve title extraction logic and optimize data-odac-navigate attribute injection in View rendering
|
|
30
|
+
- prevent data-odac-navigate injection into closing HTML tags
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
Powered by [⚡ ODAC](https://odac.run)
|
|
37
|
+
|
|
1
38
|
### ⚙️ Engine Tuning
|
|
2
39
|
|
|
3
40
|
- replace global Odac reference with private instance and update dependency overrides for security hardening
|
package/client/odac.js
CHANGED
package/docs/ai/README.md
CHANGED
|
@@ -24,7 +24,7 @@ Detailed instructions are organized into Backend and Frontend categories:
|
|
|
24
24
|
- `backend/config.md`: Configuration management and environment variables.
|
|
25
25
|
- `backend/controllers.md`: Request handling and Class-Based Controllers.
|
|
26
26
|
- `backend/cron.md`: Scheduled background tasks and automation.
|
|
27
|
-
- `backend/database.md`: High-performance query builder usage.
|
|
27
|
+
- `backend/database.md`: High-performance query builder usage, Write-Behind Cache (buffered counters, last-write-wins updates, batch inserts).
|
|
28
28
|
- `backend/forms.md`: Form processing and validation logic.
|
|
29
29
|
- `backend/ipc.md`: Inter-Process Communication and state sharing.
|
|
30
30
|
- `backend/mail.md`: Transactional email sending.
|
package/docs/ai/skills/SKILL.md
CHANGED
|
@@ -18,7 +18,7 @@ Read the specific rule files based on whether you are working on the Backend or
|
|
|
18
18
|
- [backend/config.md](backend/config.md) - Configuration management and environment variables
|
|
19
19
|
- [backend/controllers.md](backend/controllers.md) - Request handling and Class-Based Controllers
|
|
20
20
|
- [backend/cron.md](backend/cron.md) - Scheduled background tasks and automation
|
|
21
|
-
- [backend/database.md](backend/database.md) - Query Builder and DB best practices
|
|
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
23
|
- [backend/ipc.md](backend/ipc.md) - Inter-Process Communication and state sharing
|
|
24
24
|
- [backend/mail.md](backend/mail.md) - Transactional email sending
|
|
@@ -1,30 +1,32 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: backend-database-skill
|
|
3
|
-
description: High-performance ODAC database querying patterns
|
|
3
|
+
description: High-performance ODAC database querying patterns including the Write-Behind Cache for counters, last-write-wins updates, and buffered batch inserts.
|
|
4
4
|
metadata:
|
|
5
|
-
tags: backend, database, query-builder, sql,
|
|
5
|
+
tags: backend, database, query-builder, sql, write-behind-cache, performance, security
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Backend Database Skill
|
|
9
9
|
|
|
10
|
-
High-performance database operations using the ODAC Query Builder.
|
|
10
|
+
High-performance database operations using the ODAC Query Builder and Write-Behind Cache.
|
|
11
11
|
|
|
12
12
|
## Principles
|
|
13
13
|
1. **Directness**: Avoid ORM overhead. Use fluent Query Builder.
|
|
14
14
|
2. **Safety**: Always use parameterized queries (built-in).
|
|
15
15
|
3. **Efficiency**: Index foreign keys. No `SELECT *`.
|
|
16
|
+
4. **Write Coalescing**: Use `buffer` for high-frequency writes to avoid DB saturation.
|
|
16
17
|
|
|
17
|
-
## Patterns
|
|
18
|
+
## Query Builder Patterns
|
|
18
19
|
```javascript
|
|
19
|
-
const user = await Odac.DB.
|
|
20
|
+
const user = await Odac.DB.users
|
|
20
21
|
.select('id', 'name', 'email')
|
|
21
22
|
.where('status', 'active')
|
|
22
|
-
.first()
|
|
23
|
+
.first()
|
|
23
24
|
|
|
24
|
-
await Odac.DB.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
await Odac.DB.posts.insert({title: 'Hello', user_id: 1})
|
|
26
|
+
|
|
27
|
+
await Odac.DB.users.where('id', 1).update({last_login: new Date()})
|
|
28
|
+
|
|
29
|
+
const count = await Odac.DB.users.where('active', true).count() // → Number
|
|
28
30
|
```
|
|
29
31
|
|
|
30
32
|
## ID Strategy (NanoID)
|
|
@@ -43,7 +45,96 @@ module.exports = {
|
|
|
43
45
|
}
|
|
44
46
|
|
|
45
47
|
// ID is generated automatically
|
|
46
|
-
await Odac.DB.
|
|
48
|
+
await Odac.DB.posts.insert({title: 'Hello'})
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Write-Behind Cache (`buffer`)
|
|
52
|
+
|
|
53
|
+
The `buffer` API routes all writes through `Odac.Ipc`. The active IPC driver determines the scaling model — no code changes required when switching drivers.
|
|
54
|
+
|
|
55
|
+
- **`memory` driver** (default): Primary process holds all state; workers communicate via cluster IPC.
|
|
56
|
+
- **`redis` driver**: State lives in Redis — works transparently across multiple servers.
|
|
57
|
+
|
|
58
|
+
### When to suggest `buffer`
|
|
59
|
+
|
|
60
|
+
Propose the Write-Behind Cache when the user's write operation meets **all three** of these criteria:
|
|
61
|
+
1. **High frequency** — the same column or row is written on many requests (counters, timestamps, last-seen values).
|
|
62
|
+
2. **Eventual consistency is acceptable** — a few seconds' delay before the value reaches the DB is fine.
|
|
63
|
+
3. **Not safety-critical** — losing the buffered value in a crash has no financial, legal, or user-facing consequence.
|
|
64
|
+
|
|
65
|
+
**Typical candidates:**
|
|
66
|
+
- Page / post / download view counters (`increment`)
|
|
67
|
+
- Like, upvote, share counts (`increment`)
|
|
68
|
+
- User last-active timestamp, last IP (`update`)
|
|
69
|
+
- Analytics events, audit logs, activity streams (`insert`)
|
|
70
|
+
- Score accumulators, play counts, rating tallies (`increment`)
|
|
71
|
+
|
|
72
|
+
**Do NOT suggest `buffer` for:**
|
|
73
|
+
- Payments, charges, refunds, or any monetary operation
|
|
74
|
+
- Order creation, inventory decrements
|
|
75
|
+
- Anything the same request needs to read back immediately
|
|
76
|
+
- Inserts that return a generated ID the caller uses
|
|
77
|
+
|
|
78
|
+
When in doubt, use a direct DB call.
|
|
79
|
+
|
|
80
|
+
**Three operation types:**
|
|
81
|
+
|
|
82
|
+
### 1. Counter Increment (Write Coalescing)
|
|
83
|
+
Accumulates deltas — multiple increments merge into one `UPDATE col = col + delta`.
|
|
84
|
+
`get()` returns `base + pending delta` (always current, no DB read needed).
|
|
85
|
+
```javascript
|
|
86
|
+
// Increment — returns current total (DB base + buffered delta)
|
|
87
|
+
const views = await Odac.DB.posts.buffer.where(postId).increment('views')
|
|
88
|
+
const likes = await Odac.DB.posts.buffer.where(postId).increment('likes', 5)
|
|
89
|
+
|
|
90
|
+
// Read buffered counter (no extra DB round-trip)
|
|
91
|
+
const current = await Odac.DB.posts.buffer.where(postId).get('views')
|
|
92
|
+
|
|
93
|
+
// Composite key
|
|
94
|
+
await Odac.DB.post_stats.buffer.where({post_id: 1, date: '2026-04-01'}).increment('views')
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### 2. Last-Write-Wins Update (Field Coalescing)
|
|
98
|
+
Multiple updates to the same row merge column maps — 50 requests = 1 `UPDATE` at flush.
|
|
99
|
+
```javascript
|
|
100
|
+
// Columns are merged per row: first update + second update = single UPDATE at flush
|
|
101
|
+
await Odac.DB.users.buffer.where(userId).update({active_date: new Date()})
|
|
102
|
+
await Odac.DB.users.buffer.where(userId).update({last_ip: req.ip})
|
|
103
|
+
// → UPDATE users SET active_date = ?, last_ip = ? WHERE id = ? (one query)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 3. Batch Insert (Queue)
|
|
107
|
+
Rows accumulate in memory; flushed in chunks of 1000. Auto-flushes when `maxQueueSize` is reached.
|
|
108
|
+
```javascript
|
|
109
|
+
await Odac.DB.activity_log.buffer.insert({user_id: userId, action: 'page_view', meta: url})
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Manual Flush
|
|
113
|
+
```javascript
|
|
114
|
+
await Odac.DB.posts.buffer.flush() // flush this table only
|
|
115
|
+
await Odac.DB.buffer.flush() // flush everything
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Write-Behind Cache — Key Rules
|
|
119
|
+
1. **Ipc-Backed**: All buffer state goes through `Odac.Ipc`. Memory driver = Primary holds state; Redis driver = Redis holds state.
|
|
120
|
+
2. **Horizontally Scalable**: With Redis driver, multiple servers share the same buffer state. Distributed lock (`Ipc.lock`) prevents duplicate flushes.
|
|
121
|
+
3. **Crash-Safe (memory)**: LMDB checkpoint written every 30s. On restart, pending data is recovered and flushed before serving traffic.
|
|
122
|
+
4. **Crash-Safe (redis)**: Redis persistence provides durability. LMDB checkpoints are skipped.
|
|
123
|
+
5. **get() is authoritative**: Always returns `DB base + buffered delta`. Never stale.
|
|
124
|
+
6. **Flush on shutdown**: `Database.close()` triggers a final flush automatically — no data loss on graceful shutdown.
|
|
125
|
+
7. **Error resilience**: If a flush fails, data is retained in Ipc for the next cycle. Never lost silently.
|
|
126
|
+
8. **NEVER use buffer for safety-critical writes**: Payment records, order confirmations, balance changes, inventory decrements — anything where data loss has real-world consequences MUST use direct DB transactions. The buffer does not guarantee delivery before a crash.
|
|
127
|
+
|
|
128
|
+
## Configuration (`odac.json`)
|
|
129
|
+
```json
|
|
130
|
+
{
|
|
131
|
+
"buffer": {
|
|
132
|
+
"flushInterval": 5000,
|
|
133
|
+
"checkpointInterval": 30000,
|
|
134
|
+
"maxQueueSize": 10000,
|
|
135
|
+
"primaryKey": "id"
|
|
136
|
+
}
|
|
137
|
+
}
|
|
47
138
|
```
|
|
48
139
|
|
|
49
140
|
## Migration Awareness
|
|
@@ -53,4 +144,4 @@ await Odac.DB.table('posts').insert({title: 'Hello'});
|
|
|
53
144
|
4. **Indexes**: Keep index definitions in schema so add/drop is managed automatically.
|
|
54
145
|
5. **Data Changes**: Use `migration/*.js` only for one-time data transformation.
|
|
55
146
|
|
|
56
|
-
See: [migrations.md](./migrations.md)
|
|
147
|
+
See: [migrations.md](./migrations.md) | [write-behind-cache user docs](../../../backend/08-database/05-write-behind-cache.md)
|
|
@@ -24,25 +24,81 @@ IPC abstracts the underlying driver, allowing seamless transition between `memor
|
|
|
24
24
|
### 1. Key-Value Storage
|
|
25
25
|
```javascript
|
|
26
26
|
// Set value with optional TTL (seconds)
|
|
27
|
-
await Odac.Ipc.set('maintenance_mode', true)
|
|
28
|
-
await Odac.Ipc.set('cache_key', {
|
|
27
|
+
await Odac.Ipc.set('maintenance_mode', true)
|
|
28
|
+
await Odac.Ipc.set('cache_key', {data: 123}, 60)
|
|
29
29
|
|
|
30
30
|
// Get value
|
|
31
|
-
const status = await Odac.Ipc.get('maintenance_mode')
|
|
31
|
+
const status = await Odac.Ipc.get('maintenance_mode')
|
|
32
32
|
|
|
33
33
|
// Delete value
|
|
34
|
-
await Odac.Ipc.del('maintenance_mode')
|
|
34
|
+
await Odac.Ipc.del('maintenance_mode')
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
### 2.
|
|
37
|
+
### 2. Atomic Counters
|
|
38
38
|
```javascript
|
|
39
|
-
//
|
|
39
|
+
// Increment / decrement a numeric key (returns new value)
|
|
40
|
+
await Odac.Ipc.incrBy('page:views', 1) // → 1
|
|
41
|
+
await Odac.Ipc.incrBy('page:views', 5) // → 6
|
|
42
|
+
await Odac.Ipc.decrBy('page:views', 2) // → 4
|
|
43
|
+
|
|
44
|
+
// Read current value
|
|
45
|
+
const views = await Odac.Ipc.get('page:views') // → 4
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 3. Hash Maps
|
|
49
|
+
```javascript
|
|
50
|
+
// Set / merge fields (last-write-wins per field)
|
|
51
|
+
await Odac.Ipc.hset('user:1', {active_date: new Date(), last_ip: '1.2.3.4'})
|
|
52
|
+
await Odac.Ipc.hset('user:1', {score: 42})
|
|
53
|
+
|
|
54
|
+
// Get all fields
|
|
55
|
+
const fields = await Odac.Ipc.hgetall('user:1')
|
|
56
|
+
// → {active_date: ..., last_ip: '1.2.3.4', score: 42}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 4. Lists
|
|
60
|
+
```javascript
|
|
61
|
+
// Append items to the right (returns new length)
|
|
62
|
+
await Odac.Ipc.rpush('events', {type: 'click'}, {type: 'view'}) // → 2
|
|
63
|
+
|
|
64
|
+
// Read range (inclusive, -1 = last item)
|
|
65
|
+
const items = await Odac.Ipc.lrange('events', 0, -1)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 5. Sets
|
|
69
|
+
```javascript
|
|
70
|
+
// Add members
|
|
71
|
+
await Odac.Ipc.sadd('online_users', 'user:1', 'user:2')
|
|
72
|
+
|
|
73
|
+
// Get all members
|
|
74
|
+
const users = await Odac.Ipc.smembers('online_users') // → ['user:1', 'user:2']
|
|
75
|
+
|
|
76
|
+
// Remove members (returns removed count)
|
|
77
|
+
await Odac.Ipc.srem('online_users', 'user:1')
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 6. Distributed Locks
|
|
81
|
+
```javascript
|
|
82
|
+
// Acquire lock with TTL in seconds (returns true if acquired, false if already held)
|
|
83
|
+
const acquired = await Odac.Ipc.lock('flush:lock', 10)
|
|
84
|
+
if (!acquired) return // another process is holding the lock
|
|
85
|
+
|
|
86
|
+
try {
|
|
87
|
+
// ... critical section ...
|
|
88
|
+
} finally {
|
|
89
|
+
await Odac.Ipc.unlock('flush:lock')
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 7. Pub/Sub Messaging
|
|
94
|
+
```javascript
|
|
95
|
+
// Subscribe to a channel
|
|
40
96
|
await Odac.Ipc.subscribe('notifications', (msg) => {
|
|
41
|
-
console.log('Received:', msg)
|
|
42
|
-
})
|
|
97
|
+
console.log('Received:', msg)
|
|
98
|
+
})
|
|
43
99
|
|
|
44
100
|
// Publish a message from any worker
|
|
45
|
-
await Odac.Ipc.publish('notifications', {
|
|
101
|
+
await Odac.Ipc.publish('notifications', {type: 'alert', text: 'System update'})
|
|
46
102
|
```
|
|
47
103
|
|
|
48
104
|
## Configuration
|
|
@@ -57,6 +113,9 @@ In `odac.json` or a config provider:
|
|
|
57
113
|
```
|
|
58
114
|
|
|
59
115
|
## Best Practices
|
|
60
|
-
-
|
|
61
|
-
-
|
|
62
|
-
-
|
|
116
|
+
- **Async First**: All IPC operations are asynchronous.
|
|
117
|
+
- **TTL Usage**: Always set a TTL for temporary cache data to prevent memory bloat.
|
|
118
|
+
- **Scaling**: Switch to `redis` driver when deploying across multiple containers or servers. No application code changes required.
|
|
119
|
+
- **Locks**: Always release locks in a `finally` block to prevent deadlocks.
|
|
120
|
+
- **Sets for indexes**: Use `sadd/smembers/srem` to track active keys — avoids expensive SCAN operations.
|
|
121
|
+
- **Atomic counters over get+set**: Use `incrBy`/`decrBy` instead of `get` → compute → `set` to prevent race conditions.
|
|
@@ -18,7 +18,8 @@ Views in ODAC are logic-light but powerful. They support automatic XSS protectio
|
|
|
18
18
|
3. **Data Binding — Two Equivalent Syntaxes**:
|
|
19
19
|
- `<odac var="key" />`: Tag-based output (HTML-escaped, XSS-safe).
|
|
20
20
|
- `{{ key }}`: Inline/interpolation output (HTML-escaped, XSS-safe). Identical behavior to `<odac var>`.
|
|
21
|
-
- `<odac
|
|
21
|
+
- `<odac get="key" />`: Query Parameter output (HTML-escaped, XSS-safe).
|
|
22
|
+
- `<odac var="key" raw />`, `<odac get="key" raw />` or `{!! key !!}`: Raw output (use with extreme caution).
|
|
22
23
|
4. **Choosing the Right Syntax**:
|
|
23
24
|
- **Inside HTML attributes** (`src`, `alt`, `href`, `class`, `value`, etc.) → Always prefer `{{ }}`.
|
|
24
25
|
- **Inline within text or mixed HTML** → Prefer `{{ }}` for short interpolations.
|
|
@@ -112,6 +113,10 @@ module.exports = function (Odac) {
|
|
|
112
113
|
<!-- Inline text interpolation -->
|
|
113
114
|
<p>Welcome, {{ user.name }}. You have {{ notifications }} new messages.</p>
|
|
114
115
|
|
|
116
|
+
<!-- Query parameter from URL (/page?q=search) -->
|
|
117
|
+
<p>Search Results for: <odac get="q" /></p>
|
|
118
|
+
<div class="raw-content"><odac get="htmlContent" raw /></div>
|
|
119
|
+
|
|
115
120
|
<!-- Conditional -->
|
|
116
121
|
<odac:if condition="stats.users > 100">
|
|
117
122
|
<span class="badge">Popular!</span>
|
|
@@ -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.
|
|
@@ -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):**
|