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.
Files changed (143) hide show
  1. package/.agent/rules/coding.md +27 -0
  2. package/.agent/rules/memory.md +33 -0
  3. package/.agent/rules/project.md +30 -0
  4. package/.agent/rules/workflow.md +16 -0
  5. package/.github/workflows/auto-pr-description.yml +3 -1
  6. package/.github/workflows/release.yml +42 -1
  7. package/.github/workflows/test-coverage.yml +6 -5
  8. package/.github/workflows/test-publish.yml +36 -0
  9. package/.husky/pre-commit +10 -0
  10. package/.husky/pre-push +13 -0
  11. package/.releaserc.js +3 -3
  12. package/CHANGELOG.md +184 -0
  13. package/README.md +53 -34
  14. package/bin/odac.js +181 -49
  15. package/client/odac.js +878 -995
  16. package/docs/backend/01-overview/03-development-server.md +39 -46
  17. package/docs/backend/02-structure/01-typical-project-layout.md +59 -25
  18. package/docs/backend/03-config/00-configuration-overview.md +15 -6
  19. package/docs/backend/03-config/01-database-connection.md +3 -3
  20. package/docs/backend/03-config/02-static-route-mapping-optional.md +1 -1
  21. package/docs/backend/03-config/03-request-timeout.md +1 -1
  22. package/docs/backend/03-config/04-environment-variables.md +4 -4
  23. package/docs/backend/03-config/05-early-hints.md +2 -2
  24. package/docs/backend/04-routing/02-controller-less-view-routes.md +9 -3
  25. package/docs/backend/04-routing/03-api-and-data-routes.md +18 -0
  26. package/docs/backend/04-routing/07-cron-jobs.md +17 -1
  27. package/docs/backend/04-routing/09-websocket.md +29 -0
  28. package/docs/backend/05-controllers/01-how-to-build-a-controller.md +48 -3
  29. package/docs/backend/05-controllers/02-your-trusty-odac-assistant.md +2 -0
  30. package/docs/backend/05-controllers/03-controller-classes.md +61 -55
  31. package/docs/backend/05-forms/01-custom-forms.md +103 -95
  32. package/docs/backend/05-forms/02-automatic-database-insert.md +21 -21
  33. package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +17 -0
  34. package/docs/backend/07-views/02-rendering-a-view.md +1 -1
  35. package/docs/backend/07-views/03-variables.md +5 -5
  36. package/docs/backend/07-views/04-request-data.md +1 -1
  37. package/docs/backend/07-views/08-backend-javascript.md +1 -1
  38. package/docs/backend/07-views/10-styling-and-tailwind.md +93 -0
  39. package/docs/backend/08-database/01-getting-started.md +100 -0
  40. package/docs/backend/08-database/02-basics.md +136 -0
  41. package/docs/backend/08-database/03-advanced.md +84 -0
  42. package/docs/backend/08-database/04-migrations.md +48 -0
  43. package/docs/backend/09-validation/01-the-validator-service.md +1 -0
  44. package/docs/backend/10-authentication/03-register.md +9 -2
  45. package/docs/backend/10-authentication/04-odac-register-forms.md +48 -48
  46. package/docs/backend/10-authentication/05-session-management.md +16 -2
  47. package/docs/backend/10-authentication/06-odac-login-forms.md +50 -50
  48. package/docs/backend/10-authentication/07-magic-links.md +134 -0
  49. package/docs/backend/11-mail/01-the-mail-service.md +118 -28
  50. package/docs/backend/12-streaming/01-streaming-overview.md +2 -2
  51. package/docs/backend/13-utilities/01-odac-var.md +7 -7
  52. package/docs/backend/13-utilities/02-ipc.md +73 -0
  53. package/docs/frontend/01-overview/01-introduction.md +5 -1
  54. package/docs/frontend/02-ajax-navigation/01-quick-start.md +1 -1
  55. package/docs/index.json +21 -125
  56. package/eslint.config.mjs +5 -47
  57. package/jest.config.js +1 -1
  58. package/package.json +16 -7
  59. package/src/Auth.js +414 -121
  60. package/src/Config.js +12 -7
  61. package/src/Database.js +188 -0
  62. package/src/Env.js +3 -1
  63. package/src/Ipc.js +337 -0
  64. package/src/Lang.js +9 -2
  65. package/src/Mail.js +408 -37
  66. package/src/Odac.js +105 -40
  67. package/src/Request.js +71 -49
  68. package/src/Route/Cron.js +62 -18
  69. package/src/Route/Internal.js +215 -12
  70. package/src/Route/Middleware.js +7 -2
  71. package/src/Route.js +372 -109
  72. package/src/Server.js +118 -12
  73. package/src/Storage.js +169 -0
  74. package/src/Token.js +6 -4
  75. package/src/Validator.js +95 -3
  76. package/src/Var.js +22 -6
  77. package/src/View/EarlyHints.js +43 -33
  78. package/src/View/Form.js +210 -28
  79. package/src/View.js +108 -7
  80. package/src/WebSocket.js +18 -3
  81. package/template/odac.json +5 -0
  82. package/template/package.json +3 -1
  83. package/template/route/www.js +12 -10
  84. package/template/view/content/home.html +3 -3
  85. package/template/view/head/main.html +2 -2
  86. package/test/Client.test.js +168 -0
  87. package/test/Config.test.js +112 -0
  88. package/test/Lang.test.js +92 -0
  89. package/test/Odac.test.js +86 -0
  90. package/test/{framework/middleware.test.js → Route/Middleware.test.js} +2 -2
  91. package/test/{framework/Route.test.js → Route.test.js} +1 -1
  92. package/test/{framework/View → View}/EarlyHints.test.js +1 -1
  93. package/test/{framework/WebSocket.test.js → WebSocket.test.js} +2 -2
  94. package/test/scripts/check-coverage.js +4 -4
  95. package/docs/backend/08-database/01-database-connection.md +0 -99
  96. package/docs/backend/08-database/02-using-mysql.md +0 -322
  97. package/src/Mysql.js +0 -575
  98. package/template/config.json +0 -5
  99. package/test/cli/Cli.test.js +0 -36
  100. package/test/core/Candy.test.js +0 -234
  101. package/test/core/Commands.test.js +0 -538
  102. package/test/core/Config.test.js +0 -1432
  103. package/test/core/Lang.test.js +0 -250
  104. package/test/core/Process.test.js +0 -156
  105. package/test/server/Api.test.js +0 -647
  106. package/test/server/DNS.test.js +0 -2050
  107. package/test/server/DNS.test.js.bak +0 -2084
  108. package/test/server/Hub.test.js +0 -497
  109. package/test/server/Log.test.js +0 -73
  110. package/test/server/Mail.account.test_.js +0 -460
  111. package/test/server/Mail.init.test_.js +0 -411
  112. package/test/server/Mail.test_.js +0 -1340
  113. package/test/server/SSL.test_.js +0 -1491
  114. package/test/server/Server.test.js +0 -765
  115. package/test/server/Service.test_.js +0 -1127
  116. package/test/server/Subdomain.test.js +0 -440
  117. package/test/server/Web/Firewall.test.js +0 -175
  118. package/test/server/Web/Proxy.test.js +0 -397
  119. package/test/server/Web.test.js +0 -1494
  120. package/test/server/__mocks__/acme-client.js +0 -17
  121. package/test/server/__mocks__/bcrypt.js +0 -50
  122. package/test/server/__mocks__/child_process.js +0 -389
  123. package/test/server/__mocks__/crypto.js +0 -432
  124. package/test/server/__mocks__/fs.js +0 -450
  125. package/test/server/__mocks__/globalOdac.js +0 -227
  126. package/test/server/__mocks__/http.js +0 -575
  127. package/test/server/__mocks__/https.js +0 -272
  128. package/test/server/__mocks__/index.js +0 -249
  129. package/test/server/__mocks__/mail/server.js +0 -100
  130. package/test/server/__mocks__/mail/smtp.js +0 -31
  131. package/test/server/__mocks__/mailparser.js +0 -81
  132. package/test/server/__mocks__/net.js +0 -369
  133. package/test/server/__mocks__/node-forge.js +0 -328
  134. package/test/server/__mocks__/os.js +0 -320
  135. package/test/server/__mocks__/path.js +0 -291
  136. package/test/server/__mocks__/selfsigned.js +0 -8
  137. package/test/server/__mocks__/server/src/mail/server.js +0 -100
  138. package/test/server/__mocks__/server/src/mail/smtp.js +0 -31
  139. package/test/server/__mocks__/smtp-server.js +0 -106
  140. package/test/server/__mocks__/sqlite3.js +0 -394
  141. package/test/server/__mocks__/testFactories.js +0 -299
  142. package/test/server/__mocks__/testHelpers.js +0 -363
  143. 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
- ```