@odoo/owl 2.8.1 → 3.0.0-alpha.1

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 (37) hide show
  1. package/dist/owl-devtools.zip +0 -0
  2. package/dist/owl.cjs.js +579 -182
  3. package/dist/owl.es.js +572 -183
  4. package/dist/owl.iife.js +579 -182
  5. package/dist/owl.iife.min.js +1 -1
  6. package/dist/types/common/types.d.ts +28 -0
  7. package/dist/types/owl.d.ts +162 -74
  8. package/dist/types/runtime/app.d.ts +3 -0
  9. package/dist/types/runtime/cancellableContext.d.ts +15 -0
  10. package/dist/types/runtime/cancellablePromise.d.ts +15 -0
  11. package/dist/types/runtime/component.d.ts +5 -0
  12. package/dist/types/runtime/component_node.d.ts +4 -2
  13. package/dist/types/runtime/executionContext.d.ts +0 -0
  14. package/dist/types/runtime/index.d.ts +3 -0
  15. package/dist/types/runtime/listOperation.d.ts +1 -0
  16. package/dist/types/runtime/plugins.d.ts +39 -0
  17. package/dist/types/runtime/reactivity.d.ts +1 -12
  18. package/dist/types/runtime/registry.d.ts +15 -0
  19. package/dist/types/runtime/relationalModel/discussModel.d.ts +19 -0
  20. package/dist/types/runtime/relationalModel/discussModelTypes.d.ts +22 -0
  21. package/dist/types/runtime/relationalModel/field.d.ts +20 -0
  22. package/dist/types/runtime/relationalModel/model.d.ts +59 -0
  23. package/dist/types/runtime/relationalModel/modelData.d.ts +18 -0
  24. package/dist/types/runtime/relationalModel/modelRegistry.d.ts +3 -0
  25. package/dist/types/runtime/relationalModel/modelUtils.d.ts +4 -0
  26. package/dist/types/runtime/relationalModel/store.d.ts +16 -0
  27. package/dist/types/runtime/relationalModel/types.d.ts +83 -0
  28. package/dist/types/runtime/relationalModel/util.d.ts +1 -0
  29. package/dist/types/runtime/relationalModel/web/WebDataPoint.d.ts +25 -0
  30. package/dist/types/runtime/relationalModel/web/WebRecord.d.ts +131 -0
  31. package/dist/types/runtime/relationalModel/web/WebStaticList.d.ts +63 -0
  32. package/dist/types/runtime/relationalModel/web/webModel.d.ts +5 -0
  33. package/dist/types/runtime/relationalModel/web/webModelTypes.d.ts +139 -0
  34. package/dist/types/runtime/signals.d.ts +17 -0
  35. package/dist/types/runtime/task.d.ts +12 -0
  36. package/dist/types/utils/registry.d.ts +15 -0
  37. package/package.json +1 -1
package/dist/owl.cjs.js CHANGED
@@ -1596,6 +1596,14 @@ function remove(vnode, withBeforeRemove = false) {
1596
1596
  vnode.remove();
1597
1597
  }
1598
1598
 
1599
+ // Reactivity system
1600
+ var ComputationState;
1601
+ (function (ComputationState) {
1602
+ ComputationState[ComputationState["EXECUTED"] = 0] = "EXECUTED";
1603
+ ComputationState[ComputationState["STALE"] = 1] = "STALE";
1604
+ ComputationState[ComputationState["PENDING"] = 2] = "PENDING";
1605
+ })(ComputationState || (ComputationState = {}));
1606
+
1599
1607
  // Maps fibers to thrown errors
1600
1608
  const fibersInError = new WeakMap();
1601
1609
  const nodeErrorHandlers = new WeakMap();
@@ -1658,6 +1666,233 @@ function handleError(params) {
1658
1666
  }
1659
1667
  }
1660
1668
 
