metaowl 0.1.3 → 0.2.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/README.md CHANGED
@@ -1,13 +1,25 @@
1
1
  # metaowl
2
2
 
3
- > A lightweight meta-framework for [Odoo OWL](https://github.com/odoo/owl), built on top of [Vite](https://vitejs.dev).
3
+ > A comprehensive meta-framework for [Odoo OWL](https://github.com/odoo/owl), built on top of [Vite](https://vitejs.dev).
4
4
 
5
5
  [![npm version](https://img.shields.io/npm/v/metaowl.svg)](https://www.npmjs.com/package/metaowl)
6
6
  [![License: LGPL v3](https://img.shields.io/badge/License-LGPL_v3-blue.svg)](LICENSE)
7
7
  [![Node.js >=18](https://img.shields.io/badge/node-%3E%3D18-brightgreen.svg)](https://nodejs.org)
8
8
  [![GitHub Issues](https://img.shields.io/github/issues/dennisschott/metaowl.svg)](https://github.com/dennisschott/metaowl/issues)
9
9
 
10
- metaowl gives you everything you need to ship production-ready OWL applications file-based routing, app mounting, a fetch helper, `localStorage` cache, meta tag management, an SSG generator, and a batteries-included Vite plugin — so you can focus on building components instead of wiring infrastructure.
10
+ metaowl is a complete solution for building production-ready OWL applications with everything you need out of the box:
11
+
12
+ **Core Infrastructure:** File-based routing with dynamic routes, layout system, navigation guards, Pinia-inspired state management, and zero-config app mounting.
13
+
14
+ **Odoo Integration:** Full JSON-RPC client with authentication, session management, and CRUD operations.
15
+
16
+ **Developer Experience:** Composables for common patterns (auth, localStorage, fetching), form handling with validation, error boundaries, and internationalization.
17
+
18
+ **SEO & PWA:** Sitemap/robots.txt generation, structured data support, service worker integration, web app manifest, and push notifications.
19
+
20
+ **Testing & Quality:** Mock stores, router mocking, component testing utilities, plus bundled ESLint and PostCSS configs.
21
+
22
+ All powered by a batteries-included Vite plugin that handles the build pipeline, so you can focus on building components instead of wiring infrastructure.
11
23
 
12
24
  ---
13
25
 
@@ -19,6 +31,16 @@ metaowl gives you everything you need to ship production-ready OWL applications
19
31
  - [Create a New Project](#create-a-new-project)
20
32
  - [Manual Setup](#manual-setup)
21
33
  - [File-based Routing](#file-based-routing)
34
+ - [Dynamic Routes](#dynamic-routes)
35
+ - [Layouts](#layouts)
36
+ - [Navigation Guards](#navigation-guards)
37
+ - [State Management](#state-management-store)
38
+ - [Error Boundaries](#error-boundaries)
39
+ - [i18n / Internationalization](#i18n--internationalization)
40
+ - [Form Handling](#form-handling)
41
+ - [Auto-Import](#auto-import)
42
+ - [Odoo JSON-RPC Service](#odoo-json-rpc-service)
43
+ - [Composables / Hooks](#composables--hooks)
22
44
  - [CLI Reference](#cli-reference)
23
45
  - [API Reference](#api-reference)
24
46
  - [boot](#bootroutes)
@@ -27,6 +49,14 @@ metaowl gives you everything you need to ship production-ready OWL applications
27
49
  - [Meta](#meta)
28
50
  - [configureOwl](#configureowlconfig)
29
51
  - [buildRoutes](#buildroutesmodules)
52
+ - [Store](#store)
53
+ - [Layouts API](#layouts-api)
54
+ - [Router Guards](#router-guards-api)
55
+ - [Error Boundary](#error-boundary-api)
56
+ - [i18n](#i18n-api)
57
+ - [Forms](#forms-api)
58
+ - [OdooService](#odooservice-api)
59
+ - [Composables](#composables-api)
30
60
  - [Vite Plugin](#vite-plugin)
31
61
  - [metaowlPlugin](#metaowlpluginoptions)
32
62
  - [metaowlConfig](#metaowlconfigoptions)
@@ -41,10 +71,23 @@ metaowl gives you everything you need to ship production-ready OWL applications
41
71
  ## Features
42
72
 
43
73
  - **File-based routing** — mirrors Nuxt/Next.js conventions out of the box
74
+ - **Dynamic routes** — support for parameters `[id]`, optional params `[id]?`, and catch-all `[...path]`
75
+ - **Layouts** — share page structures across routes with automatic layout resolution
76
+ - **Navigation guards** — route middleware for authentication, authorization, and redirects
77
+ - **State management** — Pinia-like store system with mutations, actions, and getters
44
78
  - **App mounting** — zero-config OWL component mounting with template merging
45
79
  - **Fetch helper** — thin wrapper around the Fetch API with a configurable base URL and error handler
46
80
  - **Cache** — async-style `localStorage` wrapper (`get`, `set`, `remove`, `clear`, `keys`)
47
81
  - **Meta tags** — programmatic control over `<title>`, Open Graph, Twitter Card, canonical, and more
82
+ - **Error boundaries** — global error handling with context tracking and error pages
83
+ - **i18n** — internationalization with pluralization and interpolation support
84
+ - **Form handling** — schema validation with async support via `useForm()`
85
+ - **Auto-import** — automatic component registration with TypeScript declarations
86
+ - **Odoo RPC Service** — full JSON-RPC client with authentication and CRUD operations
87
+ - **Composables** — reusable hooks for auth, localStorage, fetching, and more
88
+ - **Testing Utilities** — mock store, router mocking, component mount helpers
89
+ - **SEO Utils** — sitemap, robots.txt, JSON-LD, Open Graph, Twitter Cards
90
+ - **PWA Support** — service worker, manifest generation, push notifications
48
91
  - **SSG generator** — statically pre-renders HTML pages with correct meta tags at build time
49
92
  - **Vite plugin** — handles `COMPONENTS` injection, XML template copying, CSS auto-import, chunk splitting, and env filtering
50
93
  - **ESLint & PostCSS** — shareable configs included; no extra dev-dependencies needed in your project
@@ -76,7 +119,7 @@ Install metaowl globally and run it interactively:
76
119
 
77
120
  ```bash
78
121
  npm install -g metaowl
79
- metaowl-create app_name
122
+ metaowl-create my-app
80
123
  ```
81
124
 
82
125
  This generates a ready-to-run project:
@@ -213,6 +256,470 @@ SSG path variants (`.html`, trailing slash, `index.html`) are added automaticall
213
256
 
214
257
  ---
215
258
 
259
+ ### Dynamic Routes
260
+
261
+ File-based routing supports dynamic segments using bracket notation. The router supports required parameters, optional parameters, and catch-all routes.
262
+
263
+ | File | URL Pattern | Example URL | Params |
264
+ |---|---|---|---|
265
+ | `pages/user/[id]/User.js` | `/user/:id` | `/user/123` | `{ id: '123' }` |
266
+ | `pages/product/[category]/[slug]/Product.js` | `/product/:category/:slug` | `/product/tech/hello` | `{ category: 'tech', slug: 'hello' }` |
267
+ | `pages/blog/[id]/[slug?]/Blog.js` | `/blog/:id/:slug?` | `/blog/123` or `/blog/123/my-post` | `{ id: '123' }` or `{ id: '123', slug: 'my-post' }` |
268
+ | `pages/docs/[...path]/Docs.js` | `/docs/:path(.*)` | `/docs/api/routing` | `{ path: 'api/routing' }` |
269
+ | `pages/[...404]/NotFound.js` | `/:path(.*)` | `/any/unknown/path` | `{ path: 'any/unknown/path' }` |
270
+
271
+ **Param Types:**
272
+
273
+ - `[param]` — Required parameter, must be present in URL
274
+ - `[param?]` — Optional parameter, may be omitted
275
+ - `[...param]` — Catch-all parameter, matches any number of segments
276
+
277
+ Access parameters in your component:
278
+
279
+ ```js
280
+ import { Component, xml } from '@odoo/owl'
281
+
282
+ export class UserPage extends Component {
283
+ static template = xml`
284
+ <div>
285
+ <h1>User Profile</h1>
286
+ <p>ID: <t t-esc="props.params.id"/></p>
287
+ </div>
288
+ `
289
+
290
+ static props = ['params']
291
+ }
292
+ ```
293
+
294
+ ---
295
+
296
+ ## Layouts
297
+
298
+ Layouts provide shared page structures. Create a `layouts/` directory alongside your `pages/`:
299
+
300
+ ```
301
+ src/
302
+ layouts/
303
+ default/
304
+ DefaultLayout.js
305
+ DefaultLayout.xml
306
+ admin/
307
+ AdminLayout.js
308
+ AdminLayout.xml
309
+ pages/
310
+ index/
311
+ Index.js → uses 'default' layout
312
+ admin/
313
+ dashboard/
314
+ Dashboard.js → can use 'admin' layout
315
+ ```
316
+
317
+ Use a layout by setting the static `layout` property:
318
+
319
+ ```js
320
+ export class DashboardPage extends Component {
321
+ static template = 'DashboardPage'
322
+ static layout = 'admin'
323
+ }
324
+ ```
325
+
326
+ If no layout is specified, the `default` layout is used automatically.
327
+
328
+ **Layout Template Convention:**
329
+
330
+ ```xml
331
+ <templates>
332
+ <t t-name="DefaultLayout">
333
+ <div class="layout-default">
334
+ <header>The Header</header>
335
+ <main>
336
+ <t t-slot="default"/>
337
+ </main>
338
+ <footer>The Footer</footer>
339
+ </div>
340
+ </t>
341
+ </templates>
342
+ ```
343
+
344
+ ---
345
+
346
+ ## Navigation Guards
347
+
348
+ Navigation guards intercept route navigation and can:
349
+ - Block access to routes
350
+ - Redirect to other routes
351
+ - Perform async checks (authentication, permissions)
352
+
353
+ ### Global Guards
354
+
355
+ ```js
356
+ import { beforeEach, afterEach } from 'metaowl'
357
+
358
+ // Run before navigation
359
+ beforeEach((to, from, next) => {
360
+ const auth = useAuthStore()
361
+
362
+ if (to.meta.requiresAuth && !auth.state.loggedIn) {
363
+ next('/login') // redirect
364
+ } else {
365
+ next() // proceed
366
+ }
367
+ })
368
+
369
+ // Run after navigation
370
+ afterEach((to, from) => {
371
+ console.log(`Navigated to ${to.path}`)
372
+ })
373
+ ```
374
+
375
+ ### Per-Route Guards
376
+
377
+ ```js
378
+ export class AdminPage extends Component {
379
+ static route = {
380
+ path: '/admin',
381
+ meta: { requiresAuth: true, role: 'admin' },
382
+ beforeEnter: (to, from, next) => {
383
+ // Check specific permissions
384
+ if (!hasAdminRole()) {
385
+ next('/unauthorized')
386
+ } else {
387
+ next()
388
+ }
389
+ }
390
+ }
391
+ }
392
+ ```
393
+
394
+ **Guard Behavior:**
395
+
396
+ - `next()` — proceed to next guard
397
+ - `next(false)` — abort navigation
398
+ - `next('/path')` — redirect to path
399
+ - `next(error)` — abort with error
400
+
401
+ ---
402
+
403
+ ## State Management (Store)
404
+
405
+ A Pinia-inspired store system with mutations, actions, and getters.
406
+
407
+ ```js
408
+ import { Store } from 'metaowl'
409
+
410
+ const useUserStore = Store.define('user', {
411
+ state: () => ({
412
+ name: '',
413
+ loggedIn: false
414
+ }),
415
+
416
+ getters: {
417
+ displayName: (state) => state.name || 'Guest'
418
+ },
419
+
420
+ mutations: {
421
+ setName: (state, name) => { state.name = name },
422
+ setLoggedIn: (state, value) => { state.loggedIn = value }
423
+ },
424
+
425
+ actions: {
426
+ async login({ commit }, credentials) {
427
+ const result = await Fetch.url('/api/login', 'POST', credentials)
428
+ commit('setName', result.name)
429
+ commit('setLoggedIn', true)
430
+ return result
431
+ },
432
+
433
+ logout({ commit }) {
434
+ commit('setName', '')
435
+ commit('setLoggedIn', false)
436
+ }
437
+ }
438
+ })
439
+ ```
440
+
441
+ **In a component:**
442
+
443
+ ```js
444
+ const store = useUserStore()
445
+
446
+ store.commit('setName', 'John') // synchronous mutation
447
+ await store.dispatch('login', { email, password }) // async action
448
+ console.log(store.getters.displayName.value) // computed getter
449
+ ```
450
+
451
+ **Persistence:**
452
+
453
+ ```js
454
+ import { Store, createPersistencePlugin } from 'metaowl'
455
+
456
+ // Automatically persist state to localStorage
457
+ Store.use(createPersistencePlugin({
458
+ storage: localStorage,
459
+ paths: ['user', 'preferences'] // only persist specific paths
460
+ }))
461
+ ```
462
+
463
+ ---
464
+
465
+ ## Error Boundaries
466
+
467
+ Handle runtime errors gracefully with automatic fallback UI:
468
+
469
+ ```js
470
+ import { ErrorBoundary } from 'metaowl'
471
+
472
+ // Wrap your app
473
+ const errorBoundary = ErrorBoundary.wrap(AppComponent)
474
+ errorBoundary.mount(document.body)
475
+
476
+ // Global error handler
477
+ ErrorBoundary.onError((error, context) => {
478
+ console.error('App error:', error, context)
479
+ // Send to error tracking service
480
+ analytics.track('error', { message: error.message, path: context?.route })
481
+ })
482
+ ```
483
+
484
+ **Error Pages:**
485
+
486
+ ```js
487
+ // src/pages/error.js
488
+ export default class ErrorPage extends Component {
489
+ static template = xml`
490
+ <div class="error-page">
491
+ <h1>Error <t t-esc="props.code || 500"/></h1>
492
+ <p t-esc="props.message"/>
493
+ <button t-on-click="goHome">Go Home</button>
494
+ </div>
495
+ `
496
+ }
497
+ ```
498
+
499
+ ---
500
+
501
+ ## i18n / Internationalization
502
+
503
+ Full-featured translation system with pluralization:
504
+
505
+ ```js
506
+ import { I18n } from 'metaowl'
507
+
508
+ await I18n.load({
509
+ locale: 'de',
510
+ messages: {
511
+ welcome: 'Willkommen, {name}!',
512
+ items: '{count, plural, one {# Item} other {# Items}}'
513
+ }
514
+ })
515
+ ```
516
+
517
+ **In templates:**
518
+
519
+ ```xml
520
+ <div t-esc="I18n.t('welcome', { name: state.username })"/>
521
+ <span t-esc="I18n.t('items', { count: state.cartItems })"/>
522
+ ```
523
+
524
+ **Pluralization:**
525
+
526
+ ```js
527
+ I18n.t('items', { count: 1 }) // "1 Item"
528
+ I18n.t('items', { count: 5 }) // "5 Items"
529
+ ```
530
+
531
+ ---
532
+
533
+ ## Form Handling
534
+
535
+ Declarative forms with validation support:
536
+
537
+ ```js
538
+ import { useForm } from 'metaowl'
539
+
540
+ class LoginPage extends Component {
541
+ setup() {
542
+ this.form = useForm({
543
+ schema: {
544
+ email: { required: true, type: 'email' },
545
+ password: { required: true, minLength: 8 }
546
+ },
547
+ onSubmit: async (values) => {
548
+ await Fetch.post('/api/login', values)
549
+ this.env.router.navigate('/dashboard')
550
+ }
551
+ })
552
+ }
553
+ }
554
+ ```
555
+
556
+ ```xml
557
+ <form t-on-submit.prevent="form.submit">
558
+ <input t-model="form.values.email" />
559
+ <span t-if="form.errors.email" t-esc="form.errors.email"/>
560
+
561
+ <input type="password" t-model="form.values.password" />
562
+ <span t-if="form.errors.password" t-esc="form.errors.password"/>
563
+
564
+ <button type="submit" t-att-disabled="form.isSubmitting">
565
+ <t t-if="form.isSubmitting">Loading...</t>
566
+ <t t-else="">Login</t>
567
+ </button>
568
+ </form>
569
+ ```
570
+
571
+ ---
572
+
573
+ ## Auto-Import
574
+
575
+ Optional automatic component registration for development productivity:
576
+
577
+ **Enable in vite.config.js:**
578
+
579
+ ```js
580
+ import { metaowlConfig } from 'metaowl/vite'
581
+
582
+ export default metaowlConfig({
583
+ autoImport: {
584
+ enabled: true,
585
+ pattern: '**/*.js'
586
+ }
587
+ })
588
+ ```
589
+
590
+ **How it works:**
591
+
592
+ 1. Components in `src/components/` are auto-scanned
593
+ 2. Type declarations are generated in `.metaowl/components.d.ts`
594
+ 3. Use components without manual imports:
595
+
596
+ ```js
597
+ // No import needed!
598
+ export default class MyPage extends Component {
599
+ static template = xml`
600
+ <div>
601
+ <Button color="primary"/>
602
+ <Card>
603
+ <Modal t-if="state.showModal"/>
604
+ </Card>
605
+ </div>
606
+ `
607
+ // Components are automatically available
608
+ // from src/components/Button/Button.js, etc.
609
+ }
610
+ ```
611
+
612
+ **Note:** Auto-import is opt-in and primarily useful during development. For production, consider explicit imports for better tree-shaking and clarity.
613
+
614
+ ---
615
+
616
+ ## Odoo JSON-RPC Service
617
+
618
+ Connect to Odoo backends with a full-featured JSON-RPC client:
619
+
620
+ ```js
621
+ import { OdooService } from 'metaowl'
622
+
623
+ // Configure connection
624
+ OdooService.configure({
625
+ baseUrl: 'https://my-odoo-instance.com',
626
+ database: 'my_database',
627
+ username: 'admin',
628
+ password: 'admin' // or apiKey
629
+ })
630
+
631
+ // Authenticate
632
+ const session = await OdooService.authenticate()
633
+ console.log(`Logged in as ${session.name}`)
634
+
635
+ // Search and read records
636
+ const partners = await OdooService.searchRead('res.partner', {
637
+ domain: [['is_company', '=', true]],
638
+ fields: ['name', 'email', 'phone'],
639
+ limit: 10
640
+ })
641
+
642
+ // Call any model method
643
+ await OdooService.call('res.partner', 'create', [{
644
+ name: 'New Partner',
645
+ email: 'partner@example.com'
646
+ }])
647
+ ```
648
+
649
+ **CRUD Operations:**
650
+
651
+ ```js
652
+ // Create
653
+ const id = await OdooService.create('res.partner', { name: 'Test' })
654
+
655
+ // Read
656
+ const records = await OdooService.read('res.partner', [id], ['name'])
657
+
658
+ // Update
659
+ await OdooService.write('res.partner', [id], { name: 'Updated' })
660
+
661
+ // Delete
662
+ await OdooService.unlink('res.partner', [id])
663
+ ```
664
+
665
+ **Session Management:**
666
+
667
+ ```js
668
+ // Check authentication
669
+ if (OdooService.isAuthenticated()) {
670
+ console.log('User:', OdooService.getSession().name)
671
+ }
672
+
673
+ // Logout
674
+ OdooService.logout()
675
+
676
+ // Listen to auth changes
677
+ OdooService.onAuthChange((session) => {
678
+ console.log(session ? 'Logged in' : 'Logged out')
679
+ })
680
+ ```
681
+
682
+ ---
683
+
684
+ ## Composables / Hooks
685
+
686
+ Reusable OWL hooks for common patterns:
687
+
688
+ ```js
689
+ import { useAuth, useLocalStorage, useFetch } from 'metaowl'
690
+
691
+ class MyComponent extends Component {
692
+ setup() {
693
+ // Authentication state
694
+ const { user, isLoggedIn, logout } = useAuth()
695
+
696
+ // Persisted state
697
+ const theme = useLocalStorage('theme', 'light')
698
+
699
+ // Data fetching
700
+ const { data, loading, error, refresh } = useFetch('/api/users')
701
+
702
+ return { user, theme, data, loading, error, refresh }
703
+ }
704
+ }
705
+ ```
706
+
707
+ **Available Composables:**
708
+
709
+ | Composable | Description |
710
+ |---|---|
711
+ | `useAuth()` | Authentication state linked to OdooService |
712
+ | `useLocalStorage(key, default)` | Reactive localStorage access |
713
+ | `useFetch(url, options)` | Data fetching with loading/error states |
714
+ | `useDebounce(value, wait)` | Debounced reactive value |
715
+ | `useThrottle(fn, wait)` | Throttled function |
716
+ | `useWindowSize()` | Reactive window dimensions |
717
+ | `useOnlineStatus()` | Network connectivity state |
718
+ | `useAsyncState(fn)` | Async operation state management |
719
+ | `useCache(key, default)` | Reactive cache access |
720
+
721
+ ---
722
+
216
723
  ## CLI Reference
217
724
 
218
725
  metaowl ships four CLI commands that use its own bundled Vite, Prettier, and ESLint binaries — no need to install them separately in your project.
@@ -386,6 +893,340 @@ buildRoutes(modules: Record<string, object>): RouteDefinition[]
386
893
 
387
894
  ---
388
895
 
896
+ ### `Store`
897
+
898
+ Pinia-inspired state management system with mutations, actions, and getters.
899
+
900
+ #### `Store.define(id, config)`
901
+
902
+ Creates a store factory function.
903
+
904
+ ```ts
905
+ const useStore = Store.define('storeId', {
906
+ state: () => ({ count: 0 }),
907
+ getters: { double: (state) => state.count * 2 },
908
+ mutations: { increment: (state) => state.count++ },
909
+ actions: { async fetchData({ commit }) { ... } }
910
+ })
911
+ ```
912
+
913
+ #### Store Instance Methods
914
+
915
+ | Method | Description |
916
+ |---|---|
917
+ | `commit(mutation, payload)` | Execute synchronous mutation |
918
+ | `dispatch(action, payload)` | Execute async action |
919
+ | `subscribe(callback)` | Listen to mutations `(mutation, state, prevState) => void` |
920
+ | `subscribeAction(callback)` | Listen to actions `(action, status, result) => void` |
921
+ | `reset()` | Reset state to initial values |
922
+
923
+ #### `Store.use(plugin)`
924
+
925
+ Register a global plugin applied to all stores.
926
+
927
+ ```ts
928
+ import { Store, createPersistencePlugin } from 'metaowl'
929
+
930
+ Store.use(createPersistencePlugin({ storage: localStorage }))
931
+ ```
932
+
933
+ ---
934
+
935
+ ### `Layouts API`
936
+
937
+ Functions for layout management.
938
+
939
+ | Function | Description |
940
+ |---|---|
941
+ | `registerLayout(name, Component)` | Register a layout |
942
+ | `getLayout(name)` | Get layout component by name |
943
+ | `setDefaultLayout(name)` | Set default layout |
944
+ | `resolveLayout(Component, path?)` | Resolve layout for component |
945
+ | `subscribeToLayouts(callback)` | Listen to layout events |
946
+
947
+ **Component Layout Property:**
948
+
949
+ ```js
950
+ export class MyPage extends Component {
951
+ static layout = 'admin' // Use 'admin' layout
952
+ }
953
+ ```
954
+
955
+ ---
956
+
957
+ ### `Router Guards API`
958
+
959
+ Functions for navigation control.
960
+
961
+ | Function | Description |
962
+ |---|---|
963
+ | `beforeEach(guard)` | Register global guard (returns unsubscribe) |
964
+ | `afterEach(hook)` | Register global after hook (returns unsubscribe) |
965
+ | `getCurrentRoute()` | Get current route object |
966
+ | `getPreviousRoute()` | Get previous route object |
967
+ | `push(path)` | Navigate to path |
968
+ | `replace(path)` | Replace current history entry |
969
+ | `back()` / `forward()` / `go(n)` | History navigation |
970
+
971
+ **Router Singleton:**
972
+
973
+ ```js
974
+ import { router } from 'metaowl'
975
+
976
+ router.beforeEach((to, from, next) => { ... })
977
+ router.push('/new-path')
978
+ ```
979
+
980
+ ---
981
+
982
+ ### `Error Boundary API`
983
+
984
+ | Function | Description |
985
+ |---|---|
986
+ | `ErrorBoundary.wrap(Component)` | Wrap component with error handling |
987
+ | `ErrorBoundary.onError(callback)` | Register global error handler `(error, context) => void` |
988
+ | `ErrorBoundary.getLastError()` | Get most recent error |
989
+ | `ErrorBoundary.clearError()` | Clear error state |
990
+
991
+ **Error Context:**
992
+
993
+ ```ts
994
+ {
995
+ route?: string,
996
+ component?: string,
997
+ timestamp: number
998
+ }
999
+ ```
1000
+
1001
+ ---
1002
+
1003
+ ### `i18n API`
1004
+
1005
+ **`I18n.load(config)`**
1006
+
1007
+ ```ts
1008
+ I18n.load({
1009
+ locale: string,
1010
+ messages: Record<string, string | MessageFunction>,
1011
+ numberFormats?: Record<string, object>,
1012
+ dateFormats?: Record<string, object>
1013
+ })
1014
+ ```
1015
+
1016
+ **`I18n.t(key, values?)`**
1017
+
1018
+ Translate a message with optional interpolation:
1019
+
1020
+ ```ts
1021
+ I18n.t('welcome', { name: 'John' }) // "Welcome, John!"
1022
+ ```
1023
+
1024
+ **`I18n.n(value, format?)`** / **`I18n.d(value, format?)`**
1025
+
1026
+ Format numbers and dates:
1027
+
1028
+ ```ts
1029
+ I18n.n(1234.5, 'currency') // "€1,234.50"
1030
+ I18n.d(new Date(), 'short') // "12.03.2026"
1031
+ ```
1032
+
1033
+ **Locale Switching:**
1034
+
1035
+ ```js
1036
+ await I18n.setLocale('en')
1037
+ console.log(I18n.locale) // "en"
1038
+ ```
1039
+
1040
+ ---
1041
+
1042
+ ### `Forms API`
1043
+
1044
+ **`useForm(options)`**
1045
+
1046
+ ```ts
1047
+ useForm({
1048
+ schema?: ValidationSchema,
1049
+ initialValues?: Record<string, any>,
1050
+ onSubmit?: (values: Record<string, any>) => Promise<void>,
1051
+ validateOnChange?: boolean,
1052
+ validateOnBlur?: boolean
1053
+ }): FormInstance
1054
+ ```
1055
+
1056
+ **Form Instance:**
1057
+
1058
+ | Property | Type | Description |
1059
+ |---|---|---|
1060
+ | `values` | `object` | Current form values |
1061
+ | `errors` | `object` | Validation errors by field |
1062
+ | `touched` | `object` | Fields that have been touched |
1063
+ | `isSubmitting` | `boolean` | Submit in progress |
1064
+ | `isValid` | `boolean` | All validation passed |
1065
+ | `isDirty` | `boolean` | Values differ from initial |
1066
+
1067
+ | Method | Description |
1068
+ |---|---|
1069
+ | `submit(event?)` | Trigger form submission |
1070
+ | `setValue(field, value)` | Set a field value |
1071
+ | `setValues(values)` | Set multiple values |
1072
+ | `setError(field, message)` | Set a field error |
1073
+ | `clearErrors()` | Clear all errors |
1074
+ | `reset()` | Reset to initial values |
1075
+ | `validate()` | Trigger validation |
1076
+
1077
+ **Validation Schema:**
1078
+
1079
+ ```js
1080
+ {
1081
+ email: {
1082
+ required: true,
1083
+ type: 'email'
1084
+ },
1085
+ age: {
1086
+ type: 'number',
1087
+ min: 0,
1088
+ max: 120
1089
+ }
1090
+ }
1091
+ ```
1092
+
1093
+ ---
1094
+
1095
+ ### `OdooService API`
1096
+
1097
+ | Method | Description |
1098
+ |---|---|
1099
+ | `configure(config)` | Configure Odoo connection (baseUrl, database, credentials) |
1100
+ | `authenticate()` | Login and get session |
1101
+ | `logout()` | Clear session |
1102
+ | `isAuthenticated()` | Check if currently logged in |
1103
+ | `getSession()` | Get current session info |
1104
+ | `onAuthChange(callback)` | Subscribe to auth state changes (returns unsubscribe) |
1105
+
1106
+ **CRUD Operations:**
1107
+
1108
+ | Method | Description |
1109
+ |---|---|
1110
+ | `searchRead(model, options)` | Search and read records |
1111
+ | `call(model, method, args, kwargs)` | Call any model method |
1112
+ | `read(model, ids, fields)` | Read specific records |
1113
+ | `create(model, values)` | Create new record |
1114
+ | `write(model, ids, values)` | Update records |
1115
+ | `unlink(model, ids)` | Delete records |
1116
+ | `searchCount(model, domain)` | Get count of matching records |
1117
+
1118
+ **Utility Methods:**
1119
+
1120
+ | Method | Description |
1121
+ |---|---|
1122
+ | `listDatabases()` | Get available databases |
1123
+ | `versionInfo()` | Get Odoo server version info |
1124
+
1125
+ **Configuration Options:**
1126
+
1127
+ ```ts
1128
+ {
1129
+ baseUrl: string, // Odoo instance URL
1130
+ database: string, // Database name
1131
+ username?: string, // Username (or use in authenticate())
1132
+ password?: string, // Password (or apiKey)
1133
+ apiKey?: string, // API Key alternative to password
1134
+ persistSession?: boolean // Persist to localStorage (default: true)
1135
+ }
1136
+ ```
1137
+
1138
+ ---
1139
+
1140
+ ### `Composables API`
1141
+
1142
+ **`useAuth()`**
1143
+
1144
+ Authentication state for Odoo integration.
1145
+
1146
+ ```ts
1147
+ {
1148
+ user: Ref<Session|null>, // Current user info
1149
+ isLoggedIn: Ref<boolean>, // Auth status
1150
+ isLoading: Ref<boolean>, // Loading state
1151
+ login: (credentials) => Promise<boolean>,
1152
+ logout: () => Promise<void>,
1153
+ checkAuth: () => Promise<boolean>
1154
+ }
1155
+ ```
1156
+
1157
+ **`useLocalStorage(key, defaultValue)`**
1158
+
1159
+ Reactive localStorage access with cross-tab sync.
1160
+
1161
+ ```ts
1162
+ const theme = useLocalStorage('theme', 'light')
1163
+
1164
+ theme.value = 'dark' // Automatically saves to localStorage
1165
+ // Other tabs are notified via storage event
1166
+ ```
1167
+
1168
+ **`useFetch(url, options)`**
1169
+
1170
+ Data fetching with reactive states.
1171
+
1172
+ ```ts
1173
+ {
1174
+ data: Ref<any>, // Fetched data
1175
+ loading: Ref<boolean>,
1176
+ error: Ref<Error|null>,
1177
+ refresh: () => Promise<void>,
1178
+ execute: (url?) => Promise<void>
1179
+ }
1180
+ ```
1181
+
1182
+ Options: `initialData`, `immediate`, `transform`, `onError`
1183
+
1184
+ **`useDebounce(value, wait)`**
1185
+
1186
+ ```ts
1187
+ const searchQuery = useState('')
1188
+ const debounced = useDebounce(searchQuery, 500)
1189
+ // debounced updates 500ms after searchQuery stops changing
1190
+ ```
1191
+
1192
+ **`useThrottle(fn, wait)`**
1193
+
1194
+ ```ts
1195
+ const throttledSearch = useThrottle((query) => {
1196
+ performSearch(query)
1197
+ }, 500)
1198
+ ```
1199
+
1200
+ **`useWindowSize()`**
1201
+
1202
+ ```ts
1203
+ const { width, height } = useWindowSize()
1204
+ const isMobile = computed(() => width.value < 768)
1205
+ ```
1206
+
1207
+ **`useOnlineStatus()`**
1208
+
1209
+ ```ts
1210
+ const isOnline = useOnlineStatus()
1211
+ // Reactive to network state changes
1212
+ ```
1213
+
1214
+ **`useAsyncState(asyncFn, options)`**
1215
+
1216
+ ```ts
1217
+ const { state, data, execute, isLoading, isSuccess, isError } =
1218
+ useAsyncState(fetchUserData, { immediate: true })
1219
+ // state: null | 'loading' | 'success' | 'error'
1220
+ ```
1221
+
1222
+ **`useCache(key, defaultValue)`**
1223
+
1224
+ ```ts
1225
+ const { value, set, get, remove, clear } = useCache('user-prefs', {})
1226
+ ```
1227
+
1228
+ ---
1229
+
389
1230
  ## Vite Plugin
390
1231
 
391
1232
  ### `metaowlPlugin(options)`
@@ -403,6 +1244,14 @@ Returns an array of Vite plugins that configure the full metaowl build pipeline.
403
1244
  | `frameworkEntry` | `'./node_modules/metaowl/index.js'` | Entry for the `framework` chunk |
404
1245
  | `restartGlobs` | `[]` | Additional globs that trigger dev-server restart |
405
1246
  | `envPrefix` | `undefined` | Only expose `process.env` vars with this prefix (plus `NODE_ENV`) |
1247
+ | `autoImport` | `{ enabled: false }` | Auto-import configuration: `{ enabled, pattern }` |
1248
+
1249
+ **Auto-Import Options:**
1250
+
1251
+ | Option | Default | Description |
1252
+ |---|---|---|
1253
+ | `enabled` | `false` | Enable component auto-import |
1254
+ | `pattern` | `'*.js'` | Glob pattern for scanning components |
406
1255
 
407
1256
  **What the plugin does:**
408
1257