odac 1.0.1 → 1.2.0
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/.agent/rules/coding.md +27 -0
- package/.agent/rules/memory.md +33 -0
- package/.agent/rules/project.md +30 -0
- package/.agent/rules/workflow.md +16 -0
- package/.github/workflows/auto-pr-description.yml +3 -1
- package/.github/workflows/release.yml +42 -1
- package/.github/workflows/test-coverage.yml +6 -5
- package/.github/workflows/test-publish.yml +36 -0
- package/.husky/pre-commit +10 -0
- package/.husky/pre-push +13 -0
- package/.releaserc.js +3 -3
- package/CHANGELOG.md +184 -0
- package/README.md +53 -34
- package/bin/odac.js +181 -49
- package/client/odac.js +878 -995
- package/docs/backend/01-overview/03-development-server.md +39 -46
- package/docs/backend/02-structure/01-typical-project-layout.md +59 -25
- package/docs/backend/03-config/00-configuration-overview.md +15 -6
- package/docs/backend/03-config/01-database-connection.md +3 -3
- package/docs/backend/03-config/02-static-route-mapping-optional.md +1 -1
- package/docs/backend/03-config/03-request-timeout.md +1 -1
- package/docs/backend/03-config/04-environment-variables.md +4 -4
- package/docs/backend/03-config/05-early-hints.md +2 -2
- package/docs/backend/04-routing/02-controller-less-view-routes.md +9 -3
- package/docs/backend/04-routing/03-api-and-data-routes.md +18 -0
- package/docs/backend/04-routing/07-cron-jobs.md +17 -1
- package/docs/backend/04-routing/09-websocket.md +29 -0
- package/docs/backend/05-controllers/01-how-to-build-a-controller.md +48 -3
- package/docs/backend/05-controllers/02-your-trusty-odac-assistant.md +2 -0
- package/docs/backend/05-controllers/03-controller-classes.md +61 -55
- package/docs/backend/05-forms/01-custom-forms.md +103 -95
- package/docs/backend/05-forms/02-automatic-database-insert.md +21 -21
- package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +17 -0
- package/docs/backend/07-views/02-rendering-a-view.md +1 -1
- package/docs/backend/07-views/03-variables.md +5 -5
- package/docs/backend/07-views/04-request-data.md +1 -1
- package/docs/backend/07-views/08-backend-javascript.md +1 -1
- package/docs/backend/07-views/10-styling-and-tailwind.md +93 -0
- package/docs/backend/08-database/01-getting-started.md +100 -0
- package/docs/backend/08-database/02-basics.md +136 -0
- package/docs/backend/08-database/03-advanced.md +84 -0
- package/docs/backend/08-database/04-migrations.md +48 -0
- package/docs/backend/09-validation/01-the-validator-service.md +1 -0
- package/docs/backend/10-authentication/03-register.md +9 -2
- package/docs/backend/10-authentication/04-odac-register-forms.md +48 -48
- package/docs/backend/10-authentication/05-session-management.md +16 -2
- package/docs/backend/10-authentication/06-odac-login-forms.md +50 -50
- package/docs/backend/10-authentication/07-magic-links.md +134 -0
- package/docs/backend/11-mail/01-the-mail-service.md +118 -28
- package/docs/backend/12-streaming/01-streaming-overview.md +2 -2
- package/docs/backend/13-utilities/01-odac-var.md +7 -7
- package/docs/backend/13-utilities/02-ipc.md +73 -0
- package/docs/frontend/01-overview/01-introduction.md +5 -1
- package/docs/frontend/02-ajax-navigation/01-quick-start.md +1 -1
- package/docs/index.json +21 -125
- package/eslint.config.mjs +5 -47
- package/jest.config.js +1 -1
- package/package.json +16 -7
- package/src/Auth.js +414 -121
- package/src/Config.js +12 -7
- package/src/Database.js +188 -0
- package/src/Env.js +3 -1
- package/src/Ipc.js +337 -0
- package/src/Lang.js +9 -2
- package/src/Mail.js +408 -37
- package/src/Odac.js +105 -40
- package/src/Request.js +71 -49
- package/src/Route/Cron.js +62 -18
- package/src/Route/Internal.js +215 -12
- package/src/Route/Middleware.js +7 -2
- package/src/Route.js +372 -109
- package/src/Server.js +118 -12
- package/src/Storage.js +169 -0
- package/src/Token.js +6 -4
- package/src/Validator.js +95 -3
- package/src/Var.js +22 -6
- package/src/View/EarlyHints.js +43 -33
- package/src/View/Form.js +210 -28
- package/src/View.js +108 -7
- package/src/WebSocket.js +18 -3
- package/template/odac.json +5 -0
- package/template/package.json +3 -1
- package/template/route/www.js +12 -10
- package/template/view/content/home.html +3 -3
- package/template/view/head/main.html +2 -2
- package/test/Client.test.js +168 -0
- package/test/Config.test.js +112 -0
- package/test/Lang.test.js +92 -0
- package/test/Odac.test.js +86 -0
- package/test/{framework/middleware.test.js → Route/Middleware.test.js} +2 -2
- package/test/{framework/Route.test.js → Route.test.js} +1 -1
- package/test/{framework/View → View}/EarlyHints.test.js +1 -1
- package/test/{framework/WebSocket.test.js → WebSocket.test.js} +2 -2
- package/test/scripts/check-coverage.js +4 -4
- package/docs/backend/08-database/01-database-connection.md +0 -99
- package/docs/backend/08-database/02-using-mysql.md +0 -322
- package/src/Mysql.js +0 -575
- package/template/config.json +0 -5
- package/test/cli/Cli.test.js +0 -36
- package/test/core/Candy.test.js +0 -234
- package/test/core/Commands.test.js +0 -538
- package/test/core/Config.test.js +0 -1432
- package/test/core/Lang.test.js +0 -250
- package/test/core/Process.test.js +0 -156
- package/test/server/Api.test.js +0 -647
- package/test/server/DNS.test.js +0 -2050
- package/test/server/DNS.test.js.bak +0 -2084
- package/test/server/Hub.test.js +0 -497
- package/test/server/Log.test.js +0 -73
- package/test/server/Mail.account.test_.js +0 -460
- package/test/server/Mail.init.test_.js +0 -411
- package/test/server/Mail.test_.js +0 -1340
- package/test/server/SSL.test_.js +0 -1491
- package/test/server/Server.test.js +0 -765
- package/test/server/Service.test_.js +0 -1127
- package/test/server/Subdomain.test.js +0 -440
- package/test/server/Web/Firewall.test.js +0 -175
- package/test/server/Web/Proxy.test.js +0 -397
- package/test/server/Web.test.js +0 -1494
- package/test/server/__mocks__/acme-client.js +0 -17
- package/test/server/__mocks__/bcrypt.js +0 -50
- package/test/server/__mocks__/child_process.js +0 -389
- package/test/server/__mocks__/crypto.js +0 -432
- package/test/server/__mocks__/fs.js +0 -450
- package/test/server/__mocks__/globalOdac.js +0 -227
- package/test/server/__mocks__/http.js +0 -575
- package/test/server/__mocks__/https.js +0 -272
- package/test/server/__mocks__/index.js +0 -249
- package/test/server/__mocks__/mail/server.js +0 -100
- package/test/server/__mocks__/mail/smtp.js +0 -31
- package/test/server/__mocks__/mailparser.js +0 -81
- package/test/server/__mocks__/net.js +0 -369
- package/test/server/__mocks__/node-forge.js +0 -328
- package/test/server/__mocks__/os.js +0 -320
- package/test/server/__mocks__/path.js +0 -291
- package/test/server/__mocks__/selfsigned.js +0 -8
- package/test/server/__mocks__/server/src/mail/server.js +0 -100
- package/test/server/__mocks__/server/src/mail/smtp.js +0 -31
- package/test/server/__mocks__/smtp-server.js +0 -106
- package/test/server/__mocks__/sqlite3.js +0 -394
- package/test/server/__mocks__/testFactories.js +0 -299
- package/test/server/__mocks__/testHelpers.js +0 -363
- package/test/server/__mocks__/tls.js +0 -229
|
@@ -1,322 +0,0 @@
|
|
|
1
|
-
## 🗄️ Using MySQL
|
|
2
|
-
|
|
3
|
-
Odac provides a powerful query builder for safe and easy database operations.
|
|
4
|
-
|
|
5
|
-
### Selecting Data
|
|
6
|
-
|
|
7
|
-
#### Get All Records
|
|
8
|
-
|
|
9
|
-
```javascript
|
|
10
|
-
const users = await Odac.Mysql.table('users').get()
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
#### Get First Record
|
|
14
|
-
|
|
15
|
-
```javascript
|
|
16
|
-
const user = await Odac.Mysql.table('users').where('id', 1).first()
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
#### Select Specific Columns
|
|
20
|
-
|
|
21
|
-
```javascript
|
|
22
|
-
const users = await Odac.Mysql.table('users')
|
|
23
|
-
.select('id', 'name', 'email')
|
|
24
|
-
.get()
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
### Where Conditions
|
|
28
|
-
|
|
29
|
-
```javascript
|
|
30
|
-
// Single condition
|
|
31
|
-
const users = await Odac.Mysql.table('users')
|
|
32
|
-
.where('status', 'active')
|
|
33
|
-
.get()
|
|
34
|
-
|
|
35
|
-
// Multiple conditions (AND)
|
|
36
|
-
const users = await Odac.Mysql.table('users')
|
|
37
|
-
.where('status', 'active')
|
|
38
|
-
.where('verified', 1)
|
|
39
|
-
.get()
|
|
40
|
-
|
|
41
|
-
// OR condition
|
|
42
|
-
const users = await Odac.Mysql.table('users')
|
|
43
|
-
.where('role', 'admin')
|
|
44
|
-
.orWhere('role', 'moderator')
|
|
45
|
-
.get()
|
|
46
|
-
|
|
47
|
-
// Comparison operators
|
|
48
|
-
const products = await Odac.Mysql.table('products')
|
|
49
|
-
.where('price', '>', 100)
|
|
50
|
-
.where('stock', '<=', 10)
|
|
51
|
-
.get()
|
|
52
|
-
|
|
53
|
-
// LIKE
|
|
54
|
-
const users = await Odac.Mysql.table('users')
|
|
55
|
-
.where('name', 'LIKE', '%John%')
|
|
56
|
-
.get()
|
|
57
|
-
|
|
58
|
-
// IN
|
|
59
|
-
const users = await Odac.Mysql.table('users')
|
|
60
|
-
.where('role', 'IN', ['admin', 'moderator'])
|
|
61
|
-
.get()
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
### Complex Where Conditions
|
|
65
|
-
|
|
66
|
-
For complex queries with nested AND/OR conditions, use arrays:
|
|
67
|
-
|
|
68
|
-
```javascript
|
|
69
|
-
// SQL: WHERE (status = 'active' AND verified = 1) OR (role = 'admin')
|
|
70
|
-
const users = await Odac.Mysql.table('users')
|
|
71
|
-
.where([
|
|
72
|
-
['status', 'active'],
|
|
73
|
-
['verified', 1]
|
|
74
|
-
])
|
|
75
|
-
.orWhere(['role', 'admin'])
|
|
76
|
-
.get()
|
|
77
|
-
|
|
78
|
-
// SQL: WHERE status = 'active' AND (role = 'admin' OR role = 'moderator')
|
|
79
|
-
const users = await Odac.Mysql.table('users')
|
|
80
|
-
.where('status', 'active')
|
|
81
|
-
.where([
|
|
82
|
-
['role', 'admin'],
|
|
83
|
-
'OR',
|
|
84
|
-
['role', 'moderator']
|
|
85
|
-
])
|
|
86
|
-
.get()
|
|
87
|
-
|
|
88
|
-
// Complex nested conditions
|
|
89
|
-
// SQL: WHERE (status = 'active' AND (role = 'admin' OR role = 'moderator'))
|
|
90
|
-
// AND (verified = 1 OR email_verified = 1)
|
|
91
|
-
const users = await Odac.Mysql.table('users')
|
|
92
|
-
.where([
|
|
93
|
-
['status', 'active'],
|
|
94
|
-
[
|
|
95
|
-
['role', 'admin'],
|
|
96
|
-
'OR',
|
|
97
|
-
['role', 'moderator']
|
|
98
|
-
]
|
|
99
|
-
])
|
|
100
|
-
.where([
|
|
101
|
-
['verified', 1],
|
|
102
|
-
'OR',
|
|
103
|
-
['email_verified', 1]
|
|
104
|
-
])
|
|
105
|
-
.get()
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
### Ordering and Limiting
|
|
109
|
-
|
|
110
|
-
```javascript
|
|
111
|
-
// Order by
|
|
112
|
-
const users = await Odac.Mysql.table('users')
|
|
113
|
-
.order('created_at', 'desc')
|
|
114
|
-
.get()
|
|
115
|
-
|
|
116
|
-
// Limit
|
|
117
|
-
const users = await Odac.Mysql.table('users')
|
|
118
|
-
.limit(10)
|
|
119
|
-
.get()
|
|
120
|
-
|
|
121
|
-
// Pagination
|
|
122
|
-
const page = 2
|
|
123
|
-
const perPage = 20
|
|
124
|
-
const users = await Odac.Mysql.table('users')
|
|
125
|
-
.limit((page - 1) * perPage, perPage)
|
|
126
|
-
.get()
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
### Counting Records
|
|
130
|
-
|
|
131
|
-
```javascript
|
|
132
|
-
const userCount = await Odac.Mysql.table('users')
|
|
133
|
-
.where('status', 'active')
|
|
134
|
-
.rows()
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
### Joins
|
|
138
|
-
|
|
139
|
-
```javascript
|
|
140
|
-
const orders = await Odac.Mysql.table('orders')
|
|
141
|
-
.leftJoin('users', 'orders.user_id', 'users.id')
|
|
142
|
-
.select('orders.*', 'users.name', 'users.email')
|
|
143
|
-
.get()
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
### Inserting Data
|
|
147
|
-
|
|
148
|
-
```javascript
|
|
149
|
-
// Insert single record
|
|
150
|
-
const result = await Odac.Mysql.table('users').insert({
|
|
151
|
-
name: 'John Doe',
|
|
152
|
-
email: 'john@example.com',
|
|
153
|
-
status: 'active'
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
console.log(result.id) // Inserted ID
|
|
157
|
-
console.log(result.affected) // Affected rows
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
### Updating Data
|
|
161
|
-
|
|
162
|
-
```javascript
|
|
163
|
-
const result = await Odac.Mysql.table('users')
|
|
164
|
-
.where('id', 1)
|
|
165
|
-
.set({
|
|
166
|
-
name: 'Jane Doe',
|
|
167
|
-
email: 'jane@example.com'
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
console.log(result.affected) // Number of updated rows
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
### Deleting Data
|
|
174
|
-
|
|
175
|
-
```javascript
|
|
176
|
-
const result = await Odac.Mysql.table('users')
|
|
177
|
-
.where('id', 1)
|
|
178
|
-
.delete()
|
|
179
|
-
|
|
180
|
-
console.log(result.affected) // Number of deleted rows
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
### Raw Queries
|
|
184
|
-
|
|
185
|
-
For complex queries, use raw SQL:
|
|
186
|
-
|
|
187
|
-
```javascript
|
|
188
|
-
// Raw value in select
|
|
189
|
-
const users = await Odac.Mysql.table('users')
|
|
190
|
-
.select('id', 'name', Odac.Mysql.raw('COUNT(*) as total'))
|
|
191
|
-
.get()
|
|
192
|
-
|
|
193
|
-
// Raw query with parameters (SAFE - recommended)
|
|
194
|
-
const result = await Odac.Mysql.run('SELECT * FROM users WHERE status = ?', ['active'])
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
#### ⚠️ Security Warning: Mysql.raw()
|
|
198
|
-
|
|
199
|
-
**CRITICAL**: `Mysql.raw()` bypasses ALL SQL injection protection. Only use with hardcoded, trusted values.
|
|
200
|
-
|
|
201
|
-
```javascript
|
|
202
|
-
// ❌ DANGEROUS - Never do this!
|
|
203
|
-
const userInput = await Odac.request('search')
|
|
204
|
-
const users = await Odac.Mysql.table('users')
|
|
205
|
-
.where('name', Odac.Mysql.raw(userInput)) // SQL INJECTION RISK!
|
|
206
|
-
.get()
|
|
207
|
-
|
|
208
|
-
// ✅ SAFE - Use query builder instead
|
|
209
|
-
const userInput = await Odac.request('search')
|
|
210
|
-
const users = await Odac.Mysql.table('users')
|
|
211
|
-
.where('name', 'LIKE', `%${userInput}%`) // Automatically escaped
|
|
212
|
-
.get()
|
|
213
|
-
|
|
214
|
-
// ✅ SAFE - Use with hardcoded values only
|
|
215
|
-
const users = await Odac.Mysql.table('users')
|
|
216
|
-
.select('id', 'name', Odac.Mysql.raw('COUNT(*) as total')) // OK - hardcoded
|
|
217
|
-
.get()
|
|
218
|
-
|
|
219
|
-
// ✅ SAFE - Use parameterized queries for dynamic values
|
|
220
|
-
const status = await Odac.request('status')
|
|
221
|
-
const result = await Odac.Mysql.run(
|
|
222
|
-
'SELECT * FROM users WHERE status = ?',
|
|
223
|
-
[status] // Automatically escaped
|
|
224
|
-
)
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
**When to use raw()**:
|
|
228
|
-
- Aggregate functions: `COUNT(*)`, `SUM(price)`, `AVG(rating)`
|
|
229
|
-
- Database functions: `NOW()`, `CONCAT()`, `DATE_FORMAT()`
|
|
230
|
-
- Complex expressions that can't be built with query builder
|
|
231
|
-
|
|
232
|
-
**Never use raw() with**:
|
|
233
|
-
- User input from forms, URLs, or cookies
|
|
234
|
-
- Data from external APIs
|
|
235
|
-
- Any untrusted source
|
|
236
|
-
|
|
237
|
-
### Group By
|
|
238
|
-
|
|
239
|
-
```javascript
|
|
240
|
-
const stats = await Odac.Mysql.table('orders')
|
|
241
|
-
.select('user_id', Odac.Mysql.raw('COUNT(*) as order_count'))
|
|
242
|
-
.groupBy('user_id')
|
|
243
|
-
.get()
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
### Complete Example
|
|
247
|
-
|
|
248
|
-
```javascript
|
|
249
|
-
module.exports = async function (Odac) {
|
|
250
|
-
const page = await Odac.request('page') || 1
|
|
251
|
-
const perPage = 20
|
|
252
|
-
const search = await Odac.request('search')
|
|
253
|
-
|
|
254
|
-
// Build query
|
|
255
|
-
let query = Odac.Mysql.table('products')
|
|
256
|
-
.select('id', 'name', 'price', 'stock')
|
|
257
|
-
.where('status', 'active')
|
|
258
|
-
|
|
259
|
-
// Add search filter
|
|
260
|
-
if (search) {
|
|
261
|
-
query = query.where('name', 'LIKE', `%${search}%`)
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Get products
|
|
265
|
-
const products = await query
|
|
266
|
-
.order('created_at', 'desc')
|
|
267
|
-
.limit((page - 1) * perPage, perPage)
|
|
268
|
-
.get()
|
|
269
|
-
|
|
270
|
-
// Get total count
|
|
271
|
-
const totalCount = await Odac.Mysql.table('products')
|
|
272
|
-
.where('status', 'active')
|
|
273
|
-
.rows()
|
|
274
|
-
|
|
275
|
-
Odac.set({
|
|
276
|
-
products: products,
|
|
277
|
-
currentPage: page,
|
|
278
|
-
totalPages: Math.ceil(totalCount / perPage)
|
|
279
|
-
})
|
|
280
|
-
|
|
281
|
-
Odac.View.set({
|
|
282
|
-
skeleton: 'main',
|
|
283
|
-
content: 'products.list'
|
|
284
|
-
})
|
|
285
|
-
}
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
### Best Practices
|
|
289
|
-
|
|
290
|
-
1. **Always use the query builder** - It protects against SQL injection
|
|
291
|
-
2. **Never use Mysql.raw() with user input** - Only use with hardcoded values
|
|
292
|
-
3. **Use parameterized queries** - When using `Mysql.run()`, always pass parameters as array
|
|
293
|
-
4. **Use async/await** - All database methods are asynchronous
|
|
294
|
-
5. **Handle errors** - Wrap database calls in try/catch blocks
|
|
295
|
-
6. **Limit results** - Use `.limit()` to prevent loading too much data
|
|
296
|
-
7. **Select only needed columns** - Use `.select()` instead of selecting all columns
|
|
297
|
-
|
|
298
|
-
### Error Handling
|
|
299
|
-
|
|
300
|
-
```javascript
|
|
301
|
-
module.exports = async function (Odac) {
|
|
302
|
-
try {
|
|
303
|
-
const user = await Odac.Mysql.table('users')
|
|
304
|
-
.where('id', await Odac.request('id'))
|
|
305
|
-
.first()
|
|
306
|
-
|
|
307
|
-
if (!user) {
|
|
308
|
-
Odac.set('error', 'User not found')
|
|
309
|
-
} else {
|
|
310
|
-
Odac.set('user', user)
|
|
311
|
-
}
|
|
312
|
-
} catch (error) {
|
|
313
|
-
console.error('Database error:', error)
|
|
314
|
-
Odac.set('error', 'An error occurred')
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
Odac.View.set({
|
|
318
|
-
skeleton: 'main',
|
|
319
|
-
content: 'user.profile'
|
|
320
|
-
})
|
|
321
|
-
}
|
|
322
|
-
```
|