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,24 +1,46 @@
1
- ## 🎓 Controller Classes
1
+ ## 🧩 Service Classes
2
2
 
3
- While simple function exports work great for basic controllers, you can also organize your code using classes. This is especially useful when you want to share logic between multiple methods or keep related functionality together.
3
+ You can organize your business logic into reusable classes. This is especially useful when you want to share logic between multiple methods or keep related functionality together.
4
4
 
5
- #### Creating a Controller Class
5
+ We recommend placing these files in the `class/` directory.
6
6
 
7
- A controller class receives the `Odac` object in its constructor, giving you access to all services throughout your class methods:
7
+ ### Service Class vs. Controller
8
+
9
+ It is important not to confuse **Service Classes** with **Class-Based Controllers**.
10
+
11
+ - **Controllers** (located in `controller/`):
12
+ - Handle HTTP requests (Input -> Process -> Response).
13
+ - Can be defined as Classes for better organization.
14
+ - Are mapped to specific Routes (e.g., via `Route.get()`).
15
+
16
+ - **Service Classes** (located in `class/`):
17
+ - Contain reusable business logic (e.g., `User.calculateReputation()`, `Mail.sendWelcome()`).
18
+ - Are **not** directly mapped to routes.
19
+ - Can be used by *multiple* controllers or other services.
20
+ - Are **Request Scoped**: They are instantiated fresh for every request and attached to the request's `Odac` instance. They correspond to the life-cycle of the request.
21
+
22
+ **Rule of Thumb:** If it talks to the browser/API client, it's a **Controller**. If it processes data behind the scenes, it's a **Service Class**.
23
+
24
+ #### Creating a Service Class
25
+
26
+ Any class file placed in the `class/` directory will be automatically detected. When a request comes in, Odac creates a **new instance** of your class and passes the current request's `Odac` object to the constructor.
27
+
28
+ This means `this.Odac` inside your class gives you access to the specific request, response, authentication state, and database for *that specific user request*.
8
29
 
9
30
  ```javascript
10
- // controller/User.js
31
+ // class/User.js
11
32
  class User {
12
33
  constructor(Odac) {
13
34
  this.Odac = Odac
14
35
  }
15
36
 
37
+
16
38
  async getProfile() {
17
39
  const user = await this.Odac.Auth.user()
18
- return this.Odac.return({
40
+ return {
19
41
  success: true,
20
42
  user: user
21
- })
43
+ }
22
44
  }
23
45
 
24
46
  async updateProfile() {
@@ -34,60 +56,44 @@ class User {
34
56
  const email = await this.Odac.request('email')
35
57
 
36
58
  // Update user in database
37
- await this.Odac.Mysql.query('UPDATE users SET name = ?, email = ? WHERE id = ?', [
38
- name,
39
- email,
40
- this.Odac.Auth.user().id
41
- ])
59
+ await this.Odac.DB.users.where('id', this.Odac.Auth.user().id).update({
60
+ name: name,
61
+ email: email
62
+ })
42
63
 
43
- return this.Odac.return({
64
+ return {
44
65
  success: true,
45
66
  message: 'Profile updated successfully'
46
- })
67
+ }
47
68
  }
48
69
  }
49
70
 
50
71
  module.exports = User
51
72
  ```
52
73
 
