miolo 3.0.0-beta.210 → 3.0.0-beta.212

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "miolo",
3
- "version": "3.0.0-beta.210",
3
+ "version": "3.0.0-beta.212",
4
4
  "description": "all-in-one koa-based server",
5
5
  "author": "Donato Lorenzo <donato@afialapis.com>",
6
6
  "contributors": [
@@ -47,7 +47,7 @@
47
47
  "@babel/plugin-proposal-decorators": "^7.29.7",
48
48
  "@babel/preset-env": "^7.29.7",
49
49
  "@babel/preset-react": "^7.29.7",
50
- "@dotenvx/dotenvx": "^1.75.0",
50
+ "@dotenvx/dotenvx": "^1.75.1",
51
51
  "@koa/bodyparser": "^6.1.0",
52
52
  "@koa/cors": "^5.0.0",
53
53
  "@koa/router": "^15.6.0",
@@ -60,8 +60,8 @@
60
60
  "@rollup/plugin-replace": "^6.0.3",
61
61
  "@tailwindcss/postcss": "^4.3.1",
62
62
  "@tailwindcss/vite": "^4.3.1",
63
- "@vitejs/plugin-react": "^6.0.2",
64
- "autoprefixer": "^10.5.0",
63
+ "@vitejs/plugin-react": "^6.0.3",
64
+ "autoprefixer": "^10.5.1",
65
65
  "cacheiro": "^0.5.0-beta.11",
66
66
  "calustra": "^1.0.0-beta.14",
67
67
  "cron": "^4.4.0",
@@ -94,7 +94,7 @@
94
94
  "statuses": "^2.0.2",
95
95
  "tailwindcss": "^4.3.1",
96
96
  "tinguir": "^0.0.7",
97
- "vite": "^8.0.16",
97
+ "vite": "^8.1.0",
98
98
  "winston": "^3.19.0",
99
99
  "winston-daily-rotate-file": "^5.0.0",
100
100
  "yargs-parser": "^22.0.0"
@@ -484,7 +484,7 @@ export default function make_config_defaults() {
484
484
  url: '',
485
485
  options: {}
486
486
  */
487
- }
487
+ },
488
488
  /*
489
489
  connection: (socket) => {},
490
490
  new_namespace: (namespace) => {},
@@ -493,6 +493,10 @@ export default function make_config_defaults() {
493
493
  listener: (data) => {}
494
494
  }]
495
495
  */
496
+ ssr: {
497
+ enabled: true,
498
+ namespace: "ssr"
499
+ }
496
500
  },
497
501
 
