miolo-react 3.0.0-beta.20 → 3.0.0-beta.201

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-react",
3
- "version": "3.0.0-beta.20",
3
+ "version": "3.0.0-beta.201",
4
4
  "description": "React utils for miolo",
5
5
  "author": "Donato Lorenzo <donato@afialapis.com>",
6
6
  "contributors": [
@@ -17,28 +17,25 @@
17
17
  "license": "MIT",
18
18
  "type": "module",
19
19
  "exports": {
20
- ".": {
21
- "development": "./src/index.mjs",
22
- "import": "./dist/miolo-react.mjs",
23
- "default": "./dist/miolo-react.umd.js"
24
- }
20
+ ".": "./src/index.mjs"
25
21
  },
26
22
  "files": [
27
- "dist",
28
- "src"
23
+ "src",
24
+ "package.json"
29
25
  ],
30
26
  "scripts": {
31
- "reset": "rm -fr package-lock.json npm-lock.yaml dist/* && npm i",
32
- "clean": "rm -fr ./dist/* ",
33
- "bundle": "npx xeira bundle --target=browser --source_index=./src/index.mjs --bundle_folder=./dist --bundle_name=miolo-react --bundle_node_polyfill=true --bundle_extension=umd,mjs",
34
- "dist": "npm run clean && npm run bundle"
27
+ "reset": "rm -fr package-lock.json npm-lock.yaml node_modules && npm i",
28
+ "lint": "biome check ./src --reporter=github",
29
+ "lint:fix": "biome check --write ./src --reporter=github",
30
+ "prepublishOnly": "npm run lint"
35
31
  },
36
32
  "dependencies": {
33
+ "idb-keyval": "^6.2.2",
37
34
  "miolo-cli": "../miolo-cli"
38
- },
35
+ },
39
36
  "peerDependencies": {
40
- "react": "^18.3.1",
41
- "react-dom": "^18.3.1"
37
+ "react": "^19.2.4",
38
+ "react-dom": "^19.2.4"
42
39
  },
43
40
  "peerDependenciesMeta": {
44
41
  "react": {
@@ -48,12 +45,7 @@
48
45
  "optional": true
49
46
  }
50
47
  },
51
- "devDependencies": {
52
- "xeira": "^1.2.3"
53
- },
54
- "eslintConfig": {
55
- "extends": [
56
- "../../node_modules/xeira/configs/eslint.react.mjs"
57
- ]
48
+ "engines": {
49
+ "node": ">=24"
58
50
  }
59
- }
51
+ }
@@ -1,14 +1,9 @@
1
- import React from 'react'
2
- import MioloContextProvider from './context/MioloContextProvider.jsx'
1
+ import MioloContextProvider from "./context/MioloContextProvider.jsx"
3
2
 
4
3
  const AppBrowser = ({ children }) => {
5
- const context = typeof window !== 'undefined' && window.__CONTEXT ? window.__CONTEXT : {};
4
+ const context = typeof window !== "undefined" && window.__CONTEXT ? window.__CONTEXT : {}
6
5
 
7
- return (
8
- <MioloContextProvider context={context}>
9
- {children}
10
- </MioloContextProvider>
11
- )
6
+ return <MioloContextProvider context={context}>{children}</MioloContextProvider>
12
7
  }
13
8
 
14
9
  export default AppBrowser
package/src/AppServer.jsx CHANGED
@@ -1,17 +1,7 @@
1
+ import MioloContextProvider from "./context/MioloContextProvider.jsx"
1
2
 
