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.
Files changed (246) hide show
  1. package/bin/build/build.mjs +53 -0
  2. package/bin/build/build_bin.mjs +17 -0
  3. package/bin/build/cli/client.mjs +52 -0
  4. package/bin/build/cli/css.mjs +22 -0
  5. package/bin/build/cli/index.mjs +40 -0
  6. package/bin/build/cli/ssr.mjs +21 -0
  7. package/bin/build/server/aliases.mjs +112 -0
  8. package/bin/build/server/babel.config.js +24 -0
  9. package/bin/build/server/banner.mjs +20 -0
  10. package/bin/build/server/bundle.mjs +111 -0
  11. package/bin/build/server/fix.mjs +15 -0
  12. package/bin/build/server/index.mjs +69 -0
  13. package/bin/build/server/options.mjs +83 -0
  14. package/bin/create/auth.mjs +23 -0
  15. package/bin/create/copy.mjs +175 -0
  16. package/bin/create/docker.mjs +25 -0
  17. package/bin/create/index.mjs +137 -0
  18. package/bin/create/pkgjson.mjs +72 -0
  19. package/bin/create/prepare-template.mjs +158 -0
  20. package/bin/create/validation.mjs +27 -0
  21. package/bin/dev/dev.mjs +32 -23
  22. package/bin/dev/dev_start.mjs +6 -5
  23. package/bin/index.mjs +94 -52
  24. package/bin/prod-bin/create-bin.mjs +42 -27
  25. package/bin/prod-bin/run.mjs +13 -9
  26. package/bin/{prod-run → run}/pid.mjs +4 -4
  27. package/bin/run/restart.mjs +13 -0
  28. package/bin/run/start.mjs +18 -0
  29. package/bin/run/stop.mjs +20 -0
  30. package/bin/util.mjs +35 -11
  31. package/package.json +59 -39
  32. package/src/config/.env +34 -12
  33. package/src/config/defaults.mjs +253 -185
  34. package/src/config/env.mjs +40 -22
  35. package/src/config/index.mjs +19 -24
  36. package/src/config/util.mjs +25 -10
  37. package/src/db-conn.mjs +34 -0
  38. package/src/engines/cron/emails.mjs +10 -5
  39. package/src/engines/cron/index.mjs +45 -51
  40. package/src/engines/cron/init.mjs +16 -17
  41. package/src/engines/cron/ipsum.mjs +65 -60
  42. package/src/engines/cron/syscheck.mjs +30 -30
  43. package/src/engines/emailer/index.mjs +1 -2
  44. package/src/engines/emailer/queue.mjs +14 -20
  45. package/src/engines/emailer/transporter.mjs +86 -74
  46. package/src/engines/geoip/index.mjs +23 -28
  47. package/src/engines/http/index.mjs +26 -15
  48. package/src/engines/logger/buildErrorEmailBody.mjs +72 -0
  49. package/src/engines/logger/index.mjs +114 -122
  50. package/src/engines/logger/injectStackTrace.mjs +59 -0
  51. package/src/engines/logger/logger_mail.mjs +47 -61
  52. package/src/engines/logger/reopenTransportOnHupSignal.mjs +12 -13
  53. package/src/engines/parser/Parser.mjs +77 -60
  54. package/src/engines/parser/index.mjs +1 -1
  55. package/src/engines/schema/diffObjs.mjs +41 -0
  56. package/src/engines/schema/index.mjs +4 -0
  57. package/src/engines/schema/input.mjs +54 -0
  58. package/src/engines/schema/output.mjs +66 -0
  59. package/src/engines/socket/index.mjs +44 -46
  60. package/src/index.mjs +15 -10
  61. package/src/middleware/auth/basic.mjs +41 -40
  62. package/src/middleware/auth/custom.mjs +10 -13
  63. package/src/middleware/auth/guest.mjs +27 -27
  64. package/src/middleware/auth/passport/index.mjs +374 -0
  65. package/src/middleware/auth/passport/session/index.mjs +43 -0
  66. package/src/middleware/auth/{credentials → passport}/session/store.mjs +35 -15
  67. package/src/middleware/auth/passport/session/store_koa_redis.mjs +3 -0
  68. package/src/middleware/context/cache/index.mjs +78 -33
  69. package/src/middleware/context/cache/options.mjs +19 -21
  70. package/src/middleware/context/db.mjs +45 -20
  71. package/src/middleware/context/index.mjs +12 -12
  72. package/src/middleware/extra.mjs +4 -5
  73. package/src/middleware/http/body.mjs +25 -25
  74. package/src/middleware/http/catcher.mjs +81 -8
  75. package/src/middleware/http/custom_blacklist.mjs +19 -16
  76. package/src/middleware/http/headers.mjs +37 -34
  77. package/src/middleware/http/ratelimit.mjs +16 -23
  78. package/src/middleware/http/request.mjs +60 -65
  79. package/src/middleware/routes/catch_js_error.mjs +30 -23
  80. package/src/middleware/routes/robots.mjs +4 -7
  81. package/src/middleware/routes/router/crud/attachCrudRoutes.mjs +108 -90
  82. package/src/middleware/routes/router/crud/getCrudConfig.mjs +31 -55
  83. package/src/middleware/routes/router/defaults.mjs +6 -19
  84. package/src/middleware/routes/router/index.mjs +17 -21
  85. package/src/middleware/routes/router/queries/attachQueriesRoutes.mjs +227 -50
  86. package/src/middleware/routes/router/queries/getQueriesConfig.mjs +45 -55
  87. package/src/middleware/routes/router/utils.mjs +41 -26
  88. package/src/middleware/ssr/context.mjs +5 -7
  89. package/src/middleware/ssr/html.mjs +66 -43
  90. package/src/middleware/ssr/loader.mjs +11 -14
  91. package/src/middleware/ssr/ssr_render.mjs +39 -22
  92. package/src/middleware/static/index.mjs +33 -14
  93. package/src/middleware/vite/devserver.mjs +38 -22
  94. package/src/middleware/vite/watcher.mjs +12 -14
  95. package/src/server-cron.mjs +13 -8
  96. package/src/server-dev.mjs +13 -16
  97. package/src/server.mjs +49 -51
  98. package/template/.agent/skills/miolo-app-arch/SKILL.md +218 -0
  99. package/template/.agent/skills/miolo-auth/SKILL.md +450 -0
  100. package/template/.agent/skills/miolo-cli-router/SKILL.md +394 -0
  101. package/template/.agent/skills/miolo-database/SKILL.md +358 -0
  102. package/template/.agent/skills/miolo-react-patterns/SKILL.md +426 -0
  103. package/template/.agent/skills/miolo-routing/SKILL.md +326 -0
  104. package/template/.agent/skills/miolo-schemas/SKILL.md +329 -0
  105. package/template/.agent/skills/miolo-session-context/SKILL.md +397 -0
  106. package/template/.agent/skills/miolo-ssr/SKILL.md +433 -0
  107. package/template/.editorconfig +18 -0
  108. package/template/.env +120 -0
  109. package/template/biome.json +63 -0
  110. package/template/components.json +21 -0
  111. package/template/db/init.sh +89 -0
  112. package/template/db/sql/00_drop.sql +2 -0
  113. package/template/db/sql/01_users.sql +31 -0
  114. package/template/db/sql/02_todos.sql +20 -0
  115. package/template/docker/Dockerfile +13 -0
  116. package/template/docker/docker-compose.yaml +79 -0
  117. package/template/gitignore +42 -0
  118. package/template/jsconfig.json +18 -0
  119. package/template/package.json +88 -0
  120. package/template/postcss.config.js +9 -0
  121. package/template/src/cli/App.jsx +25 -0
  122. package/template/src/cli/components/JsonTreeViewer.jsx +128 -0
  123. package/template/src/cli/components/shadcn-io/spinner/index.jsx +232 -0
  124. package/template/src/cli/components/stepper.jsx +408 -0
  125. package/template/src/cli/components/ui/avatar.jsx +36 -0
  126. package/template/src/cli/components/ui/badge.jsx +31 -0
  127. package/template/src/cli/components/ui/breadcrumb.jsx +97 -0
  128. package/template/src/cli/components/ui/card.jsx +73 -0
  129. package/template/src/cli/components/ui/collapsible.jsx +16 -0
  130. package/template/src/cli/components/ui/dropdown-menu.jsx +179 -0
  131. package/template/src/cli/components/ui/field.jsx +217 -0
  132. package/template/src/cli/components/ui/input.jsx +19 -0
  133. package/template/src/cli/components/ui/label.jsx +17 -0
  134. package/template/src/cli/components/ui/pagination.jsx +99 -0
  135. package/template/src/cli/components/ui/patched/alert.jsx +56 -0
  136. package/template/src/cli/components/ui/patched/button.jsx +45 -0
  137. package/template/src/cli/components/ui/patched/dialog.jsx +114 -0
  138. package/template/src/cli/components/ui/patched/sidebar.jsx +660 -0
  139. package/template/src/cli/components/ui/select.jsx +141 -0
  140. package/template/src/cli/components/ui/separator.jsx +21 -0
  141. package/template/src/cli/components/ui/sheet.jsx +115 -0
  142. package/template/src/cli/components/ui/skeleton.jsx +13 -0
  143. package/template/src/cli/components/ui/sonner.jsx +22 -0
  144. package/template/src/cli/components/ui/switch.jsx +25 -0
  145. package/template/src/cli/components/ui/table.jsx +88 -0
  146. package/template/src/cli/components/ui/textarea.jsx +16 -0
  147. package/template/src/cli/components/ui/tooltip.jsx +45 -0
  148. package/template/src/cli/config/store_keys.mjs +2 -0
  149. package/template/src/cli/context/data/DataContext.jsx +5 -0
  150. package/template/src/cli/context/data/DataProvider.jsx +44 -0
  151. package/template/src/cli/context/data/useBreads.mjs +15 -0
  152. package/template/src/cli/context/data/useDataContext.mjs +4 -0
  153. package/template/src/cli/context/session/SessionContext.mjs +4 -0
  154. package/template/src/cli/context/session/SessionProvider.jsx +31 -0
  155. package/template/src/cli/context/session/makePermissioner.mjs +34 -0
  156. package/template/src/cli/context/session/useSessionContext.mjs +6 -0
  157. package/template/src/cli/context/theme/ThemeContext.mjs +4 -0
  158. package/template/src/cli/context/theme/ThemeProvider.jsx +49 -0
  159. package/template/src/cli/context/theme/useThemeContext.mjs +6 -0
  160. package/template/src/cli/context/ui/UIContext.jsx +5 -0
  161. package/template/src/cli/context/ui/UIProvider.jsx +16 -0
  162. package/template/src/cli/context/ui/useUIContext.mjs +4 -0
  163. package/template/src/cli/context/util.mjs +17 -0
  164. package/template/src/cli/entry-cli.jsx +33 -0
  165. package/template/src/cli/hooks/useIsMobile.mjs +19 -0
  166. package/template/src/cli/hooks/useStoragedState.mjs +63 -0
  167. package/template/src/cli/index.html +29 -0
  168. package/template/src/cli/layout/app-sidebar.jsx +25 -0
  169. package/template/src/cli/layout/main-layout.jsx +63 -0
  170. package/template/src/cli/layout/nav-last-todos.jsx +72 -0
  171. package/template/src/cli/layout/nav-main.jsx +39 -0
  172. package/template/src/cli/layout/nav-user.jsx +105 -0
  173. package/template/src/cli/layout/prop-switcher.jsx +93 -0
  174. package/template/src/cli/lib/utils.mjs +10 -0
  175. package/template/src/cli/pages/Index.jsx +13 -0
  176. package/template/src/cli/pages/IndexOffline.jsx +13 -0
  177. package/template/src/cli/pages/IndexOnline.jsx +18 -0
  178. package/template/src/cli/pages/dash/Dashboard.jsx +29 -0
  179. package/template/src/cli/pages/offline/Login.jsx +43 -0
  180. package/template/src/cli/pages/offline/LoginForm.jsx +115 -0
  181. package/template/src/cli/pages/security/Security.jsx +39 -0
  182. package/template/src/cli/pages/security/SecurityForm.jsx +106 -0
  183. package/template/src/cli/pages/todos/TodoActions.jsx +99 -0
  184. package/template/src/cli/pages/todos/TodoAdd.jsx +43 -0
  185. package/template/src/cli/pages/todos/TodoList.jsx +60 -0
  186. package/template/src/cli/pages/todos/Todos.jsx +23 -0
  187. package/template/src/cli/pages/todos/context/TodosContext.jsx +5 -0
  188. package/template/src/cli/pages/todos/context/TodosProvider.jsx +191 -0
  189. package/template/src/cli/pages/todos/context/useTodosContext.mjs +4 -0
  190. package/template/src/ns/models/Todo.mjs +29 -0
  191. package/template/src/ns/models/TodoList.mjs +8 -0
  192. package/template/src/ns/models/User.mjs +40 -0
  193. package/template/src/server/bot/check_today.mjs +10 -0
  194. package/template/src/server/io/cache/base.mjs +21 -0
  195. package/template/src/server/io/db/filter.mjs +92 -0
  196. package/template/src/server/io/db/todos/delete.mjs +29 -0
  197. package/template/src/server/io/db/todos/find.mjs +13 -0
  198. package/template/src/server/io/db/todos/read.mjs +83 -0
  199. package/template/src/server/io/db/todos/toggle.mjs +37 -0
  200. package/template/src/server/io/db/todos/upsave.mjs +32 -0
  201. package/template/src/server/io/db/triggers/user.mjs +13 -0
  202. package/template/src/server/io/db/users/auth.mjs +132 -0
  203. package/template/src/server/io/db/users/pwd.mjs +38 -0
  204. package/template/src/server/io/db/users/save.mjs +17 -0
  205. package/template/src/server/miolo/auth/basic.mjs +15 -0
  206. package/template/src/server/miolo/auth/guest.mjs +3 -0
  207. package/template/src/server/miolo/auth/passport.mjs +73 -0
  208. package/template/src/server/miolo/cache.mjs +11 -0
  209. package/template/src/server/miolo/cron/foo.mjs +7 -0
  210. package/template/src/server/miolo/cron/index.mjs +28 -0
  211. package/template/src/server/miolo/cron/invalidate.mjs +21 -0
  212. package/template/src/server/miolo/db.mjs +36 -0
  213. package/template/src/server/miolo/http.mjs +14 -0
  214. package/template/src/server/miolo/index.mjs +43 -0
  215. package/template/src/server/miolo/routes/crud.mjs +16 -0
  216. package/template/src/server/miolo/routes/index.mjs +8 -0
  217. package/template/src/server/miolo/ssr/entry-server.jsx +13 -0
  218. package/template/src/server/miolo/ssr/loader.mjs +18 -0
  219. package/template/src/server/routes/index.mjs +66 -0
  220. package/template/src/server/routes/todos/mod.mjs +52 -0
  221. package/template/src/server/routes/todos/read.mjs +45 -0
  222. package/template/src/server/routes/todos/special.mjs +47 -0
  223. package/template/src/server/routes/users/user.mjs +54 -0
  224. package/template/src/server/server.mjs +10 -0
  225. package/template/src/server/utils/crypt.mjs +38 -0
  226. package/template/src/server/utils/io.mjs +15 -0
  227. package/template/src/server/utils/pwdfor.mjs +25 -0
  228. package/template/src/server/utils/schema.mjs +22 -0
  229. package/template/src/static/img/default/profile.png +0 -0
  230. package/template/src/static/img/favicon.ico +0 -0
  231. package/template/src/static/img/miolo_logo.png +0 -0
  232. package/template/src/static/img/miolo_name.png +0 -0
  233. package/template/src/static/public/manifest.json +21 -0
  234. package/template/src/static/public/sw.js +79 -0
  235. package/template/src/static/style/globals.css +156 -0
  236. package/template/src/static/style/json-tree.css +54 -0
  237. package/template/src/static/style/skeleton.css +49 -0
  238. package/bin/prod-build/build-client.mjs +0 -67
  239. package/bin/prod-build/build-server.mjs +0 -58
  240. package/bin/prod-run/restart.mjs +0 -9
  241. package/bin/prod-run/start.mjs +0 -15
  242. package/bin/prod-run/stop.mjs +0 -20
  243. package/src/engines/logger/verify.mjs +0 -22
  244. package/src/middleware/auth/credentials/index.mjs +0 -151
  245. package/src/middleware/auth/credentials/session/index.mjs +0 -24
  246. 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