-
Fill hreflang alternates,
og:locale:alternate,x-defaultand the locale switcher with each locale's own route params instead of the current locale's.On a page whose slug is translated per locale, the alternate URLs and the locale-switch target previously reused the current locale's params — pointing at URLs that do not exist in the target locale. Where the page type's
pageIndexregistration implementslocateand reports a complete per-locale map, each locale now gets its own slug, and locales the page does not exist in are omitted from the alternates rather than guessed at.Omission follows only from that complete map. A
locatethat resolves just the locale it was called in omits the map entirely, and its pages keep filling every alternate from the current locale's params — a partial map cannot distinguish "no page in this locale" from "did not look", so nothing is dropped on its word.The lookup runs alongside the page queries and is bounded by a 2s SSR budget. If it breaches, that render falls back to the previous behaviour — every alternate filled from the current locale's params — while the request completes and warms the server cache, so the next render is correct. Page types whose connector provides no
locateare unchanged, as are single-language projects and pages without dynamic params — neither performs a lookup. -
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 resolved
clientEnv, 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.The
page-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.
-
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 resolved
ClientEnvthat handlers receive is unaffected and stays in@laioutr-core/orchestr.Editor clients build the wire payload by hand. While it was
unknown, 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.
Created
July 30, 2026 12:42
-
-
Save screeny05/dac8a0d467787348f15d95162481cf85 to your computer and use it in GitHub Desktop.
Frontend-Core 0.38.0
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment