Skip to content

Commit fb84abf

Browse files
committed
fix(store): replace only NgRx slices that shrink the capped state
The size cap could swap a slice JSON can't write, or one smaller than the marker, for the marker, which made the state larger. Skip those. Also say in the SSR & HTTP docs that the timeline's transfer cache hit rule follows the withPangular({ transferCache }) options.
1 parent 82bae55 commit fb84abf

3 files changed

Lines changed: 18 additions & 2 deletions

File tree

‎apps/docs/src/content/inspectors/ssr-http.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ The tab checks the TransferState after each server response, so **cached** is wh
146146

147147
Every `HttpClient` request, tagged **SSR** or **Client**. Each row shows the method, the URL, the page that made it, the status, the time, and notes:
148148

149-
- **transfer cache**: the TransferState cache answered it. Only client requests get it, because the server never reads the cache; a server call shows its [transfer cache outcome](#transfer-cache-outcome) instead. A client request counts as a hit only when its method, response type, URL, body and params match the entry Angular stored, and Angular would cache it: no `transferCache: false`, auth headers, credentials or no-cache request.
149+
- **transfer cache**: the TransferState cache answered it. Only client requests get it, because the server never reads the cache; a server call shows its [transfer cache outcome](#transfer-cache-outcome) instead. A client request counts as a hit only when its method, response type, URL, body and params match the entry Angular stored, and Angular would cache it with the options passed to `withPangular({transferCache})`, or with its defaults without them. With the defaults, that means no `transferCache: false`, POST, auth headers, credentials or no-cache request. Options such as `includePostRequests` let more requests count, and a `filter` that returns `false` leaves one out. See [Transfer cache options](#transfer-cache-options).
150150
- **delayed N ms**: a fault rule held it back.
151151
- **mocked**: a fault rule answered it with a status below 400.
152152
- **faulted**: a fault rule failed it with a status of 400 or more.

‎packages/devtools/src/__tests__/ngrx-size-cap.test.ts‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,19 @@ describe('NgRx state size cap', () => {
5050
expect(capNgrxState('é'.repeat(140 * 1024))).toMatch(/^\[too large: 28\d KB\]$/);
5151
});
5252

53+
it('never replaces a slice that is no larger than its marker', () => {
54+
const fn = () => undefined;
55+
const capped = capNgrxState(
56+
{ a: 'x'.repeat(30), b: 1, c: fn, d: 'y'.repeat(30) },
57+
10,
58+
) as Record<string, unknown>;
59+
// The 30-character strings shrink to the marker; the number and the function stay.
60+
expect(capped['a']).toBe('[too large: 1 KB]');
61+
expect(capped['d']).toBe('[too large: 1 KB]');
62+
expect(capped['b']).toBe(1);
63+
expect(capped['c']).toBe(fn);
64+
});
65+
5366
it('caps the fields of a signal store as one state', () => {
5467
expect(capNgrxFields({ a: big(200), b: big(100) })).toEqual({
5568
a: '[too large: 201 KB]',

‎packages/devtools/src/rpc/ngrx-size-cap.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,11 @@ export function capNgrxState(value: unknown, max = NGRX_STATE_MAX_BYTES): unknow
3838
for (const { key, bytes } of sizes) {
3939
if (total <= max) break;
4040
const marker = tooLargeText(bytes);
41+
const saved = bytes - jsonBytes(marker);
42+
// A slice no larger than its marker (or one JSON can't write) would not shrink the state.
43+
if (saved <= 0) continue;
4144
out[key] = marker;
42-
total -= bytes - jsonBytes(marker);
45+
total -= saved;
4346
}
4447
return out;
4548
}

0 commit comments

Comments
 (0)