53
- #### Using Controller Classes in Routes
54
-
55
- Once you've created a controller class, you can use it in your routes just like any other controller:
56
-
57
- ```javascript
58
- // route/www.js
59
- Odac.Route.buff = 'www'
60
-
61
- // Access class methods using dot notation
62
- Odac.Route.get('/profile', 'User.getProfile')
63
- Odac.Route.post('/profile/update', 'User.updateProfile')
64
- ```
65
-
66
- #### Accessing Classes in Controllers
67
-
68
- Controller classes are automatically instantiated for each request and attached to the `Odac` object. You can access them from any controller:
69
-
70
- ```javascript
71
- module.exports = async function (Odac) {
72
- // Access your User class
73
- const profile = await Odac.User.getProfile()
74
-
75
- return Odac.return(profile)
76
- }
77
- ```
78
-
79
- #### Benefits of Controller Classes
80
-
81
- - **Organization**: Group related methods together
82
- - **Reusability**: Share logic between different routes
83
- - **Maintainability**: Easier to manage complex controllers
84
- - **Context**: The `Odac` object is always available via `this.Odac`
85
-
86
- #### Class vs Function Controllers
87
-
88
- Both approaches work perfectly fine. Use what makes sense for your project:
89
-
90
- - **Functions**: Great for simple, single-purpose controllers
91
- - **Classes**: Better for complex logic with multiple related methods
92
-
93
- The framework automatically detects whether your export is a class or a function and handles it accordingly.
74
+ #### Naming Collisions
75
+
76
+ If your class name conflicts with a built-in Odac service (like `Mail`, `DB`, `Auth`), it will be automatically placed under `Odac.App` namespace to prevent errors.
77
+
78
+ Example: `class/Mail.js` (conflicts with core Mail) -> `Odac.App.Mail`
79
+
80
+ #### Accessing Services in Controllers
81
+
82
+ Since Service classes are attached to the `Odac` instance for each request, you can access them directly by their file name.
83
+
84
+ ```javascript
85
+ // controller/get/profile.js
86
+ module.exports = async function (Odac) {
87
+ // Odac.User is a fresh instance of the User class dedicated to this request
88
+ const profile = await Odac.User.getProfile()
89
+
90
+ return Odac.return(profile)
91
+ }
92
+ ```
93
+
94
+ #### Benefits of Service Classes
95
+
96
+ - **Organization**: Group related business logic together in `class/`
97
+ - **Reusability**: Share logic between different controllers and routes
98
+ - **Context Awareness**: The `Odac` request object is injected automatically, so your services know about the current user and request.
99
+ - **Separation of Concerns**: Keep your Controllers lightweight by moving heavy logic to Services.
@@ -5,10 +5,10 @@ Odac provides an automatic form system with built-in validation, CSRF protection
5
5
  ## Basic Usage
6
6
 
7
7
  ```html
8
- <odac:form action="/contact/submit" method="POST">
9
- <odac:field name="email" type="email" label="Email">
8
+ <odac:form action="Contact.submit" method="POST">
9
+ <odac:input name="email" type="email" label="Email">
10
10
  <odac:validate rule="required|email" message="Valid email required"/>
11
- </odac:field>
11
+ </odac:input>
12
12
 
13
13
  <odac:submit text="Send" loading="Sending..."/>
14
14
  </odac:form>
@@ -18,21 +18,23 @@ Odac provides an automatic form system with built-in validation, CSRF protection
18
18
 
19
19
  ### `<odac:form>`
20
20
 
21
- - `action` - Form submission URL (optional if using `table`)
21
+ - `action` - Controller action `Controller.method` (optional if using `table`)
22
22
  - `method` - HTTP method (default: POST)
23
23
  - `table` - Database table name for automatic insert (optional)
24
24
  - `redirect` - Redirect URL after success (optional)
25
25
  - `success` - Success message (optional)
26
26
  - `class` - Additional CSS classes
27
27
  - `id` - Form ID attribute
28
+ - `clear` - Set to `false` to disable auto-clearing inputs on success (optional)
28
29
 
29
30
  ```html
30
- <!-- With custom controller -->
31
- <odac:form action="/api/save" method="POST" class="my-form" id="contact-form">
31
+ <!-- With custom controller action -->
32
+ <!-- Executes 'submit' method in 'contact' controller -->
33
+ <odac:form action="Contact.submit" method="POST" class="my-form" id="contact-form">
32
34
  <!-- fields here -->
33
35
  </odac:form>
34
36
 