2
- import React from 'react'
3
- import MioloContextProvider from './context/MioloContextProvider.jsx'
4
-
5
-
6
- const AppServer = ({context, children}) => {
7
-
8
- return (
9
- <MioloContextProvider
10
- context= {context || {}}>
11
- {children}
12
- </MioloContextProvider>
13
- )
3
+ const AppServer = ({ context, children }) => {
4
+ return <MioloContextProvider context={context || {}}>{children}</MioloContextProvider>
14
5
  }
15
6
 
16
7
  export default AppServer
17
-
@@ -1,5 +1,5 @@
1
- import React from 'react'
1
+ import React from "react"
2
2
 
3
3
  const MioloContext = React.createContext()
4
4
 
5
- export default MioloContext
5
+ export default MioloContext
@@ -1,60 +1,70 @@
1
- import React , {useState, useEffect, useCallback} from 'react'
2
- import Context from './MioloContext.mjs'
3
- import { miolo_client } from 'miolo-cli'
4
- import { useSsrDataOrReload } from '../ssr/useSsrDataOrReload.mjs'
1
+ import { miolo_client } from "miolo-cli"
2
+ import { useCallback, useEffect, useState } from "react"
3
+ import { useSsrDataOrReload } from "../ssr/useSsrDataOrReload.mjs"
4
+ import Context from "./MioloContext.mjs"
5
5
 
6
+ const MioloContextProvider = ({ context, children }) => {
7
+ const [innerContext, setInnerContext] = useState(context)
8
+ const [mioloObj, setMioloObj] = useState(miolo_client(context))
6
9
 
7
- const MioloContextProvider = ({context, children}) => {
8
- const [innerContext, setInnerContext]= useState(context)
9
- const [mioloObj, setMioloObj]= useState(miolo_client(context))
10
-
11
10
  useEffect(() => {
12
11
  setInnerContext(context)
13
12
  setMioloObj(miolo_client(context))
14
13
  }, [context])
15
-
16
- const login = useCallback(async (credentials) => {
17
- const { fetcher } = mioloObj
18
- const { config } = innerContext
19
14
 
20
- const url = config.login_url || '/login'
21
- const resp = await fetcher.login(url, credentials)
22
-
23
- if (resp?.data) {
24
- if (resp?.data?.authenticated) {
25
- setInnerContext(current => {
26
- return {
27
- ...current,
28
- ...resp?.data,
29
- }
30
- })
15
+ const localLogin = useCallback(
16
+ async (params) => {
17
+ const { fetcher } = mioloObj
18
+ const { config } = innerContext
19
+
20
+ const url = config.login_url || "/login"
21
+ const resp = await fetcher.post(url, params)
22
+
23
+ if (resp?.data) {
24
+ if (resp?.data?.authenticated) {
25
+ setInnerContext((current) => {
26
+ return {
27
+ ...current,
28
+ ...resp.data,
29
+ config: {
30
+ ...current.config,
31
+ ...(resp.data?.config || {})
32
+ }
33
+ }
34
+ })
35
+ }
36
+
37
+ return resp?.data
31
38
  }
32
39
 
33
- return resp?.data
34
- }
40
+ return {}
41
+ },
42
+ [innerContext, mioloObj]
43
+ )
35
44
 
36
- return {}
37
- }, [innerContext, mioloObj])
45
+ const googleLogin = useCallback(() => {
46
+ window.location.href = "/auth/google"
47
+ }, [])
38
48
 
39
49
  const logout = useCallback(async () => {
40
50
  const { fetcher } = mioloObj
41
51
  const { config } = innerContext
42
52
 
43
- const url = config.logout_url || '/logout'
44
- const _resp = await fetcher.logout(url)
53
+ const url = config.logout_url || "/logout"
54
+ const _resp = await fetcher.post(url)
45
55
  // resp.redirected= true
46
56
 
47
- setInnerContext(current => {
57
+ setInnerContext((current) => {
48
58
  return {
49
59
  ...current,
50
60
  user: undefined,
51
- authenticated: false,
61
+ authenticated: false
52
62
  }
53
63
  })
54
64
 
55
65
  return {
56
66
  user: undefined,
57
- authenticated: false,
67
+ authenticated: false
58
68
  }
59
69
  }, [innerContext, mioloObj])
60
70
 
@@ -62,19 +72,19 @@ const MioloContextProvider = ({context, children}) => {
62
72
  setInnerContext((current) => {
63
73
  return {
64
74
  ...current,
65
- user,
75
+ user
66
76
  }
67
77
  })
68
78
  }, [])
69
79
 
70
- const useSsrData = (name, defval, loader, modifier) => {
71
- return useSsrDataOrReload(innerContext, mioloObj, name, defval, loader, modifier)
72
- }
73
-
80
+ const useSsrData = (name, options) => {
81
+ return useSsrDataOrReload(innerContext, mioloObj, name, options)
82
+ }
83
+
74
84
  return (
75
- <Context.Provider
85
+ <Context.Provider
76
86
  value={{
77
- //context: innerContext,
87
+ //context: innerContext,
78
88
  //setContext: setInnerContext,
79
89
  //miolo: mioloObj,
80
90
  user: innerContext.user,
@@ -82,14 +92,16 @@ const MioloContextProvider = ({context, children}) => {
82
92
  authenticated: innerContext.authenticated,
83
93
  fetcher: mioloObj.fetcher,
84
94
  //socket: mioloObj.socket,
85
- login,
95
+ googleLogin,
96
+ localLogin,
86
97
  logout,
87
- useSsrData
88
- }}>
98
+ useSsrData,
99
+ authMethod: innerContext?.config?.auth_method
100
+ }}
101
+ >
89
102
  {children}
90
103
  </Context.Provider>
91
104
  )
92
105
  }
93
106
 
94
-
95
107
  export default MioloContextProvider
@@ -1,5 +1,5 @@
1
- import {useContext} from 'react'
2
- import MioloContext from './MioloContext.mjs'
1
+ import { useContext } from "react"
2
+ import MioloContext from "./MioloContext.mjs"
3
3
 
4
4
  const useMioloContext = () => useContext(MioloContext)
5
5
 
@@ -1,15 +1,10 @@
1
- import React, {useContext} from 'react'
2
- import MioloContext from './MioloContext.mjs'
1
+ import { useContext } from "react"
2
+ import MioloContext from "./MioloContext.mjs"
3
3
 
4
- /* eslint react/display-name:0 */
5
4
  const withMioloContext = (BaseComponent) => (props) => {
6
5
  const context = useContext(MioloContext)
7
6
 
8
- return (
9
- <BaseComponent {...props}
10
- {...context}/>
11
- );
7
+ return <BaseComponent {...props} {...context} />
12
8
  }
13
9
 
14
-
15
- export default withMioloContext
10
+ export default withMioloContext
package/src/index.mjs CHANGED
@@ -1,7 +1,6 @@
1
- import withMioloContext from './context/withMioloContext.jsx'
2
- import useMioloContext from './context/useMioloContext.jsx'
1
+ import AppBrowser from "./AppBrowser.jsx"
2
+ import AppServer from "./AppServer.jsx"
3
+ import useMioloContext from "./context/useMioloContext.jsx"
4
+ import withMioloContext from "./context/withMioloContext.jsx"
3
5
 
4
- import AppBrowser from './AppBrowser.jsx'
5
- import AppServer from './AppServer.jsx'
6
-
7
- export {withMioloContext, useMioloContext, AppBrowser, AppServer}
6
+ export { AppBrowser, AppServer, useMioloContext, withMioloContext }
@@ -1,32 +1,31 @@
1
-
2
1
  const _getDataFromWindow = (name) => {
3
2
  try {
4
- if (window != undefined) {
5
- const ssr_data= window.__CONTEXT.ssr_data
6
-
7
- if (ssr_data!=undefined) {
8
- if (ssr_data[name]!=undefined) {
3
+ if (window !== undefined) {
4
+ const ssr_data = window.__CONTEXT.ssr_data
5
+
6
+ if (ssr_data !== undefined) {
7
+ if (ssr_data[name] !== undefined) {
9
8
  return ssr_data[name]
10
9
  }
11
10
  }
12
11
  }
13
- } catch(e) {}
14
-
12
+ } catch (_) {}
13
+
15
14
  return undefined
16
15
  }
17
16
 
18
17
  const getSsrDataFromContext = (context, name) => {
19
- let data= undefined
18
+ let data
20
19
 
21
- if (context?.ssr_data != undefined && context?.ssr_data[name]!=undefined) {
22
- data= context.ssr_data[name]
20
+ if (context?.ssr_data !== undefined && context?.ssr_data[name] !== undefined) {
21
+ data = context.ssr_data[name]
23
22
  } else {
24
- const wdata= _getDataFromWindow(name)
25
- if (wdata != undefined) {
26
- data= wdata
23
+ const wdata = _getDataFromWindow(name)
24
+ if (wdata !== undefined) {
25
+ data = wdata
27
26
  }
28
27
  }
29
-
28
+
30
29
  return data
31
30
  }
32
31
 
@@ -0,0 +1,69 @@
1
+ import { useEffect, useRef } from "react"
2
+
3
+ export default function usePropsCheck(loader, effect, modifier, desc) {
4
+ const prevLoader = useRef(loader)
5
+ const prevEffect = useRef(effect)
6
+ const prevModifier = useRef(modifier)
7
+ const loaderChangeCount = useRef(0)
8
+ const effectChangeCount = useRef(0)
9
+ const modifierChangeCount = useRef(0)
10
+ const loaderChangeTime = useRef(0)
11
+ const effectChangeTime = useRef(0)
12
+ const modifierChangeTime = useRef(0)
13
+
14
+ useEffect(() => {
15
+ if (prevLoader.current !== undefined && prevLoader.current !== loader) {
16
+ const now = Date.now()
17
+ // Si el cambio ocurrió rápido (<100ms desde el último cambio), sumamos al contador continuo
18
+ if (now - loaderChangeTime.current < 100) {
19
+ loaderChangeCount.current += 1
20
+ if (loaderChangeCount.current >= 4) {
21
+ console.warn(
22
+ `🚨 [miolo][useSsrDataOrReload]: 'options.loader' varies too frequently. Wrap it in a useCallback! ${desc}`
23
+ )
24
+ }
25
+ } else {
26
+ // Ha pasado suficiente tiempo como para ser un cambio humano/aislado
27
+ loaderChangeCount.current = 1
28
+ }
29
+ loaderChangeTime.current = now
30
+ }
31
+ prevLoader.current = loader
32
+ }) // Evalúa en cada render
33
+
34
+ useEffect(() => {
35
+ if (prevEffect.current !== undefined && prevEffect.current !== effect) {
36
+ const now = Date.now()
37
+ if (now - effectChangeTime.current < 100) {
38
+ effectChangeCount.current += 1
39
+ if (effectChangeCount.current >= 4) {
40
+ console.warn(
41
+ `🚨 [miolo][useSsrDataOrReload]: 'options.effect' varies too frequently. Wrap it in a useMemo or useCallback! ${desc}`
42
+ )
43
+ }
44
+ } else {
45
+ effectChangeCount.current = 1
46
+ }
47
+ effectChangeTime.current = now
48
+ }
49
+ prevEffect.current = effect
50
+ }) // Evalúa en cada render
51
+
52
+ useEffect(() => {
53
+ if (prevModifier.current !== undefined && prevModifier.current !== modifier) {
54
+ const now = Date.now()
55
+ if (now - modifierChangeTime.current < 100) {
56
+ modifierChangeCount.current += 1
57
+ if (modifierChangeCount.current >= 4) {
58
+ console.warn(
59
+ `🚨 [miolo][useSsrDataOrReload]: 'options.modifier' varies too frequently. Wrap it in a useCallback! ${desc}`
60
+ )
61
+ }
62
+ } else {
63
+ modifierChangeCount.current = 1
64
+ }
65
+ modifierChangeTime.current = now
66
+ }
67
+ prevModifier.current = modifier
68
+ }) // Evalúa en cada render
69
+ }
@@ -1,43 +1,196 @@
1
- import {useState, useCallback, useEffect} from 'react'
2
- import getSsrDataFromContext from './getSsrDataFromContext.mjs'
3
-
4
- const useSsrDataOrReload = (context, miolo, name, defval, loader, modifier) => {
5
-
6
- const _maybeModify = useCallback((value) => {
7
- return modifier==undefined ? value : modifier(value)
8
- }, [modifier])
9
-
10
- const ssrDataFromContext = getSsrDataFromContext(context, name)
11
- const [ssrData, setSsrData] = useState(_maybeModify(
12
- ssrDataFromContext != undefined
13
- ? ssrDataFromContext : defval))
14
- const [needToRefresh, setNeedToRefresh] = useState(ssrDataFromContext == undefined)
15
-
16
- const refreshSsrData = useCallback(() => {
17
- if (loader == undefined) {
1
+ import { useCallback, useEffect, useMemo, useState } from "react"
2
+ import getSsrDataFromContext from "./getSsrDataFromContext.mjs"
3
+ import usePropsCheck from "./usePropsCheck.mjs"
4
+
5
+ const makeSerializable = (obj) => {
6
+ try {
7
+ return JSON.parse(JSON.stringify(obj))
8
+ } catch (_) {
9
+ return obj
10
+ }
11
+ }
12
+
13
+ const useSsrDataOrReload = (context, miolo, name, options) => {
14
+ const { fetcher } = miolo
15
+ const {
16
+ defval = [],
17
+ loader = undefined,
18
+ url = undefined,
19
+ params = undefined,
20
+ modifier = undefined,
21
+ effect = undefined,
22
+ model = undefined,
23
+ cache = false,
24
+ ttl = undefined
25
+ } = options
26
+
27
+ usePropsCheck(loader, effect, modifier, name)
28
+
29
+ const parseData = useCallback(
30
+ (value) => {
31
+ let parsed = value
32
+ if (model !== undefined) {
33
+ parsed = new model(parsed)
34
+ }
35
+ if (modifier !== undefined) {
36
+ parsed = modifier(parsed)
37
+ }
38
+ return parsed
39
+ },
40
+ [modifier, model]
41
+ )
42
+
43
+ const ssrDataFromContext = useMemo(() => {
44
+ return getSsrDataFromContext(context, name)
45
+ }, [context, name])
46
+
47
+ const [ssrData, setSsrData] = useState(
48
+ parseData(ssrDataFromContext !== undefined ? ssrDataFromContext : defval)
49
+ )
50
+
51
+ const [status, setStatus] = useState(ssrDataFromContext !== undefined ? "loaded" : "idle")
52
+ const [error, setError] = useState(undefined)
53
+
54
+ const updateSsrData = useCallback(
55
+ (data) => {
56
+ setStatus("loading")
57
+ setSsrData(parseData(data))
58
+ setStatus("loaded")
59
+ setError(undefined)
60
+ if (cache === true) {
61
+ if (typeof window !== "undefined") {
62
+ import("idb-keyval").then(({ set }) => {
63
+ set(`ssr-cache-${name}`, { data: makeSerializable(data), ts: Date.now() }).catch(
64
+ () => {}
65
+ )
66
+ })
67
+ }
68
+ }
69
+ },
70
+ [parseData, name, cache]
71
+ )
72
+
73
+ const refreshSsrData = useCallback(async () => {
74
+ if (status === "loading") {
18
75
  return
19
76
  }
20
77
 
21
- const {fetcher} = miolo
78
+ setStatus("loading")
79
+ setError(undefined)
22
80
 
23
- async function fetchData() {
24
- let nSsrData = await loader(context, fetcher)
25
- setSsrData(_maybeModify(nSsrData))
81
+ let newData
82
+
83
+ if (loader !== undefined) {
84
+ newData = await loader(context, fetcher)
85
+ } else {
86
+ if (!url) {
87
+ setError(`No url provided for ${name}`)
88
+ } else {
89
+ const resp = await fetcher.get(url, params)
90
+ if (resp.ok) {
91
+ newData = resp?.data
92
+ } else {
93
+ setError(resp?.error || "Unknown error")
94
+ }
95
+ }
26
96
  }
27
97
 
28
- fetchData()
29
- }, [context, miolo, loader, _maybeModify])
30
-
98
+ if (newData !== undefined) {
99
+ if (cache === true) {
100
+ if (typeof window !== "undefined") {
101
+ try {
102
+ const { set } = await import("idb-keyval")
103
+ await set(`ssr-cache-${name}`, { data: makeSerializable(newData), ts: Date.now() })
104
+ } catch (err) {
105
+ console.error(err)
106
+ }
107
+ }
108
+ }
109
+ setSsrData(parseData(newData))
110
+ }
111
+
112
+ setStatus("loaded")
113
+ }, [status, context, fetcher, loader, url, params, parseData, name, cache])
114
+
115
+ useEffect(() => {
116
+ let mounted = true
117
+
118
+ const loadData = async () => {
119
+ if (!mounted) return
120
+
121
+ try {
122
+ if (status === "idle") {
123
+ const changed =
124
+ effect === undefined ||
125
+ (typeof effect === "function" && effect() === true) ||
126
+ effect === true
127
+
128
+ if (changed === true) {
129
+ let cached = null
130
+ if (cache === true) {
131
+ if (typeof window !== "undefined") {
132
+ try {
133
+ const { get } = await import("idb-keyval")
134
+ cached = await get(`ssr-cache-${name}`)
135
+ } catch (err) {
136
+ console.error(err)
137
+ }
138
+ }
139
+ }
140
+
141
+ if (cached && cached.data !== undefined) {
142
+ setSsrData(parseData(cached.data))
143
+
144
+ if (ttl !== undefined && Date.now() - cached.ts > ttl * 1000) {
145
+ refreshSsrData()
146
+ } else {
147
+ setStatus("loaded")
148
+ }
149
+ } else {
150
+ refreshSsrData()
151
+ }
152
+ }
153
+ }
154
+ } catch (_) {}
155
+ }
156
+
157
+ loadData()
158
+
159
+ return () => {
160
+ mounted = false
161
+ }
162
+ }, [status, refreshSsrData, effect, name, ttl, parseData, cache])
163
+
31
164
  useEffect(() => {
32
- try {
33
- if (needToRefresh) {
34
- setNeedToRefresh(false)
35
- refreshSsrData()
165
+ if (cache === true) {
166
+ if (ssrDataFromContext !== undefined && typeof window !== "undefined") {
167
+ import("idb-keyval").then(({ set }) => {
168
+ set(`ssr-cache-${name}`, {
169
+ data: makeSerializable(ssrDataFromContext),
170
+ ts: Date.now()
171
+ }).catch(() => {})
172
+ })
36
173
  }
37
- } catch(e) {}
38
- }, [needToRefresh, refreshSsrData])
174
+ }
175
+ }, [ssrDataFromContext, name, cache])
176
+
177
+ const invalidate = useCallback(() => {
178
+ if (typeof window !== "undefined") {
179
+ import("idb-keyval").then(({ del }) => {
180
+ del(`ssr-cache-${name}`).catch(() => {})
181
+ })
182
+ }
183
+ }, [name])
39
184
 
40
- return [ssrData, (data) => setSsrData(_maybeModify(data)), refreshSsrData]
185
+ return {
186
+ data: ssrData,
187
+ setData: updateSsrData,
188
+ refresh: refreshSsrData,
189
+ invalidate,
190
+ error,
191
+ ok: error === undefined,
192
+ ready: status === "loaded"
193
+ }
41
194
  }
42
195
 
43
- export {useSsrDataOrReload}
196
+ export { useSsrDataOrReload }