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.
Files changed (79) hide show
  1. package/CHANGELOG.md +52 -0
  2. package/README.md +13 -15
  3. package/build/runtime/bin/metaowl-build.js +10 -0
  4. package/{bin → build/runtime/bin}/metaowl-create.js +96 -177
  5. package/build/runtime/bin/metaowl-dev.js +10 -0
  6. package/build/runtime/bin/metaowl-generate.js +231 -0
  7. package/build/runtime/bin/metaowl-lint.js +58 -0
  8. package/build/runtime/bin/utils.js +68 -0
  9. package/build/runtime/index.js +141 -0
  10. package/build/runtime/modules/app-mounter.js +65 -0
  11. package/build/runtime/modules/auto-import.js +140 -0
  12. package/build/runtime/modules/cache.js +49 -0
  13. package/build/runtime/modules/composables.js +353 -0
  14. package/build/runtime/modules/error-boundary.js +116 -0
  15. package/build/runtime/modules/fetch.js +31 -0
  16. package/build/runtime/modules/file-router.js +205 -0
  17. package/build/runtime/modules/forms.js +193 -0
  18. package/build/runtime/modules/i18n.js +167 -0
  19. package/build/runtime/modules/layouts.js +163 -0
  20. package/build/runtime/modules/link.js +141 -0
  21. package/build/runtime/modules/meta.js +117 -0
  22. package/build/runtime/modules/odoo-rpc.js +264 -0
  23. package/build/runtime/modules/pwa.js +262 -0
  24. package/build/runtime/modules/router.js +389 -0
  25. package/build/runtime/modules/seo.js +186 -0
  26. package/build/runtime/modules/store.js +196 -0
  27. package/build/runtime/modules/templates-manager.js +52 -0
  28. package/build/runtime/modules/test-utils.js +238 -0
  29. package/build/runtime/vite/plugin.js +183 -0
  30. package/eslint.js +29 -0
  31. package/package.json +29 -11
  32. package/CONTRIBUTING.md +0 -49
  33. package/bin/metaowl-build.js +0 -12
  34. package/bin/metaowl-dev.js +0 -12
  35. package/bin/metaowl-generate.js +0 -339
  36. package/bin/metaowl-lint.js +0 -71
  37. package/bin/utils.js +0 -82
  38. package/index.js +0 -328
  39. package/modules/app-mounter.js +0 -104
  40. package/modules/auto-import.js +0 -225
  41. package/modules/cache.js +0 -59
  42. package/modules/composables.js +0 -600
  43. package/modules/error-boundary.js +0 -228
  44. package/modules/fetch.js +0 -51
  45. package/modules/file-router.js +0 -478
  46. package/modules/forms.js +0 -353
  47. package/modules/i18n.js +0 -333
  48. package/modules/layouts.js +0 -431
  49. package/modules/link.js +0 -255
  50. package/modules/meta.js +0 -119
  51. package/modules/odoo-rpc.js +0 -511
  52. package/modules/pwa.js +0 -515
  53. package/modules/router.js +0 -769
  54. package/modules/seo.js +0 -501
  55. package/modules/store.js +0 -409
  56. package/modules/templates-manager.js +0 -89
  57. package/modules/test-utils.js +0 -532
  58. package/test/auto-import.test.js +0 -110
  59. package/test/cache.test.js +0 -55
  60. package/test/composables.test.js +0 -103
  61. package/test/dynamic-routes.test.js +0 -469
  62. package/test/error-boundary.test.js +0 -126
  63. package/test/fetch.test.js +0 -100
  64. package/test/file-router.test.js +0 -55
  65. package/test/forms.test.js +0 -203
  66. package/test/i18n.test.js +0 -188
  67. package/test/layouts.test.js +0 -395
  68. package/test/link.test.js +0 -189
  69. package/test/meta.test.js +0 -146
  70. package/test/odoo-rpc.test.js +0 -547
  71. package/test/pwa.test.js +0 -154
  72. package/test/router-guards.test.js +0 -229
  73. package/test/router.test.js +0 -77
  74. package/test/seo.test.js +0 -353
  75. package/test/store.test.js +0 -476
  76. package/test/templates-manager.test.js +0 -83
  77. package/test/test-utils.test.js +0 -314
  78. package/vite/plugin.js +0 -277
  79. package/vitest.config.js +0 -8
