odac 1.0.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agent/rules/coding.md +27 -0
- package/.agent/rules/memory.md +33 -0
- package/.agent/rules/project.md +30 -0
- package/.agent/rules/workflow.md +16 -0
- package/.github/workflows/auto-pr-description.yml +3 -1
- package/.github/workflows/release.yml +42 -1
- package/.github/workflows/test-coverage.yml +6 -5
- package/.github/workflows/test-publish.yml +36 -0
- package/.husky/pre-commit +10 -0
- package/.husky/pre-push +13 -0
- package/.releaserc.js +3 -3
- package/CHANGELOG.md +184 -0
- package/README.md +53 -34
- package/bin/odac.js +181 -49
- package/client/odac.js +878 -995
- package/docs/backend/01-overview/03-development-server.md +39 -46
- package/docs/backend/02-structure/01-typical-project-layout.md +59 -25
- package/docs/backend/03-config/00-configuration-overview.md +15 -6
- package/docs/backend/03-config/01-database-connection.md +3 -3
- package/docs/backend/03-config/02-static-route-mapping-optional.md +1 -1
- package/docs/backend/03-config/03-request-timeout.md +1 -1
- package/docs/backend/03-config/04-environment-variables.md +4 -4
- package/docs/backend/03-config/05-early-hints.md +2 -2
- package/docs/backend/04-routing/02-controller-less-view-routes.md +9 -3
- package/docs/backend/04-routing/03-api-and-data-routes.md +18 -0
- package/docs/backend/04-routing/07-cron-jobs.md +17 -1
- package/docs/backend/04-routing/09-websocket.md +29 -0
- package/docs/backend/05-controllers/01-how-to-build-a-controller.md +48 -3
- package/docs/backend/05-controllers/02-your-trusty-odac-assistant.md +2 -0
- package/docs/backend/05-controllers/03-controller-classes.md +61 -55
- package/docs/backend/05-forms/01-custom-forms.md +103 -95
- package/docs/backend/05-forms/02-automatic-database-insert.md +21 -21
- package/docs/backend/06-request-and-response/01-the-request-object-what-is-the-user-asking-for.md +17 -0
- package/docs/backend/07-views/02-rendering-a-view.md +1 -1
- package/docs/backend/07-views/03-variables.md +5 -5
- package/docs/backend/07-views/04-request-data.md +1 -1
- package/docs/backend/07-views/08-backend-javascript.md +1 -1
- package/docs/backend/07-views/10-styling-and-tailwind.md +93 -0
- package/docs/backend/08-database/01-getting-started.md +100 -0
- package/docs/backend/08-database/02-basics.md +136 -0
- package/docs/backend/08-database/03-advanced.md +84 -0
- package/docs/backend/08-database/04-migrations.md +48 -0
- package/docs/backend/09-validation/01-the-validator-service.md +1 -0
- package/docs/backend/10-authentication/03-register.md +9 -2
- package/docs/backend/10-authentication/04-odac-register-forms.md +48 -48
- package/docs/backend/10-authentication/05-session-management.md +16 -2
- package/docs/backend/10-authentication/06-odac-login-forms.md +50 -50
- package/docs/backend/10-authentication/07-magic-links.md +134 -0
- package/docs/backend/11-mail/01-the-mail-service.md +118 -28
- package/docs/backend/12-streaming/01-streaming-overview.md +2 -2
- package/docs/backend/13-utilities/01-odac-var.md +7 -7
- package/docs/backend/13-utilities/02-ipc.md +73 -0
- package/docs/frontend/01-overview/01-introduction.md +5 -1
- package/docs/frontend/02-ajax-navigation/01-quick-start.md +1 -1
- package/docs/index.json +21 -125
- package/eslint.config.mjs +5 -47
- package/jest.config.js +1 -1
- package/package.json +16 -7
- package/src/Auth.js +414 -121
- package/src/Config.js +12 -7
- package/src/Database.js +188 -0
- package/src/Env.js +3 -1
- package/src/Ipc.js +337 -0
- package/src/Lang.js +9 -2
- package/src/Mail.js +408 -37
- package/src/Odac.js +105 -40
- package/src/Request.js +71 -49
- package/src/Route/Cron.js +62 -18
- package/src/Route/Internal.js +215 -12
- package/src/Route/Middleware.js +7 -2
- package/src/Route.js +372 -109
- package/src/Server.js +118 -12
- package/src/Storage.js +169 -0
- package/src/Token.js +6 -4
- package/src/Validator.js +95 -3
- package/src/Var.js +22 -6
- package/src/View/EarlyHints.js +43 -33
- package/src/View/Form.js +210 -28
- package/src/View.js +108 -7
- package/src/WebSocket.js +18 -3
- package/template/odac.json +5 -0
- package/template/package.json +3 -1
- package/template/route/www.js +12 -10
- package/template/view/content/home.html +3 -3
- package/template/view/head/main.html +2 -2
- package/test/Client.test.js +168 -0
- package/test/Config.test.js +112 -0
- package/test/Lang.test.js +92 -0
- package/test/Odac.test.js +86 -0
- package/test/{framework/middleware.test.js → Route/Middleware.test.js} +2 -2
- package/test/{framework/Route.test.js → Route.test.js} +1 -1
- package/test/{framework/View → View}/EarlyHints.test.js +1 -1
- package/test/{framework/WebSocket.test.js → WebSocket.test.js} +2 -2
- package/test/scripts/check-coverage.js +4 -4
- package/docs/backend/08-database/01-database-connection.md +0 -99
- package/docs/backend/08-database/02-using-mysql.md +0 -322
- package/src/Mysql.js +0 -575
- package/template/config.json +0 -5
- package/test/cli/Cli.test.js +0 -36
- package/test/core/Candy.test.js +0 -234
- package/test/core/Commands.test.js +0 -538
- package/test/core/Config.test.js +0 -1432
- package/test/core/Lang.test.js +0 -250
- package/test/core/Process.test.js +0 -156
- package/test/server/Api.test.js +0 -647
- package/test/server/DNS.test.js +0 -2050
- package/test/server/DNS.test.js.bak +0 -2084
- package/test/server/Hub.test.js +0 -497
- package/test/server/Log.test.js +0 -73
- package/test/server/Mail.account.test_.js +0 -460
- package/test/server/Mail.init.test_.js +0 -411
- package/test/server/Mail.test_.js +0 -1340
- package/test/server/SSL.test_.js +0 -1491
- package/test/server/Server.test.js +0 -765
- package/test/server/Service.test_.js +0 -1127
- package/test/server/Subdomain.test.js +0 -440
- package/test/server/Web/Firewall.test.js +0 -175
- package/test/server/Web/Proxy.test.js +0 -397
- package/test/server/Web.test.js +0 -1494
- package/test/server/__mocks__/acme-client.js +0 -17
- package/test/server/__mocks__/bcrypt.js +0 -50
- package/test/server/__mocks__/child_process.js +0 -389
- package/test/server/__mocks__/crypto.js +0 -432
- package/test/server/__mocks__/fs.js +0 -450
- package/test/server/__mocks__/globalOdac.js +0 -227
- package/test/server/__mocks__/http.js +0 -575
- package/test/server/__mocks__/https.js +0 -272
- package/test/server/__mocks__/index.js +0 -249
- package/test/server/__mocks__/mail/server.js +0 -100
- package/test/server/__mocks__/mail/smtp.js +0 -31
- package/test/server/__mocks__/mailparser.js +0 -81
- package/test/server/__mocks__/net.js +0 -369
- package/test/server/__mocks__/node-forge.js +0 -328
- package/test/server/__mocks__/os.js +0 -320
- package/test/server/__mocks__/path.js +0 -291
- package/test/server/__mocks__/selfsigned.js +0 -8
- package/test/server/__mocks__/server/src/mail/server.js +0 -100
- package/test/server/__mocks__/server/src/mail/smtp.js +0 -31
- package/test/server/__mocks__/smtp-server.js +0 -106
- package/test/server/__mocks__/sqlite3.js +0 -394
- package/test/server/__mocks__/testFactories.js +0 -299
- package/test/server/__mocks__/testHelpers.js +0 -363
- package/test/server/__mocks__/tls.js +0 -229
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 `
|
|
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
|