Commit cab5256
feat(instantsearch): SSR support for composition multifeed (#6975)
* feat(helper): support multifeed composition responses
Compositions can return multiple result sets (feeds), each identified by
a `feedID`. Previously, `_runComposition` spliced only 1 result per
derived helper, discarding additional feeds. This changes `queriesCount`
to `Infinity` so all feeds are captured, and builds a `_feedResults` map
and `_feedOrder` array on the primary `SearchResults` for downstream
consumption by `connectFeeds` (Layer 2).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: bump bundlesize limits for multifeed helper changes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(helper): omit queriesCount for composition instead of using Infinity
Use undefined queriesCount to signal "take all results" in _dispatchAlgoliaResponse,
avoiding the Infinity hack. When queriesCount is undefined (composition path), use
results directly; when defined (regular search path), splice as before.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(helper): remove composition type definitions, defer to layer 2
The feedID, _feedResults, and _feedOrder type definitions have no consumers
yet. Defer the typing decision (interface vs class, naming) to Layer 2 when
connectFeeds introduces the first consumer.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(helper): use lastResults.feeds array instead of _feedResults map
Replace _feedResults (Record<string, SearchResults>) and _feedOrder
(string[]) with a single lastResults.feeds (CompositionSearchResults[])
ordered array. This simplifies the API and ensures feeds survive the
SSR getInitialResults → JSON → hydration round-trip naturally.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(helper): avoid circular ref in multifeed lastResults
Create a separate SearchResults instance for lastResults instead of
reusing feeds[0], which caused circular references during
JSON.stringify (lastResults.feeds[0] === lastResults).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* apply suggestions from code review
Co-authored-by: Haroen Viaene <hello@haroen.me>
* refactor(instantsearch): extract storeRenderState to shared util
Move storeRenderState from module-private in index.ts to render-args.ts
so FeedContainer can reuse it in the feeds connector (Layer 2).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(instantsearch): centralize index widget types, widen for feedContainer
Introduce indexWidgetTypes const and IndexWidgetType as single source of
truth for index-like widget types. Update isIndexWidget, IndexWidgetDescription,
BuiltinTypes, and BuiltinWidgetTypes to use them. Add 'ais.feeds' and
'ais.feedContainer' to builtin type unions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(metadata): use isIndexWidget to recurse into feed containers
The metadata middleware hardcoded 'ais.index' instead of using
isIndexWidget, so widgets inside feed containers were invisible
to the Algolia Crawler metadata.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(instantsearch): add FeedContainer for per-feed widget subtrees
Made-with: Cursor
* feat(instantsearch): add connectFeeds connector for multifeed compositions
Made-with: Cursor
* test(instantsearch): add unit tests for FeedContainer and connectFeeds
Made-with: Cursor
* chore: bump bundlesize limits for multifeed connector
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(instantsearch): prevent double-init in FeedContainer with initialized flag
Replace `instantSearchInstance.started` check with a local `initialized` flag
so that child widgets added before `container.init()` are not eagerly
initialized. This prevents double-init when the parent index tree calls
`init()` on the container after widgets have already been added.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(instantsearch): simplify connectFeeds to stateless feedIDs computation
Remove FeedContainer lifecycle management from the connector — widget layers
now own container creation, registration, and cleanup. The connector only
computes feedIDs from results.feeds and exposes them as render state.
- Remove `widgets` param and internal `feedContainers` map
- Change render state from `feeds: [{ feedID, container }]` to `feedIDs: string[]`
- Make `getWidgetRenderState` stateless (derives feedIDs from results)
- Simplify `dispose()` to only call `unmountFn()`
- Extract shared test helpers to `test/createFeedsTestHelpers.ts`
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(instantsearch): fix lint errors in feeds test helpers
- Use `import type` for type-only imports
- Fix import ordering in test files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* review: use setState
* feedback: chain state when removing widgets (same as in dispose below)
* feedback: register FeedsWidgetDescription in IndexRenderState
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(instantsearch): add feeds widget for vanilla JS
Add `feeds()` widget that creates per-feed DOM containers and manages
FeedContainer lifecycle. Each feed gets a scoped `<div class="ais-Feeds-feed">`
and its own FeedContainer registered with the parent index.
- DOM creation, reuse, reorder, and cleanup per feed
- Deferred removal with coalesced timer (matches connectDynamicWidgets pattern)
- Dispose merges active + pending containers for clean teardown
- Exported from both main and UMD entrypoints
- 11 unit tests + 1 integration test with real search lifecycle
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(react-instantsearch): add Feeds component and useFeeds hook
Add `<Feeds>` component that creates per-feed `IndexContext.Provider` scopes,
and `useFeeds` hook wrapping `connectFeeds` via `useConnector`.
- FeedContainer creation + registration fused in render body for SSR compat
- Deferred removal with coalesced timer (single ref pattern from useWidget)
- Unmount cleanup merges active + pending containers
- 5 unit tests + 2 integration tests (real InstantSearch + SSR)
- Exported from react-instantsearch-core
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(vue-instantsearch): add AisFeeds component
Add `<ais-feeds>` component with per-feed `AisFeedProvider` scoping via
Vue's provide/inject for `$_ais_getParentIndex`.
- Container reconciliation in watcher (side-effect-free render)
- Deferred removal with coalesced timer (single timer pattern)
- Unmount cleanup merges active + pending containers
- 5 unit tests + 1 integration test with real InstantSearch
- Exported as AisFeeds from vue-instantsearch
- Updated mock and index tests for compositionID support
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix: fix import ordering lint errors across Layer 3 files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(instantsearch): pass renderOptions to feedContainer.render() for type safety
FeedContainer.render() ignores the argument internally but IndexWidget type
requires it. Pass renderOptionsRef to satisfy TypeScript.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* temp: add examples
* fix: exclude composition examples from v4 type-check
@algolia/composition doesn't exist under algoliasearch v4.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(react-instantsearch-core): rename Feeds children render-prop to renderFeed
Switch from a children render function to a dedicated `renderFeed` prop with
an object argument `({ feedID })`. Keeps the API consistent with other React
InstantSearch components (`hitComponent`, `itemComponent`, `bannerComponent`),
which all use explicit, non-children customization props.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* fix(vue-instantsearch): use indexOf instead of findIndex in mock removeWidgets
findIndex expects a predicate function, not the widget object — the call
threw at runtime the first time removeWidgets was invoked.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(instantsearch): extract isTwoPassWidget utility
Replace inline `$$type === 'ais.dynamicWidgets'` checks with a shared
`isTwoPassWidget` predicate. This prepares for the feeds widget which
also requires a two-pass SSR cycle, and fixes a missing `shouldRefetch ||`
in InitializePromise.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feat(instantsearch): add SSR support for composition multifeed
Serialize per-feed results as `compositionFeedsResults` in
`getInitialResults`, hydrate them in `hydrateSearchClient`, and
reconstruct `lastResults.feeds` in the feeds connector so that
composition multifeed works with server-side rendering.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* test(instantsearch): add composition multifeed SSR integration test
End-to-end integration test covering the full SSR cycle: server-side
rendering with getInitialResults, hydration via hydrateSearchClient,
and client-side rehydration of per-feed results.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: add Next.js App Router composition example
Demo app showing composition multifeed with SSR using
react-instantsearch-nextjs. Excluded from v4 type-check
since it uses composition-only APIs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* refactor(react-instantsearch-core): adapt Feeds usages to renderFeed prop
Update the SSR test and the Next.js App Router composition example to the
new `renderFeed` prop introduced on Layer 3.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* chore: bump bundlesize limit for SSR multifeed
The composition multifeed SSR additions push the development bundle just
over the 256.5 kB ceiling.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Haroen Viaene <hello@haroen.me>
Co-authored-by: Dan Rodriguez <drodriguln@icloud.com>1 parent 9340185 commit cab5256
32 files changed
Lines changed: 952 additions & 30 deletions
File tree
- examples/react/next-app-router-with-composition
- app
- components
- lib
- packages
- instantsearch.js/src
- connectors
- feeds
- __tests__
- infinite-hits
- lib
- __tests__
- utils
- __tests__
- types
- react-instantsearch-core/src
- lib
- server
- __tests__
- react-instantsearch-nextjs/src
Lines changed: 34 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
Lines changed: 7 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
Lines changed: 173 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
Binary file not shown.
Lines changed: 22 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
Lines changed: 20 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
Lines changed: 13 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
Lines changed: 19 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
Lines changed: 15 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
Lines changed: 9 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
0 commit comments