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/meta.js
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module Meta
|
|
3
|
-
*
|
|
4
|
-
* Programmatic helpers for managing document meta tags at runtime.
|
|
5
|
-
*
|
|
6
|
-
* Each function is idempotent: the relevant `<meta>` or `<link>` element is
|
|
7
|
-
* created on first call if it does not already exist, then its content is
|
|
8
|
-
* updated on every subsequent call as well.
|
|
9
|
-
*
|
|
10
|
-
* Import the entire namespace via:
|
|
11
|
-
* import { Meta } from 'metaowl'
|
|
12
|
-
* Meta.title('My Page')
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
function _nameMeta(name, value) {
|
|
16
|
-
if (!value) return
|
|
17
|
-
let el = document.querySelector(`meta[name="${name}"]`)
|
|
18
|
-
if (!el) {
|
|
19
|
-
el = document.createElement('meta')
|
|
20
|
-
el.name = name
|
|
21
|
-
document.head.appendChild(el)
|
|
22
|
-
}
|
|
23
|
-
el.content = value
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function _propMeta(property, value) {
|
|
27
|
-
if (!value) return
|
|
28
|
-
let el = document.querySelector(`meta[property="${property}"]`)
|
|
29
|
-
if (!el) {
|
|
30
|
-
el = document.createElement('meta')
|
|
31
|
-
el.setAttribute('property', property)
|
|
32
|
-
document.head.appendChild(el)
|
|
33
|
-
}
|
|
34
|
-
el.content = value
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** Set the document `<title>`. @param {string} title */
|
|
38
|
-
export function title(title) {
|
|
39
|
-
if (!title) return
|
|
40
|
-
document.title = title
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/** @param {string} description */
|
|
44
|
-
export function description(description) { _nameMeta('description', description) }
|
|
45
|
-
|
|
46
|
-
/** @param {string} keywords */
|
|
47
|
-
export function keywords(keywords) { _nameMeta('keywords', keywords) }
|
|
48
|
-
|
|
49
|
-
/** @param {string} author */
|
|
50
|
-
export function author(author) { _nameMeta('author', author) }
|
|
51
|
-
|
|
52
|
-
/** @param {string} url */
|
|
53
|
-
export function canonical(url) {
|
|
54
|
-
if (!url) return
|
|
55
|
-
let el = document.querySelector('link[rel="canonical"]')
|
|
56
|
-
if (!el) {
|
|
57
|
-
el = document.createElement('link')
|
|
58
|
-
el.rel = 'canonical'
|
|
59
|
-
document.head.appendChild(el)
|
|
60
|
-
}
|
|
61
|
-
el.href = url
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/** @param {string} title */
|
|
65
|
-
export function ogTitle(title) { _propMeta('og:title', title) }
|
|
66
|
-
|
|
67
|
-
/** @param {string} description */
|
|
68
|
-
export function ogDescription(description) { _propMeta('og:description', description) }
|
|
69
|
-
|
|
70
|
-
/** @param {string} image */
|
|
71
|
-
export function ogImage(image) { _propMeta('og:image', image) }
|
|
72
|
-
|
|
73
|
-
/** @param {string} url */
|
|
74
|
-
export function ogUrl(url) { _propMeta('og:url', url) }
|
|
75
|
-
|
|
76
|
-
/** @param {string} type */
|
|
77
|
-
export function ogType(type) { _propMeta('og:type', type) }
|
|
78
|
-
|
|
79
|
-
/** @param {string} siteName */
|
|
80
|
-
export function ogSiteName(siteName) { _propMeta('og:site_name', siteName) }
|
|
81
|
-
|
|
82
|
-
/** @param {string} locale */
|
|
83
|
-
export function ogLocale(locale) { _propMeta('og:locale', locale) }
|
|
84
|
-
|
|
85
|
-
/** @param {string|number} width */
|
|
86
|
-
export function ogImageWidth(width) { _propMeta('og:image:width', width) }
|
|
87
|
-
|
|
88
|
-
/** @param {string|number} height */
|
|
89
|
-
export function ogImageHeight(height) { _propMeta('og:image:height', height) }
|
|
90
|
-
|
|
91
|
-
/** @param {string} card */
|
|
92
|
-
export function twitterCard(card) { _nameMeta('twitter:card', card) }
|
|
93
|
-
|
|
94
|
-
/** @param {string} site */
|
|
95
|
-
export function twitterSite(site) { _nameMeta('twitter:site', site) }
|
|
96
|
-
|
|
97
|
-
/** @param {string} creator */
|
|
98
|
-
export function twitterCreator(creator) { _nameMeta('twitter:creator', creator) }
|
|
99
|
-
|
|
100
|
-
/** @param {string} title */
|
|
101
|
-
export function twitterTitle(title) { _nameMeta('twitter:title', title) }
|
|
102
|
-
|
|
103
|
-
/** @param {string} description */
|
|
104
|
-
export function twitterDescription(description) { _nameMeta('twitter:description', description) }
|
|
105
|
-
|
|
106
|
-
/** @param {string} image */
|
|
107
|
-
export function twitterImage(image) { _nameMeta('twitter:image', image) }
|
|
108
|
-
|
|
109
|
-
/** @param {string} alt */
|
|
110
|
-
export function twitterImageAlt(alt) { _nameMeta('twitter:image:alt', alt) }
|
|
111
|
-
|
|
112
|
-
/** @param {string} url */
|
|
113
|
-
export function twitterUrl(url) { _nameMeta('twitter:url', url) }
|
|
114
|
-
|
|
115
|
-
/** @param {string} siteId */
|
|
116
|
-
export function twitterSiteId(siteId) { _nameMeta('twitter:site:id', siteId) }
|
|
117
|
-
|
|
118
|
-
/** @param {string} creatorId */
|
|
119
|
-
export function twitterCreatorId(creatorId) { _nameMeta('twitter:creator:id', creatorId) }
|
package/modules/odoo-rpc.js
DELETED
|
@@ -1,511 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module OdooRPC
|
|
3
|
-
*
|
|
4
|
-
* Odoo JSON-RPC Service for MetaOwl applications.
|
|
5
|
-
*
|
|
6
|
-
* Features:
|
|
7
|
-
* - JSON-RPC 2.0 compliant communication
|
|
8
|
-
* - Authentication handling
|
|
9
|
-
* - Automatic CSRF token management
|
|
10
|
-
* - Session persistence
|
|
11
|
-
* - Common Odoo operations (search_read, call, etc.)
|
|
12
|
-
*
|
|
13
|
-
* Usage:
|
|
14
|
-
* import { OdooService } from 'metaowl'
|
|
15
|
-
*
|
|
16
|
-
* // Configure connection
|
|
17
|
-
* OdooService.configure({
|
|
18
|
-
* baseUrl: 'https://my-odoo-instance.com',
|
|
19
|
-
* database: 'my_database',
|
|
20
|
-
* username: 'admin',
|
|
21
|
-
* password: 'admin'
|
|
22
|
-
* })
|
|
23
|
-
*
|
|
24
|
-
* // Authenticate
|
|
25
|
-
* await OdooService.authenticate()
|
|
26
|
-
*
|
|
27
|
-
* // Search and read records
|
|
28
|
-
* const partners = await OdooService.searchRead('res.partner', {
|
|
29
|
-
* domain: [['is_company', '=', true]],
|
|
30
|
-
* fields: ['name', 'email'],
|
|
31
|
-
* limit: 10
|
|
32
|
-
* })
|
|
33
|
-
*
|
|
34
|
-
* // Call any model method
|
|
35
|
-
* const result = await OdooService.call('res.partner', 'create', [{
|
|
36
|
-
* name: 'New Partner',
|
|
37
|
-
* email: 'partner@example.com'
|
|
38
|
-
* }])
|
|
39
|
-
*/
|
|
40
|
-
|
|
41
|
-
import Fetch from './fetch.js'
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* @typedef {Object} OdooConfig
|
|
45
|
-
* @property {string} baseUrl - Odoo instance URL
|
|
46
|
-
* @property {string} database - Database name
|
|
47
|
-
* @property {string} [username] - Username for authentication
|
|
48
|
-
* @property {string} [password] - Password for authentication
|
|
49
|
-
* @property {string} [apiKey] - API key for authentication (alternative to password)
|
|
50
|
-
* @property {boolean} [persistSession=true] - Persist session in localStorage
|
|
51
|
-
*/
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* @typedef {Object} SearchReadOptions
|
|
55
|
-
* @property {Array[]} [domain=[]] - Search domain
|
|
56
|
-
* @property {string[]} [fields=[]] - Fields to read
|
|
57
|
-
* @property {number} [limit=80] - Max records
|
|
58
|
-
* @property {number} [offset=0] - Offset for pagination
|
|
59
|
-
* @property {string} [order] - Order by clause
|
|
60
|
-
* @property {Object} [context={}] - Odoo context
|
|
61
|
-
*/
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* @typedef {Object} OdooSession
|
|
65
|
-
* @property {number} uid - User ID
|
|
66
|
-
* @property {string} username - Username
|
|
67
|
-
* @property {string} [name] - Display name
|
|
68
|
-
* @property {number} [partner_id] - Partner ID
|
|
69
|
-
* @property {string[]} [user_context] - User context
|
|
70
|
-
*/
|
|
71
|
-
|
|
72
|
-
/** @type {OdooConfig|null} */
|
|
73
|
-
let _config = null
|
|
74
|
-
|
|
75
|
-
/** @type {OdooSession|null} */
|
|
76
|
-
let _session = null
|
|
77
|
-
|
|
78
|
-
/** @type {string|null} */
|
|
79
|
-
let _csrfToken = null
|
|
80
|
-
|
|
81
|
-
/** @type {Function[]} */
|
|
82
|
-
const _authListeners = []
|
|
83
|
-
|
|
84
|
-
const SESSION_KEY = 'metaowl:odoo:session'
|
|
85
|
-
const CSRF_KEY = 'metaowl:odoo:csrf'
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Configure the Odoo RPC service.
|
|
89
|
-
*
|
|
90
|
-
* @param {OdooConfig} config
|
|
91
|
-
*/
|
|
92
|
-
export function configure(config) {
|
|
93
|
-
_config = {
|
|
94
|
-
persistSession: true,
|
|
95
|
-
...config
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// Try to restore session from localStorage
|
|
99
|
-
if (_config.persistSession) {
|
|
100
|
-
restoreSession()
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Get current configuration.
|
|
106
|
-
*
|
|
107
|
-
* @returns {OdooConfig|null}
|
|
108
|
-
*/
|
|
109
|
-
export function getConfig() {
|
|
110
|
-
return _config
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Check if service is configured.
|
|
115
|
-
*
|
|
116
|
-
* @returns {boolean}
|
|
117
|
-
*/
|
|
118
|
-
export function isConfigured() {
|
|
119
|
-
return _config !== null && !!_config.baseUrl && !!_config.database
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Restore session from localStorage.
|
|
124
|
-
*/
|
|
125
|
-
function restoreSession() {
|
|
126
|
-
try {
|
|
127
|
-
const sessionData = localStorage.getItem(SESSION_KEY)
|
|
128
|
-
const csrfData = localStorage.getItem(CSRF_KEY)
|
|
129
|
-
|
|
130
|
-
if (sessionData) {
|
|
131
|
-
_session = JSON.parse(sessionData)
|
|
132
|
-
}
|
|
133
|
-
if (csrfData) {
|
|
134
|
-
_csrfToken = csrfData
|
|
135
|
-
}
|
|
136
|
-
} catch {
|
|
137
|
-
// Ignore storage errors
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Save session to localStorage.
|
|
143
|
-
*/
|
|
144
|
-
function saveSession() {
|
|
145
|
-
if (!_config?.persistSession) return
|
|
146
|
-
|
|
147
|
-
try {
|
|
148
|
-
if (_session) {
|
|
149
|
-
localStorage.setItem(SESSION_KEY, JSON.stringify(_session))
|
|
150
|
-
} else {
|
|
151
|
-
localStorage.removeItem(SESSION_KEY)
|
|
152
|
-
}
|
|
153
|
-
if (_csrfToken) {
|
|
154
|
-
localStorage.setItem(CSRF_KEY, _csrfToken)
|
|
155
|
-
} else {
|
|
156
|
-
localStorage.removeItem(CSRF_KEY)
|
|
157
|
-
}
|
|
158
|
-
} catch {
|
|
159
|
-
// Ignore storage errors
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Make JSON-RPC request to Odoo.
|
|
165
|
-
*
|
|
166
|
-
* @param {string} service - Service name (e.g., 'common', 'object', 'db')
|
|
167
|
-
@param {string} method - Method name
|
|
168
|
-
* @param {any[]} args - Method arguments
|
|
169
|
-
* @returns {Promise<any>}
|
|
170
|
-
* @throws {Error}
|
|
171
|
-
*/
|
|
172
|
-
async function jsonRpc(service, method, args = []) {
|
|
173
|
-
if (!isConfigured()) {
|
|
174
|
-
throw new Error('[metaowl] OdooService not configured. Call configure() first.')
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const url = `${_config.baseUrl}/jsonrpc`
|
|
178
|
-
|
|
179
|
-
const payload = {
|
|
180
|
-
jsonrpc: '2.0',
|
|
181
|
-
method: 'call',
|
|
182
|
-
params: {
|
|
183
|
-
service,
|
|
184
|
-
method,
|
|
185
|
-
args
|
|
186
|
-
},
|
|
187
|
-
id: Math.floor(Math.random() * 1000000000)
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
const headers = {
|
|
191
|
-
'Content-Type': 'application/json'
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if (_csrfToken) {
|
|
195
|
-
headers['X-CSRF-Token'] = _csrfToken
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
const response = await fetch(url, {
|
|
199
|
-
method: 'POST',
|
|
200
|
-
headers,
|
|
201
|
-
body: JSON.stringify(payload),
|
|
202
|
-
credentials: 'include'
|
|
203
|
-
})
|
|
204
|
-
|
|
205
|
-
if (!response.ok) {
|
|
206
|
-
throw new Error(`[metaowl] HTTP ${response.status}: ${response.statusText}`)
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const data = await response.json()
|
|
210
|
-
|
|
211
|
-
if (data.error) {
|
|
212
|
-
const error = data.error
|
|
213
|
-
throw new Error(`[metaowl] Odoo Error: ${error.message || error.data?.message || JSON.stringify(error)}`)
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// Extract CSRF token from cookies if present
|
|
217
|
-
const setCookie = response.headers.get('set-cookie')
|
|
218
|
-
if (setCookie?.includes('csrf_token')) {
|
|
219
|
-
const match = setCookie.match(/csrf_token=([^;]+)/)
|
|
220
|
-
if (match) {
|
|
221
|
-
_csrfToken = match[1]
|
|
222
|
-
saveSession()
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
return data.result
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
/**
|
|
230
|
-
* Authenticate with Odoo.
|
|
231
|
-
*
|
|
232
|
-
* @param {string} [username] - Override configured username
|
|
233
|
-
* @param {string} [password] - Override configured password
|
|
234
|
-
* @returns {Promise<OdooSession>} Session info
|
|
235
|
-
* @throws {Error}
|
|
236
|
-
*/
|
|
237
|
-
export async function authenticate(username, password) {
|
|
238
|
-
const user = username || _config?.username
|
|
239
|
-
const pass = password || _config?.password || _config?.apiKey
|
|
240
|
-
|
|
241
|
-
if (!user || !pass) {
|
|
242
|
-
throw new Error('[metaowl] Authentication requires username and password/apiKey')
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
const uid = await jsonRpc('common', 'authenticate', [
|
|
246
|
-
_config.database,
|
|
247
|
-
user,
|
|
248
|
-
pass,
|
|
249
|
-
{}
|
|
250
|
-
])
|
|
251
|
-
|
|
252
|
-
if (!uid) {
|
|
253
|
-
throw new Error('[metaowl] Authentication failed: invalid credentials')
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
_session = {
|
|
257
|
-
uid,
|
|
258
|
-
username: user
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Get user info
|
|
262
|
-
try {
|
|
263
|
-
const userInfo = await searchRead('res.users', {
|
|
264
|
-
domain: [['id', '=', uid]],
|
|
265
|
-
fields: ['name', 'partner_id', 'lang', 'tz'],
|
|
266
|
-
limit: 1
|
|
267
|
-
})
|
|
268
|
-
|
|
269
|
-
if (userInfo.length > 0) {
|
|
270
|
-
_session.name = userInfo[0].name
|
|
271
|
-
_session.partner_id = userInfo[0].partner_id?.[0]
|
|
272
|
-
_session.lang = userInfo[0].lang
|
|
273
|
-
_session.tz = userInfo[0].tz
|
|
274
|
-
}
|
|
275
|
-
} catch {
|
|
276
|
-
// Ignore user info fetch errors
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
saveSession()
|
|
280
|
-
notifyAuthListeners()
|
|
281
|
-
|
|
282
|
-
return _session
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* Check if currently authenticated.
|
|
287
|
-
*
|
|
288
|
-
* @returns {boolean}
|
|
289
|
-
*/
|
|
290
|
-
export function isAuthenticated() {
|
|
291
|
-
return _session !== null && _session.uid !== null
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
/**
|
|
295
|
-
* Get current session.
|
|
296
|
-
*
|
|
297
|
-
* @returns {OdooSession|null}
|
|
298
|
-
*/
|
|
299
|
-
export function getSession() {
|
|
300
|
-
return _session
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
/**
|
|
304
|
-
* Logout and clear session.
|
|
305
|
-
*/
|
|
306
|
-
export function logout() {
|
|
307
|
-
_session = null
|
|
308
|
-
_csrfToken = null
|
|
309
|
-
saveSession()
|
|
310
|
-
notifyAuthListeners()
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
/**
|
|
314
|
-
* Search and read records from Odoo.
|
|
315
|
-
*
|
|
316
|
-
* @param {string} model - Model name (e.g., 'res.partner')
|
|
317
|
-
* @param {SearchReadOptions} options - Search options
|
|
318
|
-
* @returns {Promise<Object[]>} Records
|
|
319
|
-
*/
|
|
320
|
-
export async function searchRead(model, options = {}) {
|
|
321
|
-
const {
|
|
322
|
-
domain = [],
|
|
323
|
-
fields = [],
|
|
324
|
-
limit = 80,
|
|
325
|
-
offset = 0,
|
|
326
|
-
order = '',
|
|
327
|
-
context = {}
|
|
328
|
-
} = options
|
|
329
|
-
|
|
330
|
-
if (!isAuthenticated()) {
|
|
331
|
-
throw new Error('[metaowl] Not authenticated. Call authenticate() first.')
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
const args = [
|
|
335
|
-
_config.database,
|
|
336
|
-
_session.uid,
|
|
337
|
-
_config.password || _config.apiKey,
|
|
338
|
-
model,
|
|
339
|
-
'search_read',
|
|
340
|
-
[domain],
|
|
341
|
-
{ fields, limit, offset, order, context }
|
|
342
|
-
]
|
|
343
|
-
|
|
344
|
-
return await jsonRpc('object', 'execute_kw', args)
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
/**
|
|
348
|
-
* Call any model method.
|
|
349
|
-
*
|
|
350
|
-
* @param {string} model - Model name
|
|
351
|
-
* @param {string} method - Method name
|
|
352
|
-
* @param {any[]} [args=[]] - Positional arguments
|
|
353
|
-
* @param {Object} [kwargs={}] - Keyword arguments
|
|
354
|
-
* @returns {Promise<any>}
|
|
355
|
-
*/
|
|
356
|
-
export async function call(model, method, args = [], kwargs = {}) {
|
|
357
|
-
if (!isAuthenticated()) {
|
|
358
|
-
throw new Error('[metaowl] Not authenticated. Call authenticate() first.')
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
const rpcArgs = [
|
|
362
|
-
_config.database,
|
|
363
|
-
_session.uid,
|
|
364
|
-
_config.password || _config.apiKey,
|
|
365
|
-
model,
|
|
366
|
-
method,
|
|
367
|
-
args,
|
|
368
|
-
kwargs
|
|
369
|
-
]
|
|
370
|
-
|
|
371
|
-
return await jsonRpc('object', 'execute_kw', rpcArgs)
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
/**
|
|
375
|
-
* Read specific records by ID.
|
|
376
|
-
*
|
|
377
|
-
* @param {string} model - Model name
|
|
378
|
-
* @param {number[]} ids - Record IDs
|
|
379
|
-
* @param {string[]} [fields=[]] - Fields to read
|
|
380
|
-
* @returns {Promise<Object[]>}
|
|
381
|
-
*/
|
|
382
|
-
export async function read(model, ids, fields = []) {
|
|
383
|
-
return await call(model, 'read', [ids], { fields })
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
/**
|
|
387
|
-
* Create a new record.
|
|
388
|
-
*
|
|
389
|
-
* @param {string} model - Model name
|
|
390
|
-
* @param {Object} values - Field values
|
|
391
|
-
* @returns {Promise<number>} New record ID
|
|
392
|
-
*/
|
|
393
|
-
export async function create(model, values) {
|
|
394
|
-
return await call(model, 'create', [[values]])
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
/**
|
|
398
|
-
* Update existing records.
|
|
399
|
-
*
|
|
400
|
-
* @param {string} model - Model name
|
|
401
|
-
* @param {number[]} ids - Record IDs to update
|
|
402
|
-
* @param {Object} values - New field values
|
|
403
|
-
* @returns {Promise<boolean>}
|
|
404
|
-
*/
|
|
405
|
-
export async function write(model, ids, values) {
|
|
406
|
-
return await call(model, 'write', [ids, values])
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
/**
|
|
410
|
-
* Delete records.
|
|
411
|
-
*
|
|
412
|
-
* @param {string} model - Model name
|
|
413
|
-
* @param {number[]} ids - Record IDs to delete
|
|
414
|
-
* @returns {Promise<boolean>}
|
|
415
|
-
*/
|
|
416
|
-
export async function unlink(model, ids) {
|
|
417
|
-
return await call(model, 'unlink', [ids])
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
/**
|
|
421
|
-
* Get count of records matching domain.
|
|
422
|
-
*
|
|
423
|
-
* @param {string} model - Model name
|
|
424
|
-
* @param {Array[]} [domain=[]] - Search domain
|
|
425
|
-
* @returns {Promise<number>}
|
|
426
|
-
*/
|
|
427
|
-
export async function searchCount(model, domain = []) {
|
|
428
|
-
return await call(model, 'search_count', [domain])
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
/**
|
|
432
|
-
* Get list of available databases.
|
|
433
|
-
*
|
|
434
|
-
* @returns {Promise<string[]>}
|
|
435
|
-
*/
|
|
436
|
-
export async function listDatabases() {
|
|
437
|
-
return await jsonRpc('db', 'list', [])}
|
|
438
|
-
|
|
439
|
-
/**
|
|
440
|
-
* Check version of Odoo server.
|
|
441
|
-
*
|
|
442
|
-
* @returns {Promise<Object>} Version info
|
|
443
|
-
*/
|
|
444
|
-
export async function versionInfo() {
|
|
445
|
-
const response = await fetch(`${_config.baseUrl}/web/webclient/version_info`, {
|
|
446
|
-
method: 'POST',
|
|
447
|
-
headers: { 'Content-Type': 'application/json' },
|
|
448
|
-
body: '{}'
|
|
449
|
-
})
|
|
450
|
-
|
|
451
|
-
if (!response.ok) {
|
|
452
|
-
throw new Error(`[metaowl] Failed to get version info: ${response.status}`)
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
const data = await response.json()
|
|
456
|
-
return data.result
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
/**
|
|
460
|
-
* Register auth state change listener.
|
|
461
|
-
*
|
|
462
|
-
* @param {Function} callback - Called with (session|null) when auth changes
|
|
463
|
-
* @returns {Function} Unsubscribe function
|
|
464
|
-
*/
|
|
465
|
-
export function onAuthChange(callback) {
|
|
466
|
-
_authListeners.push(callback)
|
|
467
|
-
return () => {
|
|
468
|
-
const index = _authListeners.indexOf(callback)
|
|
469
|
-
if (index > -1) {
|
|
470
|
-
_authListeners.splice(index, 1)
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
/**
|
|
476
|
-
* Notify all auth listeners.
|
|
477
|
-
*/
|
|
478
|
-
function notifyAuthListeners() {
|
|
479
|
-
for (const listener of _authListeners) {
|
|
480
|
-
try {
|
|
481
|
-
listener(_session)
|
|
482
|
-
} catch {
|
|
483
|
-
// Ignore listener errors
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
/**
|
|
489
|
-
* OdooService namespace for convenient access.
|
|
490
|
-
*/
|
|
491
|
-
export const OdooService = {
|
|
492
|
-
configure,
|
|
493
|
-
getConfig,
|
|
494
|
-
isConfigured,
|
|
495
|
-
authenticate,
|
|
496
|
-
isAuthenticated,
|
|
497
|
-
getSession,
|
|
498
|
-
logout,
|
|
499
|
-
searchRead,
|
|
500
|
-
call,
|
|
501
|
-
read,
|
|
502
|
-
create,
|
|
503
|
-
write,
|
|
504
|
-
unlink,
|
|
505
|
-
searchCount,
|
|
506
|
-
listDatabases,
|
|
507
|
-
versionInfo,
|
|
508
|
-
onAuthChange
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
export default OdooService
|