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
@@ -141,7 +141,7 @@ You have full access to the `Odac` object within templates:
141
141
  module.exports = async function(Odac) {
142
142
  // Fetch user from database
143
143
  const userId = Odac.Request.get('id')
144
- const user = await Odac.Mysql.table('users')
144
+ const user = await Odac.DB.users
145
145
  .where('id', userId)
146
146
  .first()
147
147
 
@@ -179,7 +179,7 @@ module.exports = async function(Odac) {
179
179
  // Controller: controller/product.js
180
180
  module.exports = async function(Odac) {
181
181
  const productId = Odac.Request.get('id')
182
- const product = await Odac.Mysql.table('products')
182
+ const product = await Odac.DB.products
183
183
  .where('id', productId)
184
184
  .first()
185
185
 
@@ -221,7 +221,7 @@ module.exports = async function(Odac) {
221
221
  ```javascript
222
222
  // Controller: controller/products.js
223
223
  module.exports = async function(Odac) {
224
- const products = await Odac.Mysql.table('products')
224
+ const products = await Odac.DB.products
225
225
  .where('active', true)
226
226
  .get()
227
227
 
@@ -259,7 +259,7 @@ module.exports = async function(Odac) {
259
259
  **Good:**
260
260
  ```javascript
261
261
  // Controller
262
- const user = await Odac.Mysql.table('users').first()
262
+ const user = await Odac.DB.users.first()
263
263
  const isAdmin = user.role === 'admin'
264
264
 
265
265
  Odac.set({
@@ -284,7 +284,7 @@ Always handle cases where data might not exist:
284
284
  // Controller
285
285
  module.exports = async function(Odac) {
286
286
  const productId = Odac.Request.get('id')
287
- const product = await Odac.Mysql.table('products')
287
+ const product = await Odac.DB.products
288
288
  .where('id', productId)
289
289
  .first()
290
290
 
@@ -69,7 +69,7 @@ module.exports = async function(Odac) {
69
69
  const validatedPage = Math.max(1, page)
70
70
 
71
71
  // Fetch results
72
- const results = await Odac.Mysql.table('products')
72
+ const results = await Odac.DB.products
73
73
  .where('name', 'like', `%${validatedQuery}%`)
74
74
  .limit(20)
75
75
  .offset((validatedPage - 1) * 20)
@@ -379,7 +379,7 @@ You can use multiple `<script:odac>` blocks in the same view:
379
379
  ```html
380
380
  <script:odac>
381
381
  // Don't do this - should be in controller
382
- const users = await Odac.Mysql.query('SELECT * FROM users');
382
+ const users = await Odac.DB.users.get();
383
383
  const apiData = await fetch('https://api.example.com/data');
384
384
  </script:odac>
385
385
  ```
@@ -0,0 +1,93 @@
1
+ # 🎨 Styling & Tailwind CSS
2
+
3
+ Odac comes with built-in, **Zero-Config** support for Tailwind CSS v4. This means you can start building beautiful, modern interfaces right out of the box without messing with configuration files or build pipelines.
4
+
5
+ ## How it Works
6
+
7
+ The framework adopts a "Convention over Configuration" approach:
8
+
9
+ 1. **Development (`npm run dev`)**:
10
+ * The framework automatically watches your HTML, JS, and View files.
11
+ * It compiles your CSS classes using the high-performance Rust-based Tailwind CLI.
12
+ * Changes are reflected instantly.
13
+
14
+ 2. **Production (`npm run build`)**:
15
+ * The framework scans your project, builds the final CSS, and minifies it.
16
+ * The output is saved to `public/assets/css/app.css`.
17
+
18
+ 3. **Serving (`npm start`)**:
19
+ * The compiled CSS file is served statically. No background processes, no overhead.
20
+
21
+ ## Customizing CSS
22
+
23
+ By default, Odac manages everything for you internally. However, if you need to add custom CSS, fonts, or Tailwind configuration (like `@theme`), you can do so easily.
24
+
25
+ ### The Source File
26
+
27
+ Simply create a file at **`view/css/app.css`**.
28
+
29
+ If this file exists, Odac will use it as the **source** (input) for Tailwind.
30
+
31
+ **Example `view/css/app.css`:**
32
+
33
+ ```css
34
+ @import "tailwindcss";
35
+
36
+ @theme {
37
+ --font-display: "Satoshi", "sans-serif";
38
+ --color-brand: #ff5733;
39
+ }
40
+
41
+ /* Your custom CSS rules */
42
+ .hero-gradient {
43
+ background: linear-gradient(to right, var(--color-brand), #ff0000);
44
+ }
45
+ ```
46
+
47
+ ### The Output File
48
+
49
+ The compiled CSS is always output to:
50
+ **`public/assets/css/app.css`**
51
+
52
+ > **⚠️ Important:** Never edit `public/assets/css/app.css` manually. It is a generated file and will be overwritten by Odac during build or development. Always edit `view/css/app.css` instead.
53
+
54
+ ## HTML Integration
55
+
56
+ In your layout files (e.g., `view/head/main.html`), simply link to the compiled asset:
57
+
58
+ ```html
59
+ <link rel="stylesheet" href="/assets/css/app.css" />
60
+ ```
61
+
62
+ This is already set up for you in the default project template.
63
+
64
+ ## Multiple CSS Files
65
+
66
+ Odac supports multiple CSS entry points. If your application has distinct sections (e.g., a Landing Page, a Dashboard, and an Admin Panel) that require separate stylesheets, you can organize them easily.
67
+
68
+ ### How to use
69
+
70
+ Any `.css` file you place in the **`view/css/`** directory will be automatically detected, watched, and compiled by Odac.
71
+
72
+ **Input:**
73
+ * `view/css/app.css`
74
+ * `view/css/admin.css`
75
+ * `view/css/landing.css`
76
+
77
+ **Output (Compiled):**
78
+ * `public/assets/css/app.css`
79
+ * `public/assets/css/admin.css`
80
+ * `public/assets/css/landing.css`
81
+
82
+ You can then link each specific stylesheet in its respective layout file:
83
+
84
+ ```html
85
+ <!-- In Admin Layout -->
86
+ <link rel="stylesheet" href="/assets/css/admin.css" />
87
+ ```
88
+
89
+ ## Using Tailwind v4
90
+
91
+ Tailwind v4 is radically simpler. You generally don't need a `tailwind.config.js` file anymore. You can define your theme directly in CSS using the `@theme` block as shown above.
92
+
93
+ However, Odac respects standard Tailwind behavior. If you absolutely need a config file for plugins or legacy reasons, you can create one, and the Tailwind CLI will detect it. But for 99% of use cases, the Zero-Config approach is cleaner and faster.
@@ -0,0 +1,100 @@
1
+ # Getting Started
2
+
3
+ ODAC supports multiple database connections including **MySQL**, **PostgreSQL** (beta), and **SQLite**. It uses a robust and secure connection pooling mechanism.
4
+
5
+ ## Configuration
6
+
7
+ Add your database credentials to `odac.json`.
8
+
9
+ Supported configuration options:
10
+
11
+ - `host` - Database server hostname (default: `localhost`)
12
+ - `user` - Database username
13
+ - `password` - Database password
14
+ - `database` - Database name
15
+ - `port` - Database port
16
+ - `type` - Database type (`mysql`, `postgres`, `sqlite`)
17
+ - `filename` - Database file path (only for `sqlite`, default: `./dev.sqlite3`)
18
+
19
+ ### Single Connection (MySQL Default)
20
+
21
+ ```json
22
+ {
23
+ "database": {
24
+ "type": "mysql",
25
+ "host": "localhost",
26
+ "user": "root",
27
+ "password": "password",
28
+ "database": "odac_app"
29
+ }
30
+ }
31
+ ```
32
+
33
+ ### PostgreSQL
34
+
35
+ ```json
36
+ {
37
+ "database": {
38
+ "type": "postgres",
39
+ "host": "localhost",
40
+ "user": "postgres",
41
+ "password": "password",
42
+ "database": "odac_app",
43
+ "port": 5432
44
+ }
45
+ }
46
+ ```
47
+
48
+ ### Multiple Databases
49
+
50
+ You can configure multiple database connections. The connection named `default` (or the first one) is used automatically.
51
+
52
+ ```json
53
+ {
54
+ "database": {
55
+ "default": {
56
+ "type": "mysql",
57
+ "host": "localhost",
58
+ "database": "main_db"
59
+ },
60
+ "analytics": {
61
+ "type": "postgres",
62
+ "host": "analytics.example.com",
63
+ "database": "analytics_db"
64
+ }
65
+ }
66
+ }
67
+ ```
68
+
69
+ ---
70
+
71
+ ## Environment Variables
72
+
73
+ For security, **always** use environment variables for sensitive data.
74
+
75
+ **.env file:**
76
+ ```
77
+ DB_HOST=localhost
78
+ DB_USER=myuser
79
+ DB_PASSWORD=mypassword
80
+ ```
81
+
82
+ **odac.json:**
83
+ ```json
84
+ {
85
+ "database": {
86
+ "type": "mysql",
87
+ "host": "${DB_HOST}",
88
+ "user": "${DB_USER}",
89
+ "password": "${DB_PASSWORD}"
90
+ }
91
+ }
92
+ ```
93
+
94
+ ---
95
+
96
+ ## Automatic Connection
97
+
98
+ The connection is established automatically when your application starts. You don't need to write any connection code.
99
+
100
+ **Next Step:** Check out [Query Basics](./02-basics.md) to start using your database.
@@ -0,0 +1,136 @@
1
+ # Query Basics
2
+
3
+ ODAC features a powerful, efficient, and "magic" Query Builder. It allows you to interact with your database using simple, chainable methods without writing raw SQL.
4
+
5
+ ## Accessing Tables
6
+
7
+ You can access any table directly as a property of `Odac.DB`.
8
+
9
+ ```javascript
10
+ // Access the 'users' table
11
+ const query = Odac.DB.users;
12
+ ```
13
+
14
+ If you have multiple database connections defined in your config:
15
+
16
+ ```javascript
17
+ // Access 'visits' table on 'analytics' connection
18
+ const visits = Odac.DB.analytics.visits;
19
+ ```
20
+
21
+ ---
22
+
23
+ ## Retrieving Data (Select)
24
+
25
+ ### Fetch All Rows
26
+
27
+ ```javascript
28
+ const users = await Odac.DB.users.select();
29
+ ```
30
+
31
+ ### Fetch a Single Row
32
+
33
+ Use `.first()` to get a single object instead of an array.
34
+
35
+ ```javascript
36
+ const user = await Odac.DB.users.where('id', 1).first();
37
+ ```
38
+
39
+ ### Filtering (Where)
40
+
41
+ ```javascript
42
+ // Simple equals
43
+ const users = await Odac.DB.users.where('email', 'john@example.com').select();
44
+
45
+ // Comparison operators
46
+ const products = await Odac.DB.products.where('price', '>', 100).select();
47
+ const activeUsers = await Odac.DB.users.where('status', '!=', 'banned').select();
48
+
49
+ // OR statements
50
+ const staff = await Odac.DB.users
51
+ .where('role', 'admin')
52
+ .orWhere('role', 'editor')
53
+ .select();
54
+ ```
55
+
56
+ ### Ordering and Limiting
57
+
58
+ ```javascript
59
+ const latestPosts = await Odac.DB.posts
60
+ .orderBy('created_at', 'desc')
61
+ .limit(5);
62
+ ```
63
+
64
+ ### Counting Rows
65
+
66
+ ODAC simplifies counting rows. Unlike standard Knex behavior which might return objects or strings, `count()` directly returns a `Number` for simple queries.
67
+
68
+ ```javascript
69
+ const totalUsers = await Odac.DB.users.count(); // Returns: 150 (Number)
70
+
71
+ const activeAdmins = await Odac.DB.users
72
+ .where('role', 'admin')
73
+ .where('active', true)
74
+ .count(); // Returns: 5 (Number)
75
+ ```
76
+
77
+ ---
78
+
79
+ ## Inserting Data
80
+
81
+ ```javascript
82
+ // Insert a single record
83
+ await Odac.DB.users.insert({
84
+ name: 'John Doe',
85
+ email: 'john@example.com'
86
+ });
87
+
88
+ // Insert multiple records
89
+ await Odac.DB.tags.insert([
90
+ { name: 'javascript' },
91
+ { name: 'nodejs' }
92
+ ]);
93
+ ```
94
+
95
+ ---
96
+
97
+ ## Updating Data
98
+
99
+ ```javascript
100
+ await Odac.DB.users
101
+ .where('id', 1)
102
+ .update({
103
+ status: 'active',
104
+ last_login: new Date()
105
+ });
106
+ ```
107
+
108
+ ---
109
+
110
+ ## Deleting Data
111
+
112
+ await Odac.DB.users.where('id', 1).delete();
113
+ ```
114
+
115
+ ---
116
+
117
+ ## ID Generation (NanoID)
118
+
119
+ ODAC includes a built-in helper for generating robust, unique string IDs (NanoID) without needing external packages. Secure, URL-friendly, and collision-resistant.
120
+
121
+ ```javascript
122
+ // Generate a standard 21-character ID (e.g., "V1StGXR8_Z5jdHi6B-myT")
123
+ const id = Odac.DB.nanoid();
124
+
125
+ // Generate a custom length ID
126
+ const shortId = Odac.DB.nanoid(10);
127
+ ```
128
+
129
+ This is particularly useful when inserting records into tables that use string-based Primary Keys instead of auto-increment integers.
130
+
131
+ ```javascript
132
+ await Odac.DB.posts.insert({
133
+ id: Odac.DB.nanoid(),
134
+ title: 'My First Post'
135
+ });
136
+ ```
@@ -0,0 +1,84 @@
1
+ # Advanced Queries
2
+
3
+ For complex applications, ODAC provides advanced query capabilities like nested constraints, joins, transactions, and raw SQL execution.
4
+
5
+ ## Nested Where Clauses
6
+
7
+ To create complex `AND / OR` logic (like parenthesis in SQL), use a callback function with `.where()` or `.andWhere()`.
8
+
9
+ **Example:**
10
+ `SELECT * FROM users WHERE status = 'active' AND (role = 'admin' OR role = 'editor')`
11
+
12
+ **ODAC Code:**
13
+ ```javascript
14
+ await Odac.DB.users
15
+ .where('status', 'active')
16
+ .andWhere(builder => {
17
+ builder.where('role', 'admin').orWhere('role', 'editor');
18
+ })
19
+ .select();
20
+ ```
21
+
22
+ ---
23
+
24
+ ## Joins
25
+
26
+ You can join multiple tables using `.join()`, `.leftJoin()`, etc.
27
+
28
+ ```javascript
29
+ const posts = await Odac.DB.posts
30
+ .join('users', 'posts.user_id', '=', 'users.id')
31
+ .select('posts.title', 'users.name as author');
32
+ ```
33
+
34
+ ---
35
+
36
+ ## Transactions
37
+
38
+ Transactions allow you to ensure multiple database operations succeed or fail together.
39
+
40
+ ```javascript
41
+ await Odac.DB.transaction(async (trx) => {
42
+
43
+ const [userId] = await trx('users').insert({ name: 'Alice' });
44
+
45
+ await trx('accounts').insert({ user_id: userId, balance: 100 });
46
+
47
+ // If anything throws an error here, both inserts are rolled back.
48
+ });
49
+ ```
50
+
51
+ > **Note:** Use `Odac.DB.connectionName.transaction(...)` for non-default connections.
52
+
53
+ ---
54
+
55
+ ## Raw Queries & Values
56
+
57
+ ### Raw SQL Execution
58
+ If you need to execute a completely raw SQL query:
59
+
60
+ ```javascript
61
+ const result = await Odac.DB.run('SELECT email FROM users WHERE id = ?', [1]);
62
+ ```
63
+
64
+ ### Raw Values in Updates
65
+ Sometimes you need to call SQL functions (like `NOW()` or `COUNT()`) inside an update or insert.
66
+
67
+ ```javascript
68
+ await Odac.DB.users.where('id', 1).update({
69
+ updated_at: Odac.DB.raw('NOW()'),
70
+ visits: Odac.DB.raw('visits + 1')
71
+ });
72
+ ```
73
+
74
+ ---
75
+
76
+ ## Safe Table Access
77
+
78
+ If your table name conflicts with a reserved ODAC method (e.g., `transaction`, `schema`, `run`), use the `.table()` method to access it safely.
79
+
80
+ ```javascript
81
+ // Access a table named 'transaction'
82
+ const logs = await Odac.DB.table('transaction').select();
83
+ ```
84
+
@@ -0,0 +1,48 @@
1
+ # Code-First Migrations
2
+
3
+ Migration files are great, but sometimes (especially in rapid development or zero-config apps) you want dependencies to define their own table structures automatically.
4
+
5
+ ODAC uses the `.schema()` helper for this logic.
6
+
7
+ ## Ensuring Tables Exist
8
+
9
+ The `.schema()` method checks if a table exists. If it **does not exist**, it runs the provided callback to create it. If it **already exists**, it does nothing.
10
+
11
+ ```javascript
12
+ // Ensure 'products' table exists on the fly
13
+ await Odac.DB.products.schema(t => {
14
+ t.increments('id');
15
+ t.string('name').notNullable();
16
+ t.decimal('price', 10, 2);
17
+ t.boolean('is_active').defaultTo(true);
18
+
19
+ // Automatic timestamps (created_at, updated_at)
20
+ t.timestamps(true, true);
21
+ });
22
+ ```
23
+
24
+ The `t` argument is a Schema Builder. You can define columns using standard types like:
25
+ - `t.string()`
26
+ - `t.integer()`
27
+ - `t.boolean()`
28
+ - `t.text()`
29
+ - `t.date()`
30
+ - `t.json()`
31
+
32
+ ## Usage Example
33
+
34
+ A typical pattern is to define schemas in your module's initialization or before the first insert.
35
+
36
+ ```javascript
37
+ // In your controller or module
38
+ async function init() {
39
+ await Odac.DB.logs.schema(t => {
40
+ t.string('level');
41
+ t.text('message');
42
+ t.timestamps();
43
+ });
44
+ }
45
+
46
+ // Later...
47
+ await Odac.DB.logs.insert({ level: 'info', message: 'App started' });
48
+ ```
@@ -70,6 +70,7 @@ if (await validator.error()) {
70
70
  - `in:substring` - Must contain substring
71
71
  - `notin:substring` - Must not contain substring
72
72
  - `regex:pattern` - Must match regex pattern
73
+ - `!disposable` - Block disposable/temporary email providers (List is automatically updated daily)
73
74
 
74
75
  **Security:**
75
76
  - `xss` - Check for HTML tags (XSS protection)
@@ -113,18 +113,25 @@ module.exports = async function (Odac) {
113
113
 
114
114
  ## Configuration
115
115
 
116
- Make sure your `config.json` has the auth configuration:
116
+ Make sure your `odac.json` has the auth configuration:
117
117
 
118
118
  ```json
119
119
  {
120
120
  "auth": {
121
121
  "table": "users",
122
122
  "key": "id",
123
- "token": "user_tokens"
123
+ "token": "user_tokens",
124
+ "idType": "nanoid" // Options: "nanoid" (default, string) or "int" (auto-increment)
124
125
  }
125
126
  }
126
127
  ```
127
128
 
129
+ ### ID Generation Strategy
130
+ ODAC automatically detects your preferred ID strategy:
131
+ 1. **NanoID (Default)**: Generates secure, URL-friendly 21-character string IDs. Recommended for modern apps.
132
+ 2. **Auto-Increment**: If your database table uses `INTEGER` or `SERIAL` primary keys, ODAC detects this and lets the database handle ID generation.
133
+ 3. **Manual Override**: You can force a specific behavior using the `idType` config setting.
134
+
128
135
  ## Security Notes
129
136
 
130
137
  - Passwords are automatically hashed with bcrypt before storage