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,433 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: miolo-ssr
|
|
3
|
+
description: Server-Side Rendering (SSR) patterns for miolo applications. Use when implementing data preloading on the server, configuring SSR loader, or using useSsrData hook for client-side hydration and remote data loading.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Miolo Server-Side Rendering (SSR)
|
|
7
|
+
|
|
8
|
+
Server-side data preloading and client-side hydration patterns for miolo applications.
|
|
9
|
+
|
|
10
|
+
## Backend - SSR Loader
|
|
11
|
+
|
|
12
|
+
The SSR loader preloads data on the server before sending the initial HTML to the client.
|
|
13
|
+
|
|
14
|
+
### Loader Configuration
|
|
15
|
+
|
|
16
|
+
**File:** `src/server/miolo/ssr/loader.mjs`
|
|
17
|
+
|
|
18
|
+
```javascript
|
|
19
|
+
import { db_todo_read } from '#server/io/db/todos/read.mjs'
|
|
20
|
+
import { db_user_profile } from '#server/io/db/users/read.mjs'
|
|
21
|
+
|
|
22
|
+
const loader = async (ctx) => {
|
|
23
|
+
let todos = []
|
|
24
|
+
let profile = null
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
// Load data using database functions
|
|
28
|
+
todos = await db_todo_read(ctx, {})
|
|
29
|
+
todos = todos.sort((a, b) => b.created_at - a.created_at)
|
|
30
|
+
} catch (_) {}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
if (ctx.session.user) {
|
|
34
|
+
profile = await db_user_profile(ctx, { id: ctx.session.user.id })
|
|
35
|
+
}
|
|
36
|
+
} catch (_) {}
|
|
37
|
+
|
|
38
|
+
// Return object with data
|
|
39
|
+
// Keys are important - they're used in client code
|
|
40
|
+
const data = {
|
|
41
|
+
todos, // Accessible as 'todos' in client
|
|
42
|
+
profile // Accessible as 'profile' in client
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return data
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export { loader }
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
**Key points:**
|
|
52
|
+
- Export async function named `loader`
|
|
53
|
+
- Receives `ctx` with session and request data
|
|
54
|
+
- Returns object with data to preload
|
|
55
|
+
- **Object keys are important** - used as `<key>` in client `useSsrData()`
|
|
56
|
+
- Wrap in try/catch to handle errors gracefully
|
|
57
|
+
|
|
58
|
+
### URL-Based Loading
|
|
59
|
+
|
|
60
|
+
Filter data based on current URL:
|
|
61
|
+
|
|
62
|
+
```javascript
|
|
63
|
+
const loader = async (ctx) => {
|
|
64
|
+
const url = ctx.request.url
|
|
65
|
+
|
|
66
|
+
let data = {}
|
|
67
|
+
|
|
68
|
+
// Load different data based on route
|
|
69
|
+
if (url.startsWith('/dashboard')) {
|
|
70
|
+
try {
|
|
71
|
+
data.stats = await db_dashboard_stats(ctx)
|
|
72
|
+
data.recent = await db_recent_activity(ctx)
|
|
73
|
+
} catch (_) {}
|
|
74
|
+
} else if (url.startsWith('/profile')) {
|
|
75
|
+
try {
|
|
76
|
+
data.profile = await db_user_profile(ctx, { id: ctx.session.user.id })
|
|
77
|
+
data.settings = await db_user_settings(ctx, { id: ctx.session.user.id })
|
|
78
|
+
} catch (_) {}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return data
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Access URL:**
|
|
86
|
+
- `ctx.request.url` - Current request URL
|
|
87
|
+
- Load only the data needed for the current page
|
|
88
|
+
- Reduces payload size and improves performance
|
|
89
|
+
|
|
90
|
+
### User-Specific Loading
|
|
91
|
+
|
|
92
|
+
Load different data based on authentication:
|
|
93
|
+
|
|
94
|
+
```javascript
|
|
95
|
+
const loader = async (ctx) => {
|
|
96
|
+
let data = {
|
|
97
|
+
publicData: []
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Always load public data
|
|
101
|
+
try {
|
|
102
|
+
data.publicData = await db_public_items(ctx)
|
|
103
|
+
} catch (_) {}
|
|
104
|
+
|
|
105
|
+
// Load user-specific data if authenticated
|
|
106
|
+
if (ctx.session.authenticated) {
|
|
107
|
+
try {
|
|
108
|
+
data.userItems = await db_user_items(ctx, {
|
|
109
|
+
user_id: ctx.session.user.id
|
|
110
|
+
})
|
|
111
|
+
data.notifications = await db_user_notifications(ctx, {
|
|
112
|
+
user_id: ctx.session.user.id
|
|
113
|
+
})
|
|
114
|
+
} catch (_) {}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return data
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Client-Side - useSsrData Hook
|
|
122
|
+
|
|
123
|
+
The `useSsrData` hook manages SSR-preloaded data and provides remote loading fallback.
|
|
124
|
+
|
|
125
|
+
### Hook Signature
|
|
126
|
+
|
|
127
|
+
```javascript
|
|
128
|
+
const [data, setData, refreshData] = useSsrData(
|
|
129
|
+
'<key>', // Key from SSR loader object
|
|
130
|
+
defaultValue, // Default value if no data
|
|
131
|
+
async (context, fetcher) => { // Remote loader (fallback)
|
|
132
|
+
// Load data from API if not in SSR
|
|
133
|
+
const response = await fetcher.get('/api/data')
|
|
134
|
+
return processData(response.data)
|
|
135
|
+
}
|
|
136
|
+
)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**Parameters:**
|
|
140
|
+
1. **`<key>`** (string) - Key name from SSR loader object
|
|
141
|
+
2. **`defaultValue`** (any) - Default value when no data available
|
|
142
|
+
3. **`remoteLoader`** (async function) - Fallback function to load data remotely
|
|
143
|
+
|
|
144
|
+
**Returns:** `[data, setData, refreshData]`
|
|
145
|
+
- **`data`** - Current data (React state)
|
|
146
|
+
- **`setData`** - Function to set data directly (like setState)
|
|
147
|
+
- **`refreshData`** - Function to re-execute remote loader
|
|
148
|
+
|
|
149
|
+
### Access useSsrData
|
|
150
|
+
|
|
151
|
+
Available from two sources:
|
|
152
|
+
|
|
153
|
+
**1. From useMioloContext:**
|
|
154
|
+
```javascript
|
|
155
|
+
import { useMioloContext } from 'miolo-react'
|
|
156
|
+
|
|
157
|
+
function MyComponent() {
|
|
158
|
+
const { useSsrData } = useMioloContext()
|
|
159
|
+
|
|
160
|
+
const [todos, setTodos, refreshTodos] = useSsrData('todos', [], async (context, fetcher) => {
|
|
161
|
+
const { data } = await fetcher.get('/api/todo/list')
|
|
162
|
+
return data
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
return <div>{todos.length} todos</div>
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**2. From useSessionContext:**
|
|
170
|
+
```javascript
|
|
171
|
+
import useSessionContext from '#cli/context/session/useSessionContext.mjs'
|
|
172
|
+
|
|
173
|
+
function MyComponent() {
|
|
174
|
+
const { useSsrData } = useSessionContext()
|
|
175
|
+
|
|
176
|
+
const [profile, setProfile, refreshProfile] = useSsrData('profile', null, async (context, fetcher) => {
|
|
177
|
+
const { data } = await fetcher.get('/api/user/profile')
|
|
178
|
+
return data
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
return <div>{profile?.name}</div>
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Basic Usage Example
|
|
186
|
+
|
|
187
|
+
**DataProvider using useSsrData:**
|
|
188
|
+
|
|
189
|
+
```javascript
|
|
190
|
+
import { useState } from 'react'
|
|
191
|
+
import useSessionContext from '#cli/context/session/useSessionContext.mjs'
|
|
192
|
+
import TodoList from '#ns/models/TodoList.mjs'
|
|
193
|
+
import DataContext from './DataContext.jsx'
|
|
194
|
+
|
|
195
|
+
const DataProvider = ({ children }) => {
|
|
196
|
+
const [status, setStatus] = useState('loaded')
|
|
197
|
+
const { useSsrData } = useSessionContext()
|
|
198
|
+
|
|
199
|
+
const [todos, _setTodos, refreshTodos] = useSsrData('todos', [], async (context, fetcher) => {
|
|
200
|
+
setStatus('loading')
|
|
201
|
+
const { data: nTodos } = await fetcher.get('/api/todo/list')
|
|
202
|
+
setStatus('loaded')
|
|
203
|
+
return new TodoList(nTodos)
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
return (
|
|
207
|
+
<DataContext.Provider value={{
|
|
208
|
+
todos,
|
|
209
|
+
refreshTodos,
|
|
210
|
+
loading: status !== 'loaded',
|
|
211
|
+
loaded: status === 'loaded'
|
|
212
|
+
}}>
|
|
213
|
+
{children}
|
|
214
|
+
</DataContext.Provider>
|
|
215
|
+
)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export default DataProvider
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**How it works:**
|
|
222
|
+
1. **SSR hit** - Miolo finds `'todos'` in SSR loader, uses that data
|
|
223
|
+
2. **SSR miss** - Key not found in SSR, executes remote loader
|
|
224
|
+
3. **Client refresh** - Calling `refreshTodos()` re-executes remote loader
|
|
225
|
+
|
|
226
|
+
### Remote Loader Function
|
|
227
|
+
|
|
228
|
+
The third parameter to `useSsrData` is only executed when:
|
|
229
|
+
- Data is not available in SSR loader (no matching key)
|
|
230
|
+
- User calls `refreshData()` to reload
|
|
231
|
+
|
|
232
|
+
```javascript
|
|
233
|
+
const [items, setItems, refreshItems] = useSsrData(
|
|
234
|
+
'items',
|
|
235
|
+
[],
|
|
236
|
+
async (context, fetcher) => {
|
|
237
|
+
// This runs ONLY if:
|
|
238
|
+
// 1. SSR loader didn't provide 'items' data
|
|
239
|
+
// 2. User calls refreshItems()
|
|
240
|
+
|
|
241
|
+
const { data } = await fetcher.get('/api/items/list')
|
|
242
|
+
|
|
243
|
+
// Can transform data before returning
|
|
244
|
+
return data.map(item => new ItemModel(item))
|
|
245
|
+
}
|
|
246
|
+
)
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
**Remote loader parameters:**
|
|
250
|
+
- **`context`** - Application context
|
|
251
|
+
- **`fetcher`** - Authenticated fetch wrapper (see miolo-fetcher skill)
|
|
252
|
+
|
|
253
|
+
### Setting Data Manually
|
|
254
|
+
|
|
255
|
+
Use `setData` to update state directly:
|
|
256
|
+
|
|
257
|
+
```javascript
|
|
258
|
+
const [todos, setTodos, refreshTodos] = useSsrData('todos', [], remoteLoader)
|
|
259
|
+
|
|
260
|
+
const handleAddTodo = async (newTodo) => {
|
|
261
|
+
// Optimistic update
|
|
262
|
+
setTodos([...todos, newTodo])
|
|
263
|
+
|
|
264
|
+
// Save to backend
|
|
265
|
+
const response = await fetcher.post('/api/todo/save', newTodo)
|
|
266
|
+
|
|
267
|
+
if (!response.ok) {
|
|
268
|
+
// Rollback on error
|
|
269
|
+
await refreshTodos()
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Refreshing Data
|
|
275
|
+
|
|
276
|
+
Call `refreshData` to re-execute the remote loader:
|
|
277
|
+
|
|
278
|
+
```javascript
|
|
279
|
+
const [notifications, setNotifications, refreshNotifications] = useSsrData(
|
|
280
|
+
'notifications',
|
|
281
|
+
[],
|
|
282
|
+
async (context, fetcher) => {
|
|
283
|
+
const { data } = await fetcher.get('/api/notifications')
|
|
284
|
+
return data
|
|
285
|
+
}
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
// Manually refresh
|
|
289
|
+
const handleRefresh = () => {
|
|
290
|
+
refreshNotifications() // Re-executes remote loader
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Auto-refresh every 30 seconds
|
|
294
|
+
useEffect(() => {
|
|
295
|
+
const interval = setInterval(refreshNotifications, 30000)
|
|
296
|
+
return () => clearInterval(interval)
|
|
297
|
+
}, [])
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Complex Data Transformations
|
|
301
|
+
|
|
302
|
+
Transform data in remote loader:
|
|
303
|
+
|
|
304
|
+
```javascript
|
|
305
|
+
import TodoList from '#ns/models/TodoList.mjs'
|
|
306
|
+
|
|
307
|
+
const [todos, setTodos, refreshTodos] = useSsrData(
|
|
308
|
+
'todos',
|
|
309
|
+
new TodoList([]),
|
|
310
|
+
async (context, fetcher) => {
|
|
311
|
+
const { data: rawTodos } = await fetcher.get('/api/todo/list')
|
|
312
|
+
|
|
313
|
+
// Transform to model class
|
|
314
|
+
const todoList = new TodoList(rawTodos)
|
|
315
|
+
|
|
316
|
+
// Apply filters
|
|
317
|
+
todoList.filterByStatus('active')
|
|
318
|
+
|
|
319
|
+
// Sort
|
|
320
|
+
todoList.sortByDate('desc')
|
|
321
|
+
|
|
322
|
+
return todoList
|
|
323
|
+
}
|
|
324
|
+
)
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### Multiple Data Sources
|
|
328
|
+
|
|
329
|
+
Load multiple independent data sources:
|
|
330
|
+
|
|
331
|
+
```javascript
|
|
332
|
+
const DataProvider = ({ children }) => {
|
|
333
|
+
const { useSsrData } = useSessionContext()
|
|
334
|
+
|
|
335
|
+
const [todos, setTodos, refreshTodos] = useSsrData('todos', [], loadTodos)
|
|
336
|
+
const [profile, setProfile, refreshProfile] = useSsrData('profile', null, loadProfile)
|
|
337
|
+
const [stats, setStats, refreshStats] = useSsrData('stats', {}, loadStats)
|
|
338
|
+
|
|
339
|
+
return (
|
|
340
|
+
<DataContext.Provider value={{
|
|
341
|
+
todos, refreshTodos,
|
|
342
|
+
profile, refreshProfile,
|
|
343
|
+
stats, refreshStats
|
|
344
|
+
}}>
|
|
345
|
+
{children}
|
|
346
|
+
</DataContext.Provider>
|
|
347
|
+
)
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
## Best Practices
|
|
352
|
+
|
|
353
|
+
### Backend (SSR Loader)
|
|
354
|
+
|
|
355
|
+
1. **Wrap in try/catch** - Always handle errors gracefully, don't crash SSR
|
|
356
|
+
2. **Return default values** - Provide sensible defaults (empty arrays, null, etc.)
|
|
357
|
+
3. **Use meaningful keys** - Choose clear, descriptive names for data keys
|
|
358
|
+
4. **Load only needed data** - Filter by URL to reduce payload size
|
|
359
|
+
5. **Check authentication** - Load user-specific data only if authenticated
|
|
360
|
+
6. **Keep it fast** - SSR loader blocks initial render, optimize queries
|
|
361
|
+
|
|
362
|
+
### Client (useSsrData)
|
|
363
|
+
|
|
364
|
+
1. **Provide default values** - Always specify sensible defaults
|
|
365
|
+
2. **Transform in remote loader** - Process data before returning
|
|
366
|
+
3. **Handle loading states** - Track loading status in remote loader
|
|
367
|
+
4. **Use setData for optimistic updates** - Update UI immediately, sync later
|
|
368
|
+
5. **Refresh on mutations** - Call refreshData after creating/updating data
|
|
369
|
+
6. **Don't over-refresh** - Only refresh when data might have changed
|
|
370
|
+
|
|
371
|
+
## Common Patterns
|
|
372
|
+
|
|
373
|
+
### Loading State Management
|
|
374
|
+
|
|
375
|
+
```javascript
|
|
376
|
+
const DataProvider = ({ children }) => {
|
|
377
|
+
const [status, setStatus] = useState('loaded')
|
|
378
|
+
const { useSsrData } = useSessionContext()
|
|
379
|
+
|
|
380
|
+
const [data, setData, refreshData] = useSsrData('data', [], async (context, fetcher) => {
|
|
381
|
+
setStatus('loading')
|
|
382
|
+
try {
|
|
383
|
+
const response = await fetcher.get('/api/data')
|
|
384
|
+
setStatus('loaded')
|
|
385
|
+
return response.data
|
|
386
|
+
} catch (error) {
|
|
387
|
+
setStatus('error')
|
|
388
|
+
return []
|
|
389
|
+
}
|
|
390
|
+
})
|
|
391
|
+
|
|
392
|
+
return (
|
|
393
|
+
<DataContext.Provider value={{
|
|
394
|
+
data,
|
|
395
|
+
refreshData,
|
|
396
|
+
loading: status === 'loading',
|
|
397
|
+
loaded: status === 'loaded',
|
|
398
|
+
error: status === 'error'
|
|
399
|
+
}}>
|
|
400
|
+
{children}
|
|
401
|
+
</DataContext.Provider>
|
|
402
|
+
)
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Conditional Loading
|
|
407
|
+
|
|
408
|
+
```javascript
|
|
409
|
+
const loader = async (ctx) => {
|
|
410
|
+
const data = {}
|
|
411
|
+
|
|
412
|
+
// Only load heavy data for specific routes
|
|
413
|
+
if (ctx.request.url.includes('/admin')) {
|
|
414
|
+
try {
|
|
415
|
+
data.adminStats = await db_admin_stats(ctx)
|
|
416
|
+
} catch (_) {}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return data
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
## Related Skills
|
|
424
|
+
|
|
425
|
+
- **miolo-session-context** - Access `useSsrData` from session context
|
|
426
|
+
- **miolo-fetcher** - Fetcher usage in remote loaders (future skill)
|
|
427
|
+
- **miolo-react-patterns** - Context provider patterns
|
|
428
|
+
|
|
429
|
+
## Examples from miolo-sample
|
|
430
|
+
|
|
431
|
+
See actual implementations:
|
|
432
|
+
- `src/server/miolo/ssr/loader.mjs` - SSR loader configuration
|
|
433
|
+
- `src/cli/context/data/DataProvider.jsx` - useSsrData usage
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
root = true
|
|
2
|
+
charset = utf-8
|
|
3
|
+
|
|
4
|
+
[*]
|
|
5
|
+
end_of_line = lf
|
|
6
|
+
insert_final_newline = true
|
|
7
|
+
|
|
8
|
+
[*.{js,jsx}]
|
|
9
|
+
indent_style = space
|
|
10
|
+
indent_size = 2
|
|
11
|
+
|
|
12
|
+
[*.{css,scss}]
|
|
13
|
+
indent_style = space
|
|
14
|
+
indent_size = 2
|
|
15
|
+
|
|
16
|
+
[{package.json,.travis.yml}]
|
|
17
|
+
indent_style = space
|
|
18
|
+
indent_size = 2
|
package/template/.env
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#
|
|
2
|
+
# General
|
|
3
|
+
#
|
|
4
|
+
|
|
5
|
+
MIOLO_NAME=miolo-sample
|
|
6
|
+
MIOLO_INTRE_LOCALE=es
|
|
7
|
+
|
|
8
|
+
#
|
|
9
|
+
# HTTP
|
|
10
|
+
#
|
|
11
|
+
|
|
12
|
+
MIOLO_PORT=8001
|
|
13
|
+
MIOLO_HOSTNAME=localhost
|
|
14
|
+
MIOLO_HOSTNAME_DOCKER=0.0.0.0
|
|
15
|
+
MIOLO_HTTP_CORS=simple # true | false | simple. For {options}, use JS
|
|
16
|
+
MIOLO_HTTP_PROXY=false # true | false. For {options}, use JS
|
|
17
|
+
|
|
18
|
+
MIOLO_RATELIMIT_WHITELIST_IPS=172.22.0.1
|
|
19
|
+
MIOLO_REQUEST_LAZY=2 # seconds to consider lazy a request
|
|
20
|
+
MIOLO_REQUEST_SLOW=4 # seconds to consider slow a request
|
|
21
|
+
MIOLO_GEOIP_ENABLED=true
|
|
22
|
+
MIOLO_GEOIP_LOCAL_IPS=127.0.0.1,172.22.0.1,172.19.0.1
|
|
23
|
+
|
|
24
|
+
#
|
|
25
|
+
# If google auth
|
|
26
|
+
#
|
|
27
|
+
MIOLO_AUTH_GOOGLE_CLIENT_ID=123456
|
|
28
|
+
MIOLO_AUTH_GOOGLE_CLIENT_SECRET=123456
|
|
29
|
+
MIOLO_AUTH_GOOGLE_CALLBACK_URL=/auth/google/callback
|
|
30
|
+
|
|
31
|
+
#
|
|
32
|
+
# Session
|
|
33
|
+
#
|
|
34
|
+
|
|
35
|
+
MIOLO_SESSION_KEY='miolo-sample.sess'
|
|
36
|
+
MIOLO_SESSION_SALT=00000000-0000-0000-0000-000000000000
|
|
37
|
+
MIOLO_SESSION_SECRET=00000000-0000-0000-0000-000000000000
|
|
38
|
+
MIOLO_SESSION_MAX_AGE=864000000
|
|
39
|
+
MIOLO_SESSION_SECURE=false
|
|
40
|
+
MIOLO_SESSION_RENEW=true
|
|
41
|
+
MIOLO_SESSION_SAME_SITE=lax # lax | strict
|
|
42
|
+
|
|
43
|
+
#
|
|
44
|
+
# Database
|
|
45
|
+
#
|
|
46
|
+
|
|
47
|
+
MIOLO_DB_DIALECT=postgres
|
|
48
|
+
MIOLO_DB_DATABASE=miolo-sample
|
|
49
|
+
MIOLO_DB_DOCKER_HOST=postgres
|
|
50
|
+
MIOLO_DB_HOST=localhost
|
|
51
|
+
MIOLO_DB_PORT=5432
|
|
52
|
+
MIOLO_DB_USER=postgres
|
|
53
|
+
MIOLO_DB_PASSWORD=postgres
|
|
54
|
+
MIOLO_DB_POOL_MAX=5
|
|
55
|
+
MIOLO_DB_POOL_MIN=0
|
|
56
|
+
MIOLO_DB_POOL_IDLE_TIMEOUT_MS=10000
|
|
57
|
+
|
|
58
|
+
#
|
|
59
|
+
# Logging
|
|
60
|
+
#
|
|
61
|
+
|
|
62
|
+
MIOLO_LOG_LEVEL=verbose
|
|
63
|
+
MIOLO_LOG_CONSOLE_ENABLED=true
|
|
64
|
+
MIOLO_LOG_FILE_ENABLED=false
|
|
65
|
+
MIOLO_LOG_MAIL_ENABLED=false
|
|
66
|
+
#MIOLO_LOG_MAIL_LEVEL=warn
|
|
67
|
+
MIOLO_LOG_MAIL_FROM=miolo-sample@miolo-sample.com
|
|
68
|
+
MIOLO_LOG_MAIL_TO=miolo-sample@miolo-sample.com
|
|
69
|
+
|
|
70
|
+
#
|
|
71
|
+
# Mailer
|
|
72
|
+
#
|
|
73
|
+
|
|
74
|
+
MIOLO_MAILER_SILENT=true
|
|
75
|
+
MIOLO_MAILER_HOST=mail.miolo-sample.com
|
|
76
|
+
MIOLO_MAILER_PORT=25
|
|
77
|
+
MIOLO_MAILER_FROM=miolo-sample@miolo-sample.com
|
|
78
|
+
MIOLO_MAILER_TO=miolo-sample@miolo-sample.com
|
|
79
|
+
|
|
80
|
+
# MIOLO_MAILER_AUTH_METHOD=PLAIN # PLAIN / LOGIN
|
|
81
|
+
# # If LOGIN, you need to specify:
|
|
82
|
+
# MIOLO_MAILER_SMTP_USER=noreply@mail.com
|
|
83
|
+
# MIOLO_MAILER_SMTP_PASS=****
|
|
84
|
+
|
|
85
|
+
#
|
|
86
|
+
# Cache
|
|
87
|
+
#
|
|
88
|
+
|
|
89
|
+
MIOLO_REDIS_HOSTNAME=127.0.0.1
|
|
90
|
+
MIOLO_REDIS_HOSTNAME_DOCKER=redis
|
|
91
|
+
MIOLO_REDIS_PORT=6379
|
|
92
|
+
|
|
93
|
+
MIOLO_CACHE_TYPE=redis
|
|
94
|
+
MIOLO_CACHE_VERSION=1
|
|
95
|
+
MIOLO_CACHE_CALUSTRA_VERSION=1
|
|
96
|
+
MIOLO_CACHE_CALUSTRA_TTL=86400000
|
|
97
|
+
MIOLO_CACHE_SESSION_VERSION=1
|
|
98
|
+
MIOLO_CACHE_SESSION_TTL=864000000
|
|
99
|
+
|
|
100
|
+
#
|
|
101
|
+
# Others
|
|
102
|
+
#
|
|
103
|
+
|
|
104
|
+
MIOLO_DOTENVX_DEBUG=false
|
|
105
|
+
|
|
106
|
+
#
|
|
107
|
+
# Build
|
|
108
|
+
#
|
|
109
|
+
|
|
110
|
+
MIOLO_BUILD_HTML_FILE=./src/cli/index.html
|
|
111
|
+
MIOLO_BUILD_CLIENT_ENTRY=./src/cli/entry-cli.jsx
|
|
112
|
+
MIOLO_BUILD_CLIENT_DEST=./build/cli
|
|
113
|
+
MIOLO_BUILD_CLIENT_SUFFIX=iife.bundle.min
|
|
114
|
+
MIOLO_BUILD_SERVER_SSR_ENTRY=./src/server/miolo/ssr/entry-server.jsx
|
|
115
|
+
MIOLO_BUILD_SERVER_ENTRY=./src/server/server.mjs
|
|
116
|
+
MIOLO_BUILD_SERVER_DEST=./build/server
|
|
117
|
+
MIOLO_BUILD_SERVER_EXT=node.bundle.mjs
|
|
118
|
+
MIOLO_BUILD_CONFIG_ENTRY=./src/server/miolo/index.mjs
|
|
119
|
+
MIOLO_DEV_WATCH_ENABLED=true
|
|
120
|
+
MIOLO_DEV_WATCH_DIRS=./src/server
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://biomejs.dev/schemas/2.5.0/schema.json",
|
|
3
|
+
"files": {
|
|
4
|
+
"includes": [
|
|
5
|
+
"**/*",
|
|
6
|
+
"!dist",
|
|
7
|
+
"!build",
|
|
8
|
+
"!lib",
|
|
9
|
+
"!libs",
|
|
10
|
+
"!arredemo"
|
|
11
|
+
]
|
|
12
|
+
},
|
|
13
|
+
"assist": {
|
|
14
|
+
"actions": {
|
|
15
|
+
"source": {
|
|
16
|
+
"organizeImports": "on"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"linter": {
|
|
21
|
+
"enabled": true,
|
|
22
|
+
"rules": {
|
|
23
|
+
"recommended": true,
|
|
24
|
+
"complexity": {
|
|
25
|
+
"noStaticOnlyClass": "off"
|
|
26
|
+
},
|
|
27
|
+
"suspicious": {
|
|
28
|
+
"noVar": "warn",
|
|
29
|
+
"noConsole": "off",
|
|
30
|
+
"noAssignInExpressions": "off"
|
|
31
|
+
},
|
|
32
|
+
"style": {
|
|
33
|
+
"useTemplate": "off"
|
|
34
|
+
},
|
|
35
|
+
"correctness": {
|
|
36
|
+
"noUnusedVariables": "warn",
|
|
37
|
+
"useExhaustiveDependencies": "warn",
|
|
38
|
+
"useHookAtTopLevel": "error"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"formatter": {
|
|
43
|
+
"enabled": true,
|
|
44
|
+
"indentStyle": "space",
|
|
45
|
+
"indentWidth": 2,
|
|
46
|
+
"lineWidth": 100
|
|
47
|
+
},
|
|
48
|
+
"javascript": {
|
|
49
|
+
"parser": {
|
|
50
|
+
"unsafeParameterDecoratorsEnabled": true
|
|
51
|
+
},
|
|
52
|
+
"formatter": {
|
|
53
|
+
"quoteStyle": "double",
|
|
54
|
+
"semicolons": "asNeeded",
|
|
55
|
+
"trailingCommas": "none"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"css": {
|
|
59
|
+
"parser": {
|
|
60
|
+
"tailwindDirectives": true
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "new-york",
|
|
4
|
+
"rsc": false,
|
|
5
|
+
"tsx": false,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "",
|
|
8
|
+
"css": "src/public/style/globals.css",
|
|
9
|
+
"baseColor": "neutral",
|
|
10
|
+
"cssVariables": true,
|
|
11
|
+
"prefix": ""
|
|
12
|
+
},
|
|
13
|
+
"aliases": {
|
|
14
|
+
"components": "#cli/components",
|
|
15
|
+
"utils": "#cli/lib/utils.mjs",
|
|
16
|
+
"ui": "#cli/components/ui",
|
|
17
|
+
"lib": "#cli/lib",
|
|
18
|
+
"hooks": "#cli/hooks"
|
|
19
|
+
},
|
|
20
|
+
"iconLibrary": "lucide"
|
|
21
|
+
}
|