1669
+ let Effects;
1670
+ let CurrentComputation;
1671
+ function signal(value, opts) {
1672
+ const atom = {
1673
+ value,
1674
+ observers: new Set(),
1675
+ name: opts === null || opts === void 0 ? void 0 : opts.name,
1676
+ };
1677
+ const read = () => {
1678
+ onReadAtom(atom);
1679
+ return atom.value;
1680
+ };
1681
+ const write = (newValue) => {
1682
+ if (typeof newValue === "function") {
1683
+ newValue = newValue(atom.value);
1684
+ }
1685
+ if (Object.is(atom.value, newValue))
1686
+ return;
1687
+ atom.value = newValue;
1688
+ onWriteAtom(atom);
1689
+ };
1690
+ return {
1691
+ get: read,
1692
+ set: write,
1693
+ };
1694
+ }
1695
+ function effect(fn, opts) {
1696
+ var _a, _b;
1697
+ const effectComputation = {
1698
+ state: ComputationState.STALE,
1699
+ value: undefined,
1700
+ compute() {
1701
+ // In case the cleanup read an atom.
1702
+ // todo: test it
1703
+ CurrentComputation = undefined;
1704
+ // `removeSources` is made by `runComputation`.
1705
+ unsubscribeEffect(effectComputation);
1706
+ CurrentComputation = effectComputation;
1707
+ return fn();
1708
+ },
1709
+ sources: new Set(),
1710
+ childrenEffect: [],
1711
+ name: opts === null || opts === void 0 ? void 0 : opts.name,
1712
+ };
1713
+ (_b = (_a = CurrentComputation === null || CurrentComputation === void 0 ? void 0 : CurrentComputation.childrenEffect) === null || _a === void 0 ? void 0 : _a.push) === null || _b === void 0 ? void 0 : _b.call(_a, effectComputation);
1714
+ updateComputation(effectComputation);
1715
+ // Remove sources and unsubscribe
1716
+ return () => {
1717
+ // In case the cleanup read an atom.
1718
+ // todo: test it
1719
+ const previousComputation = CurrentComputation;
1720
+ CurrentComputation = undefined;
1721
+ unsubscribeEffect(effectComputation);
1722
+ CurrentComputation = previousComputation;
1723
+ };
1724
+ }
1725
+ // export function computed<T>(fn: () => T, opts?: Opts) {
1726
+ // // todo: handle cleanup
1727
+ // let computedComputation: Computation = {
1728
+ // state: ComputationState.STALE,
1729
+ // sources: new Set(),
1730
+ // isEager: true,
1731
+ // compute: () => {
1732
+ // return fn();
1733
+ // },
1734
+ // value: undefined,
1735
+ // name: opts?.name,
1736
+ // };
1737
+ // updateComputation(computedComputation);
1738
+ // }
1739
+ function derived(fn, opts) {
1740
+ // todo: handle cleanup
1741
+ let derivedComputation;
1742
+ return () => {
1743
+ derivedComputation !== null && derivedComputation !== void 0 ? derivedComputation : (derivedComputation = {
1744
+ state: ComputationState.STALE,
1745
+ sources: new Set(),
1746
+ compute: () => {
1747
+ onWriteAtom(derivedComputation);
1748
+ return fn();
1749
+ },
1750
+ isDerived: true,
1751
+ value: undefined,
1752
+ observers: new Set(),
1753
+ name: opts === null || opts === void 0 ? void 0 : opts.name,
1754
+ });
1755
+ updateComputation(derivedComputation);
1756
+ return derivedComputation.value;
1757
+ };
1758
+ }
1759
+ function onReadAtom(atom) {
1760
+ if (!CurrentComputation)
1761
+ return;
1762
+ CurrentComputation.sources.add(atom);
1763
+ atom.observers.add(CurrentComputation);
1764
+ }
1765
+ function onWriteAtom(atom) {
1766
+ collectEffects(() => {
1767
+ for (const ctx of atom.observers) {
1768
+ if (ctx.state === ComputationState.EXECUTED) {
1769
+ if (ctx.isDerived)
1770
+ markDownstream(ctx);
1771
+ else
1772
+ Effects.push(ctx);
1773
+ }
1774
+ ctx.state = ComputationState.STALE;
1775
+ }
1776
+ });
1777
+ batchProcessEffects();
1778
+ }
1779
+ function collectEffects(fn) {
1780
+ if (Effects)
1781
+ return fn();
1782
+ Effects = [];
1783
+ try {
1784
+ return fn();
1785
+ }
1786
+ finally {
1787
+ }
1788
+ }
1789
+ const batchProcessEffects = batched(processEffects);
1790
+ function processEffects() {
1791
+ if (!Effects)
1792
+ return;
1793
+ for (const computation of Effects) {
1794
+ updateComputation(computation);
1795
+ }
1796
+ Effects = undefined;
1797
+ }
1798
+ function withoutReactivity(fn) {
1799
+ return runWithComputation(undefined, fn);
1800
+ }
1801
+ function getCurrentComputation() {
1802
+ return CurrentComputation;
1803
+ }
1804
+ function setComputation(computation) {
1805
+ CurrentComputation = computation;
1806
+ }
1807
+ // todo: should probably use updateComputation instead.
1808
+ function runWithComputation(computation, fn) {
1809
+ const previousComputation = CurrentComputation;
1810
+ CurrentComputation = computation;
1811
+ let result;
1812
+ try {
1813
+ result = fn();
1814
+ }
1815
+ finally {
1816
+ CurrentComputation = previousComputation;
1817
+ }
1818
+ return result;
1819
+ }
1820
+ function updateComputation(computation) {
1821
+ var _a;
1822
+ const state = computation.state;
1823
+ if (computation.isDerived)
1824
+ onReadAtom(computation);
1825
+ if (state === ComputationState.EXECUTED)
1826
+ return;
1827
+ if (state === ComputationState.PENDING) {
1828
+ computeSources(computation);
1829
+ // If the state is still not stale after processing the sources, it means
1830
+ // none of the dependencies have changed.
1831
+ // todo: test it
1832
+ if (computation.state !== ComputationState.STALE) {
1833
+ computation.state = ComputationState.EXECUTED;
1834
+ return;
1835
+ }
1836
+ }
1837
+ // todo: test performance. We might want to avoid removing the atoms to
1838
+ // directly re-add them at compute. Especially as we are making them stale.
1839
+ removeSources(computation);
1840
+ const previousComputation = CurrentComputation;
1841
+ CurrentComputation = computation;
1842
+ computation.value = (_a = computation.compute) === null || _a === void 0 ? void 0 : _a.call(computation);
1843
+ computation.state = ComputationState.EXECUTED;
1844
+ CurrentComputation = previousComputation;
1845
+ }
1846
+ function removeSources(computation) {
1847
+ const sources = computation.sources;
1848
+ for (const source of sources) {
1849
+ const observers = source.observers;
1850
+ observers.delete(computation);
1851
+ // todo: if source has no effect observer anymore, remove its sources too
1852
+ // todo: test it
1853
+ }
1854
+ sources.clear();
1855
+ }
1856
+ function unsubscribeEffect(effectComputation) {
1857
+ removeSources(effectComputation);
1858
+ cleanupEffect(effectComputation);
1859
+ for (const children of effectComputation.childrenEffect) {
1860
+ // Consider it executed to avoid it's re-execution
1861
+ // todo: make a test for it
1862
+ children.state = ComputationState.EXECUTED;
1863
+ removeSources(children);
1864
+ unsubscribeEffect(children);
1865
+ }
1866
+ effectComputation.childrenEffect.length = 0;
1867
+ }
1868
+ function cleanupEffect(computation) {
1869
+ // the computation.value of an effect is a cleanup function
1870
+ const cleanupFn = computation.value;
1871
+ if (cleanupFn && typeof cleanupFn === "function") {
1872
+ cleanupFn();
1873
+ computation.value = undefined;
1874
+ }
1875
+ }
1876
+ function markDownstream(derived) {
1877
+ for (const observer of derived.observers) {
1878
+ // if the state has already been marked, skip it
1879
+ if (observer.state)
1880
+ continue;
1881
+ observer.state = ComputationState.PENDING;
1882
+ if (observer.isDerived)
1883
+ markDownstream(observer);
1884
+ else
1885
+ Effects.push(observer);
1886
+ }
1887
+ }
1888
+ function computeSources(derived) {
1889
+ for (const source of derived.sources) {
1890
+ if (!("compute" in source))
1891
+ continue;
1892
+ updateComputation(source);
1893
+ }
1894
+ }
1895
+
1661
1896
  function makeChildFiber(node, parent) {
1662
1897
  let current = node.fiber;
1663
1898
  if (current) {
@@ -1779,13 +2014,16 @@ class Fiber {
1779
2014
  const node = this.node;
1780
2015
  const root = this.root;
1781
2016
  if (root) {
1782
- try {
1783
- this.bdom = true;
1784
- this.bdom = node.renderFn();
1785
- }
1786
- catch (e) {
1787
- node.app.handleError({ node, error: e });
1788
- }
2017
+ // todo: should use updateComputation somewhere else.
2018
+ runWithComputation(node.signalComputation, () => {
2019
+ try {
2020
+ this.bdom = true;
2021
+ this.bdom = node.renderFn();
2022
+ }
2023
+ catch (e) {
2024
+ node.app.handleError({ node, error: e });
2025
+ }
2026
+ });
1789
2027
  root.setCounter(root.counter - 1);
1790
2028
  }
1791
2029
  }
@@ -1917,11 +2155,6 @@ class MountFiber extends RootFiber {
1917
2155
 
1918
2156
  // Special key to subscribe to, to be notified of key creation/deletion
1919
2157
  const KEYCHANGES = Symbol("Key changes");
1920
- // Used to specify the absence of a callback, can be used as WeakMap key but
1921
- // should only be used as a sentinel value and never called.
1922
- const NO_CALLBACK = () => {
1923
- throw new Error("Called NO_CALLBACK. Owl is broken, please report this to the maintainers.");
1924
- };
1925
2158
  const objectToString = Object.prototype.toString;
1926
2159
  const objectHasOwnProperty = Object.prototype.hasOwnProperty;
1927
2160
  // Use arrays because Array.includes is faster than Set.has for small arrays
@@ -1958,8 +2191,8 @@ function canBeMadeReactive(value) {
1958
2191
  * @param value the value make reactive
1959
2192
  * @returns a reactive for the given object when possible, the original otherwise
1960
2193
  */
1961
- function possiblyReactive(val, cb) {
1962
- return canBeMadeReactive(val) ? reactive(val, cb) : val;
2194
+ function possiblyReactive(val) {
2195
+ return canBeMadeReactive(val) ? reactive(val) : val;
1963
2196
  }
1964
2197
  const skipped = new WeakSet();
1965
2198
  /**
@@ -1981,7 +2214,23 @@ function markRaw(value) {
1981
2214
  function toRaw(value) {
1982
2215
  return targets.has(value) ? targets.get(value) : value;
1983
2216
  }
1984
- const targetToKeysToCallbacks = new WeakMap();
2217
+ const targetToKeysToAtomItem = new WeakMap();
2218
+ function getTargetKeyAtom(target, key) {
2219
+ let keyToAtomItem = targetToKeysToAtomItem.get(target);
2220
+ if (!keyToAtomItem) {
2221
+ keyToAtomItem = new Map();
2222
+ targetToKeysToAtomItem.set(target, keyToAtomItem);
2223
+ }
2224
+ let atom = keyToAtomItem.get(key);
2225
+ if (!atom) {
2226
+ atom = {
2227
+ value: undefined,
2228
+ observers: new Set(),
2229
+ };
2230
+ keyToAtomItem.set(key, atom);
2231
+ }
2232
+ return atom;
2233
+ }
1985
2234
  /**
1986
2235
  * Observes a given key on a target with an callback. The callback will be
1987
2236
  * called when the given key changes on the target.
@@ -1991,22 +2240,8 @@ const targetToKeysToCallbacks = new WeakMap();
1991
2240
  * or deletion)
1992
2241
  * @param callback the function to call when the key changes
1993
2242
  */
1994
- function observeTargetKey(target, key, callback) {
1995
- if (callback === NO_CALLBACK) {
1996
- return;
1997
- }
1998
- if (!targetToKeysToCallbacks.get(target)) {
1999
- targetToKeysToCallbacks.set(target, new Map());
2000
- }
2001
- const keyToCallbacks = targetToKeysToCallbacks.get(target);
2002
- if (!keyToCallbacks.get(key)) {
2003
- keyToCallbacks.set(key, new Set());
2004
- }
2005
- keyToCallbacks.get(key).add(callback);
2006
- if (!callbacksToTargets.has(callback)) {
2007
- callbacksToTargets.set(callback, new Set());
2008
- }
2009
- callbacksToTargets.get(callback).add(target);
2243
+ function onReadTargetKey(target, key) {
2244
+ onReadAtom(getTargetKeyAtom(target, key));
2010
2245
  }
2011
2246
  /**
2012
2247
  * Notify Reactives that are observing a given target that a key has changed on
@@ -2017,60 +2252,16 @@ function observeTargetKey(target, key, callback) {
2017
2252
  * @param key the key that changed (or Symbol `KEYCHANGES` if a key was created
2018
2253
  * or deleted)
2019
2254
  */
2020
- function notifyReactives(target, key) {
2021
- const keyToCallbacks = targetToKeysToCallbacks.get(target);
2022
- if (!keyToCallbacks) {
2255
+ function onWriteTargetKey(target, key) {
2256
+ const keyToAtomItem = targetToKeysToAtomItem.get(target);
2257
+ if (!keyToAtomItem) {
2023
2258
  return;
2024
2259
  }
2025
- const callbacks = keyToCallbacks.get(key);
2026
- if (!callbacks) {
2260
+ const atom = keyToAtomItem.get(key);
2261
+ if (!atom) {
2027
2262
  return;
2028
2263
  }
2029
- // Loop on copy because clearReactivesForCallback will modify the set in place
2030
- for (const callback of [...callbacks]) {
2031
- clearReactivesForCallback(callback);
2032
- callback();
2033
- }
2034
- }
2035
- const callbacksToTargets = new WeakMap();
2036
- /**
2037
- * Clears all subscriptions of the Reactives associated with a given callback.
2038
- *
2039
- * @param callback the callback for which the reactives need to be cleared
2040
- */
2041
- function clearReactivesForCallback(callback) {
2042
- const targetsToClear = callbacksToTargets.get(callback);
2043
- if (!targetsToClear) {
2044
- return;
2045
- }
2046
- for (const target of targetsToClear) {
2047
- const observedKeys = targetToKeysToCallbacks.get(target);
2048
- if (!observedKeys) {
2049
- continue;
2050
- }
2051
- for (const [key, callbacks] of observedKeys.entries()) {
2052
- callbacks.delete(callback);
2053
- if (!callbacks.size) {
2054
- observedKeys.delete(key);
2055
- }
2056
- }
2057
- }
2058
- targetsToClear.clear();
2059
- }
2060
- function getSubscriptions(callback) {
2061
- const targets = callbacksToTargets.get(callback) || [];
2062
- return [...targets].map((target) => {
2063
- const keysToCallbacks = targetToKeysToCallbacks.get(target);
2064
- let keys = [];
2065
- if (keysToCallbacks) {
2066
- for (const [key, cbs] of keysToCallbacks) {
2067
- if (cbs.has(callback)) {
2068
- keys.push(key);
2069
- }
2070
- }
2071
- }
2072
- return { target, keys };
2073
- });
2264
+ onWriteAtom(atom);
2074
2265
  }
2075
2266
  // Maps reactive objects to the underlying target
2076
2267
  const targets = new WeakMap();
@@ -2102,7 +2293,7 @@ const reactiveCache = new WeakMap();
2102
2293
  * reactive has changed
2103
2294
  * @returns a proxy that tracks changes to it
2104
2295
  */
2105
- function reactive(target, callback = NO_CALLBACK) {
2296
+ function reactive(target) {
2106
2297
  if (!canBeMadeReactive(target)) {
2107
2298
  throw new OwlError(`Cannot make the given value reactive`);
2108
2299
  }
@@ -2111,22 +2302,19 @@ function reactive(target, callback = NO_CALLBACK) {
2111
2302
  }
2112
2303
  if (targets.has(target)) {
2113
2304
  // target is reactive, create a reactive on the underlying object instead
2114
- return reactive(targets.get(target), callback);
2115
- }
2116
- if (!reactiveCache.has(target)) {
2117
- reactiveCache.set(target, new WeakMap());
2118
- }
2119
- const reactivesForTarget = reactiveCache.get(target);
2120
- if (!reactivesForTarget.has(callback)) {
2121
- const targetRawType = rawType(target);
2122
- const handler = COLLECTION_RAW_TYPES.includes(targetRawType)
2123
- ? collectionsProxyHandler(target, callback, targetRawType)
2124
- : basicProxyHandler(callback);
2125
- const proxy = new Proxy(target, handler);
2126
- reactivesForTarget.set(callback, proxy);
2127
- targets.set(proxy, target);
2305
+ return target;
2128
2306
  }
2129
- return reactivesForTarget.get(callback);
2307
+ const reactive = reactiveCache.get(target);
2308
+ if (reactive)
2309
+ return reactive;
2310
+ const targetRawType = rawType(target);
2311
+ const handler = COLLECTION_RAW_TYPES.includes(targetRawType)
2312
+ ? collectionsProxyHandler(target, targetRawType)
2313
+ : basicProxyHandler();
2314
+ const proxy = new Proxy(target, handler);
2315
+ reactiveCache.set(target, proxy);
2316
+ targets.set(proxy, target);
2317
+ return proxy;
2130
2318
  }
2131
2319
  /**
2132
2320
  * Creates a basic proxy handler for regular objects and arrays.
@@ -2134,7 +2322,7 @@ function reactive(target, callback = NO_CALLBACK) {
2134
2322
  * @param callback @see reactive
2135
2323
  * @returns a proxy handler object
2136
2324
  */
2137
- function basicProxyHandler(callback) {
2325
+ function basicProxyHandler() {
2138
2326
  return {
2139
2327
  get(target, key, receiver) {
2140
2328
  // non-writable non-configurable properties cannot be made reactive
@@ -2142,41 +2330,41 @@ function basicProxyHandler(callback) {
2142
2330
  if (desc && !desc.writable && !desc.configurable) {
2143
2331
  return Reflect.get(target, key, receiver);
2144
2332
  }
2145
- observeTargetKey(target, key, callback);
2146
- return possiblyReactive(Reflect.get(target, key, receiver), callback);
2333
+ onReadTargetKey(target, key);
2334
+ return possiblyReactive(Reflect.get(target, key, receiver));
2147
2335
  },
2148
2336
  set(target, key, value, receiver) {
2149
2337
  const hadKey = objectHasOwnProperty.call(target, key);
2150
2338
  const originalValue = Reflect.get(target, key, receiver);
2151
2339
  const ret = Reflect.set(target, key, toRaw(value), receiver);
2152
2340
  if (!hadKey && objectHasOwnProperty.call(target, key)) {
2153
- notifyReactives(target, KEYCHANGES);
2341
+ onWriteTargetKey(target, KEYCHANGES);
2154
2342
  }
2155
2343
  // While Array length may trigger the set trap, it's not actually set by this
2156
2344
  // method but is updated behind the scenes, and the trap is not called with the
2157
2345
  // new value. We disable the "same-value-optimization" for it because of that.
2158
2346
  if (originalValue !== Reflect.get(target, key, receiver) ||
2159
2347
  (key === "length" && Array.isArray(target))) {
2160
- notifyReactives(target, key);
2348
+ onWriteTargetKey(target, key);
2161
2349
  }
2162
2350
  return ret;
2163
2351
  },
2164
2352
  deleteProperty(target, key) {
2165
2353
  const ret = Reflect.deleteProperty(target, key);
2166
2354
  // TODO: only notify when something was actually deleted
2167
- notifyReactives(target, KEYCHANGES);
2168
- notifyReactives(target, key);
2355
+ onWriteTargetKey(target, KEYCHANGES);
2356
+ onWriteTargetKey(target, key);
2169
2357
  return ret;
2170
2358
  },
2171
2359
  ownKeys(target) {
2172
- observeTargetKey(target, KEYCHANGES, callback);
2360
+ onReadTargetKey(target, KEYCHANGES);
2173
2361
  return Reflect.ownKeys(target);
2174
2362
  },
2175
2363
  has(target, key) {
2176
2364
  // TODO: this observes all key changes instead of only the presence of the argument key
2177
2365
  // observing the key itself would observe value changes instead of presence changes
2178
2366
  // so we may need a finer grained system to distinguish observing value vs presence.
2179
- observeTargetKey(target, KEYCHANGES, callback);
2367
+ onReadTargetKey(target, KEYCHANGES);
2180
2368
  return Reflect.has(target, key);
2181
2369
  },
2182
2370
  };
@@ -2189,11 +2377,11 @@ function basicProxyHandler(callback) {
2189
2377
  * @param target @see reactive
2190
2378
  * @param callback @see reactive
2191
2379
  */
2192
- function makeKeyObserver(methodName, target, callback) {
2380
+ function makeKeyObserver(methodName, target) {
2193
2381
  return (key) => {
2194
2382
  key = toRaw(key);
2195
- observeTargetKey(target, key, callback);
2196
- return possiblyReactive(target[methodName](key), callback);
2383
+ onReadTargetKey(target, key);
2384
+ return possiblyReactive(target[methodName](key));
2197
2385
  };
2198
2386
  }
2199
2387
  /**
@@ -2204,14 +2392,14 @@ function makeKeyObserver(methodName, target, callback) {
2204
2392
  * @param target @see reactive
2205
2393
  * @param callback @see reactive
2206
2394
  */
2207
- function makeIteratorObserver(methodName, target, callback) {
2395
+ function makeIteratorObserver(methodName, target) {
2208
2396
  return function* () {
2209
- observeTargetKey(target, KEYCHANGES, callback);
2397
+ onReadTargetKey(target, KEYCHANGES);
2210
2398
  const keys = target.keys();
2211
2399
  for (const item of target[methodName]()) {
2212
2400
  const key = keys.next().value;
2213
- observeTargetKey(target, key, callback);
2214
- yield possiblyReactive(item, callback);
2401
+ onReadTargetKey(target, key);
2402
+ yield possiblyReactive(item);
2215
2403
  }
2216
2404
  };
2217
2405
  }
@@ -2223,12 +2411,12 @@ function makeIteratorObserver(methodName, target, callback) {
2223
2411
  * @param target @see reactive
2224
2412
  * @param callback @see reactive
2225
2413
  */
2226
- function makeForEachObserver(target, callback) {
2414
+ function makeForEachObserver(target) {
2227
2415
  return function forEach(forEachCb, thisArg) {
2228
- observeTargetKey(target, KEYCHANGES, callback);
2416
+ onReadTargetKey(target, KEYCHANGES);
2229
2417
  target.forEach(function (val, key, targetObj) {
2230
- observeTargetKey(target, key, callback);
2231
- forEachCb.call(thisArg, possiblyReactive(val, callback), possiblyReactive(key, callback), possiblyReactive(targetObj, callback));
2418
+ onReadTargetKey(target, key);
2419
+ forEachCb.call(thisArg, possiblyReactive(val), possiblyReactive(key), possiblyReactive(targetObj));
2232
2420
  }, thisArg);
2233
2421
  };
2234
2422
  }
@@ -2250,10 +2438,10 @@ function delegateAndNotify(setterName, getterName, target) {
2250
2438
  const ret = target[setterName](key, value);
2251
2439
  const hasKey = target.has(key);
2252
2440
  if (hadKey !== hasKey) {
2253
- notifyReactives(target, KEYCHANGES);
2441
+ onWriteTargetKey(target, KEYCHANGES);
2254
2442
  }
2255
2443
  if (originalValue !== target[getterName](key)) {
2256
- notifyReactives(target, key);
2444
+ onWriteTargetKey(target, key);
2257
2445
  }
2258
2446
  return ret;
2259
2447
  };
@@ -2268,9 +2456,9 @@ function makeClearNotifier(target) {
2268
2456
  return () => {
2269
2457
  const allKeys = [...target.keys()];
2270
2458
  target.clear();
2271
- notifyReactives(target, KEYCHANGES);
2459
+ onWriteTargetKey(target, KEYCHANGES);
2272
2460
  for (const key of allKeys) {
2273
- notifyReactives(target, key);
2461
+ onWriteTargetKey(target, key);
2274
2462
  }
2275
2463
  };
2276
2464
  }
@@ -2282,40 +2470,40 @@ function makeClearNotifier(target) {
2282
2470
  * reactives that the key which is being added or deleted has been modified.
2283
2471
  */
2284
2472
  const rawTypeToFuncHandlers = {
2285
- Set: (target, callback) => ({
2286
- has: makeKeyObserver("has", target, callback),
2473
+ Set: (target) => ({
2474
+ has: makeKeyObserver("has", target),
2287
2475
  add: delegateAndNotify("add", "has", target),
2288
2476
  delete: delegateAndNotify("delete", "has", target),
2289
- keys: makeIteratorObserver("keys", target, callback),
2290
- values: makeIteratorObserver("values", target, callback),
2291
- entries: makeIteratorObserver("entries", target, callback),
2292
- [Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
2293
- forEach: makeForEachObserver(target, callback),
2477
+ keys: makeIteratorObserver("keys", target),
2478
+ values: makeIteratorObserver("values", target),
2479
+ entries: makeIteratorObserver("entries", target),
2480
+ [Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target),
2481
+ forEach: makeForEachObserver(target),
2294
2482
  clear: makeClearNotifier(target),
2295
2483
  get size() {
2296
- observeTargetKey(target, KEYCHANGES, callback);
2484
+ onReadTargetKey(target, KEYCHANGES);
2297
2485
  return target.size;
2298
2486
  },
2299
2487
  }),
2300
- Map: (target, callback) => ({
2301
- has: makeKeyObserver("has", target, callback),
2302
- get: makeKeyObserver("get", target, callback),
2488
+ Map: (target) => ({
2489
+ has: makeKeyObserver("has", target),
2490
+ get: makeKeyObserver("get", target),
2303
2491
  set: delegateAndNotify("set", "get", target),
2304
2492
  delete: delegateAndNotify("delete", "has", target),
2305
- keys: makeIteratorObserver("keys", target, callback),
2306
- values: makeIteratorObserver("values", target, callback),
2307
- entries: makeIteratorObserver("entries", target, callback),
2308
- [Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
2309
- forEach: makeForEachObserver(target, callback),
2493
+ keys: makeIteratorObserver("keys", target),
2494
+ values: makeIteratorObserver("values", target),
2495
+ entries: makeIteratorObserver("entries", target),
2496
+ [Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target),
2497
+ forEach: makeForEachObserver(target),
2310
2498
  clear: makeClearNotifier(target),
2311
2499
  get size() {
2312
- observeTargetKey(target, KEYCHANGES, callback);
2500
+ onReadTargetKey(target, KEYCHANGES);
2313
2501
  return target.size;
2314
2502
  },
2315
2503
  }),
2316
- WeakMap: (target, callback) => ({
2317
- has: makeKeyObserver("has", target, callback),
2318
- get: makeKeyObserver("get", target, callback),
2504
+ WeakMap: (target) => ({
2505
+ has: makeKeyObserver("has", target),
2506
+ get: makeKeyObserver("get", target),
2319
2507
  set: delegateAndNotify("set", "get", target),
2320
2508
  delete: delegateAndNotify("delete", "has", target),
2321
2509
  }),
@@ -2327,18 +2515,18 @@ const rawTypeToFuncHandlers = {
2327
2515
  * @param target @see reactive
2328
2516
  * @returns a proxy handler object
2329
2517
  */
2330
- function collectionsProxyHandler(target, callback, targetRawType) {
2518
+ function collectionsProxyHandler(target, targetRawType) {
2331
2519
  // TODO: if performance is an issue we can create the special handlers lazily when each
2332
2520
  // property is read.
2333
- const specialHandlers = rawTypeToFuncHandlers[targetRawType](target, callback);
2334
- return Object.assign(basicProxyHandler(callback), {
2521
+ const specialHandlers = rawTypeToFuncHandlers[targetRawType](target);
2522
+ return Object.assign(basicProxyHandler(), {
2335
2523
  // FIXME: probably broken when part of prototype chain since we ignore the receiver
2336
2524
  get(target, key) {
2337
2525
  if (objectHasOwnProperty.call(specialHandlers, key)) {
2338
2526
  return specialHandlers[key];
2339
2527
  }
2340
- observeTargetKey(target, key, callback);
2341
- return possiblyReactive(target[key], callback);
2528
+ onReadTargetKey(target, key);
2529
+ return possiblyReactive(target[key]);
2342
2530
  },
2343
2531
  });
2344
2532
  }
@@ -2372,7 +2560,6 @@ function applyDefaultProps(props, defaultProps) {
2372
2560
  // -----------------------------------------------------------------------------
2373
2561
  // Integration with reactivity system (useState)
2374
2562
  // -----------------------------------------------------------------------------
2375
- const batchedRenderFunctions = new WeakMap();
2376
2563
  /**
2377
2564
  * Creates a reactive object that will be observed by the current component.
2378
2565
  * Reading data from the returned object (eg during rendering) will cause the
@@ -2384,15 +2571,7 @@ const batchedRenderFunctions = new WeakMap();
2384
2571
  * @see reactive
2385
2572
  */
2386
2573
  function useState(state) {
2387
- const node = getCurrent();
2388
- let render = batchedRenderFunctions.get(node);
2389
- if (!render) {
2390
- render = batched(node.render.bind(node, false));
2391
- batchedRenderFunctions.set(node, render);
2392
- // manual implementation of onWillDestroy to break cyclic dependency
2393
- node.willDestroy.push(clearReactivesForCallback.bind(null, render));
2394
- }
2395
- return reactive(state, render);
2574
+ return reactive(state);
2396
2575
  }
2397
2576
  class ComponentNode {
2398
2577
  constructor(C, props, app, parent, parentKey) {
@@ -2415,6 +2594,13 @@ class ComponentNode {
2415
2594
  this.parent = parent;
2416
2595
  this.props = props;
2417
2596
  this.parentKey = parentKey;
2597
+ this.pluginManager = parent ? parent.pluginManager : app.pluginManager;
2598
+ this.signalComputation = {
2599
+ value: undefined,
2600
+ compute: () => this.render(false),
2601
+ sources: new Set(),
2602
+ state: ComputationState.EXECUTED,
2603
+ };
2418
2604
  const defaultProps = C.defaultProps;
2419
2605
  props = Object.assign({}, props);
2420
2606
  if (defaultProps) {
@@ -2422,16 +2608,13 @@ class ComponentNode {
2422
2608
  }
2423
2609
  const env = (parent && parent.childEnv) || app.env;
2424
2610
  this.childEnv = env;
2425
- for (const key in props) {
2426
- const prop = props[key];
2427
- if (prop && typeof prop === "object" && targets.has(prop)) {
2428
- props[key] = useState(prop);
2429
- }
2430
- }
2611
+ const previousComputation = getCurrentComputation();
2612
+ setComputation(this.signalComputation);
2431
2613
  this.component = new C(props, env, this);
2432
2614
  const ctx = Object.assign(Object.create(this.component), { this: this.component });
2433
2615
  this.renderFn = app.getTemplate(C.template).bind(this.component, ctx, this);
2434
2616
  this.component.setup();
2617
+ setComputation(previousComputation);
2435
2618
  currentNode = null;
2436
2619
  }
2437
2620
  mountComponent(target, options) {
@@ -2446,7 +2629,11 @@ class ComponentNode {
2446
2629
  }
2447
2630
  const component = this.component;
2448
2631
  try {
2449
- await Promise.all(this.willStart.map((f) => f.call(component)));
2632
+ let prom;
2633
+ withoutReactivity(() => {
2634
+ prom = Promise.all(this.willStart.map((f) => f.call(component)));
2635
+ });
2636
+ await prom;
2450
2637
  }
2451
2638
  catch (e) {
2452
2639
  this.app.handleError({ node: this, error: e });
@@ -2555,15 +2742,10 @@ class ComponentNode {
2555
2742
  if (defaultProps) {
2556
2743
  applyDefaultProps(props, defaultProps);
2557
2744
  }
2558
- currentNode = this;
2559
- for (const key in props) {
2560
- const prop = props[key];
2561
- if (prop && typeof prop === "object" && targets.has(prop)) {
2562
- props[key] = useState(prop);
2563
- }
2564
- }
2565
- currentNode = null;
2566
- const prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
2745
+ let prom;
2746
+ withoutReactivity(() => {
2747
+ prom = Promise.all(this.willUpdateProps.map((f) => f.call(component, props)));
2748
+ });
2567
2749
  await prom;
2568
2750
  if (fiber !== this.fiber) {
2569
2751
  return;
@@ -2671,10 +2853,6 @@ class ComponentNode {
2671
2853
  get name() {
2672
2854
  return this.component.constructor.name;
2673
2855
  }
2674
- get subscriptions() {
2675
- const render = batchedRenderFunctions.get(this);
2676
- return render ? getSubscriptions(render) : [];
2677
- }
2678
2856
  }
2679
2857
 
2680
2858
  const TIMEOUT = Symbol("timeout");
@@ -2796,6 +2974,9 @@ class Component {
2796
2974
  this.env = env;
2797
2975
  this.__owl__ = node;
2798
2976
  }
2977
+ get plugins() {
2978
+ return this.__owl__.pluginManager.plugins;
2979
+ }
2799
2980
  setup() { }
2800
2981
  render(deep = false) {
2801
2982
  this.__owl__.render(deep === true);
@@ -5911,6 +6092,171 @@ class Scheduler {
5911
6092
  // interactions with other code, such as test frameworks that override them
5912
6093
  Scheduler.requestAnimationFrame = window.requestAnimationFrame.bind(window);
5913
6094
 
6095
+ // import { PluginCtor } from "./component";
6096
+ class Plugin {
6097
+ constructor() {
6098
+ this.plugins = {};
6099
+ this.resources = {};
6100
+ this.__meta__ = { isDestroyed: false };
6101
+ // getResource(name: string) {
6102
+ // // todo
6103
+ // }
6104
+ // dispatchTo(resourceName, ...args) {
6105
+ // for (let handler of this.getResource(name)) {
6106
+ // if (typeof handler === "function") {
6107
+ // handler(...args);
6108
+ // } else {
6109
+ // throw new Error("resource value should be a function")
6110
+ // }
6111
+ // }
6112
+ // }
6113
+ }
6114
+ setup() { }
6115
+ destroy() { }
6116
+ get isDestroyed() {
6117
+ return this.__meta__.isDestroyed;
6118
+ }
6119
+ }
6120
+ Plugin.id = "";
6121
+ Plugin.dependencies = [];
6122
+ // can act and replace another plugin
6123
+ // static replaceOtherPlugin: null | string = null;
6124
+ // can define the type of resources, and some information, such as, is the
6125
+ // resource global or not
6126
+ Plugin.resources = {};
6127
+ class PluginManager {
6128
+ constructor(parent, Plugins) {
6129
+ this._children = [];
6130
+ this._parent = parent;
6131
+ parent === null || parent === void 0 ? void 0 : parent._children.push(this);
6132
+ this.plugins = parent ? Object.create(parent.plugins) : {};
6133
+ this.resources = parent ? Object.create(parent.resources) : {};
6134
+ // instantiate all plugins
6135
+ const plugins = [];
6136
+ const PLUGINS = Array.isArray(Plugins) ? Plugins : Plugins();
6137
+ for (let P of toposort(PLUGINS, this.plugins)) {
6138
+ if (P.resources) {
6139
+ for (let r in P.resources) {
6140
+ const sources = reactive({});
6141
+ const fn = derived(() => {
6142
+ const result = [];
6143
+ for (let name in sources) {
6144
+ const plugin = sources[name];
6145
+ const value = plugin.resources[r];
6146
+ if (Array.isArray(value)) {
6147
+ result.push(...value);
6148
+ }
6149
+ else {
6150
+ result.push(value);
6151
+ }
6152
+ }
6153
+ return result;
6154
+ });
6155
+ this.resources[r] = { sources, fn };
6156
+ }
6157
+ }
6158
+ const p = new P();
6159
+ plugins.push(p);
6160
+ this.plugins[P.id] = p;
6161
+ for (let dep of P.dependencies) {
6162
+ p.plugins[dep] = this.plugins[dep];
6163
+ }
6164
+ }
6165
+ // aggregate resources
6166
+ for (let name in this.plugins) {
6167
+ const p = this.plugins[name];
6168
+ for (let r in p.resources) {
6169
+ this.resources[r].sources[name] = p;
6170
+ // const value = p.resources[r];
6171
+ // if (Array.isArray(value)) {
6172
+ // this.resources[r].push(...value);
6173
+ // } else {
6174
+ // this.resources[r].push(value);
6175
+ // }
6176
+ }
6177
+ }
6178
+ // setup phase
6179
+ for (let p of plugins) {
6180
+ p.setup();
6181
+ }
6182
+ }
6183
+ destroy() {
6184
+ for (let children of this._children) {
6185
+ children.destroy();
6186
+ }
6187
+ const plugins = [];
6188
+ for (let id in this.plugins) {
6189
+ if (this.plugins.hasOwnProperty(id)) {
6190
+ const plugin = this.plugins[id];
6191
+ // resources
6192
+ for (let r in plugin.resources) {
6193
+ delete this.resources[r].sources[id];
6194
+ }
6195
+ plugins.push(this.plugins[id]);
6196
+ delete this.plugins[id];
6197
+ }
6198
+ }
6199
+ while (plugins.length) {
6200
+ const plugin = plugins.pop();
6201
+ plugin.destroy();
6202
+ plugin.__meta__.isDestroyed = true;
6203
+ }
6204
+ }
6205
+ getPlugin(name) {
6206
+ return this.plugins[name] || null;
6207
+ }
6208
+ getResource(name) {
6209
+ return this.resources[name].fn();
6210
+ }
6211
+ }
6212
+ function toposort(Plugins, plugins) {
6213
+ const visited = new Set();
6214
+ const temp = new Set();
6215
+ const sorted = [];
6216
+ const mapping = {};
6217
+ for (const P of Plugins) {
6218
+ if (!P.id.length) {
6219
+ throw new Error(`Plugin ${P.name} has no id`);
6220
+ }
6221
+ if (P.id in mapping) {
6222
+ throw new Error("A plugin with the same ID is already defined");
6223
+ }
6224
+ mapping[P.id] = P;
6225
+ }
6226
+ const visit = (P) => {
6227
+ if (visited.has(P.id))
6228
+ return;
6229
+ if (temp.has(P.id)) {
6230
+ throw new Error(`Circular dependency: ${P.id}`);
6231
+ }
6232
+ temp.add(P.id);
6233
+ for (const dep of P.dependencies || []) {
6234
+ const Dep = mapping[dep];
6235
+ if (Dep) {
6236
+ visit(Dep);
6237
+ }
6238
+ else {
6239
+ if (!(dep in plugins)) {
6240
+ throw new Error(`Missing dependency "${dep}" for plugin "${P.id}"`);
6241
+ }
6242
+ }
6243
+ }
6244
+ temp.delete(P.id);
6245
+ visited.add(P.id);
6246
+ sorted.push(P);
6247
+ };
6248
+ for (const P of Plugins) {
6249
+ visit(P);
6250
+ }
6251
+ return sorted;
6252
+ }
6253
+ function usePlugins(Plugins) {
6254
+ const node = getCurrent();
6255
+ const manager = new PluginManager(node.pluginManager, Plugins);
6256
+ node.pluginManager = manager;
6257
+ onWillDestroy(() => manager.destroy());
6258
+ }
6259
+
5914
6260
  let hasBeenLogged = false;
5915
6261
  const apps = new Set();
5916
6262
  window.__OWL_DEVTOOLS__ || (window.__OWL_DEVTOOLS__ = { apps, Fiber, RootFiber, toRaw, reactive });
@@ -5923,6 +6269,7 @@ class App extends TemplateSet {
5923
6269
  this.name = config.name || "";
5924
6270
  this.Root = Root;
5925
6271
  apps.add(this);
6272
+ this.pluginManager = new PluginManager(null, config.Plugins || []);
5926
6273
  if (config.test) {
5927
6274
  this.dev = true;
5928
6275
  }
@@ -6138,6 +6485,43 @@ const mainEventHandler = (data, ev, currentTarget) => {
6138
6485
  return stopped;
6139
6486
  };
6140
6487
 
6488
+ class Registry {
6489
+ constructor(name, schema) {
6490
+ this._map = reactive(Object.create(null));
6491
+ this._name = name || "registry";
6492
+ this._schema = schema;
6493
+ const entries = derived(() => {
6494
+ return Object.entries(this._map)
6495
+ .sort((el1, el2) => el1[1][0] - el2[1][0])
6496
+ .map(([str, elem]) => [str, elem[1]]);
6497
+ });
6498
+ const items = derived(() => entries().map((e) => e[1]));
6499
+ Object.defineProperty(this, "items", {
6500
+ get() {
6501
+ return items;
6502
+ },
6503
+ });
6504
+ Object.defineProperty(this, "entries", {
6505
+ get() {
6506
+ return entries;
6507
+ },
6508
+ });
6509
+ }
6510
+ set(key, value, sequence = 50) {
6511
+ if (this._schema) {
6512
+ validate(value, this._schema);
6513
+ }
6514
+ this._map[key] = [sequence, value];
6515
+ }
6516
+ get(key, defaultValue) {
6517
+ const hasKey = key in this._map;
6518
+ if (!hasKey && arguments.length < 2) {
6519
+ throw new Error(`KeyNotFoundError: Cannot find key "${key}" in this registry`);
6520
+ }
6521
+ return hasKey ? this._map[key][1] : defaultValue;
6522
+ }
6523
+ }
6524
+
6141
6525
  function status(component) {
6142
6526
  switch (component.__owl__.status) {
6143
6527
  case 0 /* NEW */:
@@ -6211,21 +6595,26 @@ function useChildSubEnv(envExtension) {
6211
6595
  * NaN !== NaN, which will cause the effect to rerun on every patch.
6212
6596
  */
6213
6597
  function useEffect(effect, computeDependencies = () => [NaN]) {
6598
+ const context = getCurrent().component.__owl__.signalComputation;
6214
6599
  let cleanup;
6215
6600
  let dependencies;
6216
- onMounted(() => {
6217
- dependencies = computeDependencies();
6601
+ const runEffect = () => runWithComputation(context, () => {
6218
6602
  cleanup = effect(...dependencies);
6219
6603
  });
6604
+ const computeDependenciesWithContext = () => runWithComputation(context, computeDependencies);
6605
+ onMounted(() => {
6606
+ dependencies = computeDependenciesWithContext();
6607
+ runEffect();
6608
+ });
6220
6609
  onPatched(() => {
6221
- const newDeps = computeDependencies();
6610
+ const newDeps = computeDependenciesWithContext();
6222
6611
  const shouldReapply = newDeps.some((val, i) => val !== dependencies[i]);
6223
6612
  if (shouldReapply) {
6224
6613
  dependencies = newDeps;
6225
6614
  if (cleanup) {
6226
6615
  cleanup();
6227
6616
  }
6228
- cleanup = effect(...dependencies);
6617
+ runEffect();
6229
6618
  }
6230
6619
  });
6231
6620
  onWillUnmount(() => cleanup && cleanup());
@@ -6289,9 +6678,14 @@ exports.App = App;
6289
6678
  exports.Component = Component;
6290
6679
  exports.EventBus = EventBus;
6291
6680
  exports.OwlError = OwlError;
6681
+ exports.Plugin = Plugin;
6682
+ exports.PluginManager = PluginManager;
6683
+ exports.Registry = Registry;
6292
6684
  exports.__info__ = __info__;
6293
6685
  exports.batched = batched;
6294
6686
  exports.blockDom = blockDom;
6687
+ exports.derived = derived;
6688
+ exports.effect = effect;
6295
6689
  exports.htmlEscape = htmlEscape;
6296
6690
  exports.loadFile = loadFile;
6297
6691
  exports.markRaw = markRaw;
@@ -6308,6 +6702,7 @@ exports.onWillStart = onWillStart;
6308
6702
  exports.onWillUnmount = onWillUnmount;
6309
6703
  exports.onWillUpdateProps = onWillUpdateProps;
6310
6704
  exports.reactive = reactive;
6705
+ exports.signal = signal;
6311
6706
  exports.status = status;
6312
6707
  exports.toRaw = toRaw;
6313
6708
  exports.useChildSubEnv = useChildSubEnv;
@@ -6315,15 +6710,17 @@ exports.useComponent = useComponent;
6315
6710
  exports.useEffect = useEffect;
6316
6711
  exports.useEnv = useEnv;
6317
6712
  exports.useExternalListener = useExternalListener;
6713
+ exports.usePlugins = usePlugins;
6318
6714
  exports.useRef = useRef;
6319
6715
  exports.useState = useState;
6320
6716
  exports.useSubEnv = useSubEnv;
6321
6717
  exports.validate = validate;
6322
6718
  exports.validateType = validateType;
6323
6719
  exports.whenReady = whenReady;
6720
+ exports.withoutReactivity = withoutReactivity;
6324
6721
  exports.xml = xml;
6325
6722
 
6326
6723
 
6327
- __info__.date = '2025-09-23T07:17:45.055Z';
6328
- __info__.hash = '5211116';
6724
+ __info__.date = '2025-11-21T12:26:21.756Z';
6725
+ __info__.hash = '938f3f6';
6329
6726
  __info__.url = 'https://github.com/odoo/owl';