react-on-rails-pro 17.0.0-rc.7 → 17.0.0-rc.8

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.
@@ -286,24 +286,31 @@ export const createRSCProvider = ({ getServerComponent, domNodeId, }) => {
286
286
  // refetch or reload (#3929). Evict the rejected entry so the next
287
287
  // same-key `getComponent` starts a fresh fetch.
288
288
  //
289
- // Deferred one macrotask (`setTimeout(0)`, not `queueMicrotask`) so
290
- // React's Suspense machinery observes the rejection on the cached
291
- // promise before the entry is removed; a microtask could interleave
292
- // with React's own queue and evict before Suspense reads it. Guarded on
293
- // promise identity so a newer same-key promise (a fresh `getComponent`
294
- // or a `refetchComponent`) installed during that window is not evicted
295
- // by this stale failure. Pins are preserved because the `.finally`
296
- // below still owns the matching `unpin` (mirrors the delete +
297
- // deferred-unpin handoff in `restoreLastSuccessfulPromise`).
289
+ // Deferred past the first macrotask (`setTimeout(0)`, not
290
+ // `queueMicrotask`) so React's Suspense machinery observes the
291
+ // rejection on the cached promise before the entry is removed; a
292
+ // microtask could interleave with React's own queue and evict before
293
+ // Suspense reads it. The extra macrotask also lets the `.finally`
294
+ // below release the in-flight pin before the key becomes available for
295
+ // a fresh same-key `getComponent`; otherwise a repeated failing request
296
+ // can replace the rejected promise with a new pending promise before
297
+ // the user ErrorBoundary commits its fallback (#4522). Guarded on
298
+ // promise identity so a newer same-key promise (such as
299
+ // `refetchComponent`) installed during that window is not evicted by
300
+ // this stale failure.
298
301
  //
299
302
  // NOTE: payloads that RESOLVE with an `Error` value are intentionally
300
303
  // left cached here; whether those should also retry is a separate
301
304
  // `getServerComponent` contract decision tracked apart from #3929.
305
+ let payloadRejected = false;
302
306
  const evictPromiseIfRejected = (error) => {
307
+ payloadRejected = true;
303
308
  setTimeout(() => {
304
- if (fetchRSCPromises.get(key, false) === promise) {
305
- fetchRSCPromises.deletePreservingPins(key);
306
- }
309
+ setTimeout(() => {
310
+ if (fetchRSCPromises.get(key, false) === promise) {
311
+ fetchRSCPromises.deleteWithoutEvict(key);
312
+ }
313
+ }, 0);
307
314
  }, 0);
308
315
  throw error;
309
316
  };
@@ -337,6 +344,14 @@ export const createRSCProvider = ({ getServerComponent, domNodeId, }) => {
337
344
  // resolving mounted routes can evict early visible keys before their
338
345
  // layout effects get to pin them as mounted.
339
346
  setTimeout(() => {
347
+ if (payloadRejected) {
348
+ // The rejected entry is already scheduled for deletion in the
349
+ // next macrotask. Releasing its pin must not reconcile over-cap
350
+ // eviction against healthy entries during this short grace
351
+ // window.
352
+ fetchRSCPromises.unpinWithoutEvict(key);
353
+ return;
354
+ }
340
355
  fetchRSCPromises.unpin(key);
341
356
  }, 0);
342
357
  });
@@ -96,6 +96,8 @@ export declare class BoundedLRU<V> {
96
96
  deleteWithoutEvict(key: string, preservePins?: boolean): void;
97
97
  deletePreservingPins(key: string): void;
98
98
  unpin(key: string): void;
99
+ unpinWithoutEvict(key: string): void;
100
+ private releasePin;
99
101
  /**
100
102
  * Evict least-recently-used unpinned keys until the map is back within
101
103
  * `maxEntries`. `preventEvictingKey`, when given, is treated as un-evictable
@@ -160,6 +160,12 @@ export class BoundedLRU {
160
160
  this.deleteWithoutEvict(key, true);
161
161
  }
162
162
  unpin(key) {
163
+ this.releasePin(key, true);
164
+ }
165
+ unpinWithoutEvict(key) {
166
+ this.releasePin(key, false);
167
+ }
168
+ releasePin(key, reconcileEviction) {
163
169
  this.assertNotEvicting('unpin');
164
170
  const count = this.pins.get(key);
165
171
  if (count === undefined) {
@@ -170,6 +176,9 @@ export class BoundedLRU {
170
176
  return;
171
177
  }
172
178
  this.pins.delete(key);
179
+ if (!reconcileEviction) {
180
+ return;
181
+ }
173
182
  // The in-flight operation that held this pin just settled, so the key is
174
183
  // now the most-recently-used. `get` promotes only when the key is still
175
184
  // present; a key already deleted, e.g. by `restoreLastSuccessfulPromise`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-on-rails-pro",
3
- "version": "17.0.0-rc.7",
3
+ "version": "17.0.0-rc.8",
4
4
  "description": "React on Rails Pro package with React Server Components support",
5
5
  "main": "lib/ReactOnRails.full.js",
6
6
  "type": "module",
@@ -67,7 +67,7 @@
67
67
  "./ServerComponentFetchError": "./lib/ServerComponentFetchError.js"
68
68
  },
69
69
  "dependencies": {
70
- "react-on-rails": "17.0.0-rc.7"
70
+ "react-on-rails": "17.0.0-rc.8"
71
71
  },
72
72
  "peerDependencies": {
73
73
  "react": ">= 16",