Changelogs
Orchestr Changelog
Changelog for @laioutr-core/orchestr following Keep a Changelog and Semantic Versioning.
All notable changes to Orchestr (@laioutr-core/orchestr), the Laioutr data-fetching and query orchestration layer, will be documented in this file.
0.41.1 - 2026-08-13
Patch Changes
- Render a streamed query result once it has settled, rather than once per response chunk
On a server-rendered page, the first client-side navigation that ran a query re-rendered on every chunk of the streamed response — including the window where a link's entity ids have arrived but the entities themselves still report no components. Sections reading those components rendered against that half-loaded state and threw, which read as intermittent because a retry usually landed after the response had finished.
A streamed response is now published once, complete, and a query started from a server-rendered page keeps the data already on screen until it finishes.
0.38.2 - 2026-07-31
Patch Changes
- Report
listPagesFrom'sendCursorat any stopping position, not only at thetakeboundary.
A consumer that stopped iterating early — on a wall-clock budget, say — readendCursorasundefinedand could not distinguish that from an exhausted enumeration, so a partial walk was recorded as complete. The resume point is now computed from the walk's live position, whichpaginatehas always tracked.
Two fields make the outcome of a pass unambiguous.exhaustedis the termination signal for an accumulation loop;endCursoris only ever "where the next pass starts",undefinedmeaning the beginning both going in and coming out.progressedreports whether the pass durably advanced — false when it took nothing and false when it threw, in both of which casesendCursoris the token the pass was given. A loop must stop on!progressedas well as onexhausted, or a pass that takes nothing repeats forever.
The token names the position after the last entry handed over, so collect an entry before breaking out of the loop. A pass that throws reports the token it started from rather than the position it died at, so retrying it loses nothing.toArray()callers see no change: draining totakereports the same token as before, and draining to exhaustion still reportsundefined.
0.38.1 - 2026-07-30
Patch Changes
- Add
listPagesFromfor page-index enumerations that cannot finish in one request.paginatetakes an optionalstartCursorand exposescursor/consumedSinceCursor, so a walk can report where it stopped.listPagesFrom(token, { take, resumeFrom })builds on that: it returns a stream with anendCursorthe caller persists to continue later. Collecting each pass'sendCursoryields independently servable shards, which is what a sharded sitemap needs.
It is cursor-addressed and never touches the page-index chunk cache, so no TTL bounds a consumer's progress across visits.listPagesis unchanged and keeps serving the cached enumeration exactly as before.
Page-index handlers receive an optionalstartCursor; pass it topaginateto become resumable. Ignoring it keeps today's behaviour, butlistPagesFromthrows for such a handler rather than silently restarting at entry 0 on every pass. Both shipped product connectors are resumable.
0.38.0 - 2026-07-29
Minor Changes
- Add the
pageIndexorchestr handler kind — one registration per page type that owns that page type's whole page-space.defineOrchestr.pageIndex({ for, label?, batchSize?, list, search?, count?, locate?, cache?, order? }):listwalks the whole page-space in stable order, returning onePageIndexEntryper concrete page ({ params, subject?, meta }) as an array or async iterable; the newpaginate()helper turns a cursor-paged platform API into one. It is called withbatchSize— how many entries the platform serves in a single request, declared once on the registration and defaulting to 100 — and never with a bound, so a walk always caches a complete enumeration.searchanswers a search term with a relevance-ordered top-N, receiving thetermplus atakealready clamped tobatchSize. It is optional: without it a page type still answers search terms, because the runner scans the first 1000 enumerated entries and matches them on title and route params. Implementingsearchbuys relevance ordering and coverage past that scan rather than the capability itself.countsupplies a cheap total for chunked sitemaps and picker totals; consumers degrade when it is absent.locateis a point lookup returningPageIndexLocateResult({ subject?, meta?, locales? }) — a page's route params in every locale it exists in, plus the located page's metadata in the locale the lookup was made in.localescarries a deliberate distinction: present, it is the complete set, and a locale missing from it means the page has no counterpart there, so consumers drop that alternate rather than guess a URL. Absent, it means the connector resolved only the locale it was called in, and consumers fall back to that locale's params. A registration that can answer for one locale must omitlocalesrather than return a single-key map, which would assert absence for every locale it never looked up.cachetunes the enumerate, search and locate tiers independently; walks are cached in cursor-page chunks with stale-while-revalidate and a subject tag index.orderbreaks ties between registrations, higher wins.
Every handler receives the resolvedclientEnv, so a connector scopes its platform reads to the active market withclientEnv.market.id— the same value the runner keys its caches by.
Consumer surface is auto-imported server utils:listPages()enumerates a page type in stable order andsearchPages()returns a relevance-ordered top-N, both as aPageIndexEntryStream(for await,.toArray());countPages()returns a page type's cheap total;locatePage()performs the point lookup; andinvalidateEntity()drops cached chunks referencing an entity.
Thepage-index/listandpage-index/locateendpoints serve these to editor clients under the secret-protected/api/laioutr/namespace.locateis also served ungated atPOST /api/orchestr/page-index/locate, which the frontend itself calls to resolve a page's per-locale slugs — that lookup runs during client-side navigation as well as SSR, so it can never hold the project secret, and it discloses only the route params the rendered hreflang tags publish anyway. Reverse proxies or edge rules that restrict the app's API paths must allow it. Thepage-index/listendpoint validates each enumerated entry on its own and drops the ones that fail with a warning, so a single malformed entry costs one page rather than the whole enumeration. Reflection gains apageIndexmap keyed by page-type token: a key means the type is enumerable,locatemarks the point-lookup capability, andlabel/appLabel/logoUrlcarry the providing app's identity for editor pickers.PageIndexEntry,PageIndexLocateResult,ReflectedPageIndexand the endpoint request/response schemas are exported from@laioutr-core/core-types/orchestr;PageSubjectReffrom@laioutr-core/core-types/common.
Page types without a registration behave exactly as before — an empty stream and one warning. Providers are never required to implement this. - Breaking: Type the
clientEnvfield of thequery-templatesandpage-indexrequest schemas asWireClientEnvrather thanunknown.
Breaking:WireClientEnvnow lives in@laioutr-core/core-types/orchestr, alongside the request schemas that carry it, and is no longer exported from@laioutr-core/orchestr. A handler for theorchestr:client-env:modifyhook takes it from there instead:// before import type { WireClientEnv } from '#orchestr/types'; // after import type { WireClientEnv } from '@laioutr-core/core-types/orchestr';
The resolvedClientEnvthat handlers receive is unaffected and stays in@laioutr-core/orchestr.
Editor clients build the wire payload by hand. While it wasunknown, any object satisfied the type — and because every field ofWireClientEnvis optional, a misspelled key such asmarketidformarketIdalso passed validation, so the request resolved against the default market with no error anywhere. Such a key is now a compile error at the call site, and a request carrying a malformedclientEnvis rejected with400naming the offending path instead of failing further in as a500.
0.37.1 - 2026-07-25
Patch Changes
ClientEnvnow includes adomainfield — the market domain (host, path, language) the current request resolved to. Read it for the request's canonical host instead of assumingmarket.defaultDomain.
The i18n config check now warns when two domains in the same market use the same language, which makes the resolved domain ambiguous — give them region-qualified locales (e.g.de-DEvsde-AT).
0.37.0 - 2026-07-23
Minor Changes
- Breaking: Make
clientEnv.isPreviewa server-verified fact instead of a browser claim, so a handler can safely return unpublished content when it is set.
The client env is now two types.WireClientEnvis what the browser sends (isPreview,previewToken,marketId,languageId,custom) and is untrusted. It no longer carrieslocaleorcurrency— the server derives both from the market and language it resolves, and a request that still sends them has them ignored.ClientEnvis what handlers receive, and it is produced only byresolveClientEnv(). That function verifies the presented preview token, drops it before handlers can see it, and turns the wire'smarketId/languageIdinto fullmarket/languageobjects validated against the project's i18n config — so the language a handler serves can never disagree with the market it reads.isPreviewis true only when the client asked for preview and the server verified the token; a middleware can no longer overridemarket,languageorisPreview.
Query, link and component caches are preview-aware: keys carry the preview stage, and caching is bypassed entirely while previewing, so unpublished content is never stored and can never be served to a shopper.
AddsinvalidateOrchestrQueries()(auto-imported) to drop every stored query result at once, for changes that are not part of a query's cache key — entering or leaving content preview being the motivating case.
Breaking:ClientEnvnow carries requiredmarketandlanguage. Handlers keep reading it as before, but anything that builds one by hand must supply them, or go throughresolveClientEnv().
Before:await runQuery(Token, args, { locale: 'de-DE', currency: 'EUR', isPreview: false }, event);
After:await runQuery(Token, args, resolveClientEnv(event, rawClientEnvFromRequest), event);ClientEnv.localeandClientEnv.currencyare deprecated. They keep resolving, but they are flat copies of fields the resolved objects already carry, and the resolved objects also carry the region codes, fallback chain and domains the strings drop.
Before:const { locale, currency } = clientEnv;
After:const locale = clientEnv.language.code; const currency = clientEnv.market.currency;
If your handler's output varies by market, append a scalar such asclientEnv.market.slugin your owngetKeySuffix— the default cache key deliberately does not widen withClientEnv, andmarket/languageare cyclic, soJSON.stringify(clientEnv)throws.
Cache keys now include the preview stage, so entries written by earlier versions are orphaned. Expect one cold-cache window against a shared cache after deploying; nothing has to be flushed by hand.
0.35.0 - 2026-07-14
Minor Changes
- Breaking: Media libraries are now connected as an Orchestr integration facet. A connector declares static capabilities (search, tags, folders, sorts, upload transfer) and uses opaque-cursor pagination, explicit type/tag filtering, optional folder navigation, and proxied or staged upload with per-file results. Define one on the app's Orchestr builder instead of the standalone factory:
// Before export default defineMediaLibraryProvider({ name, label, iconSrc, list, upload }); // After export default defineShopify.mediaLibrary({ capabilities: { search: true, folders: false, sorts, upload: { transfer: 'staged' } }, list, createUploadTargets, finalizeUploads, });defineMediaLibraryProvider()still works as a deprecated shim — existing connectors keep registering without a rewrite, in a degraded mode (no folders, no staged upload, no declared sorts).ProjectFrontendContext.mediaLibrariesnow carries descriptors{ id, label, iconSrc, capabilities }.
The Shopify connector uploads via staged targets and blocks until each file isREADYbefore returning it (one failed file no longer sinks the batch). The Shopware connector gains folder browsing over the real media-folder tree.
This frontend-core version is the threshold for the CockpitmediaLibraryV2capability gate; the Cockpit media picker is updated separately to speak the new contract.
Folder browsing is folded into the singlelistmethod:MediaListResult.folderscarries the queried location's subfolders on the first (cursorless) page; the separatebrowseFoldersmethod andmedia-foldersroute are removed. Every media source now carries an optionalorigin({ libraryId, externalId? }), stamped by the.mediaLibrary()wrapper, which also validates all adapter output at the trust boundary (canonical Zod parse, URL-scheme guard — including nested poster/cover images — capability/response agreement) and logs a server-side warning for every dropped item. Browse items may carry a transientstatus(processing/failed) surfaced in the picker grid.
Media-library handlers now receive the per-request context built by the app'sextendRequestinitwares as their second argument —list(query, ctx)— so adapters use the initware-provided clients instead of constructing their own.MediaQuerygainsscope: 'folder' | 'all'to distinguish a whole-library search from browsing the root level (on Shopware, root holds only unfiled assets), and both bundled adapters now honorMediaQuery.typeserver-side.
0.34.0 - 2026-07-13
Minor Changes
- Breaking: The
@laioutr/loggernuxt module has been removed. It is no longer installed byfrontend-coreororchestr, and the package itself is no longer published. Internal logging now goes throughconsola(Nuxt's standard logger). This removes the pino dependency chain and prepares for an OpenTelemetry-based observability setup.
What this means for your project:- The auto-imported
useLogger()composable and server util are gone. Useconsolainstead:// Before const logger = useLogger('my-scope'); // After import { consola } from 'consola'; const logger = consola.withTag('my-scope'); - The
$loggerglobal (globalThis.$logger,event.node.req.log) is no longer provided. - The
ltrLoggerconfig key (logLevelServer,logLevelClient,logForDevelopment,logNitroRequestsVerbose,logNitroResponsesVerbose) is no longer read — remove it from yournuxt.config.ts. - Request-id middleware (pino-http request logging,
x-request-idresponse header, Sentry request-id tagging) is no longer included.
- The auto-imported
Patch Changes
- Component reflection now lists each entity component's resolvers with the effective one first — the resolver
get()actually selects at runtime (highestorder, last-registered on ties). They were previously returned in registration order, so tools readingimplementations[0]to attribute a component to its providing app (e.g. the Studio dynamic-data-source picker) could show the wrong app when several installed apps resolve the same component. The picker now shows the icon of the app that actually provides each value.
0.32.1 - 2026-06-30
Patch Changes
- Fix SSR 500 (
[nuxt] instance unavailable) on data-bound pages.renderQueryToWireresolved the Nuxt app at call time viacallHookSync, but it runs inside lazily-evaluated computeds (e.g. the SEO head getters), which execute outside Nuxt's async context — thereuseNuxtApp()throws. The Nuxt app is now captured during composable setup and threaded through, so query-to-wire conversion is safe to run from any phase (render, head serialization, watchers).
0.28.14
Added
- Orchestr: Queries now respect URL aliases and the
isRootconfiguration. Root queries use prefix-less URL params (e.g.,?p=2instead of?queryId[p]=2), resulting in cleaner URLs for listing and search pages.
Fixed
- Orchestr: Fixed query results not updating on client-side navigation. A
markRawoptimization on the orchestr store'squeryResultsprevented Vue from detecting when queries transitioned from loading to resolved; the store now replaces the inner reference after streaming completes to trigger reactivity correctly. - Orchestr: Patch values from valtio state changes are now always plain, serializable objects. Live proxy references and raw valtio targets are no longer leaked into patches, preventing serialization errors via
structuredCloneorpostMessage.
0.28.11
Added
- Orchestr: Exported
OrchestrBuildertypes so apps can re-export their builders with correct TypeScript types.
0.28.9
Fixed
- Orchestr: Fixed
useRoute()returning stale route data in studio preview. Preview mode has no<NuxtPage>, so thepage:finishhook that syncs Nuxt's internal route ref never fired. Preview now emitspage:finishafter each navigation to keepuseRoute()current.
0.28.7
Added
- Orchestr: Cache keys for queries, links, and component resolvers now automatically include
locale:currencyfromClientEnv. This prevents multi-language storefronts from serving stale cross-locale cached data.ComponentResolver.getKeySuffixnow receivesClientEnvas an argument.
Fixed
- Orchestr: Fixed loading-state not updating correctly from async watchers.
0.27.0
Fixed
- Orchestr: Queries now correctly respect all query-aliases during navigation.
0.26.0
Changed
- Orchestr: Removed
inputfrom links. Entities can now be passed directly through links.
0.21.0
Added
- Orchestr: Added
pathproperty to error chunks for easier error attribution. - Orchestr: Queries now respect the default query limit from
RcQueryLoadSpec.
Fixed
- Orchestr: Fixed
shouldLoadbehaviour in query-handlers.
0.20.0
Added
- Orchestr: New API endpoint for clearing cache data.
- Orchestr: Query-handlers can now pass component overrides that take precedence over regular component data for a specific query.
- Orchestr: Passthrough data is now stored by token string instead of token object, fixing issues with restoring passthrough from cache.
0.19.0
Added
- Orchestr: Experimental tracing and summary support. Activate by passing
options: { dev: { enableTracing: true } }with queries.
0.18.0
Added
- Orchestr: Added missing client-side action hooks.
- Orchestr: Added
passthrough.requirefor declaring required passthrough data.
Fixed
- Orchestr: Fixed missing
runWithTracecalls. - Orchestr:
ComponentResolverno longer double-caches components that are already cached.
0.17.0
Added
- Orchestr: Basic request tracer. Activate by sending queries with
options: { dev: { enableTracing: true } }.
0.16.2
Fixed
- Orchestr: Fixed crash when accessing an entity that was not received from the pinia store.
0.16.1
Fixed
- Orchestr: Fixed cache-key escaping.
0.16.0
Added
- Orchestr: Implemented passthrough caching.
0.15.0
Added
- Orchestr: Implemented proper component cache.
0.14.0
Added
- Orchestr: Added
isPreviewproperty toClientEnv. - Orchestr: Introduced
extendRequestas the replacement for the removeduseOnceandextendClientEnv.
Changed
- Orchestr: Removed
useOnceandextendClientEnv. UseextendRequestinstead.
0.13.0
Added
- Orchestr: Implemented caching mechanism.
0.12.0
Added
- Orchestr: Added stable-hash for the orchestr pinia-store.
- Orchestr:
templateProvidersfor queries are now reflected via the reflect API.
Orchestr Devtools (legacy 1.x)
These entries predate the devtools moving onto the Orchestr version line. Devtools changes now appear in the Orchestr versions above, going forward.
1.7.0
Changed
- Orchestr Devtools: Moved to a dedicated Nuxt Devtools tab for a cleaner development experience, replacing the previous standalone overlay panel.
1.6.0
Added
- Orchestr Devtools: Experimental Sankey diagram visualization for query data flow.
1.5.0
Added
- Orchestr Devtools: Added missing component resolver hint to the devtools panel.
1.4.16
Added
- Orchestr Devtools:
projectSecretprotection can now be disabled via configuration.