package/modules/pwa.js DELETED
@@ -1,515 +0,0 @@
1
- /**
2
- * @module PWA
3
- *
4
- * Progressive Web App utilities for MetaOwl applications.
5
- *
6
- * Features:
7
- * - Web App Manifest generation
8
- * - Service Worker registration and management
9
- * - Offline/online status detection
10
- * - Background sync helpers
11
- * - Push notification utilities
12
- * - Cache management for offline support
13
- *
14
- * Usage:
15
- * import { PWA } from 'metaowl'
16
- *
17
- * // Generate manifest
18
- * const manifest = PWA.generateManifest({
19
- * name: 'My App',
20
- * shortName: 'MyApp',
21
- * themeColor: '#007bff'
22
- * })
23
- *
24
- * // Register service worker
25
- * await PWA.registerServiceWorker('/sw.js')
26
- *
27
- * // Check online status
28
- * if (PWA.isOnline()) {
29
- * // sync data
30
- * }
31
- */
32
-
33
- /**
34
- * @typedef {Object} ManifestOptions
35
- * @property {string} name - Full app name
36
- * @property {string} shortName - Short name (max 12 chars)
37
- * @property {string} [description] - App description
38
- * @property {string} [startUrl='./'] - Start URL
39
- * @property {string} [display='standalone'] - Display mode
40
- * @property {string} [themeColor='#000000'] - Theme color
41
- * @property {string} [backgroundColor='#ffffff'] - Background color
42
- * @property {string} [scope='./'] - App scope
43
- * @property {Array<{src: string, sizes: string, type: string}>} [icons] - App icons
44
- */
45
-
46
- /**
47
- * Generate Web App Manifest JSON.
48
- *
49
- * @param {ManifestOptions} options - Manifest options
50
- * @returns {Object} Web App Manifest object
51
- *
52
- * @example
53
- * const manifest = generateManifest({
54
- * name: 'My Awesome App',
55
- * shortName: 'MyApp',
56
- * description: 'A great app built with MetaOwl',
57
- * themeColor: '#007bff',
58
- * backgroundColor: '#ffffff',
59
- * icons: [
60
- * { src: '/icon-192.png', sizes: '192x192', type: 'image/png' },
61
- * { src: '/icon-512.png', sizes: '512x512', type: 'image/png' }
62
- * ]
63
- * })
64
- */
65
- export function generateManifest(options) {
66
- const {
67
- name,
68
- shortName,
69
- description,
70
- startUrl = './',
71
- display = 'standalone',
72
- themeColor = '#000000',
73
- backgroundColor = '#ffffff',
74
- scope = './',
75
- icons = []
76
- } = options
77
-
78
- const manifest = {
79
- name,
80
- short_name: shortName,
81
- start_url: startUrl,
82
- display,
83
- theme_color: themeColor,
84
- background_color: backgroundColor,
85
- scope
86
- }
87
-
88
- if (description) {
89
- manifest.description = description
90
- }
91
-
92
- if (icons.length > 0) {
93
- manifest.icons = icons
94
- }
95
-
96
- // Add default orientation
97
- manifest.orientation = 'any'
98
-
99
- return manifest
100
- }
101
-
102
- /**
103
- * Register the service worker.
104
- *
105
- * @param {string} path - Service Worker script path
106
- * @param {Object} options - Registration options
107
- * @param {Function} [options.onUpdate] - Called when update found
108
- * @param {Function} [options.onReady] - Called when SW is active
109
- * @returns {Promise<ServiceWorkerRegistration|null>}
110
- *
111
- * @example
112
- * await registerServiceWorker('/sw.js', {
113
- * onUpdate: (registration) => {
114
- * console.log('New version available!')
115
- * },
116
- * onReady: (registration) => {
117
- * console.log('App is ready for offline use')
118
- * }
119
- * })
120
- */
121
- export async function registerServiceWorker(path, options = {}) {
122
- const { onUpdate, onReady } = options
123
-
124
- if (!('serviceWorker' in navigator)) {
125
- console.warn('[PWA] Service workers not supported')
126
- return null
127
- }
128
-
129
- try {
130
- const registration = await navigator.serviceWorker.register(path)
131
-
132
- registration.addEventListener('updatefound', () => {
133
- const newWorker = registration.installing
134
-
135
- newWorker.addEventListener('statechange', () => {
136
- if (newWorker.state === 'installed' && navigator.serviceWorker.controller) {
137
- // New update available
138
- if (onUpdate) {
139
- onUpdate(registration)
140
- }
141
- } else if (newWorker.state === 'activated') {
142
- // Service worker is active
143
- if (onReady) {
144
- onReady(registration)
145
- }
146
- }
147
- })
148
- })
149
-
150
- // Check if already active
151
- if (registration.active) {
152
- if (onReady) {
153
- onReady(registration)
154
- }
155
- }
156
-
157
- return registration
158
- } catch (error) {
159
- console.error('[PWA] Service worker registration failed:', error)
160
- return null
161
- }
162
- }
163
-
164
- /**
165
- * Unregister the service worker.
166
- *
167
- * @returns {Promise<boolean>} True if unregistered successfully
168
- */
169
- export async function unregisterServiceWorker() {
170
- if (!('serviceWorker' in navigator)) {
171
- return false
172
- }
173
-
174
- try {
175
- const registration = await navigator.serviceWorker.ready
176
- await registration.unregister()
177
- return true
178
- } catch {
179
- return false
180
- }
181
- }
182
-
183
- /**
184
- * Check if app is running as installed PWA.
185
- *
186
- * @returns {boolean}
187
- */
188
- export function isStandalone() {
189
- // Check if matchMedia exists (browser environment)
190
- if (window.matchMedia) {
191
- return window.matchMedia('(display-mode: standalone)').matches ||
192
- window.navigator?.standalone === true
193
- }
194
- return false
195
- }
196
-
197
- /**
198
- * Check if online.
199
- *
200
- * @returns {boolean}
201
- */
202
- export function isOnline() {
203
- return navigator.onLine
204
- }
205
-
206
- /**
207
- * Subscribe to online/offline events.
208
- *
209
- * @param {Object} callbacks - Callback functions
210
- * @param {Function} callbacks.onOnline - Called when going online
211
- * @param {Function} callbacks.onOffline - Called when going offline
212
- * @returns {Function} Unsubscribe function
213
- *
214
- * @example
215
- * const unsubscribe = subscribeToConnectivity({
216
- * onOnline: () => console.log('Back online'),
217
- * onOffline: () => console.log('Gone offline')
218
- * })
219
- *
220
- * // Later: unsubscribe()
221
- */
222
- export function subscribeToConnectivity(callbacks) {
223
- const { onOnline, onOffline } = callbacks
224
-
225
- const handleOnline = () => {
226
- if (onOnline) onOnline()
227
- }
228
-
229
- const handleOffline = () => {
230
- if (onOffline) onOffline()
231
- }
232
-
233
- window.addEventListener('online', handleOnline)
234
- window.addEventListener('offline', handleOffline)
235
-
236
- return () => {
237
- window.removeEventListener('online', handleOnline)
238
- window.removeEventListener('offline', handleOffline)
239
- }
240
- }
241
-
242
- /**
243
- * Request persistent storage.
244
- *
245
- * @returns {Promise<boolean>} True if persistent storage granted
246
- */
247
- export async function requestPersistentStorage() {
248
- if (navigator.storage && navigator.storage.persist) {
249
- return await navigator.storage.persist()
250
- }
251
- return false
252
- }
253
-
254
- /**
255
- * Get storage usage information.
256
- *
257
- * @returns {Promise<{usage: number, quota: number}|null>}
258
- */
259
- export async function getStorageInfo() {
260
- if (navigator.storage && navigator.storage.estimate) {
261
- return await navigator.storage.estimate()
262
- }
263
- return null
264
- }
265
-
266
- /**
267
- * Trigger a background sync event.
268
- *
269
- * @param {string} tag - Sync tag name
270
- * @returns {Promise<boolean>} True if sync registered
271
- */
272
- export async function sync(tag) {
273
- if (!('serviceWorker' in navigator)) {
274
- return false
275
- }
276
-
277
- try {
278
- const registration = await navigator.serviceWorker.ready
279
-
280
- if ('sync' in registration) {
281
- await registration.sync.register(tag)
282
- return true
283
- }
284
- } catch {
285
- // Background sync not supported or failed
286
- }
287
-
288
- return false
289
- }
290
-
291
- /**
292
- * Request notification permission and subscribe.
293
- *
294
- * @param {Object} options - Push options
295
- * @param {string} options.serverUrl - Push server URL
296
- * @param {string} options.publicKey - VAPID public key
297
- * @returns {Promise<PushSubscription|null>}
298
- *
299
- * @example
300
- * const subscription = await subscribeToPush({
301
- * serverUrl: 'https://myserver.com/push',
302
- * publicKey: 'BLCxhd...'
303
- * })
304
- */
305
- export async function subscribeToPush(options) {
306
- const { serverUrl, publicKey } = options
307
-
308
- if (!('serviceWorker' in navigator) || !('PushManager' in window)) {
309
- console.warn('[PWA] Push notifications not supported')
310
- return null
311
- }
312
-
313
- try {
314
- // Check permission
315
- const permission = await Notification.requestPermission()
316
- if (permission !== 'granted') {
317
- return null
318
- }
319
-
320
- const registration = await navigator.serviceWorker.ready
321
-
322
- // Subscribe
323
- const subscription = await registration.pushManager.subscribe({
324
- userVisibleOnly: true,
325
- applicationServerKey: urlBase64ToUint8Array(publicKey)
326
- })
327
-
328
- // Send to server
329
- if (serverUrl) {
330
- await fetch(serverUrl, {
331
- method: 'POST',
332
- headers: { 'Content-Type': 'application/json' },
333
- body: JSON.stringify(subscription)
334
- })
335
- }
336
-
337
- return subscription
338
- } catch (error) {
339
- console.error('[PWA] Push subscription failed:', error)
340
- return null
341
- }
342
- }
343
-
344
- /**
345
- * Unsubscribe from push notifications.
346
- *
347
- * @returns {Promise<boolean>}
348
- */
349
- export async function unsubscribeFromPush() {
350
- if (!('serviceWorker' in navigator)) {
351
- return false
352
- }
353
-
354
- try {
355
- const registration = await navigator.serviceWorker.ready
356
- const subscription = await registration.pushManager.getSubscription()
357
-
358
- if (subscription) {
359
- await subscription.unsubscribe()
360
- return true
361
- }
362
- } catch {
363
- // Ignore errors
364
- }
365
-
366
- return false
367
- }
368
-
369
- /**
370
- * Show a notification.
371
- *
372
- * @param {string} title - Notification title
373
- * @param {Object} options - Notification options
374
- * @returns {Promise<void>}
375
- */
376
- export async function showNotification(title, options = {}) {
377
- if (!('serviceWorker' in navigator)) {
378
- return
379
- }
380
-
381
- try {
382
- const registration = await navigator.serviceWorker.ready
383
- await registration.showNotification(title, options)
384
- } catch (error) {
385
- console.error('[PWA] Show notification failed:', error)
386
- }
387
- }
388
-
389
- /**
390
- * Convert URL-safe base64 to Uint8Array.
391
- *
392
- * @param {string} base64String
393
- * @returns {Uint8Array}
394
- */
395
- function urlBase64ToUint8Array(base64String) {
396
- const padding = '='.repeat((4 - (base64String.length % 4)) % 4)
397
- const base64 = (base64String + padding)
398
- .replace(/\-/g, '+')
399
- .replace(/_/g, '/')
400
-
401
- const rawData = window.atob(base64)
402
- const outputArray = new Uint8Array(rawData.length)
403
-
404
- for (let i = 0; i < rawData.length; ++i) {
405
- outputArray[i] = rawData.charCodeAt(i)
406
- }
407
-
408
- return outputArray
409
- }
410
-
411
- /**
412
- * Cache management for PWA.
413
- */
414
- export const cache = {
415
- /**
416
- * Add resources to cache.
417
- *
418
- * @param {string} cacheName - Cache name
419
- * @param {string[]} urls - URLs to cache
420
- * @returns {Promise<void>}
421
- */
422
- async add(cacheName, urls) {
423
- if (!('caches' in window)) return
424
-
425
- const cache = await caches.open(cacheName)
426
- await cache.addAll(urls)
427
- },
428
-
429
- /**
430
- * Remove resources from cache.
431
- *
432
- * @param {string} cacheName - Cache name
433
- * @param {string[]} urls - URLs to remove
434
- * @returns {Promise<void>}
435
- */
436
- async remove(cacheName, urls) {
437
- if (!('caches' in window)) return
438
-
439
- const cache = await caches.open(cacheName)
440
- for (const url of urls) {
441
- await cache.delete(url)
442
- }
443
- },
444
-
445
- /**
446
- * Clear all caches.
447
- *
448
- * @returns {Promise<void>}
449
- */
450
- async clear() {
451
- if (!('caches' in window)) return
452
-
453
- const keys = await caches.keys()
454
- await Promise.all(keys.map(key => caches.delete(key)))
455
- },
456
-
457
- /**
458
- * Get cache info.
459
- *
460
- * @returns {Promise<Array<{name: string, size: number}>>}
461
- */
462
- async info() {
463
- if (!('caches' in window)) return []
464
-
465
- const keys = await caches.keys()
466
- const info = []
467
-
468
- for (const key of keys) {
469
- const cache = await caches.open(key)
470
- const keys = await cache.keys()
471
- info.push({ name: key, size: keys.length })
472
- }
473
-
474
- return info
475
- }
476
- }
477
-
478
- /**
479
- * Check PWA capabilities.
480
- *
481
- * @returns {Object} Capability info
482
- */
483
- export function checkCapabilities() {
484
- return {
485
- serviceWorker: 'serviceWorker' in navigator,
486
- push: 'PushManager' in window,
487
- notifications: 'Notification' in window,
488
- backgroundSync: false, // Would require ServiceWorkerRegistration check in real browser
489
- persistentStorage: !!(navigator.storage && navigator.storage.persist),
490
- addToHomeScreen: !isStandalone(),
491
- offline: 'serviceWorker' in navigator
492
- }
493
- }
494
-
495
- /**
496
- * PWA namespace for convenient access.
497
- */
498
- export const PWA = {
499
- generateManifest,
500
- registerServiceWorker,
501
- unregisterServiceWorker,
502
- isStandalone,
503
- isOnline,
504
- subscribeToConnectivity,
505
- requestPersistentStorage,
506
- getStorageInfo,
507
- sync,
508
- subscribeToPush,
509
- unsubscribeFromPush,
510
- showNotification,
511
- cache,
512
- checkCapabilities
513
- }
514
-
515
- export default PWA