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,326 @@
1
+ ---
2
+ name: miolo-routing
3
+ description: API routing patterns for miolo applications. Use when creating or modifying API endpoints, registering routes, handling HTTP requests, or structuring server-side route handlers in miolo apps. For validation schemas, see miolo-schemas skill.
4
+ ---
5
+
6
+ # Miolo Routing Patterns
7
+
8
+ Standard patterns for creating API routes in miolo applications following miolo conventions.
9
+
10
+ ## Route Organization
11
+
12
+ Routes are organized in `src/server/routes/` by domain/feature:
13
+
14
+ ```
15
+ src/server/routes/
16
+ ├── index.mjs # Route registration array
17
+ ├── users/ # User-related endpoints
18
+ │ └── user.mjs
19
+ ├── todos/ # Todo endpoints
20
+ │ ├── mod.mjs # CRUD operations
21
+ │ ├── read.mjs # Read queries
22
+ │ └── special.mjs # Special operations
23
+ └── [feature]/ # Additional feature routes
24
+ ```
25
+
26
+ ## Route Registration Pattern
27
+
28
+ All routes are registered in `src/server/routes/index.mjs` using miolo's route array format:
29
+
30
+ ```javascript
31
+ import { r_item_list, r_item_find } from './items/read.mjs'
32
+ import { r_item_upsave, r_item_delete } from './items/mod.mjs'
33
+
34
+ const auth = {
35
+ require: true,
36
+ action: 'redirect',
37
+ redirect_url: '/page/login'
38
+ }
39
+
40
+ export default [{
41
+ prefix: 'api',
42
+ routes: [
43
+ { method: 'GET', url: '/item/list', auth, callback: r_item_list },
44
+ { method: 'GET', url: '/item/find', auth, callback: r_item_find },
45
+ { method: 'POST', url: '/item/save', auth, callback: r_item_upsave },
46
+ { method: 'POST', url: '/item/delete', auth, callback: r_item_delete },
47
+ ]
48
+ }]
49
+ ```
50
+
51
+ **Key elements:**
52
+ - Export default array of route groups
53
+ - Each group has `prefix` and `routes` array
54
+ - Each route has `method`, `url`, optional `auth`, and `callback`
55
+ - Import route handler functions from feature files
56
+
57
+ ## Creating Route Handlers
58
+
59
+ ### Basic Handler Structure
60
+
61
+ Route handlers receive `(ctx, params)` and return `{ ok, data }` or `{ ok, error }`:
62
+
63
+ ```javascript
64
+ import { db_item_read } from '#server/io/db/items/read.mjs'
65
+
66
+ export async function r_item_list(ctx, params) {
67
+ try {
68
+ ctx.miolo.logger.info('[r_item_list] Fetching items')
69
+
70
+ const items = await db_item_read(ctx, params)
71
+
72
+ ctx.miolo.logger.info('[r_item_list] Found items')
73
+ return { ok: true, data: items }
74
+
75
+ } catch (error) {
76
+ ctx.miolo.logger.error(`[r_item_list] Error: ${error}`)
77
+ return { ok: false, error: error?.message }
78
+ }
79
+ }
80
+ ```
81
+
82
+ **Handler conventions:**
83
+ - Function names start with `r_` (route)
84
+ - Take `(ctx, params)` as arguments
85
+ - Return object with `ok` boolean
86
+ - Include `data` on success, `error` on failure
87
+ - Use `ctx.miolo.logger` for logging (`info` level)
88
+ - Wrap in try/catch
89
+
90
+ ### Accessing Request Data
91
+
92
+ ```javascript
93
+ export async function r_item_find(ctx, params) {
94
+ // params contains:
95
+ // - URL query parameters (?foo=bar)
96
+ // - POST body data
97
+ // - Already validated if schema provided
98
+
99
+ const { id } = params
100
+
101
+ // Access user from context
102
+ const user = ctx.state.user
103
+
104
+ // Access logger
105
+ ctx.miolo.logger.info(`User ${user.id} requesting item ${id}`)
106
+
107
+ return { ok: true, data: item }
108
+ }
109
+ ```
110
+
111
+ ### CRUD Operations Example
112
+
113
+ Complete CRUD implementation:
114
+
115
+ ```javascript
116
+ // items/mod.mjs
117
+ import { db_item_upsave } from '#server/io/db/items/upsave.mjs'
118
+ import { db_item_delete } from '#server/io/db/items/delete.mjs'
119
+
120
+ export async function r_item_upsave(ctx, params) {
121
+ try {
122
+ ctx.miolo.logger.info(`[r_item_upsave] Saving item ${params?.id || 'new'}`)
123
+
124
+ const item = await db_item_upsave(ctx, params)
125
+
126
+ ctx.miolo.logger.info(`[r_item_upsave] Saved item ${item.id}`)
127
+ return { ok: true, data: item }
128
+
129
+ } catch (error) {
130
+ ctx.miolo.logger.error(`[r_item_upsave] Error: ${error}`)
131
+ return { ok: false, error: error?.message }
132
+ }
133
+ }
134
+
135
+ export async function r_item_delete(ctx, params) {
136
+ try {
137
+ ctx.miolo.logger.info(`[r_item_delete] Deleting item ${params.id}`)
138
+
139
+ await db_item_delete(ctx, params)
140
+
141
+ ctx.miolo.logger.info(`[r_item_delete] Deleted item ${params.id}`)
142
+ return { ok: true, data: { deleted: true } }
143
+
144
+ } catch (error) {
145
+ ctx.miolo.logger.error(`[r_item_delete] Error: ${error}`)
146
+ return { ok: false, error: error?.message }
147
+ }
148
+ }
149
+ ```
150
+
151
+ ### Request Validation with Joi
152
+
153
+ Add schema validation inline or as wrapper:
154
+
155
+ **Inline schema:**
156
+ ```javascript
157
+ import Joi from 'joi'
158
+
159
+ export default [{
160
+ prefix: 'api',
161
+ routes: [
162
+ {
163
+ method: 'GET',
164
+ url: '/item/search',
165
+ auth,
166
+ callback: r_item_search,
167
+ schema: {
168
+ input: Joi.object({
169
+ query: Joi.string().min(3).required(),
170
+ limit: Joi.number().min(1).max(100).default(20)
171
+ }),
172
+ output: Joi.object({
173
+ ok: Joi.boolean().required(),
174
+ data: Joi.array().items(Joi.object()).required(),
175
+ error: Joi.string().optional()
176
+ })
177
+ }
178
+ }
179
+ ]
180
+ }]
181
+ ```
182
+
183
+ **Wrapper function:**
184
+ ```javascript
185
+ import { with_miolo_input_schema } from 'miolo'
186
+ import Joi from 'joi'
187
+
188
+ const itemSchema = Joi.object({
189
+ description: Joi.string().required(),
190
+ done: Joi.bool().optional().default(false)
191
+ })
192
+
193
+ export default [{
194
+ prefix: 'api',
195
+ routes: [
196
+ {
197
+ method: 'POST',
198
+ url: '/item/save',
199
+ auth,
200
+ callback: with_miolo_input_schema(r_item_upsave, itemSchema)
201
+ }
202
+ ]
203
+ }]
204
+ ```
205
+
206
+ ## Authentication
207
+
208
+ Configure authentication per route:
209
+
210
+ ```javascript
211
+ // Require authentication
212
+ const auth = {
213
+ require: true,
214
+ action: 'redirect',
215
+ redirect_url: '/page/login'
216
+ }
217
+
218
+ // Public route (no auth)
219
+ { method: 'GET', url: '/public/data', callback: r_public_data }
220
+
221
+ // Protected route (with auth)
222
+ { method: 'GET', url: '/private/data', auth, callback: r_private_data }
223
+ ```
224
+
225
+ Authenticated routes automatically have `ctx.state.user` populated.
226
+
227
+ ## Multi-file Organization
228
+
229
+ For complex features, split into multiple files:
230
+
231
+ ```
232
+ routes/todos/
233
+ ├── mod.mjs # CRUD operations (upsave, delete, toggle)
234
+ ├── read.mjs # Read operations (list, find)
235
+ └── special.mjs # Special operations (count, batch, etc.)
236
+ ```
237
+
238
+ Each file exports handler functions, all registered in `routes/index.mjs`.
239
+
240
+ ## Common Patterns
241
+
242
+ ### Logging Pattern
243
+
244
+ ```javascript
245
+ export async function r_item_operation(ctx, params) {
246
+ try {
247
+ ctx.miolo.logger.info(`[r_item_operation] Starting with id ${params.id}`)
248
+
249
+ const result = await db_item_operation(ctx, params)
250
+
251
+ ctx.miolo.logger.info(`[r_item_operation] Success for id ${params.id}`)
252
+ return { ok: true, data: result }
253
+
254
+ } catch (error) {
255
+ ctx.miolo.logger.error(`[r_item_operation] Error for id ${params.id}: ${error}`)
256
+ return { ok: false, error: error?.message }
257
+ }
258
+ }
259
+ ```
260
+
261
+ ### Error Handling
262
+
263
+ ```javascript
264
+ export async function r_item_find(ctx, params) {
265
+ try {
266
+ const item = await db_item_find(ctx, params)
267
+
268
+ if (!item) {
269
+ return { ok: false, error: 'Item not found' }
270
+ }
271
+
272
+ return { ok: true, data: item }
273
+
274
+ } catch (error) {
275
+ ctx.miolo.logger.error(`[r_item_find] Error: ${error}`)
276
+ return { ok: false, error: error?.message }
277
+ }
278
+ }
279
+ ```
280
+
281
+ ## Adding a New Feature
282
+
283
+ 1. **Create database functions** in `src/server/io/db/feature/`:
284
+ ```javascript
285
+ // io/db/items/read.mjs
286
+ export async function db_item_read(ctx, params) { /* ... */ }
287
+ ```
288
+
289
+ 2. **Create route handlers** in `src/server/routes/feature/`:
290
+ ```javascript
291
+ // routes/items/mod.mjs
292
+ export async function r_item_list(ctx, params) { /* ... */ }
293
+ export async function r_item_upsave(ctx, params) { /* ... */ }
294
+ ```
295
+
296
+ 3. **Register routes** in `routes/index.mjs`:
297
+ ```javascript
298
+ import { r_item_list, r_item_upsave } from './items/mod.mjs'
299
+
300
+ export default [{
301
+ prefix: 'api',
302
+ routes: [
303
+ { method: 'GET', url: '/item/list', auth, callback: r_item_list },
304
+ { method: 'POST', url: '/item/save', auth, callback: r_item_upsave },
305
+ ]
306
+ }]
307
+ ```
308
+
309
+ ## Best Practices
310
+
311
+ 1. **Use database abstraction** - Never write SQL in routes, use `io/db/` functions
312
+ 2. **Consistent naming** - Routes start with `r_`, database functions with `db_`
313
+ 3. **Always log** - Use `ctx.miolo.logger` for all operations (`info`level)
314
+ 4. **Return consistent format** - Always `{ ok, data }` or `{ ok, error }`
315
+ 5. **Validate inputs (and outputs)** - Use Joi schemas for validation
316
+ 6. **Handle errors** - Wrap in try/catch, return meaningful errors
317
+ 7. **Check user access** - Use `ctx.state.user` to verify permissions
318
+ 8. **Keep handlers thin** - Business logic in `io/db/`, routes only handle HTTP
319
+
320
+ ## Examples from miolo-sample
321
+
322
+ See actual implementations:
323
+ - `src/server/routes/index.mjs` - Route registration
324
+ - `src/server/routes/todos/mod.mjs` - CRUD handlers
325
+ - `src/server/routes/todos/read.mjs` - Read operations
326
+ - `src/server/routes/todos/special.mjs` - Custom operations
@@ -0,0 +1,329 @@
1
+ ---
2
+ name: miolo-schemas
3
+ description: Joi validation schemas and patterns for miolo applications. Use when implementing request/parameter validation for routes or database functions, creating reusable schema components, or using with_miolo_input_schema and with_miolo_output_schema wrappers.
4
+ ---
5
+
6
+ # Miolo Validation Schemas
7
+
8
+ Joi validation schema patterns for miolo routes and database functions.
9
+
10
+ ## Schema Wrappers: with_miolo_input_schema and with_miolo_output_schema
11
+
12
+ Use `with_miolo_input_schema` to wrap route handlers and database functions to validate incoming parameters.
13
+
14
+ ```javascript
15
+ import { with_miolo_input_schema } from 'miolo'
16
+ import Joi from 'joi'
17
+
18
+ // Define schema
19
+ const todoSchema = Joi.object({
20
+ id: Joi.number().integer().optional(),
21
+ description: Joi.string().required(),
22
+ done: Joi.boolean().optional().default(false)
23
+ })
24
+
25
+ // Wrap function with schema
26
+ async function _r_todo_upsave(ctx, params) {
27
+ // params are already validated here
28
+ const { id, description, done } = params
29
+ // ...
30
+ }
31
+
32
+ export const r_todo_upsave = with_miolo_input_schema(_r_todo_upsave, todoSchema)
33
+ ```
34
+
35
+ **Key benefits:**
36
+ - Automatic validation before function execution (input) or before sending the response (output)
37
+ - Validation errors handled automatically
38
+ - Default values applied
39
+ - Type coercion
40
+ - Extraneous fields removed (diff log generated on output schemas)
41
+
42
+ ### Using `with_miolo_output_schema`
43
+
44
+ Use `with_miolo_output_schema` to ensure that data returned by a function matches the expected format, automatically stripping any extra properties not defined in the schema.
45
+
46
+ ```javascript
47
+ import { with_miolo_output_schema } from 'miolo'
48
+ import Joi from 'joi'
49
+
50
+ const todoOutputSchema = Joi.object({
51
+ ok: Joi.boolean().required(),
52
+ data: Joi.object({
53
+ id: Joi.number().required(),
54
+ description: Joi.string().required()
55
+ })
56
+ })
57
+
58
+ export const db_todo_insert = with_miolo_output_schema(_db_todo_insert, todoOutputSchema)
59
+ ```
60
+
61
+ ## Using Schemas in Routes
62
+
63
+ Route definitions accept a `schema` object that can contain both `input` and `output` schemas:
64
+
65
+ ### Inline Schema (Route Definition)
66
+
67
+ Define schema directly in route configuration:
68
+
69
+ ```javascript
70
+ import Joi from 'joi'
71
+
72
+ export default [{
73
+ prefix: 'api',
74
+ routes: [
75
+ {
76
+ method: 'GET',
77
+ url: '/todo/last_hours',
78
+ auth,
79
+ callback: r_todo_count_last_hours,
80
+ schema: {
81
+ input: Joi.object({
82
+ hours: Joi.number().min(1).max(24).required()
83
+ }),
84
+ output: Joi.object({
85
+ ok: Joi.boolean().required(),
86
+ data: Joi.number().required()
87
+ })
88
+ }
89
+ }
90
+ ]
91
+ }]
92
+ ```
93
+
94
+ ### Wrapper Schema (Function)
95
+
96
+ Wrap the handler function with schema:
97
+
98
+ ```javascript
99
+ import { with_miolo_input_schema } from 'miolo'
100
+ import Joi from 'joi'
101
+
102
+ const todoSchema = Joi.object({
103
+ done: Joi.bool().optional().default(false)
104
+ })
105
+
106
+ export default [{
107
+ prefix: 'api',
108
+ routes: [
109
+ {
110
+ method: 'POST',
111
+ url: '/todo/fake',
112
+ auth,
113
+ callback: with_miolo_input_schema(r_todo_insert_fake, todoSchema)
114
+ }
115
+ ]
116
+ }]
117
+ ```
118
+
119
+ ## Using Schemas in Database Functions
120
+
121
+ **Strongly recommended** to validate database function parameters:
122
+
123
+ ```javascript
124
+ import { with_miolo_input_schema } from 'miolo'
125
+ import Joi from 'joi'
126
+ import { opt_int, bool_null, opt_str_null } from '#server/utils/schema.mjs'
127
+
128
+ // Internal implementation
129
+ async function _db_todo_read(ctx, filter) {
130
+ ctx.miolo.logger.verbose('[db_todo_read] Reading todos...')
131
+
132
+ const conn = await ctx.miolo.db.get_connection()
133
+ const options = { transaction: undefined }
134
+
135
+ // ... query logic
136
+
137
+ return todos
138
+ }
139
+
140
+ // Schema definition using partial schemas
141
+ const todo_read_schema = Joi.object({
142
+ id: opt_int,
143
+ todo_id: opt_int,
144
+ description: opt_str_null,
145
+ done: bool_null
146
+ })
147
+
148
+ // Export wrapped function
149
+ export const db_todo_read = with_miolo_input_schema(_db_todo_read, todo_read_schema)
150
+ ```
151
+
152
+ **Pattern:**
153
+ 1. Create private implementation function with `_` prefix
154
+ 2. Define schema using partial schemas from `utils/schema.mjs`
155
+ 3. Export wrapped function with `with_miolo_input_schema`
156
+
157
+ ## Partial Schemas (Reusable Components)
158
+
159
+ Miolo provides common validation patterns in `src/server/utils/schema.mjs`:
160
+
161
+ ```javascript
162
+ import Joi from 'joi'
163
+
164
+ // Integer (optional)
165
+ export const opt_int = Joi.number().integer().optional()
166
+
167
+ // String (optional, nullable)
168
+ export const opt_str_null = Joi.string().optional().allow(null)
169
+
170
+ // Boolean (nullable)
171
+ export const bool_null = Joi.boolean().optional().allow(null)
172
+
173
+ // Email
174
+ export const email = Joi.string().email().required()
175
+
176
+ // Positive integer
177
+ export const pos_int = Joi.number().integer().min(1).required()
178
+
179
+ // ... add more as needed
180
+ ```
181
+
182
+ **Usage:**
183
+ ```javascript
184
+ import { opt_int, opt_str_null, email } from '#server/utils/schema.mjs'
185
+
186
+ const userSchema = Joi.object({
187
+ id: opt_int,
188
+ email: email,
189
+ name: opt_str_null,
190
+ age: pos_int
191
+ })
192
+ ```
193
+
194
+ ## Common Schema Patterns
195
+
196
+ ### Optional vs Required
197
+
198
+ ```javascript
199
+ // Required field
200
+ description: Joi.string().required()
201
+
202
+ // Optional field
203
+ id: Joi.number().integer().optional()
204
+
205
+ // Optional with default
206
+ done: Joi.boolean().optional().default(false)
207
+ ```
208
+
209
+ ### Nullable Fields
210
+
211
+ ```javascript
212
+ // Allow null
213
+ name: Joi.string().allow(null).optional()
214
+
215
+ // Using partial schema
216
+ name: opt_str_null
217
+ ```
218
+
219
+ ### Type Validation
220
+
221
+ ```javascript
222
+ // Integer
223
+ id: Joi.number().integer()
224
+
225
+ // String with min/max length
226
+ description: Joi.string().min(3).max(500)
227
+
228
+ // Email
229
+ email: Joi.string().email()
230
+
231
+ // Boolean
232
+ active: Joi.boolean()
233
+
234
+ // Array
235
+ tags: Joi.array().items(Joi.string())
236
+
237
+ // Enum
238
+ status: Joi.string().valid('pending', 'active', 'completed')
239
+ ```
240
+
241
+ ### Number Constraints
242
+
243
+ ```javascript
244
+ // Range
245
+ age: Joi.number().min(18).max(100)
246
+
247
+ // Positive
248
+ count: Joi.number().positive()
249
+
250
+ // Integer only
251
+ quantity: Joi.number().integer()
252
+ ```
253
+
254
+ ### String Constraints
255
+
256
+ ```javascript
257
+ // Min/max length
258
+ name: Joi.string().min(3).max(50)
259
+
260
+ // Pattern (regex)
261
+ code: Joi.string().pattern(/^[A-Z]{3}[0-9]{3}$/)
262
+
263
+ // Case insensitive
264
+ email: Joi.string().email().lowercase()
265
+
266
+ // Trim whitespace
267
+ username: Joi.string().trim()
268
+ ```
269
+
270
+ ### Conditional Validation
271
+
272
+ ```javascript
273
+ const schema = Joi.object({
274
+ id: Joi.number().integer().optional(),
275
+ description: Joi.when('id', {
276
+ is: Joi.exist(),
277
+ then: Joi.string().optional(), // Optional when updating
278
+ otherwise: Joi.string().required() // Required when creating
279
+ })
280
+ })
281
+ ```
282
+
283
+ ## Best Practices
284
+
285
+ 1. **Always validate route parameters and outputs** - Use `schema: { input, output }` in route definition.
286
+ 2. **Strongly recommended for db functions** - Wrap all db_*() functions with schemas using `with_miolo_input_schema` or `with_miolo_output_schema`.
287
+ 3. **Use partial schemas** - Reuse common patterns from `utils/schema.mjs`
288
+ 4. **Add to partial schemas** - Extend `utils/schema.mjs` with new reusable patterns
289
+ 5. **Private + public pattern** - Use `_function_name` for implementation, export wrapped version
290
+ 6. **Default values** - Use `.default()` for optional fields with sensible defaults
291
+ 7. **Allow null carefully** - Only use `.allow(null)` when null is a valid business value
292
+ 8. **Validate early** - Validation should happen before any business logic
293
+ 9. **Remove unused properties** - `with_miolo_output_schema` ensures the client doesn't get extraneous fields.
294
+
295
+ ## Extending Partial Schemas
296
+
297
+ Add new reusable patterns to `src/server/utils/schema.mjs`:
298
+
299
+ ```javascript
300
+ // Add new partial schemas
301
+ export const uuid = Joi.string().uuid().required()
302
+ export const opt_uuid = Joi.string().uuid().optional()
303
+ export const date_iso = Joi.date().iso().required()
304
+ export const opt_date_iso = Joi.date().iso().optional()
305
+ export const pagination = Joi.object({
306
+ page: Joi.number().integer().min(1).default(1),
307
+ limit: Joi.number().integer().min(1).max(100).default(20)
308
+ })
309
+ ```
310
+
311
+ Then use across your application:
312
+
313
+ ```javascript
314
+ import { uuid, date_iso, pagination } from '#server/utils/schema.mjs'
315
+
316
+ const eventSchema = Joi.object({
317
+ id: uuid,
318
+ created_at: date_iso,
319
+ ...pagination
320
+ })
321
+ ```
322
+
323
+ ## Examples from miolo-sample
324
+
325
+ See actual implementations:
326
+ - `src/server/utils/schema.mjs` - Partial schema definitions
327
+ - `src/server/io/db/todos/read.mjs` - Database function with schema
328
+ - `src/server/io/db/todos/upsave.mjs` - Insert/update with schema
329
+ - `src/server/routes/index.mjs` - Route schemas (inline and wrapper)