refactor(environment): move the paging rules to environmentStatePaging.ts

The paging parameters, `pagesOnly` and `pageSize` leave environmentStateRules.ts for a module of their own, so the
rules module keeps room under the line limit on the branches built on this one. No behaviour changes: the verdicts
over the 50 captures are unchanged plan by plan, and the smoke applies all 31 served write tools.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Edgar ArakelyanandClaude Opus 5.5 committed 2026-10-21 11:07:19 -07:00
1 parent 0ae64400ee
commit 6257de93a1
5 files changed
+59 -58

No files matched your search

@@ -2,8 +2,6 @@ import { stateBodyJson } from "../../domain/analysisView/EnvironmentStateAccumul
import {
collectionPathOf,
listItems,
pageSize,
pagesOnly,
missingRequiredFields,
renderStateEntity,
renderStateList,
@@ -17,6 +15,10 @@ import {
type StateListView,
type StateView,
} from "../../domain/analysisView/environmentStateRules.js";
import {
pageSize,
pagesOnly,
} from "../../domain/analysisView/environmentStatePaging.js";
import { isAbsence } from "../../domain/analysisView/environmentStateWriteChecks.js";
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
import {
@@ -7,7 +7,6 @@ import {
isObject,
lastSegmentValue,
listItems,
pagesOnly,
renderStateEntity,
renderStateList,
replaceAt,
@@ -20,6 +19,7 @@ import {
type StateToolFacts,
type StateView,
} from "./environmentStateRules.js";
import { pagesOnly } from "./environmentStatePaging.js";
import { ENTRY_BYTES, textBytes } from "./webNetworkRouteTemplates.js";
/** A read tool whose recordings locate entities of one type at one pointer. */
@@ -0,0 +1,53 @@
import {
iterateUrlQueryParameters,
parseUrlQueryParameterNames,
} from "../recordedTransportUrl.js";
/** Query parameters that page or bound a list without filtering it. */
const PAGING_PARAMETERS = new Set([
"limit",
"offset",
"page",
"per_page",
"perpage",
"page_size",
"pagesize",
"size",
"start",
"_start",
"_end",
]);
/** Whether a query only pages its list: no parameters, or paging parameters alone. */
export const pagesOnly = (query: string | undefined): boolean =>
query === undefined ||
[...parseUrlQueryParameterNames(query)].every((name) =>
PAGING_PARAMETERS.has(name.toLowerCase()),
);
/** Paging parameters that bound how many items a page holds. */
const PAGE_SIZE_PARAMETERS = new Set([
"limit",
"per_page",
"perpage",
"page_size",
"pagesize",
"size",
]);
/** The most items a query's page holds: its smallest whole-number page-size value or `_start`–`_end` range width, or undefined without one. */
export const pageSize = (query: string | undefined): number | undefined => {
let size = Infinity;
const range = { start: 0, end: Infinity };
for (const [name, value] of iterateUrlQueryParameters(query ?? "")) {
const key = name.toLowerCase();
if (!/^\d+$/u.test(value)) continue;
// A size of zero bounds nothing: applications read it as no limit, as a whole line naming it shows.
if (PAGE_SIZE_PARAMETERS.has(key)) {
if (Number(value) > 0) size = Math.min(size, Number(value));
} else if (key === "_start") range.start = Number(value);
else if (key === "_end") range.end = Number(value);
}
const width = Math.min(size, Math.max(range.end - range.start, 0));
return Number.isFinite(width) ? width : undefined;
};
@@ -1,8 +1,4 @@
import { canonicalJson } from "../comparisonSemantics.js";
import {
iterateUrlQueryParameters,
parseUrlQueryParameterNames,
} from "../recordedTransportUrl.js";
import type { JsonValue } from "../jsonValue.js";
import type { StateWritePlan } from "./environmentStateWriteRules.js";
import { ENTRY_BYTES, textBytes } from "./webNetworkRouteTemplates.js";
@@ -147,55 +143,6 @@ const QUERY_SEGMENTS = new Set([
"_count",
"_msearch",
]);
/** Query parameters that page or bound a list without filtering it. */
const PAGING_PARAMETERS = new Set([
"limit",
"offset",
"page",
"per_page",
"perpage",
"page_size",
"pagesize",
"size",
"start",
"_start",
"_end",
]);
/** Whether a query only pages its list: no parameters, or paging parameters alone. */
export const pagesOnly = (query: string | undefined): boolean =>
query === undefined ||
[...parseUrlQueryParameterNames(query)].every((name) =>
PAGING_PARAMETERS.has(name.toLowerCase()),
);
/** Paging parameters that bound how many items a page holds. */
const PAGE_SIZE_PARAMETERS = new Set([
"limit",
"per_page",
"perpage",
"page_size",
"pagesize",
"size",
]);
/** The most items a query's page holds: its smallest whole-number page-size value or `_start`–`_end` range width, or undefined without one. */
export const pageSize = (query: string | undefined): number | undefined => {
let size = Infinity;
const range = { start: 0, end: Infinity };
for (const [name, value] of iterateUrlQueryParameters(query ?? "")) {
const key = name.toLowerCase();
if (!/^\d+$/u.test(value)) continue;
// A size of zero bounds nothing: applications read it as no limit, as a whole line naming it shows.
if (PAGE_SIZE_PARAMETERS.has(key)) {
if (Number(value) > 0) size = Math.min(size, Number(value));
} else if (key === "_start") range.start = Number(value);
else if (key === "_end") range.end = Number(value);
}
const width = Math.min(size, Math.max(range.end - range.start, 0));
return Number.isFinite(width) ? width : undefined;
};
/** Identity field names tried, in order, when no path parameter proves one. */
export const IDENTITY_NAMES = [
"id",
@@ -13,8 +13,6 @@ import {
lastSegmentValue,
listItems,
missingRequiredFields,
pagesOnly,
pageSize,
renderStateEntity,
renderStateList,
replaceAt,
@@ -30,6 +28,7 @@ import {
type StateToolFacts,
type StateView,
} from "./environmentStateRules.js";
import { pagesOnly, pageSize } from "./environmentStatePaging.js";
/** A query as replay compares a call's: decoded values by name, in any order; undefined when it repeats a name with another value, which no call's arguments can carry. */
const queryKey = (query: string | undefined): string | undefined => {