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,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)
|