35
- <!-- With automatic DB insert -->
37
+ <!-- With automatic DB insert (no controller needed) -->
36
38
  <odac:form table="waitlist" redirect="/" success="Thank you for joining!">
37
39
  <!-- fields here -->
38
40
  </odac:form>
@@ -40,69 +42,69 @@ Odac provides an automatic form system with built-in validation, CSRF protection
40
42
 
41
43
  ## Field Types
42
44
 
43
- ### `<odac:field>`
45
+ ### `<odac:input>`
44
46
 
45
47
  Supports all standard HTML input types:
46
48
 
47
49
  ```html
48
50
  <!-- Text input with multiple validations -->
49
- <odac:field name="username" type="text" label="Username" placeholder="Enter username">
51
+ <odac:input name="username" type="text" label="Username" placeholder="Enter username">
50
52
  <odac:validate rule="required" message="Username is required"/>
51
53
  <odac:validate rule="minlen:3" message="Username must be at least 3 characters"/>
52
54
  <odac:validate rule="maxlen:20" message="Username cannot exceed 20 characters"/>
53
55
  <odac:validate rule="alphanumeric" message="Username can only contain letters and numbers"/>
54
- </odac:field>
56
+ </odac:input>
55
57
 
56
58
  <!-- Email input -->
57
- <odac:field name="email" type="email" label="Email Address" placeholder="your@email.com">
59
+ <odac:input name="email" type="email" label="Email Address" placeholder="your@email.com">
58
60
  <odac:validate rule="required" message="Email address is required"/>
59
61
  <odac:validate rule="email" message="Please enter a valid email address"/>
60
62
  <odac:validate rule="maxlen:100" message="Email is too long"/>
61
- </odac:field>
63
+ </odac:input>
62
64
 
63
65
  <!-- Password input with strong validation -->
64
- <odac:field name="password" type="password" label="Password">
66
+ <odac:input name="password" type="password" label="Password">
65
67
  <odac:validate rule="required" message="Password is required"/>
66
68
  <odac:validate rule="minlen:8" message="Password must be at least 8 characters long"/>
67
69
  <odac:validate rule="maxlen:50" message="Password is too long"/>
68
- </odac:field>
70
+ </odac:input>
69
71
 
70
72
  <!-- Textarea with character limits -->
71
- <odac:field name="message" type="textarea" label="Your Message" placeholder="Tell us what you think...">
73
+ <odac:input name="message" type="textarea" label="Your Message" placeholder="Tell us what you think...">
72
74
  <odac:validate rule="required" message="Please enter your message"/>
73
75
  <odac:validate rule="minlen:10" message="Message must be at least 10 characters"/>
74
76
  <odac:validate rule="maxlen:500" message="Message cannot exceed 500 characters"/>
75
- </odac:field>
77
+ </odac:input>
76
78
 
77
79
  <!-- Checkbox for terms acceptance -->
78
- <odac:field name="agree" type="checkbox" label="I agree to the Terms of Service and Privacy Policy">
80
+ <odac:input name="agree" type="checkbox" label="I agree to the Terms of Service and Privacy Policy">
79
81
  <odac:validate rule="accepted" message="You must accept the terms to continue"/>
80
- </odac:field>
82
+ </odac:input>
81
83
 
82
84
  <!-- Number input with range -->
83
- <odac:field name="age" type="number" label="Your Age">
85
+ <odac:input name="age" type="number" label="Your Age">
84
86
  <odac:validate rule="required" message="Age is required"/>
85
87
  <odac:validate rule="min:18" message="You must be at least 18 years old"/>
86
88
  <odac:validate rule="max:120" message="Please enter a valid age"/>
87
- </odac:field>
89
+ </odac:input>
88
90
 
89
91
  <!-- Phone number -->
90
- <odac:field name="phone" type="text" label="Phone Number" placeholder="+1 (555) 123-4567">
92
+ <odac:input name="phone" type="text" label="Phone Number" placeholder="+1 (555) 123-4567">
91
93
  <odac:validate rule="required" message="Phone number is required"/>
92
94
  <odac:validate rule="minlen:10" message="Phone number must be at least 10 digits"/>
93
- </odac:field>
95
+ </odac:input>
94
96
 
95
97
  <!-- URL input -->
96
- <odac:field name="website" type="url" label="Website" placeholder="https://example.com">
98
+ <odac:input name="website" type="url" label="Website" placeholder="https://example.com">
97
99
  <odac:validate rule="url" message="Please enter a valid URL"/>
98
- </odac:field>
100
+ </odac:input>
99
101
 
100
102
  <!-- Name with alpha validation -->
101
- <odac:field name="full_name" type="text" label="Full Name" placeholder="John Doe">
103
+ <odac:input name="full_name" type="text" label="Full Name" placeholder="John Doe">
102
104
  <odac:validate rule="required" message="Full name is required"/>
103
105
  <odac:validate rule="minlen:2" message="Name must be at least 2 characters"/>
104
106
  <odac:validate rule="maxlen:50" message="Name is too long"/>
105
- </odac:field>
107
+ </odac:input>
106
108
  ```
