metaowl 0.4.0 → 0.5.0
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/CHANGELOG.md +52 -0
- package/README.md +13 -15
- package/build/runtime/bin/metaowl-build.js +10 -0
- package/{bin → build/runtime/bin}/metaowl-create.js +96 -177
- package/build/runtime/bin/metaowl-dev.js +10 -0
- package/build/runtime/bin/metaowl-generate.js +231 -0
- package/build/runtime/bin/metaowl-lint.js +58 -0
- package/build/runtime/bin/utils.js +68 -0
- package/build/runtime/index.js +141 -0
- package/build/runtime/modules/app-mounter.js +65 -0
- package/build/runtime/modules/auto-import.js +140 -0
- package/build/runtime/modules/cache.js +49 -0
- package/build/runtime/modules/composables.js +353 -0
- package/build/runtime/modules/error-boundary.js +116 -0
- package/build/runtime/modules/fetch.js +31 -0
- package/build/runtime/modules/file-router.js +205 -0
- package/build/runtime/modules/forms.js +193 -0
- package/build/runtime/modules/i18n.js +167 -0
- package/build/runtime/modules/layouts.js +163 -0
- package/build/runtime/modules/link.js +141 -0
- package/build/runtime/modules/meta.js +117 -0
- package/build/runtime/modules/odoo-rpc.js +264 -0
- package/build/runtime/modules/pwa.js +262 -0
- package/build/runtime/modules/router.js +389 -0
- package/build/runtime/modules/seo.js +186 -0
- package/build/runtime/modules/store.js +196 -0
- package/build/runtime/modules/templates-manager.js +52 -0
- package/build/runtime/modules/test-utils.js +238 -0
- package/build/runtime/vite/plugin.js +183 -0
- package/eslint.js +29 -0
- package/package.json +29 -11
- package/CONTRIBUTING.md +0 -49
- package/bin/metaowl-build.js +0 -12
- package/bin/metaowl-dev.js +0 -12
- package/bin/metaowl-generate.js +0 -339
- package/bin/metaowl-lint.js +0 -71
- package/bin/utils.js +0 -82
- package/index.js +0 -328
- package/modules/app-mounter.js +0 -104
- package/modules/auto-import.js +0 -225
- package/modules/cache.js +0 -59
- package/modules/composables.js +0 -600
- package/modules/error-boundary.js +0 -228
- package/modules/fetch.js +0 -51
- package/modules/file-router.js +0 -478
- package/modules/forms.js +0 -353
- package/modules/i18n.js +0 -333
- package/modules/layouts.js +0 -431
- package/modules/link.js +0 -255
- package/modules/meta.js +0 -119
- package/modules/odoo-rpc.js +0 -511
- package/modules/pwa.js +0 -515
- package/modules/router.js +0 -769
- package/modules/seo.js +0 -501
- package/modules/store.js +0 -409
- package/modules/templates-manager.js +0 -89
- package/modules/test-utils.js +0 -532
- package/test/auto-import.test.js +0 -110
- package/test/cache.test.js +0 -55
- package/test/composables.test.js +0 -103
- package/test/dynamic-routes.test.js +0 -469
- package/test/error-boundary.test.js +0 -126
- package/test/fetch.test.js +0 -100
- package/test/file-router.test.js +0 -55
- package/test/forms.test.js +0 -203
- package/test/i18n.test.js +0 -188
- package/test/layouts.test.js +0 -395
- package/test/link.test.js +0 -189
- package/test/meta.test.js +0 -146
- package/test/odoo-rpc.test.js +0 -547
- package/test/pwa.test.js +0 -154
- package/test/router-guards.test.js +0 -229
- package/test/router.test.js +0 -77
- package/test/seo.test.js +0 -353
- package/test/store.test.js +0 -476
- package/test/templates-manager.test.js +0 -83
- package/test/test-utils.test.js +0 -314
- package/vite/plugin.js +0 -277
- package/vitest.config.js +0 -8
package/modules/composables.js
DELETED
|
@@ -1,600 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module Composables
|
|
3
|
-
*
|
|
4
|
-
* Reusable composables/hooks for MetaOwl OWL applications.
|
|
5
|
-
*
|
|
6
|
-
* This module provides a collection of commonly used patterns
|
|
7
|
-
* that can be shared across components.
|
|
8
|
-
*
|
|
9
|
-
* Available composables:
|
|
10
|
-
* - useAuth: Authentication state management
|
|
11
|
-
* - useLocalStorage: Reactive localStorage access
|
|
12
|
-
* - useFetch: Data fetching with loading states
|
|
13
|
-
* - useDebounce: Debounced values
|
|
14
|
-
* - useThrottle: Throttled function execution
|
|
15
|
-
* - useWindowSize: Reactive window dimensions
|
|
16
|
-
* - useOnlineStatus: Network connectivity detection
|
|
17
|
-
* - useAsyncState: Async operation state management
|
|
18
|
-
*
|
|
19
|
-
* Usage:
|
|
20
|
-
* import { useAuth, useLocalStorage, useFetch } from 'metaowl'
|
|
21
|
-
*
|
|
22
|
-
* class MyComponent extends Component {
|
|
23
|
-
* setup() {
|
|
24
|
-
* const { user, isLoggedIn, login, logout } = useAuth()
|
|
25
|
-
* const theme = useLocalStorage('theme', 'light')
|
|
26
|
-
* const { data, loading, error, refresh } = useFetch('/api/data')
|
|
27
|
-
*
|
|
28
|
-
* return { user, theme, data, loading, error }
|
|
29
|
-
* }
|
|
30
|
-
* }
|
|
31
|
-
*/
|
|
32
|
-
|
|
33
|
-
import { useState, onMounted, onWillUnmount } from '@odoo/owl'
|
|
34
|
-
import Cache from './cache.js'
|
|
35
|
-
import Fetch from './fetch.js'
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Authentication state composable.
|
|
39
|
-
*
|
|
40
|
-
* Integrates with the OdooService for authentication state.
|
|
41
|
-
* Automatically syncs with auth changes.
|
|
42
|
-
*
|
|
43
|
-
* @returns {Object} Auth state and methods
|
|
44
|
-
* @property {Ref<Object|null>} user - Current user info
|
|
45
|
-
* @property {Ref<boolean>} isLoggedIn - Authentication status
|
|
46
|
-
* @property {Ref<boolean>} isLoading - Loading state
|
|
47
|
-
* @property {Function} login - Login method
|
|
48
|
-
* @property {Function} logout - Logout method
|
|
49
|
-
* @property {Function} checkAuth - Check current auth status
|
|
50
|
-
*
|
|
51
|
-
* @example
|
|
52
|
-
* const { user, isLoggedIn, logout } = useAuth()
|
|
53
|
-
*
|
|
54
|
-
* // In template
|
|
55
|
-
* <div t-if="isLoggedIn.value">
|
|
56
|
-
* Welcome, <t t-esc="user.value?.name"/>
|
|
57
|
-
* <button t-on-click="logout">Logout</button>
|
|
58
|
-
* </div>
|
|
59
|
-
*/
|
|
60
|
-
export function useAuth() {
|
|
61
|
-
const user = useState(null)
|
|
62
|
-
const isLoggedIn = useState(false)
|
|
63
|
-
const isLoading = useState(false)
|
|
64
|
-
let unsubscribe = null
|
|
65
|
-
|
|
66
|
-
onMounted(async () => {
|
|
67
|
-
// Try to import OdooService dynamically to avoid circular deps
|
|
68
|
-
try {
|
|
69
|
-
const { OdooService } = await import('./odoo-rpc.js')
|
|
70
|
-
|
|
71
|
-
// Set initial state
|
|
72
|
-
isLoggedIn.value = OdooService.isAuthenticated()
|
|
73
|
-
user.value = OdooService.getSession()
|
|
74
|
-
|
|
75
|
-
// Subscribe to auth changes
|
|
76
|
-
unsubscribe = OdooService.onAuthChange((session) => {
|
|
77
|
-
user.value = session
|
|
78
|
-
isLoggedIn.value = session !== null
|
|
79
|
-
})
|
|
80
|
-
} catch {
|
|
81
|
-
// OdooService not available, auth stays false
|
|
82
|
-
}
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
onWillUnmount(() => {
|
|
86
|
-
if (unsubscribe) {
|
|
87
|
-
unsubscribe()
|
|
88
|
-
}
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
const login = async (credentials) => {
|
|
92
|
-
isLoading.value = true
|
|
93
|
-
try {
|
|
94
|
-
const { OdooService } = await import('./odoo-rpc.js')
|
|
95
|
-
await OdooService.authenticate(credentials?.username, credentials?.password)
|
|
96
|
-
return true
|
|
97
|
-
} catch (error) {
|
|
98
|
-
return false
|
|
99
|
-
} finally {
|
|
100
|
-
isLoading.value = false
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const logout = async () => {
|
|
105
|
-
try {
|
|
106
|
-
const { OdooService } = await import('./odoo-rpc.js')
|
|
107
|
-
OdooService.logout()
|
|
108
|
-
} catch {
|
|
109
|
-
// Ignore errors
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const checkAuth = async () => {
|
|
114
|
-
try {
|
|
115
|
-
const { OdooService } = await import('./odoo-rpc.js')
|
|
116
|
-
return OdooService.isAuthenticated()
|
|
117
|
-
} catch {
|
|
118
|
-
return false
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return {
|
|
123
|
-
user,
|
|
124
|
-
isLoggedIn,
|
|
125
|
-
isLoading,
|
|
126
|
-
login,
|
|
127
|
-
logout,
|
|
128
|
-
checkAuth
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* Reactive localStorage composable.
|
|
134
|
-
*
|
|
135
|
-
* Automatically syncs with localStorage and other components
|
|
136
|
-
* using the same key.
|
|
137
|
-
*
|
|
138
|
-
* @param {string} key - localStorage key
|
|
139
|
-
* @param {any} defaultValue - Default value if not found
|
|
140
|
-
* @returns {Ref<any>} Reactive reference to stored value
|
|
141
|
-
*
|
|
142
|
-
* @example
|
|
143
|
-
* const theme = useLocalStorage('theme', 'light')
|
|
144
|
-
*
|
|
145
|
-
* // Update value (automatically saves to localStorage)
|
|
146
|
-
* theme.value = 'dark'
|
|
147
|
-
*
|
|
148
|
-
* // Access value
|
|
149
|
-
* console.log(theme.value) // 'dark'
|
|
150
|
-
*/
|
|
151
|
-
export function useLocalStorage(key, defaultValue = null) {
|
|
152
|
-
const state = useState(() => {
|
|
153
|
-
try {
|
|
154
|
-
const item = localStorage.getItem(key)
|
|
155
|
-
return item !== null ? JSON.parse(item) : defaultValue
|
|
156
|
-
} catch {
|
|
157
|
-
return defaultValue
|
|
158
|
-
}
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
// Watch for changes and sync to localStorage
|
|
162
|
-
const originalSet = state.__set
|
|
163
|
-
|
|
164
|
-
// Override the setter to persist to localStorage
|
|
165
|
-
Object.defineProperty(state, 'value', {
|
|
166
|
-
get() {
|
|
167
|
-
return state.__value
|
|
168
|
-
},
|
|
169
|
-
set(newValue) {
|
|
170
|
-
state.__value = newValue
|
|
171
|
-
try {
|
|
172
|
-
if (newValue === null) {
|
|
173
|
-
localStorage.removeItem(key)
|
|
174
|
-
} else {
|
|
175
|
-
localStorage.setItem(key, JSON.stringify(newValue))
|
|
176
|
-
}
|
|
177
|
-
} catch {
|
|
178
|
-
// Ignore storage errors
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
})
|
|
182
|
-
|
|
183
|
-
// Listen for changes from other tabs/windows
|
|
184
|
-
const handleStorage = (event) => {
|
|
185
|
-
if (event.key === key) {
|
|
186
|
-
try {
|
|
187
|
-
state.__value = event.newValue !== null
|
|
188
|
-
? JSON.parse(event.newValue)
|
|
189
|
-
: defaultValue
|
|
190
|
-
} catch {
|
|
191
|
-
state.__value = defaultValue
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
onMounted(() => {
|
|
197
|
-
window.addEventListener('storage', handleStorage)
|
|
198
|
-
})
|
|
199
|
-
|
|
200
|
-
onWillUnmount(() => {
|
|
201
|
-
window.removeEventListener('storage', handleStorage)
|
|
202
|
-
})
|
|
203
|
-
|
|
204
|
-
return state
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Data fetching composable.
|
|
209
|
-
*
|
|
210
|
-
* Provides reactive loading, error, and data states
|
|
211
|
-
* for HTTP requests.
|
|
212
|
-
*
|
|
213
|
-
* @param {string|Ref<string>} url - URL to fetch
|
|
214
|
-
* @param {Object} options - Fetch options
|
|
215
|
-
* @param {Object} [options.initialData=null] - Initial data value
|
|
216
|
-
* @param {boolean} [options.immediate=true] - Fetch immediately on mount
|
|
217
|
-
* @param {Function} [options.transform] - Transform response data
|
|
218
|
-
* @param {Function} [options.onError] - Error handler
|
|
219
|
-
* @returns {Object} Fetch state and control methods
|
|
220
|
-
* @property {Ref<any>} data - Fetched data
|
|
221
|
-
* @property {Ref<boolean>} loading - Loading state
|
|
222
|
-
* @property {Ref<Error|null>} error - Error state
|
|
223
|
-
* @property {Function} refresh - Refetch data
|
|
224
|
-
* @property {Function} execute - Execute fetch with optional new URL
|
|
225
|
-
*
|
|
226
|
-
* @example
|
|
227
|
-
* const { data, loading, error, refresh } = useFetch('/api/users')
|
|
228
|
-
*
|
|
229
|
-
* // In template
|
|
230
|
-
* <div t-if="loading.value">Loading...</div>
|
|
231
|
-
* <div t-elif="error.value">Error: <t t-esc="error.value.message"/></div>
|
|
232
|
-
* <div t-else="">
|
|
233
|
-
* <div t-foreach="data.value" t-as="user" t-key="user.id">
|
|
234
|
-
* <t t-esc="user.name"/>
|
|
235
|
-
* </div>
|
|
236
|
-
* </div>
|
|
237
|
-
*/
|
|
238
|
-
export function useFetch(url, options = {}) {
|
|
239
|
-
const {
|
|
240
|
-
initialData = null,
|
|
241
|
-
immediate = true,
|
|
242
|
-
transform = (data) => data,
|
|
243
|
-
onError = null,
|
|
244
|
-
...fetchOptions
|
|
245
|
-
} = options
|
|
246
|
-
|
|
247
|
-
const data = useState(initialData)
|
|
248
|
-
const loading = useState(false)
|
|
249
|
-
const error = useState(null)
|
|
250
|
-
|
|
251
|
-
const execute = async (executeUrl = null) => {
|
|
252
|
-
const fetchUrl = executeUrl || (typeof url === 'object' ? url.value : url)
|
|
253
|
-
|
|
254
|
-
if (!fetchUrl) return
|
|
255
|
-
|
|
256
|
-
loading.value = true
|
|
257
|
-
error.value = null
|
|
258
|
-
|
|
259
|
-
try {
|
|
260
|
-
const result = await Fetch.url(fetchUrl, fetchOptions)
|
|
261
|
-
|
|
262
|
-
if (result === null) {
|
|
263
|
-
throw new Error('Request failed')
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
data.value = transform(result)
|
|
267
|
-
return data.value
|
|
268
|
-
} catch (err) {
|
|
269
|
-
error.value = err
|
|
270
|
-
if (onError) {
|
|
271
|
-
onError(err)
|
|
272
|
-
}
|
|
273
|
-
throw err
|
|
274
|
-
} finally {
|
|
275
|
-
loading.value = false
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
const refresh = () => execute()
|
|
280
|
-
|
|
281
|
-
onMounted(() => {
|
|
282
|
-
if (immediate) {
|
|
283
|
-
execute()
|
|
284
|
-
}
|
|
285
|
-
})
|
|
286
|
-
|
|
287
|
-
// Watch for URL changes if url is a reactive reference
|
|
288
|
-
if (typeof url === 'object' && url.__owl__?.reactivity) {
|
|
289
|
-
// In a real implementation, we'd use a watch effect here
|
|
290
|
-
// For now, manual refresh is required
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
return {
|
|
294
|
-
data,
|
|
295
|
-
loading,
|
|
296
|
-
error,
|
|
297
|
-
refresh,
|
|
298
|
-
execute
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/**
|
|
303
|
-
* Debounce a reactive value.
|
|
304
|
-
*
|
|
305
|
-
* Delays updating the value until after wait milliseconds
|
|
306
|
-
* have elapsed since the last update.
|
|
307
|
-
*
|
|
308
|
-
* @param {Ref<any>} value - Reactive value to debounce
|
|
309
|
-
* @param {number} [wait=300] - Debounce wait time in ms
|
|
310
|
-
* @returns {Ref<any>} Debounced value
|
|
311
|
-
*
|
|
312
|
-
* @example
|
|
313
|
-
* const searchQuery = useState('')
|
|
314
|
-
* const debouncedQuery = useDebounce(searchQuery, 500)
|
|
315
|
-
*
|
|
316
|
-
* // debouncedQuery updates only 500ms after searchQuery stops changing
|
|
317
|
-
*/
|
|
318
|
-
export function useDebounce(value, wait = 300) {
|
|
319
|
-
const debouncedValue = useState(value.value)
|
|
320
|
-
let timeout = null
|
|
321
|
-
|
|
322
|
-
// Create a proxy to watch for changes
|
|
323
|
-
Object.defineProperty(value, 'value', {
|
|
324
|
-
get() {
|
|
325
|
-
return value.__value
|
|
326
|
-
},
|
|
327
|
-
set(newValue) {
|
|
328
|
-
value.__value = newValue
|
|
329
|
-
clearTimeout(timeout)
|
|
330
|
-
timeout = setTimeout(() => {
|
|
331
|
-
debouncedValue.value = newValue
|
|
332
|
-
}, wait)
|
|
333
|
-
}
|
|
334
|
-
})
|
|
335
|
-
|
|
336
|
-
onWillUnmount(() => {
|
|
337
|
-
clearTimeout(timeout)
|
|
338
|
-
})
|
|
339
|
-
|
|
340
|
-
return debouncedValue
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/**
|
|
344
|
-
* Throttle a function.
|
|
345
|
-
*
|
|
346
|
-
* Ensures the function is called at most once per wait period.
|
|
347
|
-
*
|
|
348
|
-
* @param {Function} fn - Function to throttle
|
|
349
|
-
* @param {number} [wait=300] - Throttle wait time in ms
|
|
350
|
-
* @returns {Function} Throttled function
|
|
351
|
-
*
|
|
352
|
-
* @example
|
|
353
|
-
* const throttledSearch = useThrottle((query) => {
|
|
354
|
-
* performSearch(query)
|
|
355
|
-
* }, 500)
|
|
356
|
-
*
|
|
357
|
-
* // Can call multiple times, but executes at most once per 500ms
|
|
358
|
-
*/
|
|
359
|
-
export function useThrottle(fn, wait = 300) {
|
|
360
|
-
let lastCall = 0
|
|
361
|
-
let timeout = null
|
|
362
|
-
|
|
363
|
-
const throttled = (...args) => {
|
|
364
|
-
const now = Date.now()
|
|
365
|
-
const remaining = wait - (now - lastCall)
|
|
366
|
-
|
|
367
|
-
if (remaining <= 0) {
|
|
368
|
-
clearTimeout(timeout)
|
|
369
|
-
lastCall = now
|
|
370
|
-
fn(...args)
|
|
371
|
-
} else if (!timeout) {
|
|
372
|
-
timeout = setTimeout(() => {
|
|
373
|
-
lastCall = Date.now()
|
|
374
|
-
timeout = null
|
|
375
|
-
fn(...args)
|
|
376
|
-
}, remaining)
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
onWillUnmount(() => {
|
|
381
|
-
clearTimeout(timeout)
|
|
382
|
-
})
|
|
383
|
-
|
|
384
|
-
return throttled
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* Track window size reactively.
|
|
389
|
-
*
|
|
390
|
-
* @returns {Object} Window dimensions
|
|
391
|
-
* @property {Ref<number>} width - Window inner width
|
|
392
|
-
* @property {Ref<number>} height - Window inner height
|
|
393
|
-
*
|
|
394
|
-
* @example
|
|
395
|
-
* const { width, height } = useWindowSize()
|
|
396
|
-
*
|
|
397
|
-
* // Reactive to window resizing
|
|
398
|
-
* const isMobile = () => width.value < 768
|
|
399
|
-
*/
|
|
400
|
-
export function useWindowSize() {
|
|
401
|
-
const width = useState(window.innerWidth)
|
|
402
|
-
const height = useState(window.innerHeight)
|
|
403
|
-
|
|
404
|
-
const handleResize = () => {
|
|
405
|
-
width.value = window.innerWidth
|
|
406
|
-
height.value = window.innerHeight
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
onMounted(() => {
|
|
410
|
-
window.addEventListener('resize', handleResize)
|
|
411
|
-
})
|
|
412
|
-
|
|
413
|
-
onWillUnmount(() => {
|
|
414
|
-
window.removeEventListener('resize', handleResize)
|
|
415
|
-
})
|
|
416
|
-
|
|
417
|
-
return { width, height }
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
/**
|
|
421
|
-
* Track online/offline status.
|
|
422
|
-
*
|
|
423
|
-
* @returns {Ref<boolean>} True if online
|
|
424
|
-
*
|
|
425
|
-
* @example
|
|
426
|
-
* const isOnline = useOnlineStatus()
|
|
427
|
-
*
|
|
428
|
-
* <div t-if="!isOnline.value" class="offline-banner">
|
|
429
|
-
* You are offline
|
|
430
|
-
* </div>
|
|
431
|
-
*/
|
|
432
|
-
export function useOnlineStatus() {
|
|
433
|
-
const isOnline = useState(navigator.onLine)
|
|
434
|
-
|
|
435
|
-
const handleOnline = () => {
|
|
436
|
-
isOnline.value = true
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
const handleOffline = () => {
|
|
440
|
-
isOnline.value = false
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
onMounted(() => {
|
|
444
|
-
window.addEventListener('online', handleOnline)
|
|
445
|
-
window.addEventListener('offline', handleOffline)
|
|
446
|
-
})
|
|
447
|
-
|
|
448
|
-
onWillUnmount(() => {
|
|
449
|
-
window.removeEventListener('online', handleOnline)
|
|
450
|
-
window.removeEventListener('offline', handleOffline)
|
|
451
|
-
})
|
|
452
|
-
|
|
453
|
-
return isOnline
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
/**
|
|
457
|
-
* Manage async operation state.
|
|
458
|
-
*
|
|
459
|
-
* Tracks loading, error, and data states for any async function.
|
|
460
|
-
*
|
|
461
|
-
* @param {Function} asyncFn - Async function to execute
|
|
462
|
-
* @param {Object} options - Options
|
|
463
|
-
* @param {boolean} [options.immediate=false] - Execute immediately
|
|
464
|
-
* @param {any} [options.initialData=null] - Initial data value
|
|
465
|
-
* @returns {Object} Async state
|
|
466
|
-
* @property {Ref<any>} state - Current state (null, 'loading', 'success', 'error')
|
|
467
|
-
* @property {Ref<any>} data - Result data
|
|
468
|
-
* @property {Ref<Error|null>} error - Error if any
|
|
469
|
-
* @property {Function} execute - Execute the async function
|
|
470
|
-
* @property {boolean} isLoading - True if loading
|
|
471
|
-
* @property {boolean} isSuccess - True if succeeded
|
|
472
|
-
* @property {boolean} isError - True if errored
|
|
473
|
-
*
|
|
474
|
-
* @example
|
|
475
|
-
* const fetchUser = async (id) => {
|
|
476
|
-
* return await api.getUser(id)
|
|
477
|
-
* }
|
|
478
|
-
*
|
|
479
|
-
* const { state, data, execute, isLoading } = useAsyncState(fetchUser)
|
|
480
|
-
*
|
|
481
|
-
* // Execute
|
|
482
|
-
* await execute(123)
|
|
483
|
-
*
|
|
484
|
-
* // Check state
|
|
485
|
-
* if (isLoading.value) console.log('Loading...')
|
|
486
|
-
* if (isSuccess.value) console.log('User:', data.value)
|
|
487
|
-
*/
|
|
488
|
-
export function useAsyncState(asyncFn, options = {}) {
|
|
489
|
-
const { immediate = false, initialData = null } = options
|
|
490
|
-
|
|
491
|
-
const state = useState(null) // null, 'loading', 'success', 'error'
|
|
492
|
-
const data = useState(initialData)
|
|
493
|
-
const error = useState(null)
|
|
494
|
-
|
|
495
|
-
const execute = async (...args) => {
|
|
496
|
-
state.value = 'loading'
|
|
497
|
-
error.value = null
|
|
498
|
-
|
|
499
|
-
try {
|
|
500
|
-
const result = await asyncFn(...args)
|
|
501
|
-
data.value = result
|
|
502
|
-
state.value = 'success'
|
|
503
|
-
return result
|
|
504
|
-
} catch (err) {
|
|
505
|
-
error.value = err
|
|
506
|
-
state.value = 'error'
|
|
507
|
-
throw err
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
if (immediate) {
|
|
512
|
-
onMounted(() => {
|
|
513
|
-
execute()
|
|
514
|
-
})
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
return {
|
|
518
|
-
state,
|
|
519
|
-
data,
|
|
520
|
-
error,
|
|
521
|
-
execute,
|
|
522
|
-
isLoading: () => state.value === 'loading',
|
|
523
|
-
isSuccess: () => state.value === 'success',
|
|
524
|
-
isError: () => state.value === 'error'
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
/**
|
|
529
|
-
* Reactive cache composable.
|
|
530
|
-
*
|
|
531
|
-
* Uses the MetaOwl Cache module with reactive updates.
|
|
532
|
-
*
|
|
533
|
-
* @param {string} key - Cache key
|
|
534
|
-
* @param {any} defaultValue - Default value
|
|
535
|
-
* @returns {Object} Cache operations
|
|
536
|
-
* @property {Ref<any>} value - Cached value
|
|
537
|
-
* @property {Function} set - Set cache value
|
|
538
|
-
* @property {Function} get - Get cache value
|
|
539
|
-
* @property {Function} remove - Remove from cache
|
|
540
|
-
* @property {Function} clear - Clear entire cache
|
|
541
|
-
*
|
|
542
|
-
* @example
|
|
543
|
-
* const { value, set } = useCache('user-preferences', {})
|
|
544
|
-
*
|
|
545
|
-
* set({ theme: 'dark' })
|
|
546
|
-
* console.log(value.value) // { theme: 'dark' }
|
|
547
|
-
*/
|
|
548
|
-
export function useCache(key, defaultValue = null) {
|
|
549
|
-
const value = useState(() => {
|
|
550
|
-
try {
|
|
551
|
-
return Cache.get(key) || defaultValue
|
|
552
|
-
} catch {
|
|
553
|
-
return defaultValue
|
|
554
|
-
}
|
|
555
|
-
})
|
|
556
|
-
|
|
557
|
-
const set = (newValue) => {
|
|
558
|
-
value.value = newValue
|
|
559
|
-
Cache.set(key, newValue)
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
const get = () => {
|
|
563
|
-
const cached = Cache.get(key)
|
|
564
|
-
value.value = cached
|
|
565
|
-
return cached
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
const remove = () => {
|
|
569
|
-
value.value = defaultValue
|
|
570
|
-
Cache.remove(key)
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
const clear = () => {
|
|
574
|
-
value.value = defaultValue
|
|
575
|
-
Cache.clear()
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
return {
|
|
579
|
-
value,
|
|
580
|
-
set,
|
|
581
|
-
get,
|
|
582
|
-
remove,
|
|
583
|
-
clear
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
// Export all composables as a namespace
|
|
588
|
-
export const Composables = {
|
|
589
|
-
useAuth,
|
|
590
|
-
useLocalStorage,
|
|
591
|
-
useFetch,
|
|
592
|
-
useDebounce,
|
|
593
|
-
useThrottle,
|
|
594
|
-
useWindowSize,
|
|
595
|
-
useOnlineStatus,
|
|
596
|
-
useAsyncState,
|
|
597
|
-
useCache
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
export default Composables
|