Two additions to OWL — Odoo’s reactive component framework — address pain points that developers have been working around for a while. One solves the “flash of empty state” problem when components depend on async data. The other eliminates the need to define types twice when using OWL’s schema validation system.
Hold the First Render Until the Data Is Ready
OWL’s asyncComputedis a reactive primitive that wraps an async function — think of it as a computed value that can fetch data from an API, run a database query, or perform any async operation. The value updates automatically when its reactive dependencies change, and the framework tracks loading and error states.
The missing piece was coordination with the component lifecycle. Components that depend on async computed data would mount immediately and render in a loading state, then re-render once the data arrived. For some use cases that’s fine. For others — initial page loads, navigation transitions, server-rendered content — you want the component to wait until its data is ready before painting anything to the screen.
The new currentPromise()method makes that possible. It returns a promise that resolves as soon as no fetcher run is in flight. If a fetch is running, it waits for that fetch (or any subsequent fetch that supersedes it) to settle. If nothing is running, it resolves immediately. The promise never rejects — errors are surfaced through the existing error()accessor.
The practical use is a single line in onWillStart:
onWillStart(() => this.data.currentPromise())That’s it. The component won’t render until the initial async data has arrived. No loading spinners, no conditional rendering, no race conditions between the lifecycle hook and the reactive system. The implementation tracks the in-flight run as a deferred that moves in lockstep with the loading state, and it’s resolved on dispose so awaiters never hang if the component is destroyed before the fetch completes.
Schema Types Without the Duplication
OWL includes a runtime type validation system. You define schemas using a builder API — t.object(),t.string(), t.number(),t.array()— and the framework validates props, state, and other data against those schemas at runtime. It’s useful for catching bugs early, especially in a codebase that doesn’t use TypeScript end-to-end.
The frustration was that you couldn’t easily reference the type that a schema describes. If you had a schema for a user object, and you wanted to type-annotate a function that accepts a user, you had to manually define a separate TypeScript interface or JSDoc typedef that duplicated the schema’s shape. Change the schema, and you had to remember to update the type definition too.
The new .type property solves this. Every schema validator now carries a phantom type field that TypeScript can infer. In TypeScript, you writetype User = typeof userSchema.typeand get a properly inferred type that matches the schema’s shape. In plain JavaScript with JSDoc, you use @param {userSchema.type}in your documentation comments. The type stays in sync with the schema because it is the schema.
Intersection Type Defaults Now Actually Apply
A related fix addresses a bug in how OWL’s type system handled default values inside intersection types. The t.and()combinator lets you merge multiple schemas — useful for mixins, composed props, and layered configurations. But theapplyDefaultsfunction didn’t walk into intersection validators, so any default values declared inside a t.and() schema were silently ignored.
The fix tags intersection validators with their member types and folds the default-application logic over each member, threading the value through. Copy-on-write semantics are preserved, so the input object is never mutated. If nothing changes, the original value is returned as-is.
Package Fixes for Real-World Toolchains
Two additional fixes address packaging issues that surfaced as OWL gets used in more diverse environments. The CommonJS bundle file was renamed from .js to .cjsbecause OWL’spackage.json declares "type": "module", which caused Node.js to misinterpret the CommonJS bundle as an ES module and throw a ReferenceError onmodule.exports.
Separately, several phantom brand symbols used by OWL’s TypeScript types (isProps, hasDefault,isOptional, typeBrand) are now exported from the package. These symbols are type-only — they add nothing to the JavaScript bundle — but without the export, downstream projects that emitted their own .d.tsfiles would hit a TypeScript error about inaccessible unique symbols. The Odoo Spreadsheet project was the first to run into this, but any library that re-exports OWL-typed values would have encountered the same issue.
The Bigger Picture
These changes reflect OWL’s maturation from an internal framework into something that needs to work across different environments, build systems, and developer workflows. Async computed values with lifecycle coordination, proper type extraction, CommonJS compatibility, and clean TypeScript declarations are the kind of plumbing that libraries need to get right before they can be adopted confidently outside their original context. OWL is getting there, one release at a time.