107
109
 
108
110
  ### Field Attributes
@@ -121,9 +123,9 @@ Supports all standard HTML input types:
121
123
  Add validation rules to fields:
122
124
 
123
125
  ```html
124
- <odac:field name="username" type="text">
126
+ <odac:input name="username" type="text">
125
127
  <odac:validate rule="required|minlen:3|maxlen:20" message="Username must be 3-20 characters"/>
126
- </odac:field>
128
+ </odac:input>
127
129
  ```
128
130
 
129
131
  ### Available Rules
@@ -202,59 +204,64 @@ Automatically set field values without user input:
202
204
  <odac:submit text="Save" loading="Saving..." class="btn btn-primary" id="save-btn"/>
203
205
  ```
204
206
 
205
- ## Controller Handler
207
+ ## Controller Handler (Server Actions)
208
+
209
+ Handle form submission directly in your controller. The action is defined as `ControllerName.methodName`.
206
210
 
207
- Handle form submission in your controller:
211
+ **View:**
212
+ ```html
213
+ <odac:form action="Contact.submit">
214
+ ...
215
+ </odac:form>
216
+ ```
217
+
218
+ **Controller (controller/Contact.js):**
208
219
 
209
220
  ```javascript
210
- module.exports = {
211
- submit: Odac => {
212
- // Access validated form data
213
- const data = Odac.formData
214
-
215
- // data contains all field values
216
- console.log(data.email, data.message)
217
-
218
- // Process the data (save to database, send email, etc.)
219
-
220
- // Return success response
221
- return Odac.return({
222
- result: {
223
- success: true,
224
- message: 'Form submitted successfully!',
225
- redirect: '/thank-you' // Optional redirect
226
- }
227
- })
221
+ module.exports = class Contact {
222
+ constructor(Odac) {
223
+ this.Odac = Odac
224
+ }
225
+
226
+ async submit(form) {
227
+ // 1. Access validated clean data
228
+ const { email, message } = form.data
229
+
230
+ // 2. Perform your logic
231
+ // await this.Odac.Mail().send(email, message)
232
+
233
+ // 3. Return success easily
234
+ return form.success('Message sent successfully!', '/thank-you')
228
235
  }
229
236
  }
230
237
  ```
231
238
 
232
239
  ### Error Handling
233
240
 
234
- Return validation errors:
241
+ Return errors using the helper method:
235
242
 
236
243
  ```javascript
237
- module.exports = {
238
- submit: Odac => {
239
- const data = Odac.formData
244
+ module.exports = class Contact {
245
+ constructor(Odac) {
246
+ this.Odac = Odac
247
+ }
248
+
249
+ async submit(form) {
250
+ const { email } = form.data
240
251
 
241
- // Custom validation
242
- if (data.email.includes('spam')) {
243
- return Odac.return({
244
- result: {success: false},
245
- errors: {
246
- email: 'This email is not allowed'
247
- }
248
- })
252
+ // Custom backend validation
253
+ if (email.includes('spam')) {
254
+ // Returns field-specific error
255
+ return form.error('email', 'Spam is not allowed!')
249
256
  }
250
257
 
251
- return Odac.return({
252
- result: {success: true, message: 'Success!'}
253
- })
258
+ return form.success('Success!')
254
259
  }
255
260
  }
256
261
  ```
257
262
 
263
+ **Note:** No route definition is needed for the action! The form system handles the routing securely.
264
+
258
265
  ## Automatic Database Insert
259
266
 
260
267
  Forms can automatically insert data into database without writing a controller:
@@ -263,13 +270,13 @@ Forms can automatically insert data into database without writing a controller:
263
270
 
264
271
  ```html
265
272
  <odac:form table="waitlist" redirect="/" success="Thank you for joining!">
266
- <odac:field name="email" type="email" label="Email">
273
+ <odac:input name="email" type="email" label="Email">
267
274
  <odac:validate rule="required|email|unique" message="Valid email required"/>
268
- </odac:field>
275
+ </odac:input>
269
276
 
270
- <odac:field name="name" type="text" label="Name">
277
+ <odac:input name="name" type="text" label="Name">
271
278
  <odac:validate rule="required|minlen:2" message="Name required"/>
272
- </odac:field>
279
+ </odac:input>
273
280
 
274
281
  <odac:set name="created_at" compute="now"/>
275
282
  <odac:set name="ip" compute="ip"/>
@@ -312,22 +319,22 @@ That's it! No controller needed. The form will:
312
319
  <div class="contact-page">
313
320
  <h1>Contact Us</h1>
314
321
 
315
- <odac:form action="/contact/submit" method="POST" class="contact-form">
316
- <odac:field name="name" type="text" label="Your Name" placeholder="Enter your name">
322
+ <odac:form action="Contact.submit" method="POST" class="contact-form">
323
+ <odac:input name="name" type="text" label="Your Name" placeholder="Enter your name">
317
324
  <odac:validate rule="required|minlen:3" message="Name must be at least 3 characters"/>
318
- </odac:field>
325
+ </odac:input>
319
326
 
320
- <odac:field name="email" type="email" label="Email" placeholder="your@email.com">
327
+ <odac:input name="email" type="email" label="Email" placeholder="your@email.com">
321
328
  <odac:validate rule="required|email" message="Please enter a valid email"/>
322
- </odac:field>
329
+ </odac:input>
323
330
 
324
- <odac:field name="subject" type="text" label="Subject" placeholder="What is this about?">
331
+ <odac:input name="subject" type="text" label="Subject" placeholder="What is this about?">
325
332
  <odac:validate rule="required|minlen:5" message="Subject must be at least 5 characters"/>
326
- </odac:field>
333
+ </odac:input>
327
334
 
328
- <odac:field name="message" type="textarea" label="Message" placeholder="Your message...">
335
+ <odac:input name="message" type="textarea" label="Message" placeholder="Your message...">
329
336
  <odac:validate rule="required|minlen:10" message="Message must be at least 10 characters"/>
330
- </odac:field>
337
+ </odac:input>
331
338
 
332
339
  <odac:submit text="Send Message" loading="Sending..." class="btn btn-primary"/>
333
340
  </odac:form>
@@ -337,29 +344,29 @@ That's it! No controller needed. The form will:
337
344
  ### Controller (controller/contact.js)
338
345
 
339
346
  ```javascript
340
- module.exports = {
341
- index: Odac => {
342
- Odac.View.skeleton('default')
343
- Odac.View.set({content: 'contact'})
344
- Odac.View.print()
345
- },
346
-
347
- submit: Odac => {
348
- const data = Odac.formData
347
+ module.exports = class Contact {
348
+ constructor(Odac) {
349
+ this.Odac = Odac
350
+ }
351
+
352
+ async index() {
353
+ this.Odac.View.skeleton('default')
354
+ this.Odac.View.set({content: 'contact'})
355
+ this.Odac.View.print()
356
+ }
357
+
358
+ async submit(form) {
359
+ // Get validated data from form helper
360
+ const { name, email, subject, message } = form.data
349
361
 
350
362
  // Save to database
351
- // await Odac.Mysql.query('INSERT INTO contacts SET ?', data)
363
+ // Save to database
364
+ // await this.Odac.DB.contacts.insert({ name, email, subject, message })
352
365
 
353
366
  // Send email notification
354
- // await Odac.Mail().to('admin@example.com').subject('New Contact').send(data.message)
367
+ // await this.Odac.Mail().to('admin@example.com').subject('New Contact').send(message)
355
368
 
356
- return Odac.return({
357
- result: {
358
- success: true,
359
- message: 'Thank you! We will get back to you soon.',
360
- redirect: '/'
361
- }
362
- })
369
+ return form.success('Thank you! We will get back to you soon.', '/')
363
370
  }
364
371
  }
365
372
  ```
@@ -368,7 +375,7 @@ module.exports = {
368
375
 
369
376
  ```javascript
370
377
  Odac.Route.page('/contact', 'contact')
371
- Odac.Route.post('/contact/submit', 'contact.submit')
378
+ // Note: No route needed for contact.submit action!
372
379
  ```
373
380
 
374
381
  ## Features
@@ -380,6 +387,7 @@ Odac.Route.post('/contact/submit', 'contact.submit')
380
387
  - **Loading States** - Automatic button state management
381
388
  - **Error Display** - Automatic error message rendering
382
389
  - **Success Messages** - Built-in success message handling
390
+ - **Auto-Clear** - Clears inputs on success (disable with `clear="false"`)
383
391
  - **Redirect Support** - Optional redirect after successful submission
384
392
 
385
393
  ## Security
@@ -6,9 +6,9 @@ Forms can automatically insert data into your database without writing any contr
6
6
 
7
7
  ```html
8
8
  <odac:form table="waitlist">
9
- <odac:field name="email" type="email" label="Email">
9
+ <odac:input name="email" type="email" label="Email">
10
10
  <odac:validate rule="required|email|unique"/>
11
- </odac:field>
11
+ </odac:input>
12
12
 
13
13
  <odac:submit text="Join"/>
14
14
  </odac:form>
@@ -45,13 +45,13 @@ CREATE TABLE `waitlist` (
45
45
  <h1>Join Our Waitlist</h1>
46
46
 
47
47
  <odac:form table="waitlist" redirect="/" success="Thank you for joining!">
48
- <odac:field name="email" type="email" label="Email" placeholder="your@email.com">
48
+ <odac:input name="email" type="email" label="Email" placeholder="your@email.com">
49
49
  <odac:validate rule="required|email|unique" message="Please enter a valid email"/>
50
- </odac:field>
50
+ </odac:input>
51
51
 
52
- <odac:field name="name" type="text" label="Name" placeholder="Your name">
52
+ <odac:input name="name" type="text" label="Name" placeholder="Your name">
53
53
  <odac:validate rule="required|minlen:2" message="Name is required"/>
54
- </odac:field>
54
+ </odac:input>
55
55
 
56
56
  <odac:set name="created_at" compute="now"/>
57
57
  <odac:set name="ip" compute="ip"/>
@@ -110,9 +110,9 @@ Custom success message to display.
110
110
  Use `unique` rule to prevent duplicate entries:
111
111
 
112
112
  ```html
113
- <odac:field name="email" type="email">
113
+ <odac:input name="email" type="email">
114
114
  <odac:validate rule="required|email|unique" message="This email is already registered"/>
115
- </odac:field>
115
+ </odac:input>
116
116
  ```
117
117
 
118
118
  The system will:
@@ -163,9 +163,9 @@ Only set if field is empty:
163
163
 
164
164
  ```html
165
165
  <odac:form table="newsletter" success="Thanks for subscribing!">
166
- <odac:field name="email" type="email">
166
+ <odac:input name="email" type="email">
167
167
  <odac:validate rule="required|email|unique"/>
168
- </odac:field>
168
+ </odac:input>
169
169
 
170
170
  <odac:set name="subscribed_at" compute="now"/>
171
171
  <odac:set name="status" value="active"/>
@@ -178,13 +178,13 @@ Only set if field is empty:
178
178
 
179
179
  ```html
180
180
  <odac:form table="feedback" redirect="/" success="Thank you for your feedback!">
181
- <odac:field name="rating" type="number" label="Rating (1-5)">
181
+ <odac:input name="rating" type="number" label="Rating (1-5)">
182
182
  <odac:validate rule="required|min:1|max:5"/>
183
- </odac:field>
183
+ </odac:input>
184
184
 
185
- <odac:field name="comment" type="textarea" label="Comment">
185
+ <odac:input name="comment" type="textarea" label="Comment">
186
186
  <odac:validate rule="required|minlen:10"/>
187
- </odac:field>
187
+ </odac:input>
188
188
 
189
189
  <odac:set name="created_at" compute="now"/>
190
190
  <odac:set name="ip" compute="ip"/>
@@ -197,17 +197,17 @@ Only set if field is empty:
197
197
 
198
198
  ```html
199
199
  <odac:form table="beta_requests" success="You're on the list!">
200
- <odac:field name="email" type="email">
200
+ <odac:input name="email" type="email">
201
201
  <odac:validate rule="required|email|unique"/>
202
- </odac:field>
202
+ </odac:input>
203
203
 
204
- <odac:field name="company" type="text">
204
+ <odac:input name="company" type="text">
205
205
  <odac:validate rule="required"/>
206
- </odac:field>
206
+ </odac:input>
207
207
 
208
- <odac:field name="use_case" type="textarea">
208
+ <odac:input name="use_case" type="textarea">
209
209
  <odac:validate rule="required|minlen:20"/>
210
- </odac:field>
210
+ </odac:input>
211
211
 
212
212
  <odac:set name="requested_at" compute="now"/>
213
213
  <odac:set name="status" value="pending"/>
@@ -270,7 +270,7 @@ Odac.Route.post('/contact/submit', async Odac => {
270
270
  await sendEmail(Odac.formData.email, 'Thank you!')
271
271
 
272
272
  // Manually insert to database if needed
273
- await Odac.Mysql.query('INSERT INTO contacts SET ?', Odac.formData)
273
+ await Odac.DB.contacts.insert(Odac.formData)
274
274
 
275
275
  return Odac.return({
276
276
  result: {success: true, message: 'Message sent!'}
@@ -94,3 +94,20 @@ module.exports = async function (Odac) {
94
94
  })
95
95
  }
96
96
  ```
97
+
98
+ ### Session Data
99
+
100
+ You can store data in the current user's session using `Odac.session()`. This data persists across requests.
101
+
102
+ ```javascript
103
+ module.exports = async function (Odac) {
104
+ // Set a session value
105
+ Odac.session('cart_id', 12345)
106
+
107
+ // Get a session value
108
+ const cartId = Odac.session('cart_id')
109
+
110
+ // Remove a session value
111
+ Odac.session('cart_id', null)
112
+ }
113
+ ```
@@ -118,7 +118,7 @@ Create a separate view part for the `<head>` section:
118
118
  ```javascript
119
119
  module.exports = async function (Odac) {
120
120
  const productId = Odac.Request.get('id')
121
- const product = await Odac.Mysql.table('products')
121
+ const product = await Odac.DB.table('products')
122
122
  .where('id', productId)
123
123
  .first()
124
124