miolo 3.0.0-beta.21 → 3.0.0-beta.210
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/bin/build/build.mjs +53 -0
- package/bin/build/build_bin.mjs +17 -0
- package/bin/build/cli/client.mjs +52 -0
- package/bin/build/cli/css.mjs +22 -0
- package/bin/build/cli/index.mjs +40 -0
- package/bin/build/cli/ssr.mjs +21 -0
- package/bin/build/server/aliases.mjs +112 -0
- package/bin/build/server/babel.config.js +24 -0
- package/bin/build/server/banner.mjs +20 -0
- package/bin/build/server/bundle.mjs +111 -0
- package/bin/build/server/fix.mjs +15 -0
- package/bin/build/server/index.mjs +69 -0
- package/bin/build/server/options.mjs +83 -0
- package/bin/create/auth.mjs +23 -0
- package/bin/create/copy.mjs +175 -0
- package/bin/create/docker.mjs +25 -0
- package/bin/create/index.mjs +137 -0
- package/bin/create/pkgjson.mjs +72 -0
- package/bin/create/prepare-template.mjs +158 -0
- package/bin/create/validation.mjs +27 -0
- package/bin/dev/dev.mjs +32 -23
- package/bin/dev/dev_start.mjs +6 -5
- package/bin/index.mjs +94 -52
- package/bin/prod-bin/create-bin.mjs +42 -27
- package/bin/prod-bin/run.mjs +13 -9
- package/bin/{prod-run → run}/pid.mjs +4 -4
- package/bin/run/restart.mjs +13 -0
- package/bin/run/start.mjs +18 -0
- package/bin/run/stop.mjs +20 -0
- package/bin/util.mjs +35 -11
- package/package.json +59 -39
- package/src/config/.env +34 -12
- package/src/config/defaults.mjs +253 -185
- package/src/config/env.mjs +40 -22
- package/src/config/index.mjs +19 -24
- package/src/config/util.mjs +25 -10
- package/src/db-conn.mjs +34 -0
- package/src/engines/cron/emails.mjs +10 -5
- package/src/engines/cron/index.mjs +45 -51
- package/src/engines/cron/init.mjs +16 -17
- package/src/engines/cron/ipsum.mjs +65 -60
- package/src/engines/cron/syscheck.mjs +30 -30
- package/src/engines/emailer/index.mjs +1 -2
- package/src/engines/emailer/queue.mjs +14 -20
- package/src/engines/emailer/transporter.mjs +86 -74
- package/src/engines/geoip/index.mjs +23 -28
- package/src/engines/http/index.mjs +26 -15
- package/src/engines/logger/buildErrorEmailBody.mjs +72 -0
- package/src/engines/logger/index.mjs +114 -122
- package/src/engines/logger/injectStackTrace.mjs +59 -0
- package/src/engines/logger/logger_mail.mjs +47 -61
- package/src/engines/logger/reopenTransportOnHupSignal.mjs +12 -13
- package/src/engines/parser/Parser.mjs +77 -60
- package/src/engines/parser/index.mjs +1 -1
- package/src/engines/schema/diffObjs.mjs +41 -0
- package/src/engines/schema/index.mjs +4 -0
- package/src/engines/schema/input.mjs +54 -0
- package/src/engines/schema/output.mjs +66 -0
- package/src/engines/socket/index.mjs +44 -46
- package/src/index.mjs +15 -10
- package/src/middleware/auth/basic.mjs +41 -40
- package/src/middleware/auth/custom.mjs +10 -13
- package/src/middleware/auth/guest.mjs +27 -27
- package/src/middleware/auth/passport/index.mjs +374 -0
- package/src/middleware/auth/passport/session/index.mjs +43 -0
- package/src/middleware/auth/{credentials → passport}/session/store.mjs +35 -15
- package/src/middleware/auth/passport/session/store_koa_redis.mjs +3 -0
- package/src/middleware/context/cache/index.mjs +78 -33
- package/src/middleware/context/cache/options.mjs +19 -21
- package/src/middleware/context/db.mjs +45 -20
- package/src/middleware/context/index.mjs +12 -12
- package/src/middleware/extra.mjs +4 -5
- package/src/middleware/http/body.mjs +25 -25
- package/src/middleware/http/catcher.mjs +81 -8
- package/src/middleware/http/custom_blacklist.mjs +19 -16
- package/src/middleware/http/headers.mjs +37 -34
- package/src/middleware/http/ratelimit.mjs +16 -23
- package/src/middleware/http/request.mjs +60 -65
- package/src/middleware/routes/catch_js_error.mjs +30 -23
- package/src/middleware/routes/robots.mjs +4 -7
- package/src/middleware/routes/router/crud/attachCrudRoutes.mjs +108 -90
- package/src/middleware/routes/router/crud/getCrudConfig.mjs +31 -55
- package/src/middleware/routes/router/defaults.mjs +6 -19
- package/src/middleware/routes/router/index.mjs +17 -21
- package/src/middleware/routes/router/queries/attachQueriesRoutes.mjs +227 -50
- package/src/middleware/routes/router/queries/getQueriesConfig.mjs +45 -55
- package/src/middleware/routes/router/utils.mjs +41 -26
- package/src/middleware/ssr/context.mjs +5 -7
- package/src/middleware/ssr/html.mjs +66 -43
- package/src/middleware/ssr/loader.mjs +11 -14
- package/src/middleware/ssr/ssr_render.mjs +39 -22
- package/src/middleware/static/index.mjs +33 -14
- package/src/middleware/vite/devserver.mjs +38 -22
- package/src/middleware/vite/watcher.mjs +12 -14
- package/src/server-cron.mjs +13 -8
- package/src/server-dev.mjs +13 -16
- package/src/server.mjs +49 -51
- package/template/.agent/skills/miolo-app-arch/SKILL.md +218 -0
- package/template/.agent/skills/miolo-auth/SKILL.md +450 -0
- package/template/.agent/skills/miolo-cli-router/SKILL.md +394 -0
- package/template/.agent/skills/miolo-database/SKILL.md +358 -0
- package/template/.agent/skills/miolo-react-patterns/SKILL.md +426 -0
- package/template/.agent/skills/miolo-routing/SKILL.md +326 -0
- package/template/.agent/skills/miolo-schemas/SKILL.md +329 -0
- package/template/.agent/skills/miolo-session-context/SKILL.md +397 -0
- package/template/.agent/skills/miolo-ssr/SKILL.md +433 -0
- package/template/.editorconfig +18 -0
- package/template/.env +120 -0
- package/template/biome.json +63 -0
- package/template/components.json +21 -0
- package/template/db/init.sh +89 -0
- package/template/db/sql/00_drop.sql +2 -0
- package/template/db/sql/01_users.sql +31 -0
- package/template/db/sql/02_todos.sql +20 -0
- package/template/docker/Dockerfile +13 -0
- package/template/docker/docker-compose.yaml +79 -0
- package/template/gitignore +42 -0
- package/template/jsconfig.json +18 -0
- package/template/package.json +88 -0
- package/template/postcss.config.js +9 -0
- package/template/src/cli/App.jsx +25 -0
- package/template/src/cli/components/JsonTreeViewer.jsx +128 -0
- package/template/src/cli/components/shadcn-io/spinner/index.jsx +232 -0
- package/template/src/cli/components/stepper.jsx +408 -0
- package/template/src/cli/components/ui/avatar.jsx +36 -0
- package/template/src/cli/components/ui/badge.jsx +31 -0
- package/template/src/cli/components/ui/breadcrumb.jsx +97 -0
- package/template/src/cli/components/ui/card.jsx +73 -0
- package/template/src/cli/components/ui/collapsible.jsx +16 -0
- package/template/src/cli/components/ui/dropdown-menu.jsx +179 -0
- package/template/src/cli/components/ui/field.jsx +217 -0
- package/template/src/cli/components/ui/input.jsx +19 -0
- package/template/src/cli/components/ui/label.jsx +17 -0
- package/template/src/cli/components/ui/pagination.jsx +99 -0
- package/template/src/cli/components/ui/patched/alert.jsx +56 -0
- package/template/src/cli/components/ui/patched/button.jsx +45 -0
- package/template/src/cli/components/ui/patched/dialog.jsx +114 -0
- package/template/src/cli/components/ui/patched/sidebar.jsx +660 -0
- package/template/src/cli/components/ui/select.jsx +141 -0
- package/template/src/cli/components/ui/separator.jsx +21 -0
- package/template/src/cli/components/ui/sheet.jsx +115 -0
- package/template/src/cli/components/ui/skeleton.jsx +13 -0
- package/template/src/cli/components/ui/sonner.jsx +22 -0
- package/template/src/cli/components/ui/switch.jsx +25 -0
- package/template/src/cli/components/ui/table.jsx +88 -0
- package/template/src/cli/components/ui/textarea.jsx +16 -0
- package/template/src/cli/components/ui/tooltip.jsx +45 -0
- package/template/src/cli/config/store_keys.mjs +2 -0
- package/template/src/cli/context/data/DataContext.jsx +5 -0
- package/template/src/cli/context/data/DataProvider.jsx +44 -0
- package/template/src/cli/context/data/useBreads.mjs +15 -0
- package/template/src/cli/context/data/useDataContext.mjs +4 -0
- package/template/src/cli/context/session/SessionContext.mjs +4 -0
- package/template/src/cli/context/session/SessionProvider.jsx +31 -0
- package/template/src/cli/context/session/makePermissioner.mjs +34 -0
- package/template/src/cli/context/session/useSessionContext.mjs +6 -0
- package/template/src/cli/context/theme/ThemeContext.mjs +4 -0
- package/template/src/cli/context/theme/ThemeProvider.jsx +49 -0
- package/template/src/cli/context/theme/useThemeContext.mjs +6 -0
- package/template/src/cli/context/ui/UIContext.jsx +5 -0
- package/template/src/cli/context/ui/UIProvider.jsx +16 -0
- package/template/src/cli/context/ui/useUIContext.mjs +4 -0
- package/template/src/cli/context/util.mjs +17 -0
- package/template/src/cli/entry-cli.jsx +33 -0
- package/template/src/cli/hooks/useIsMobile.mjs +19 -0
- package/template/src/cli/hooks/useStoragedState.mjs +63 -0
- package/template/src/cli/index.html +29 -0
- package/template/src/cli/layout/app-sidebar.jsx +25 -0
- package/template/src/cli/layout/main-layout.jsx +63 -0
- package/template/src/cli/layout/nav-last-todos.jsx +72 -0
- package/template/src/cli/layout/nav-main.jsx +39 -0
- package/template/src/cli/layout/nav-user.jsx +105 -0
- package/template/src/cli/layout/prop-switcher.jsx +93 -0
- package/template/src/cli/lib/utils.mjs +10 -0
- package/template/src/cli/pages/Index.jsx +13 -0
- package/template/src/cli/pages/IndexOffline.jsx +13 -0
- package/template/src/cli/pages/IndexOnline.jsx +18 -0
- package/template/src/cli/pages/dash/Dashboard.jsx +29 -0
- package/template/src/cli/pages/offline/Login.jsx +43 -0
- package/template/src/cli/pages/offline/LoginForm.jsx +115 -0
- package/template/src/cli/pages/security/Security.jsx +39 -0
- package/template/src/cli/pages/security/SecurityForm.jsx +106 -0
- package/template/src/cli/pages/todos/TodoActions.jsx +99 -0
- package/template/src/cli/pages/todos/TodoAdd.jsx +43 -0
- package/template/src/cli/pages/todos/TodoList.jsx +60 -0
- package/template/src/cli/pages/todos/Todos.jsx +23 -0
- package/template/src/cli/pages/todos/context/TodosContext.jsx +5 -0
- package/template/src/cli/pages/todos/context/TodosProvider.jsx +191 -0
- package/template/src/cli/pages/todos/context/useTodosContext.mjs +4 -0
- package/template/src/ns/models/Todo.mjs +29 -0
- package/template/src/ns/models/TodoList.mjs +8 -0
- package/template/src/ns/models/User.mjs +40 -0
- package/template/src/server/bot/check_today.mjs +10 -0
- package/template/src/server/io/cache/base.mjs +21 -0
- package/template/src/server/io/db/filter.mjs +92 -0
- package/template/src/server/io/db/todos/delete.mjs +29 -0
- package/template/src/server/io/db/todos/find.mjs +13 -0
- package/template/src/server/io/db/todos/read.mjs +83 -0
- package/template/src/server/io/db/todos/toggle.mjs +37 -0
- package/template/src/server/io/db/todos/upsave.mjs +32 -0
- package/template/src/server/io/db/triggers/user.mjs +13 -0
- package/template/src/server/io/db/users/auth.mjs +132 -0
- package/template/src/server/io/db/users/pwd.mjs +38 -0
- package/template/src/server/io/db/users/save.mjs +17 -0
- package/template/src/server/miolo/auth/basic.mjs +15 -0
- package/template/src/server/miolo/auth/guest.mjs +3 -0
- package/template/src/server/miolo/auth/passport.mjs +73 -0
- package/template/src/server/miolo/cache.mjs +11 -0
- package/template/src/server/miolo/cron/foo.mjs +7 -0
- package/template/src/server/miolo/cron/index.mjs +28 -0
- package/template/src/server/miolo/cron/invalidate.mjs +21 -0
- package/template/src/server/miolo/db.mjs +36 -0
- package/template/src/server/miolo/http.mjs +14 -0
- package/template/src/server/miolo/index.mjs +43 -0
- package/template/src/server/miolo/routes/crud.mjs +16 -0
- package/template/src/server/miolo/routes/index.mjs +8 -0
- package/template/src/server/miolo/ssr/entry-server.jsx +13 -0
- package/template/src/server/miolo/ssr/loader.mjs +18 -0
- package/template/src/server/routes/index.mjs +66 -0
- package/template/src/server/routes/todos/mod.mjs +52 -0
- package/template/src/server/routes/todos/read.mjs +45 -0
- package/template/src/server/routes/todos/special.mjs +47 -0
- package/template/src/server/routes/users/user.mjs +54 -0
- package/template/src/server/server.mjs +10 -0
- package/template/src/server/utils/crypt.mjs +38 -0
- package/template/src/server/utils/io.mjs +15 -0
- package/template/src/server/utils/pwdfor.mjs +25 -0
- package/template/src/server/utils/schema.mjs +22 -0
- package/template/src/static/img/default/profile.png +0 -0
- package/template/src/static/img/favicon.ico +0 -0
- package/template/src/static/img/miolo_logo.png +0 -0
- package/template/src/static/img/miolo_name.png +0 -0
- package/template/src/static/public/manifest.json +21 -0
- package/template/src/static/public/sw.js +79 -0
- package/template/src/static/style/globals.css +156 -0
- package/template/src/static/style/json-tree.css +54 -0
- package/template/src/static/style/skeleton.css +49 -0
- package/bin/prod-build/build-client.mjs +0 -67
- package/bin/prod-build/build-server.mjs +0 -58
- package/bin/prod-run/restart.mjs +0 -9
- package/bin/prod-run/start.mjs +0 -15
- package/bin/prod-run/stop.mjs +0 -20
- package/src/engines/logger/verify.mjs +0 -22
- package/src/middleware/auth/credentials/index.mjs +0 -151
- package/src/middleware/auth/credentials/session/index.mjs +0 -24
- package/src/middleware/auth/credentials/session/store_koa_redis.mjs +0 -3
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: miolo-database
|
|
3
|
+
description: Database query patterns and organization for miolo applications. Use when creating database queries, organizing io/db structure, writing SQL, or implementing data access layers in miolo apps. For validation schemas, see miolo-schemas skill.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Miolo Database Patterns
|
|
7
|
+
|
|
8
|
+
Database query organization and patterns for miolo applications using PostgreSQL.
|
|
9
|
+
|
|
10
|
+
## Database Layer Organization
|
|
11
|
+
|
|
12
|
+
All database queries are in `src/server/io/db/`, organized by domain:
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
src/server/io/db/
|
|
16
|
+
├── filter.mjs # Common query filters
|
|
17
|
+
├── users/ # User-related queries
|
|
18
|
+
│ ├── auth.mjs
|
|
19
|
+
│ ├── pwd.mjs
|
|
20
|
+
│ └── save.mjs
|
|
21
|
+
├── todos/ # Todo queries
|
|
22
|
+
│ ├── read.mjs
|
|
23
|
+
│ ├── upsave.mjs
|
|
24
|
+
│ ├── delete.mjs
|
|
25
|
+
│ └── toggle.mjs
|
|
26
|
+
└── [feature]/ # Feature-specific queries
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Database Query Pattern
|
|
30
|
+
|
|
31
|
+
All database functions follow consistent naming and structure:
|
|
32
|
+
|
|
33
|
+
```javascript
|
|
34
|
+
// src/server/io/db/items/read.mjs
|
|
35
|
+
|
|
36
|
+
export async function db_item_read(ctx, params) {
|
|
37
|
+
ctx.miolo.logger.verbose('[db_item_read] Reading items...')
|
|
38
|
+
|
|
39
|
+
const { user_id } = params
|
|
40
|
+
|
|
41
|
+
const conn = await ctx.miolo.db.get_connection()
|
|
42
|
+
const options = { transaction: undefined }
|
|
43
|
+
|
|
44
|
+
const query = `
|
|
45
|
+
SELECT id, description, created_at
|
|
46
|
+
FROM items
|
|
47
|
+
WHERE user_id = $1
|
|
48
|
+
ORDER BY created_at DESC
|
|
49
|
+
`
|
|
50
|
+
|
|
51
|
+
const items = await conn.select(query, [user_id], options)
|
|
52
|
+
|
|
53
|
+
ctx.miolo.logger.verbose(`[db_item_read] Read ${items.length} items`)
|
|
54
|
+
return items
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Naming conventions:**
|
|
59
|
+
- Functions start with `db_`
|
|
60
|
+
- Format: `db_[domain]_[action]`
|
|
61
|
+
- Examples: `db_user_auth`, `db_todo_upsave`, `db_item_delete`
|
|
62
|
+
|
|
63
|
+
**Function signature:**
|
|
64
|
+
- Takes `(ctx, params)` as arguments
|
|
65
|
+
- `ctx` contains `ctx.miolo.db` for database access
|
|
66
|
+
- `params` contains all query parameters
|
|
67
|
+
|
|
68
|
+
## CRUD Operations
|
|
69
|
+
|
|
70
|
+
### Read (List)
|
|
71
|
+
|
|
72
|
+
```javascript
|
|
73
|
+
export async function db_todo_read(ctx, params) {
|
|
74
|
+
ctx.miolo.logger.verbose('[db_todo_read] Reading todos...')
|
|
75
|
+
|
|
76
|
+
const { user_id, status } = params
|
|
77
|
+
|
|
78
|
+
const conn = await ctx.miolo.db.get_connection()
|
|
79
|
+
const options = { transaction: undefined }
|
|
80
|
+
|
|
81
|
+
let query = 'SELECT * FROM todos WHERE user_id = $1'
|
|
82
|
+
const values = [user_id]
|
|
83
|
+
|
|
84
|
+
if (status) {
|
|
85
|
+
query += ' AND status = $2'
|
|
86
|
+
values.push(status)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
query += ' ORDER BY created_at DESC'
|
|
90
|
+
|
|
91
|
+
const todos = await conn.select(query, values, options)
|
|
92
|
+
|
|
93
|
+
ctx.miolo.logger.verbose(`[db_todo_read] Read ${todos.length} todos`)
|
|
94
|
+
return todos
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Read (Single)
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
export async function db_todo_find(ctx, params) {
|
|
102
|
+
ctx.miolo.logger.verbose(`[db_todo_find] Finding todo ${params.id}...`)
|
|
103
|
+
|
|
104
|
+
const { id, user_id } = params
|
|
105
|
+
|
|
106
|
+
const conn = await ctx.miolo.db.get_connection()
|
|
107
|
+
const options = { transaction: undefined }
|
|
108
|
+
|
|
109
|
+
const query = `
|
|
110
|
+
SELECT * FROM todos
|
|
111
|
+
WHERE id = $1 AND user_id = $2
|
|
112
|
+
`
|
|
113
|
+
|
|
114
|
+
const todos = await conn.select(query, [id, user_id], options)
|
|
115
|
+
|
|
116
|
+
ctx.miolo.logger.verbose(`[db_todo_find] Todo ${id} ${todos[0] ? 'found' : 'not found'}`)
|
|
117
|
+
return todos[0] || null
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Insert/Update (Upsave)
|
|
122
|
+
|
|
123
|
+
```javascript
|
|
124
|
+
export async function db_todo_upsave(ctx, params) {
|
|
125
|
+
const { id, description, user_id, done = false } = params
|
|
126
|
+
|
|
127
|
+
ctx.miolo.logger.verbose(`[db_todo_upsave] ${id ? 'Updating' : 'Inserting'} todo...`)
|
|
128
|
+
|
|
129
|
+
const conn = await ctx.miolo.db.get_connection()
|
|
130
|
+
const options = { transaction: undefined }
|
|
131
|
+
|
|
132
|
+
const Todo = await conn.get_model('todo')
|
|
133
|
+
|
|
134
|
+
if (id) {
|
|
135
|
+
// Update existing
|
|
136
|
+
const nrecs = await Todo.update(
|
|
137
|
+
{ description, done },
|
|
138
|
+
{ id, user_id },
|
|
139
|
+
options
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
ctx.miolo.logger.verbose(`[db_todo_upsave] Updated ${nrecs} todos`)
|
|
143
|
+
return { ...params, id }
|
|
144
|
+
} else {
|
|
145
|
+
// Insert new
|
|
146
|
+
const newId = await Todo.insert(
|
|
147
|
+
{ description, done, user_id },
|
|
148
|
+
options
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
ctx.miolo.logger.verbose(`[db_todo_upsave] Inserted todo with id ${newId}`)
|
|
152
|
+
return { ...params, id: newId }
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Delete
|
|
158
|
+
|
|
159
|
+
```javascript
|
|
160
|
+
export async function db_todo_delete(ctx, params) {
|
|
161
|
+
ctx.miolo.logger.verbose(`[db_todo_delete] Deleting todo ${params.id}...`)
|
|
162
|
+
|
|
163
|
+
const { id } = params
|
|
164
|
+
|
|
165
|
+
const conn = await ctx.miolo.db.get_connection()
|
|
166
|
+
const options = { transaction: undefined }
|
|
167
|
+
|
|
168
|
+
const Todo = await conn.get_model('todo')
|
|
169
|
+
|
|
170
|
+
await Todo.delete({ id }, options)
|
|
171
|
+
|
|
172
|
+
ctx.miolo.logger.verbose(`[db_todo_delete] Deleted todo ${id}`)
|
|
173
|
+
return id
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Query Execution
|
|
178
|
+
|
|
179
|
+
Always get a connection first, then use the connection methods:
|
|
180
|
+
|
|
181
|
+
```javascript
|
|
182
|
+
const conn = await ctx.miolo.db.get_connection()
|
|
183
|
+
const options = { transaction: undefined }
|
|
184
|
+
|
|
185
|
+
// SELECT query
|
|
186
|
+
const users = await conn.select('SELECT * FROM users WHERE id = $1', [userId], options)
|
|
187
|
+
|
|
188
|
+
// Single row
|
|
189
|
+
const user = users[0]
|
|
190
|
+
|
|
191
|
+
// Check if found
|
|
192
|
+
if (users.length === 0) {
|
|
193
|
+
return null
|
|
194
|
+
}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Query Filters with make_query_filter
|
|
198
|
+
|
|
199
|
+
All SELECT queries should use `make_query_filter()` from `io/db/filter.mjs` to build WHERE clauses:
|
|
200
|
+
|
|
201
|
+
**File:** `src/server/io/db/filter.mjs`
|
|
202
|
+
|
|
203
|
+
```javascript
|
|
204
|
+
import { make_query_filter } from '#server/io/db/filter.mjs'
|
|
205
|
+
|
|
206
|
+
export async function db_todo_read(ctx, filter) {
|
|
207
|
+
ctx.miolo.logger.verbose('[db_todo_read] Reading todos...')
|
|
208
|
+
ctx.miolo.logger.silly(`[db_todo_read] filter: ${JSON.stringify(filter)}`)
|
|
209
|
+
|
|
210
|
+
const conn = await ctx.miolo.db.get_connection()
|
|
211
|
+
const options = { transaction: undefined }
|
|
212
|
+
|
|
213
|
+
let query = `
|
|
214
|
+
SELECT *
|
|
215
|
+
FROM todo AS t
|
|
216
|
+
*WHERE*`
|
|
217
|
+
|
|
218
|
+
const [where, values] = make_query_filter(filter, {
|
|
219
|
+
todo_id: { alias: 't.id' },
|
|
220
|
+
description: { op: '~*' }, // Case-insensitive regex match
|
|
221
|
+
done: { coalesce: false, alias: 'COALESCE(done, false)' }
|
|
222
|
+
}, {
|
|
223
|
+
fields: ['id', 'todo_id', 'description', 'done']
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
// IMPORTANT: Always check that at least one filter is provided
|
|
227
|
+
if (values.length === 0) {
|
|
228
|
+
throw new Error('[db_todo_read] At least one filter must be specified')
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
query = query.replace('*WHERE*', where)
|
|
232
|
+
|
|
233
|
+
const todos = await conn.select(query, values, options)
|
|
234
|
+
|
|
235
|
+
ctx.miolo.logger.verbose(`[db_todo_read] Read ${todos.length} todos`)
|
|
236
|
+
return todos
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**make_query_filter parameters:**
|
|
241
|
+
|
|
242
|
+
1. **filter** - Object with filter values from request
|
|
243
|
+
2. **field_config** - Configuration for each filterable field:
|
|
244
|
+
- `alias` - SQL column alias (e.g., `'t.id'`)
|
|
245
|
+
- `op` - Operator (e.g., `'~*'` for case-insensitive match, `'='` default)
|
|
246
|
+
- `coalesce` - Default value for NULL fields
|
|
247
|
+
3. **options** - Configuration:
|
|
248
|
+
- `fields` - Array of allowed filter field names
|
|
249
|
+
|
|
250
|
+
**Returns:** `[whereClause, values]`
|
|
251
|
+
- `whereClause` - SQL WHERE clause string
|
|
252
|
+
- `values` - Array of parameterized values
|
|
253
|
+
|
|
254
|
+
**Usage pattern:**
|
|
255
|
+
```javascript
|
|
256
|
+
const [where, values] = make_query_filter(params, {
|
|
257
|
+
user_id: { alias: 'u.id' },
|
|
258
|
+
email: { op: 'ILIKE' },
|
|
259
|
+
status: { alias: 'status' }
|
|
260
|
+
}, {
|
|
261
|
+
fields: ['user_id', 'email', 'status']
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
// Always validate that at least one filter is provided
|
|
265
|
+
if (values.length === 0) {
|
|
266
|
+
throw new Error('At least one filter must be specified')
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
let query = `SELECT * FROM users AS u *WHERE*`
|
|
270
|
+
query = query.replace('*WHERE*', where)
|
|
271
|
+
const users = await conn.select(query, values, options)
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
## Database Configuration
|
|
276
|
+
|
|
277
|
+
Database connection configured in `src/server/miolo/db.mjs`:
|
|
278
|
+
|
|
279
|
+
```javascript
|
|
280
|
+
export default {
|
|
281
|
+
db: {
|
|
282
|
+
database: process.env.POSTGRES_DB,
|
|
283
|
+
host: process.env.IS_DOCKER === "true" ? process.env.MIOLO_DB_DOCKER_HOST : process.env.MIOLO_DB_HOST,
|
|
284
|
+
port: process.env.POSTGRES_PORT,
|
|
285
|
+
user: process.env.POSTGRES_USER,
|
|
286
|
+
password: process.env.POSTGRES_PASSWORD,
|
|
287
|
+
max: process.env.MIOLO_DB_POOL_MAX,
|
|
288
|
+
min: process.env.MIOLO_DB_POOL_MIN,
|
|
289
|
+
idleTimeoutMillis: process.env.MIOLO_DB_POOL_IDLE_TIMEOUT_MS,
|
|
290
|
+
},
|
|
291
|
+
options: {
|
|
292
|
+
// check https://github.com/afialapis/calustra?tab=readme-ov-file#options
|
|
293
|
+
tables: {
|
|
294
|
+
name: 'todo',
|
|
295
|
+
useDateFields: true,
|
|
296
|
+
useUserFields: {
|
|
297
|
+
use: true,
|
|
298
|
+
fieldNames: {
|
|
299
|
+
created_by: 'created_by',
|
|
300
|
+
last_update_by: 'last_update_by'
|
|
301
|
+
}
|
|
302
|
+
},
|
|
303
|
+
triggers: {
|
|
304
|
+
beforeInsert: (conn, params, options) => {
|
|
305
|
+
return [params, options, true]
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
Environment variables in `.env`:
|
|
314
|
+
```
|
|
315
|
+
POSTGRES_DB=myapp_dev
|
|
316
|
+
POSTGRES_HOST=localhost
|
|
317
|
+
POSTGRES_PORT=5432
|
|
318
|
+
POSTGRES_USER=postgres
|
|
319
|
+
POSTGRES_PASSWORD=password
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## Database Initialization
|
|
323
|
+
|
|
324
|
+
SQL migrations in `db/sql/` executed in alphabetical order:
|
|
325
|
+
|
|
326
|
+
```
|
|
327
|
+
db/sql/
|
|
328
|
+
├── 00_drop.sql # Drop tables (development)
|
|
329
|
+
├── 01_users.sql # Create users table
|
|
330
|
+
├── 02_items.sql # Create items table
|
|
331
|
+
└── 03_indexes.sql # Create indexes
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
Run initialization:
|
|
335
|
+
```bash
|
|
336
|
+
./db/init.sh myapp_db
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
## Best Practices
|
|
340
|
+
|
|
341
|
+
1. **Always parameterize** - Never concatenate user input into SQL
|
|
342
|
+
2. **Require filters** - Always check `values.length > 0` after `make_query_filter()` and throw error if no filters
|
|
343
|
+
3. **Check ownership** - Include `user_id` in WHERE clauses to enforce access control
|
|
344
|
+
4. **Return RETURNING** - Use `RETURNING *` for INSERT/UPDATE to get created data
|
|
345
|
+
5. **Handle not found** - Return `null` for single records, `[]` for lists
|
|
346
|
+
6. **Use transactions** - For multi-query operations that must succeed/fail together
|
|
347
|
+
7. **Index properly** - Add indexes on frequently queried columns
|
|
348
|
+
8. **Limit results** - Always use LIMIT for list queries to prevent large responses
|
|
349
|
+
9. **Keep queries in io/db/** - Never write SQL in route handlers
|
|
350
|
+
10. **Always log** - Use `ctx.miolo.logger` for all operations (`verbose` level)
|
|
351
|
+
|
|
352
|
+
## Examples from miolo-sample
|
|
353
|
+
|
|
354
|
+
See actual implementations:
|
|
355
|
+
- `src/server/io/db/todos/read.mjs` - Read queries
|
|
356
|
+
- `src/server/io/db/todos/upsave.mjs` - Insert/update pattern
|
|
357
|
+
- `src/server/io/db/users/auth.mjs` - Authentication query
|
|
358
|
+
- `src/server/miolo/db.mjs` - Database configuration
|