498
502
  build: {
@@ -48,7 +48,23 @@ function init_socket(app, config) {
48
48
  }
49
49
 
50
50
  io.on("connection", (socket) => {
51
- logger.info(`[socket] Connection from ... `) // ${i.ip} ${i.id}`)
51
+ logger.info(`[socket] Connection from ... `)
52
+
53
+ if (config?.ssr?.enabled === true) {
54
+ socket.on("ssr-versions", async () => {
55
+ logger.verbose(`[socket] ssr-versions requested for ${socket.id}`)
56
+ try {
57
+ const versions = await app.context.miolo.cache.get_cache_versions()
58
+ const ssr_versions = versions[config?.ssr?.namespace || "ssr"] || {}
59
+ socket.emit("ssr-versions", ssr_versions)
60
+ logger.verbose(
61
+ `[socket] ssr-versions sent for ${socket.id}: ${JSON.stringify(ssr_versions)}`
62
+ )
63
+ } catch (err) {
64
+ logger.error(`[socket] Error getting cache versions: ${err.message}`)
65
+ }
66
+ })
67
+ }
52
68
 
53
69
  if (config?.connection) {
54
70
  config.connection(socket)
@@ -59,6 +75,42 @@ function init_socket(app, config) {
59
75
  }
60
76
  })
61
77
 
78
+ if (config?.ssr?.enabled === true) {
79
+ io.emitSsrVersions = async (ctx) => {
80
+ logger.verbose(`[socket] Emitting ssr-versions`)
81
+ try {
82
+ const versions = await ctx.miolo.cache.get_cache_versions()
83
+ const ssr_versions = versions[config?.ssr?.namespace || "ssr"] || {}
84
+ io.emit("ssr-versions", ssr_versions)
85
+ logger.verbose(`[socket] ssr-versions sent: ${JSON.stringify(ssr_versions)}`)
86
+ } catch (err) {
87
+ logger.error(`[socket] Error emitting cache versions: ${err.message}`)
88
+ }
89
+ }
90
+
91
+ io.emitSsrInvalidate = async (ctx, name) => {
92
+ logger.verbose(`[socket] Emitting ssr-invalidate for ${name}`)
93
+ try {
94
+ const socketId = ctx.request?.headers?.["x-socket-id"]
95
+ io.emit("ssr-invalidate", { name, exclude_socket_id: socketId })
96
+ logger.verbose(`[socket] ssr-invalidate sent for ${name}`)
97
+ } catch (err) {
98
+ logger.error(`[socket] Error emitting ssr-invalidate: ${err.message}`)
99
+ }
100
+ }
101
+
102
+ io.emitSsrRefresh = async (ctx, name) => {
103
+ logger.verbose(`[socket] Emitting ssr-refresh for ${name}`)
104
+ try {
105
+ const socketId = ctx.request?.headers?.["x-socket-id"]
106
+ io.emit("ssr-refresh", { name, exclude_socket_id: socketId })
107
+ logger.verbose(`[socket] ssr-refresh sent for ${name}`)
108
+ } catch (err) {
109
+ logger.error(`[socket] Error emitting ssr-refresh: ${err.message}`)
110
+ }
111
+ }
112
+ }
113
+
62
114
  app.context.miolo.io = io
63
115
  }
64
116
 
@@ -94,12 +94,90 @@ export function init_context_cache(config, logger) {
94
94
  _glob_cache_mother = undefined
95
95
  }
96
96
 
97
+ const set_cache_version = async (name, key, version = Date.now()) => {
98
+ const cache_mother = await _get_cache_mother(config, logger)
99
+ await cache_mother.setItem(`versions:${name}:${key}`, version)
100
+ }
101
+
102
+ const get_cache_versions = async () => {
103
+ const cache_mother = await _get_cache_mother(config, logger)
104
+ const keys = await cache_mother.getKeys("versions:*")
105
+ const versions = {}
106
+ for (const key of keys) {
107
+ const parts = key.split(":")
108
+ if (parts.length === 3) {
109
+ const name = parts[1]
110
+ const item = parts[2]
111
+ const version = await cache_mother.getItem(key)
112
+ if (!versions[name]) {
113
+ versions[name] = {}
114
+ }
115
+ versions[name][item] = version
116
+ }
117
+ }
118
+ return versions
119
+ }
120
+
121
+ const _item_get_or_set = async (name, key, callback, parse_read, parse_write) => {
122
+ const cache = await get_cache(name)
123
+
124
+ if (!cache) {
125
+ logger.warn(`[cache] Cache ${name} is not ready!`)
126
+ }
127
+
128
+ if (cache) {
129
+ if (await cache.hasItem(key)) {
130
+ return parse_read(await cache.getItem(key))
131
+ }
132
+ }
133
+
134
+ const data = await callback()
135
+
136
+ if (cache) {
137
+ await cache.setItem(key, parse_write(data))
138
+
139
+ await set_cache_version(name, key)
140
+ }
141
+
142
+ return data
143
+ }
144
+
145
+ const item_get_or_set = async (name, key, callback) => {
146
+ return await _item_get_or_set(
147
+ name,
148
+ key,
149
+ callback,
150
+ (data) => data,
151
+ (data) => data
152
+ )
153
+ }
154
+
155
+ const item_get_or_set_json = async (name, key, callback) => {
156
+ return await _item_get_or_set(name, key, callback, JSON.parse, JSON.stringify)
157
+ }
158
+
159
+ const item_unset = async (name, key) => {
160
+ const cache = await get_cache(name)
161
+
162
+ if (!cache) {
163
+ logger.warn(`[cache] Cache ${name} is not ready!`)
164
+ }
165
+
166
+ await cache.unsetItem(key)
167
+ await set_cache_version(name, key)
168
+ }
169
+
97
170
  const cache_ctx = {
98
171
  init,
99
172
  get_cache,
100
173
  get_cache_names,
174
+ set_cache_version,
175
+ get_cache_versions,
101
176
  drop_cache,
102
- close
177
+ close,
178
+ item_get_or_set,
179
+ item_get_or_set_json,
180
+ item_unset
103
181
  }
104
182
 
105
183
  return cache_ctx
@@ -1,6 +1,5 @@
1
1
  import { init_config } from "./config/index.mjs"
2
2
  import { init_cron } from "./engines/cron/index.mjs"
3
- import { init_socket } from "./engines/socket/index.mjs"
4
3
  import { init_context_middleware } from "./middleware/context/index.mjs"
5
4
 
6
5
  export async function miolo_cron(makeConfig) {
@@ -22,9 +21,6 @@ export async function miolo_cron(makeConfig) {
22
21
  app.start = async () => {
23
22
  // Init and reset db connection
24
23
  await app.context.miolo.db.init_connection()
25
- if (app.context.miolo?.io) {
26
- app.context.miolo.io.attach(app.http.server)
27
- }
28
24
  await app.cron.start()
29
25
  }
30
26
 
@@ -38,8 +34,5 @@ export async function miolo_cron(makeConfig) {
38
34
  await app.start()
39
35
  }
40
36
 
41
- // Socket.io
42
- init_socket(app, config?.socket)
43
-
44
37
  return app
45
38
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "$schema": "https://biomejs.dev/schemas/2.5.0/schema.json",
2
+ "$schema": "https://biomejs.dev/schemas/2.5.1/schema.json",
3
3
  "files": {
4
4
  "includes": [
5
5
  "**/*",
@@ -45,15 +45,15 @@
45
45
  "intre": "^3.0.0-beta.4",
46
46
  "joi": "^18.2.3",
47
47
  "lucide-react": "^1.21.0",
48
- "miolo-cli": "^3.0.0-beta.210",
48
+ "miolo-cli": "^3.0.0-beta.212",
49
49
  "miolo-model": "file:../miolo-model",
50
- "miolo-react": "^3.0.0-beta.210",
50
+ "miolo-react": "^3.0.0-beta.212",
51
51
  "next-themes": "^0.4.6",
52
52
  "radix-ui": "^1.6.0",
53
53
  "react": "^19.2.7",
54
54
  "react-dom": "^19.2.7",
55
55
  "react-router": "^7.16.0",
56
- "recharts": "^3.8.1",
56
+ "recharts": "^3.9.0",
57
57
  "sonner": "^2.0.7",
58
58
  "tailwind": "^4.0.0",
59
59
  "tailwind-merge": "^3.6.0",
@@ -61,8 +61,8 @@
61
61
  "tw-animate-css": "^1.4.0"
62
62
  },
63
63
  "devDependencies": {
64
- "@biomejs/biome": "2.5.0",
65
- "miolo": "^3.0.0-beta.210",
64
+ "@biomejs/biome": "2.5.1",
65
+ "miolo": "^3.0.0-beta.212",
66
66
  "sass-embedded": "^1.100.0"
67
67
  },
68
68
  "overrides": {
@@ -10,15 +10,15 @@ const TodosProvider = ({ children }) => {
10
10
  const { logger } = useMioloContext()
11
11
  const { useSsrData, fetcher, socket, authenticated } = useSessionContext()
12
12
  const { toast } = useUIContext()
13
- const [useCrud, setUseCrud] = useState(true)
13
+ const [useCrud, setUseCrud] = useState(false)
14
14
  const [socketInited, setSocketInited] = useState(false)
15
15
 
16
16
  const {
17
17
  data: todoList,
18
18
  setData: setTodoList,
19
19
  refresh: refreshTodoList,
20
- ready,
21
- invalidate: invalidateTodoList
20
+ ready
21
+ // invalidate: invalidateTodoList
22
22
  } = useSsrData("todos", {
23
23
  model: TodoList,
24
24
  loader: useCallback(
@@ -0,0 +1,39 @@
1
+ import { db_todo_read } from "#server/io/db/todos/read.mjs"
2
+ import { COMMON_CACHE_NS } from "#server/miolo/cache/ns.mjs"
3
+
4
+ export async function ch_todo_read(ctx, params) {
5
+ ctx.miolo.logger.verbose(`[cache][todo] Reading todos`)
6
+
7
+ const todos = await ctx.miolo.cache.item_get_or_set_json(COMMON_CACHE_NS, `todos`, async () => {
8
+ const filter = {}
9
+ const options = {}
10
+ return await db_todo_read(ctx, { filter, options })
11
+ })
12
+
13
+ if (params?.options?.limit) {
14
+ return todos.slice(0, params.options.limit)
15
+ }
16
+
17
+ return todos
18
+ }
19
+
20
+ export async function ch_todo_find(ctx, params) {
21
+ ctx.miolo.logger.verbose(`[cache][todo] Finding todo`)
22
+
23
+ const todos = await ch_todo_read(ctx)
24
+
25
+ return todos.find((todo) => todo.id === params?.filter?.id)
26
+ }
27
+
28
+ export async function ch_todo_invalidate(ctx) {
29
+ ctx.miolo.logger.verbose(`[cache][todo] Invalidating todos cache`)
30
+ await ctx.miolo.cache.item_unset(COMMON_CACHE_NS, `todos`)
31
+
32
+ if (!ctx.miolo.io) {
33
+ ctx.miolo.logger.warn(`[cache][todo] IO not available`)
34
+ return
35
+ }
36
+
37
+ ctx.miolo.logger.verbose(`[cache][todo] Invalidating connected clients`)
38
+ await ctx.miolo.io.emitSsrRefresh(ctx, "todos")
39
+ }
@@ -12,7 +12,7 @@ async function _db_todo_delete(ctx, params) {
12
12
 
13
13
  const Todo = await conn.get_model("todo")
14
14
 
15
- const todo = await db_todo_find(ctx, params.id)
15
+ const todo = await db_todo_find(ctx, { filter: { id: params?.id } })
16
16
 
17
17
  if (todo == null) {
18
18
  throw new Error(`[db_todo_delete] Trying to delete a todo that does not exist`)
@@ -7,7 +7,7 @@ import { db_todo_upsave } from "./upsave.mjs"
7
7
  async function _db_todo_toggle(ctx, params) {
8
8
  ctx.miolo.logger.verbose(`[db_todo_toggle] Toggling todo with tid ${params?.id}`)
9
9
 
10
- const todo = await db_todo_find(ctx, params)
10
+ const todo = await db_todo_find(ctx, { filter: { id: params?.id } })
11
11
 
12
12
  if (todo == null) {
13
13
  throw new Error(`[db_todo_toggle] Trying to toggle a todo that does not exist`)
@@ -0,0 +1 @@
1
+ export const COMMON_CACHE_NS = "miolo-sample-default"
@@ -1,9 +1,10 @@
1
1
  import { intre_now, intre_to_str } from "intre"
2
2
  import { blue } from "tinguir"
3
+ import { ch_todo_invalidate } from "#server/io/cache/todos.mjs"
3
4
  import { db_todo_upsave } from "#server/io/db/todos/upsave.mjs"
4
5
 
5
- export const cacheInvalidate = async (miolo) => {
6
- miolo.logger.info(`${blue("[cron][cache_invalidate]")} Invalidating cache...`)
6
+ export const autoTodosInsert = async (miolo) => {
7
+ miolo.logger.info(`${blue("[cron][auto-todos]")} Inserting auto todo...`)
7
8
 
8
9
  // broadcast an event to all clients
9
10
  //miolo.io.emit("ssr-invalidate", { name: "todos" })
@@ -15,7 +16,9 @@ export const cacheInvalidate = async (miolo) => {
15
16
  done: false
16
17
  })
17
18
 
18
- miolo.io.emit("ssr-refresh", { name: "todos" })
19
+ await ch_todo_invalidate({ miolo })
20
+ //
21
+ // miolo.io.emit("ssr-refresh", { name: "todos" })
19
22
 
20
- miolo.logger.info(`${blue("[cron][cache_invalidate]")} Cache invalidated!`)
23
+ miolo.logger.info(`${blue("[cron][auto-todos]")} Done!`)
21
24
  }
@@ -1,5 +1,5 @@
1
+ import { autoTodosInsert } from "./auto-todos.mjs"
1
2
  import { onTickFoo } from "./foo.mjs"
2
- import { cacheInvalidate } from "./invalidate.mjs"
3
3
 
4
4
  // check https://github.com/kelektiv/node-cron#readme
5
5
  //
@@ -16,9 +16,9 @@ export default function init_cron() {
16
16
  })
17
17
 
18
18
  cronList.push({
19
- name: "miolo-sample-cache-invalidate",
19
+ name: "miolo-sample-auto-todos",
20
20
  cronTime: "*/1 * * * *",
21
- onTick: cacheInvalidate,
21
+ onTick: autoTodosInsert,
22
22
  start: true
23
23
  })
24
24
 
@@ -1,9 +1,10 @@
1
1
  import passport from "./auth/passport.mjs"
2
- import cache from "./cache.mjs"
2
+ import cache from "./cache/index.mjs"
3
3
  import init_cron from "./cron/index.mjs"
4
4
  import db from "./db.mjs"
5
5
  import http from "./http.mjs"
6
6
  import routes from "./routes/index.mjs"
7
+ import socket from "./socket.mjs"
7
8
  import { loader } from "./ssr/loader.mjs"
8
9
 
9
10
  export default () => {
@@ -20,24 +21,6 @@ export default () => {
20
21
  ssr: { loader }
21
22
  },
22
23
  cron: init_cron(),
23
- socket: {
24
- enabled: true,
25
- namespaces: [
26
- {
27
- name: "todos-update",
28
- listener: (data) => {
29
- console.log("TODOS UPDATED!!!")
30
- console.log(data)
31
- }
32
- },
33
- {
34
- name: "ping",
35
- listener: (data) => {
36
- console.log("PING!!!")
37
- console.log(data)
38
- }
39
- }
40
- ]
41
- }
24
+ socket
42
25
  }
43
26
  }
@@ -0,0 +1,25 @@
1
+ import { COMMON_CACHE_NS } from "./cache/ns.mjs"
2
+
3
+ export default {
4
+ enabled: true,
5
+ namespaces: [
6
+ {
7
+ name: "todos-update",
8
+ listener: (data) => {
9
+ console.log("TODOS UPDATED!!!")
10
+ console.log(data)
11
+ }
12
+ },
13
+ {
14
+ name: "ping",
15
+ listener: (data) => {
16
+ console.log("PING!!!")
17
+ console.log(data)
18
+ }
19
+ }
20
+ ],
21
+ ssr: {
22
+ enabled: true,
23
+ namespace: COMMON_CACHE_NS
24
+ }
25
+ }
@@ -1,10 +1,10 @@
1
- import { db_todo_read } from "#server/io/db/todos/read.mjs"
1
+ import { ch_todo_read } from "#server/io/cache/todos.mjs"
2
2
 
3
3
  const loader = async (ctx) => {
4
4
  let lastTodos = []
5
5
 
6
6
  try {
7
- lastTodos = await db_todo_read(ctx, { options: { limit: 3 } })
7
+ lastTodos = await ch_todo_read(ctx, { options: { limit: 3 } })
8
8
  lastTodos = lastTodos.sort((a, b) => b.created_at - a.created_at)
9
9
  } catch (_) {}
10
10
 
@@ -1,3 +1,4 @@
1
+ import { ch_todo_invalidate } from "#server/io/cache/todos.mjs"
1
2
  import { db_todo_delete } from "#server/io/db/todos/delete.mjs"
2
3
  import { db_todo_toggle } from "#server/io/db/todos/toggle.mjs"
3
4
  import { db_todo_upsave } from "#server/io/db/todos/upsave.mjs"
@@ -10,6 +11,8 @@ export async function r_todo_upsave(ctx, params) {
10
11
 
11
12
  const res = await db_todo_upsave(ctx, params)
12
13
 
14
+ await ch_todo_invalidate(ctx)
15
+
13
16
  ctx.miolo.logger.info(`[r_todo_upsave] Upsaved todo info for tid ${params?.id || "new"}`)
14
17
  return { ok: true, data: res }
15
18
  } catch (error) {
@@ -26,6 +29,8 @@ export async function r_todo_delete(ctx, params) {
26
29
 
27
30
  const res = await db_todo_delete(ctx, params)
28
31
 
32
+ await ch_todo_invalidate(ctx)
33
+
29
34
  ctx.miolo.logger.info(`[r_todo_delete] Deleted todo for tid ${params.id}`)
30
35
  return { ok: true, data: res }
31
36
  } catch (error) {
@@ -40,6 +45,8 @@ export async function r_todo_toggle_done(ctx, params) {
40
45
 
41
46
  const done = await db_todo_toggle(ctx, params)
42
47
 
48
+ await ch_todo_invalidate(ctx)
49
+
43
50
  ctx.miolo.logger.info(`[r_todo_toggle_done] Toggled todo with tid ${params?.id}`)
44
51
 
45
52
  return { ok: true, data: { done } }
@@ -1,11 +1,10 @@
1
- import { db_todo_find } from "#server/io/db/todos/find.mjs"
2
- import { db_todo_read } from "#server/io/db/todos/read.mjs"
1
+ import { ch_todo_find, ch_todo_read } from "#server/io/cache/todos.mjs"
3
2
 
4
3
  export async function r_todo_list(ctx, _params) {
5
4
  try {
6
5
  ctx.miolo.logger.info(`[r_todo_list] Reading todo list`)
7
6
 
8
- const res = await db_todo_read(ctx, { filter: {}, options: {} })
7
+ const res = await ch_todo_read(ctx, { filter: {}, options: {} })
9
8
 
10
9
  ctx.miolo.logger.info(`[r_todo_list] Read todo list (${res.length})`)
11
10
  return { ok: true, data: res }
@@ -20,7 +19,7 @@ export async function r_todo_last(ctx, params) {
20
19
  try {
21
20
  ctx.miolo.logger.info(`[r_todo_last] Reading last todos`)
22
21
 
23
- const res = await db_todo_read(ctx, { options: { limit: 3 } })
22
+ const res = await ch_todo_read(ctx, { options: { limit: 3 } })
24
23
 
25
24
  ctx.miolo.logger.info(`[r_todo_last] Read last todos (${res.length})`)
26
25
  return { ok: true, data: res }
@@ -34,7 +33,7 @@ export async function r_todo_find(ctx, params) {
34
33
  try {
35
34
  ctx.miolo.logger.info(`[r_todo_find] Reading todo for tid ${params?.id}`)
36
35
 
37
- const todo = await db_todo_find(ctx, { filter: { id: params?.id } })
36
+ const todo = await ch_todo_find(ctx, { filter: { id: params?.id } })
38
37
 
39
38
  ctx.miolo.logger.info(`[r_todo_find] Read todo for tid ${params?.id}`)
40
39
  return { ok: todo?.id !== undefined, data: todo }
@@ -1,21 +0,0 @@
1
- export async function cache_get_or_set_json(ctx, name, key, callback) {
2
- const cache = await ctx.miolo.cache.get_cache(name)
3
-
4
- if (!cache) {
5
- ctx.miolo.logger.warn(`[cache] Cache ${name} is not ready!`)
6
- }
7
-
8
- if (cache) {
9
- if (await cache.hasItem(key)) {
10
- return JSON.parse(await cache.getItem(key))
11
- }
12
- }
13
-
14
- const data = await callback()
15
-
16
- if (cache) {
17
- await cache.setItem(key, JSON.stringify(data))
18
- }
19
-
20
- return data
21
- }