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
@@ -0,0 +1,134 @@
1
+ # Magic Links
2
+
3
+ Magic links provide a passwordless authentication method where verification links are sent directly to the user's email address. Odac offers both a zero-configuration component and a programmatic API to implement this feature.
4
+
5
+ ## The `<odac:magic-login>` Component
6
+
7
+ The simplest way to implement magic links is using the built-in view component. This works similarly to the standard `<odac:login>` component but requires no password field.
8
+
9
+ ### Basic Usage
10
+
11
+ Use the `<odac:magic-login>` tag in your view. If you provide no inner content, Odac will automatically generate a default email input and submit button.
12
+
13
+ ```html
14
+ <odac:magic-login redirect="/dashboard" />
15
+ ```
16
+
17
+ ### Attributes
18
+
19
+ | Attribute | Description | Default |
20
+ |-----------|-------------|---------|
21
+ | `redirect` | URL to redirect to after successful login (Required) | `null` |
22
+ | `email-label` | Label text for the default email field | `"Email Address"` |
23
+ | `submit-text` | Text for the submit button | `"Send Magic Link"` |
24
+
25
+ ### Customized Usage
26
+
27
+ You can fully customize the form by adding your own inputs and buttons inside the tag.
28
+
29
+ ```html
30
+ <div class="auth-card">
31
+ <h2>Sign In</h2>
32
+ <p>We'll send a login link to your email.</p>
33
+
34
+ <odac:magic-login redirect="/dashboard">
35
+ <div class="form-group">
36
+ <label for="email">Work Email</label>
37
+ <odac:input name="email" type="email" placeholder="name@company.com" class="form-control">
38
+ <odac:validate rule="required|email" message="Please enter a valid work email"/>
39
+ </odac:input>
40
+ </div>
41
+
42
+ <odac:submit class="btn btn-primary w-full">Email me a login link</odac:submit>
43
+ </odac:magic-login>
44
+ </div>
45
+ ```
46
+
47
+ ## Backend API
48
+
49
+ For more complex requirements, such as building a custom API or integrating with other flows, you can use the `Odac.Auth` class directly.
50
+
51
+ ### Requesting a Magic Link
52
+
53
+ Use `magic` to generate a token and send the email.
54
+
55
+ ```javascript
56
+ // In your controller (e.g., AuthController.js)
57
+
58
+ async sendLink(req, res) {
59
+ const email = req.input('email');
60
+
61
+ // Returns { success: boolean, message: string, error?: string }
62
+ const result = await Odac.Auth.magic(email, {
63
+ redirect: '/dashboard', // Redirect after verification
64
+ subject: 'Log in to MyApp', // Email subject
65
+ template: 'auth/magic-link' // View path for the email template
66
+ });
67
+
68
+ if (result.success) {
69
+ return res.json({ message: result.message });
70
+ } else {
71
+ return res.status(400).json({ error: result.error });
72
+ }
73
+ }
74
+ ```
75
+
76
+ ### Verification Logic
77
+
78
+ Odac handles the verification route automatically at `/_odac/magic-verify`. However, if you are building a custom flow, you might interact with the underlying data model directly using `Odac.DB`.
79
+
80
+ ## Configuration
81
+
82
+ Magic links usage is configured in your `odac.json` file under the `auth` object.
83
+
84
+ ```json
85
+ {
86
+ "auth": {
87
+ "table": "users", // Table where users are stored
88
+ "magicTable": "magic_links", // Table to store tokens (auto-created)
89
+ "magicLinkRateLimit": 3600000, // Rate limit window in ms (Default: 1 hour)
90
+ "magicLinkMaxAttempts": 2, // Max emails per address per window
91
+ "magicLinkMaxAttemptsPerIP": 5, // Max requests per IP per window
92
+ "magicLinkSessionCooldown": 30000, // Session cooldown in ms (Default: 30s)
93
+ "passwordless": true, // Optimization: Skip generating passwords for new users (Default: false)
94
+ "passwordField": "password" // Column name for password (Default: password)
95
+ }
96
+ }
97
+ ```
98
+
99
+ ## Auto-Registration & Passwordless Mode
100
+
101
+ When a user verifies a magic link:
102
+
103
+ 1. **Existing User**: They are logged in immediately.
104
+ 2. **New User**: An account is automatically created for them (Auto-Registration).
105
+
106
+ By default, Odac attempts to generate a secure random password for new users to satisfy typical database constraints. However, if your application is purely passwordless (e.g. your storage schema doesn't have a `password` column at all), you should set `"passwordless": true` in your config. This optimizes performance by skipping the password generation step.
107
+
108
+ Even without this setting, Odac is smart enough to retry registration without a password if the database rejects the initial attempt due to a missing password column.
109
+
110
+ ## Security & Best Practices
111
+
112
+ 1. **Rate Limiting**: Odac enforces a 3-layer defense system:
113
+ * **Session Cooldown**: immediate cookie-based block for rapid clicks (Default: 30s).
114
+ * **IP Limit**: prevents mass attacks from a single source.
115
+ * **Email Limit**: prevents spamming a specific user.
116
+ 2. **Token Security**: Tokens are hashed in the database (`token_hash`) using secure hashing algorithms. The raw token is only sent to the user's email and never stored.
117
+ 3. **One-Time Use**: Tokens are immediately deleted upon successful verification or expiration.
118
+ 4. **User Enumeration**: To prevent attackers from checking if an email exists in your system, the `magic` method (and the form) will return a "success" message even if the user does not exist.
119
+
120
+ ## Email Template
121
+
122
+ If using the default mailer, you can create a custom email template at `views/auth/magic-link.html`. The template receives the following variables:
123
+
124
+ - `link`: The full verification URL.
125
+ - `network`: The hostname.
126
+ - `ip`: The request IP address.
127
+
128
+ ```html
129
+ <!-- views/auth/magic-link.html -->
130
+ <h1>Login Request</h1>
131
+ <p>Click the link below to log in to {{ network }}:</p>
132
+ <a href="{{ link }}">Login Now</a>
133
+ <p>This link was requested from IP: {{ ip }}</p>
134
+ ```
@@ -1,42 +1,132 @@
1
1
  ## ✉️ The `Mail` Service
2
2
 
3
- The `Odac.Mail` service is your friendly neighborhood postal worker. It provides a super simple way to send emails using the mail server settings you've already configured in the Odac core.
3
+ The `Odac.Mail` service provides a fluent, chainable interface to send emails. **It is a zero-config service** designed to work exclusively with the **ODAC Core** server. If you are running Odac, the mail service works out of the box without any additional setup.
4
4
 
5
- #### How to Send an Email
5
+ ### How to Send an Email
6
6
 
7
- `Odac.Mail.send(to, subject, htmlBody)`
7
+ The `Odac.Mail` class uses a builder pattern. You start by instantiating it with a template name, set various properties, and finally call `send()`.
8
8
 
9
- * `to`: The email address of the person you're sending it to.
10
- * `subject`: The subject line for your email.
11
- * `htmlBody`: The main content of your email. You can even use HTML tags to make it look fancy!
9
+ #### Syntax
12
10
 
13
- Just like the database service, `send` is an `async` method, so using it with `async/await` is the way to go.
11
+ ```javascript
12
+ await Odac.Mail('template_name')
13
+ .from('sender@example.com', 'Sender Name')
14
+ .to('recipient@example.com')
15
+ .subject('Your Subject Here')
16
+ .send({
17
+ variable1: 'value1',
18
+ variable2: 'value2'
19
+ });
20
+ ```
21
+
22
+ * **Template**: The constructor takes the name of the HTML template file located in `view/mail/`.
23
+ * **Data**: The object passed to `send()` contains key-value pairs that replace `{key}` placeholders in your HTML template.
14
24
 
15
- #### Example: A Simple Contact Form
25
+ #### Example: Contact Form Controller
16
26
 
17
- Let's imagine you have a controller that handles a contact form on your website.
27
+ Here is a complete example of how to use the Mail service inside a controller:
18
28
 
19
29
  ```javascript
20
30
  module.exports = async function (Odac) {
21
- const { recipient, subject, message } = Odac.Request.post;
22
-
23
- // It's always a good idea to check your data first!
24
- if (!recipient || !subject || !message) {
25
- return { error: 'Oops! You missed a required field.' };
26
- }
27
-
28
- try {
29
- // Let's try to send the email
30
- await Odac.Mail.send(recipient, subject, `<p>${message}</p>`);
31
-
32
- // If we get here, it worked!
33
- return { success: true, message: 'Email sent successfully!' };
34
- } catch (error) {
35
- // If something went wrong...
36
- console.error('Oh no, the email failed to send:', error);
37
- return { error: 'Something went wrong while trying to send the email.' };
38
- }
31
+ const { name, email, message } = Odac.Request.post;
32
+
33
+ // 1. Validate Input
34
+ if (!name || !email || !message) {
35
+ return { error: 'Please fill in all fields.' };
36
+ }
37
+
38
+ try {
39
+ // 2. Prepare and Send Email
40
+ const result = await Odac.Mail('contact_form_notification')
41
+ .from('system@myapp.com', 'My App System')
42
+ .to('admin@myapp.com')
43
+ .subject('New Contact Form Submission')
44
+ .send({
45
+ user_name: name,
46
+ user_email: email,
47
+ user_message: message,
48
+ timestamp: new Date().toISOString()
49
+ });
50
+
51
+ // 3. Check Result
52
+ if (result) {
53
+ return { success: true, message: 'Thank you! We received your message.' };
54
+ } else {
55
+ return { error: 'Failed to send email.' };
56
+ }
57
+
58
+ } catch (e) {
59
+ console.error(e);
60
+ return { error: 'An unexpected error occurred.' };
61
+ }
39
62
  }
40
63
  ```
41
64
 
42
- And that's all there is to it. You're now a master of email!
65
+ ### Template File
66
+
67
+ Create your HTML template in `view/mail/contact_form_notification.html`:
68
+
69
+ ```html
70
+ <h1>New Contact Message</h1>
71
+ <p><strong>From:</strong> {user_name} ({user_email})</p>
72
+ <p><strong>Time:</strong> {timestamp}</p>
73
+ <hr>
74
+ <p>{user_message}</p>
75
+ ```
76
+
77
+ ### Sending Plain Text or Raw HTML (No Template)
78
+
79
+ You can send emails **without creating a template file** by using the `.text()` or `.html()` methods directly. This is useful for simple notifications or dynamic content.
80
+
81
+ **Note:** If you provide both a template name AND manual content, the template will take precedence.
82
+
83
+ #### 1. Plain Text Email
84
+
85
+ Use the `.text()` method to send a simple, text-only email. The `Content-Type` will automatically be set to `text/plain`.
86
+
87
+ ```javascript
88
+ await Odac.Mail() // No template name required
89
+ .to('recipient@example.com')
90
+ .subject('System Alert')
91
+ .text('Server load is critical. Please check immediately.')
92
+ .send();
93
+ ```
94
+
95
+ > **Note:** Plain text does not support HTML tags like links (`<a href>`). If you write a URL (e.g., `https://odac.run`), most email clients will automatically make it clickable.
96
+
97
+ #### 2. Raw HTML Email
98
+
99
+ Use the `.html()` method to send an HTML email without a file.
100
+
101
+ ```javascript
102
+ await Odac.Mail()
103
+ .to('user@example.com')
104
+ .subject('Welcome')
105
+ .html('<h1>Welcome!</h1><p>Please <a href="https://example.com">click here</a>.</p>')
106
+ .send();
107
+ ```
108
+
109
+ If you use `.html()`, the system will automatically generate a plain-text version of your email by stripping the tags, just like it does for templates.
110
+
111
+ ### Advanced Usage
112
+
113
+ #### Custom Headers
114
+
115
+ You can inject custom headers into the email using the `header()` method:
116
+
117
+ ```javascript
118
+ .header({
119
+ 'X-Custom-Header': 'CustomValue',
120
+ 'Reply-To': 'support@example.com'
121
+ })
122
+ ```
123
+
124
+ #### Chainable Methods
125
+
126
+ * `from(email, name)`: Sets the sender.
127
+ * `to(email)`: Sets the recipient.
128
+ * `subject(text)`: Sets the subject line.
129
+ * `text(content)`: Sets the plain text body (used if no template is provided).
130
+ * `html(content)`: Sets the HTML body (used if no template is provided).
131
+ * `header(object)`: Merges custom headers.
132
+ * `send(data)`: Compiles the template with `data`, connects to the Odac Core, and sends the email payload. Returns a `Promise`.
@@ -91,7 +91,7 @@ module.exports = async (Odac) => {
91
91
  // controller/users/get/index.js
92
92
  module.exports = async (Odac) => {
93
93
  Odac.stream(async function* () {
94
- const users = await Odac.Mysql.table('users').get()
94
+ const users = await Odac.DB.users.get()
95
95
 
96
96
  for (const user of users) {
97
97
  yield user
@@ -194,7 +194,7 @@ module.exports = async (Odac) => {
194
194
  let hasMore = true
195
195
 
196
196
  while (hasMore) {
197
- const posts = await Odac.Mysql.table('posts')
197
+ const posts = await Odac.DB.posts
198
198
  .limit(10)
199
199
  .offset((page - 1) * 10)
200
200
  .get()
@@ -313,7 +313,7 @@ module.exports = async function(Odac) {
313
313
  const profileSlug = Odac.Var(username).slug()
314
314
 
315
315
  // Save user
316
- await Odac.Mysql.table('users').insert({
316
+ await Odac.DB.users.insert({
317
317
  email: email,
318
318
  username: username,
319
319
  password: hashedPassword,
@@ -332,7 +332,7 @@ module.exports = async function(Odac) {
332
332
  const password = Odac.Request.post('password')
333
333
 
334
334
  // Find user
335
- const user = await Odac.Mysql.table('users')
335
+ const user = await Odac.DB.users
336
336
  .where('email', email)
337
337
  .first()
338
338
 
@@ -369,19 +369,19 @@ module.exports = async function(Odac) {
369
369
  const slug = Odac.Var(title).slug()
370
370
 
371
371
  // Check if slug exists
372
- const exists = await Odac.Mysql.table('posts')
372
+ const exists = await Odac.DB.posts
373
373
  .where('slug', slug)
374
374
  .first()
375
375
 
376
376
  if (exists) {
377
377
  // Add timestamp to make unique
378
378
  const uniqueSlug = `${slug}-${Date.now()}`
379
- await Odac.Mysql.table('posts').insert({
379
+ await Odac.DB.posts.insert({
380
380
  title: title,
381
381
  slug: uniqueSlug
382
382
  })
383
383
  } else {
384
- await Odac.Mysql.table('posts').insert({
384
+ await Odac.DB.posts.insert({
385
385
  title: title,
386
386
  slug: slug
387
387
  })
@@ -464,7 +464,7 @@ module.exports = async function(Odac) {
464
464
  const encryptedCard = Odac.Var(creditCard).encrypt()
465
465
 
466
466
  // Save encrypted data
467
- await Odac.Mysql.table('payments').insert({
467
+ await Odac.DB.payments.insert({
468
468
  user_id: Odac.Auth.id(),
469
469
  card: encryptedCard
470
470
  })
@@ -474,7 +474,7 @@ module.exports = async function(Odac) {
474
474
 
475
475
  // Later, to retrieve and decrypt
476
476
  module.exports = async function(Odac) {
477
- const payment = await Odac.Mysql.table('payments')
477
+ const payment = await Odac.DB.payments
478
478
  .where('user_id', Odac.Auth.id())
479
479
  .first()
480
480
 
@@ -0,0 +1,73 @@
1
+ # IPC (Inter-Process Communication)
2
+
3
+ Odac provides a built-in IPC system that allows your application workers to communicate with each other, share data, and sync states. It abstracts the underlying driver, allowing you to switch between in-memory (cluster) and Redis-based communication with a simple configuration change.
4
+
5
+ ## Configuration
6
+
7
+ To configure the IPC system, update your project configuration. The default driver is `memory`.
8
+
9
+ ```javascript
10
+ // config.js
11
+ module.exports = {
12
+ // ...
13
+ database: {
14
+ // ... other dbs
15
+ redis: {
16
+ default: {
17
+ // node-redis connection options
18
+ // e.g., url: 'redis://user:pass@host:port'
19
+ }
20
+ }
21
+ },
22
+ ipc: {
23
+ driver: 'memory', // Options: 'memory' or 'redis'
24
+ redis: 'default' // The name of the redis connection to use (if driver is 'redis')
25
+ }
26
+ }
27
+ ```
28
+
29
+ ### Drivers
30
+
31
+ - **memory**: Uses Node.js `cluster` IPC. Ideal for single-server deployments. Data is stored in the Main/Primary process RAM and shared across workers via `process.send`.
32
+ - **redis**: Uses a Redis server. Ideal for multi-server/horizontal scaling deployments. Requires a configured Redis connection.
33
+
34
+ ## Usage
35
+
36
+ You can access the IPC module via `Odac.Ipc`.
37
+
38
+ ### Data Storage (Key-Value)
39
+
40
+ You can set, get, and delete values. These values are shared across all workers.
41
+
42
+ ```javascript
43
+ // Set a value (with optional TTL in seconds)
44
+ await Odac.Ipc.set('maintenance_mode', true);
45
+ await Odac.Ipc.set('temp_cache', { foo: 'bar' }, 60);
46
+
47
+ // Get a value
48
+ const isMaintenance = await Odac.Ipc.get('maintenance_mode');
49
+
50
+ // Delete a value
51
+ await Odac.Ipc.del('maintenance_mode');
52
+ ```
53
+
54
+ > [!NOTE]
55
+ > `get`, `set`, and `del` are asynchronous and return Promises.
56
+
57
+ ### Pub/Sub (Messaging)
58
+
59
+ You can publish messages to channels and subscribe to them in other workers.
60
+
61
+ ```javascript
62
+ // Subscribe to a channel
63
+ // Best practice: Subscribe in your app initialization or a Service Provider
64
+ await Odac.Ipc.subscribe('chat:global', (message) => {
65
+ console.log('New chat message:', message);
66
+ });
67
+
68
+ // Publish a message
69
+ await Odac.Ipc.publish('chat:global', { user: 'Emre', text: 'Hello World' });
70
+ ```
71
+
72
+ > [!TIP]
73
+ > When using `memory` driver, the subscription listener is registered in the current worker. When a message is published, it goes to the Main process and is then broadcasted to all subscribed workers.
@@ -131,7 +131,11 @@ odac.get('/api/users', function(data) {
131
131
  ## Other Utility Functions
132
132
 
133
133
  - **`Odac.client()`**: Returns a unique client identifier from a cookie.
134
- - **`Odac.data()`**: Returns data from the `candy_data` cookie, which is set by the backend. This data includes the current page and the initial CSRF token.
134
+ - **`Odac.data(key)`**: Returns shared data passed from the backend via `Odac.share`. You can get the full data object or a specific key:
135
+ ```javascript
136
+ let allData = odac.data();
137
+ let user = odac.data('user'); // Returns null if not exists
138
+ ```
135
139
  - **`Odac.page()`**: Returns the identifier of the current page. This is the controller name (e.g., `'user'`) or view name (e.g., `'dashboard'`) set by the backend. Use this to conditionally run code for specific pages.
136
140
  - **`Odac.storage()`**: A wrapper for `localStorage`.
137
141
  ```javascript
@@ -347,7 +347,7 @@ Odac.action({
347
347
  **Page Identifier Rules:**
348
348
  - **With controller**: Uses controller filename (e.g., `user.js` → `'user'`)
349
349
  - **With view object**: Uses `content` or `all` value (e.g., `{content: 'dashboard'}` → `'dashboard'`)
350
- - Accessible via `Odac.page()` or `document.documentElement.dataset.candyPage`
350
+ - Accessible via `Odac.page()`
351
351
 
352
352
  ## Server Variables
353
353
 
package/docs/index.json CHANGED
@@ -1,124 +1,4 @@
1
1
  {
2
- "server": [
3
- {
4
- "file": "01-installation",
5
- "title": "Installation",
6
- "children": [
7
- {
8
- "file": "01-quick-install.md",
9
- "title": "Quick Install"
10
- },
11
- {
12
- "file": "02-manual-installation-via-npm.md",
13
- "title": "Manual Installation (via NPM)"
14
- }
15
- ]
16
- },
17
- {
18
- "file": "02-get-started",
19
- "title": "Getting Started",
20
- "children": [
21
- {
22
- "file": "01-core-concepts.md",
23
- "title": "Core Concepts"
24
- },
25
- {
26
- "file": "02-basic-commands.md",
27
- "title": "Basic Commands"
28
- },
29
- {
30
- "file": "03-cli-reference.md",
31
- "title": "CLI Reference"
32
- },
33
- {
34
- "file": "04-cli-quick-reference.md",
35
- "title": "CLI Quick Reference"
36
- }
37
- ]
38
- },
39
- {
40
- "file": "03-service",
41
- "title": "Running a Service",
42
- "children": [
43
- {
44
- "file": "01-start-a-new-service.md",
45
- "title": "Start a New Service"
46
- },
47
- {
48
- "file": "02-delete-a-service.md",
49
- "title": "Delete a Service"
50
- }
51
- ]
52
- },
53
- {
54
- "file": "04-web",
55
- "title": "Managing Websites",
56
- "children": [
57
- {
58
- "file": "01-create-a-website.md",
59
- "title": "Create a Website"
60
- },
61
- {
62
- "file": "02-list-websites.md",
63
- "title": "List Websites"
64
- },
65
- {
66
- "file": "03-delete-a-website.md",
67
- "title": "Delete a Website"
68
- }
69
- ]
70
- },
71
- {
72
- "file": "05-subdomain",
73
- "title": "Managing Subdomains",
74
- "children": [
75
- {
76
- "file": "01-create-a-subdomain.md",
77
- "title": "Create a Subdomain"
78
- },
79
- {
80
- "file": "02-list-subdomains.md",
81
- "title": "List Subdomains"
82
- },
83
- {
84
- "file": "03-delete-a-subdomain.md",
85
- "title": "Delete a Subdomain"
86
- }
87
- ]
88
- },
89
- {
90
- "file": "06-ssl",
91
- "title": "Managing SSL",
92
- "children": [
93
- {
94
- "file": "01-renew-an-ssl-certificate.md",
95
- "title": "Renew an SSL Certificate"
96
- }
97
- ]
98
- },
99
- {
100
- "file": "07-mail",
101
- "title": "Managing Mail",
102
- "children": [
103
- {
104
- "file": "01-create-a-mail-account.md",
105
- "title": "Create a Mail Account"
106
- },
107
- {
108
- "file": "02-delete-a-mail-account.md",
109
- "title": "Delete a Mail Account"
110
- },
111
- {
112
- "file": "03-list-mail-accounts.md",
113
- "title": "List Mail Accounts"
114
- },
115
- {
116
- "file": "04-change-account-password.md",
117
- "title": "Change Account Password"
118
- }
119
- ]
120
- }
121
- ],
122
2
  "backend": [
123
3
  {
124
4
  "file": "01-overview",
@@ -134,7 +14,7 @@
134
14
  },
135
15
  {
136
16
  "file": "03-development-server.md",
137
- "title": "Development Server"
17
+ "title": "CLI Commands & Deployment"
138
18
  }
139
19
  ]
140
20
  },
@@ -321,6 +201,10 @@
321
201
  {
322
202
  "file": "09-comments.md",
323
203
  "title": "Comments"
204
+ },
205
+ {
206
+ "file": "10-styling-and-tailwind.md",
207
+ "title": "Styling & Tailwind CSS"
324
208
  }
325
209
  ]
326
210
  },
@@ -329,12 +213,20 @@
329
213
  "title": "Database",
330
214
  "children": [
331
215
  {
332
- "file": "01-database-connection.md",
333
- "title": "Database Connection"
216
+ "file": "01-getting-started.md",
217
+ "title": "Getting Started"
218
+ },
219
+ {
220
+ "file": "02-basics.md",
221
+ "title": "Query Basics"
334
222
  },
335
223
  {
336
- "file": "02-using-mysql.md",
337
- "title": "Using MySQL"
224
+ "file": "03-advanced.md",
225
+ "title": "Advanced Queries"
226
+ },
227
+ {
228
+ "file": "04-migrations.md",
229
+ "title": "Code-First Migrations"
338
230
  }
339
231
  ]
340
232
  },
@@ -375,6 +267,10 @@
375
267
  {
376
268
  "file": "06-odac-login-forms.md",
377
269
  "title": "Odac Login Forms (Zero-Config)"
270
+ },
271
+ {
272
+ "file": "07-magic-links.md",
273
+ "title": "Magic Links"
378
274
  }
379
275
  ]
380
276
  },