Skip to content

Commit c8cc010

Browse files
committed
fix(infra): centralize non-finite numeric option bounds
1 parent 6e25112 commit c8cc010

5 files changed

Lines changed: 33 additions & 17 deletions

File tree

src/infra/dedupe.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { resolveGlobalSingleton } from "../shared/global-singleton.js";
22
import { pruneMapToMaxSize } from "./map-size.js";
3+
import { resolveNonNegativeIntegerOption } from "./numeric-options.js";
34

45
export type DedupeCache = {
56
check: (key: string | undefined | null, now?: number) => boolean;
@@ -14,13 +15,12 @@ export type DedupeCacheOptions = {
1415
maxSize: number;
1516
};
1617

17-
export function resolveDedupeNonNegativeInteger(value: number, fallback: number): number {
18-
return Number.isFinite(value) ? Math.max(0, Math.floor(value)) : fallback;
19-
}
18+
/** @deprecated Use resolveNonNegativeIntegerOption for new internal numeric option normalization. */
19+
export { resolveNonNegativeIntegerOption as resolveDedupeNonNegativeInteger };
2020

2121
export function createDedupeCache(options: DedupeCacheOptions): DedupeCache {
22-
const ttlMs = resolveDedupeNonNegativeInteger(options.ttlMs, 0);
23-
const maxSize = resolveDedupeNonNegativeInteger(options.maxSize, 0);
22+
const ttlMs = resolveNonNegativeIntegerOption(options.ttlMs, 0);
23+
const maxSize = resolveNonNegativeIntegerOption(options.maxSize, 0);
2424
const cache = new Map<string, number>();
2525

2626
const touch = (key: string, now: number) => {

src/infra/numeric-options.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function resolveNonNegativeIntegerOption(value: number, fallback: number): number {
2+
return Number.isFinite(value) ? Math.max(0, Math.floor(value)) : fallback;
3+
}

src/infra/outbound/directory-cache.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,4 +69,16 @@ describe("DirectoryCache", () => {
6969
cache.clear(cfg);
7070
expect(cache.get("a", cfg)).toBeUndefined();
7171
});
72+
73+
it("uses the default max size when maxSize is non-finite", () => {
74+
const cache = new DirectoryCache<number>(60_000, Number.NaN);
75+
const cfg = {} as OpenClawConfig;
76+
77+
for (let i = 0; i <= 2000; i++) {
78+
cache.set(`key-${i}`, i, cfg);
79+
}
80+
81+
expect(cache.get("key-0", cfg)).toBeUndefined();
82+
expect(cache.get("key-2000", cfg)).toBe(2000);
83+
});
7284
});

src/infra/outbound/directory-cache.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { ChannelDirectoryEntryKind, ChannelId } from "../../channels/plugins/types.public.js";
22
import type { OpenClawConfig } from "../../config/types.openclaw.js";
3+
import { resolveNonNegativeIntegerOption } from "../numeric-options.js";
34

45
type CacheEntry<T> = {
56
value: T;
@@ -22,13 +23,12 @@ export function buildDirectoryCacheKey(key: DirectoryCacheKey): string {
2223
export class DirectoryCache<T> {
2324
private readonly cache = new Map<string, CacheEntry<T>>();
2425
private lastConfigRef: OpenClawConfig | null = null;
26+
private readonly ttlMs: number;
2527
private readonly maxSize: number;
2628

27-
constructor(
28-
private readonly ttlMs: number,
29-
maxSize = 2000,
30-
) {
31-
this.maxSize = Math.max(1, Math.floor(maxSize));
29+
constructor(ttlMs: number, maxSize = 2000) {
30+
this.ttlMs = resolveNonNegativeIntegerOption(ttlMs, 0);
31+
this.maxSize = Math.max(1, resolveNonNegativeIntegerOption(maxSize, 2000));
3232
}
3333

3434
get(key: string, cfg: OpenClawConfig): T | undefined {

src/plugin-sdk/persistent-dedupe.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { createDedupeCache, resolveDedupeNonNegativeInteger } from "../infra/dedupe.js";
1+
import { createDedupeCache } from "../infra/dedupe.js";
2+
import { resolveNonNegativeIntegerOption } from "../infra/numeric-options.js";
23
import type { FileLockOptions } from "./file-lock.js";
34
import { withFileLock } from "./file-lock.js";
45
import { readJsonFileWithFallback, writeJsonFileAtomically } from "./json-store.js";
@@ -148,9 +149,9 @@ function isRecentTimestamp(seenAt: number | undefined, ttlMs: number, now: numbe
148149

149150
/** Create a dedupe helper that combines in-memory fast checks with a lock-protected disk store. */
150151
export function createPersistentDedupe(options: PersistentDedupeOptions): PersistentDedupe {
151-
const ttlMs = resolveDedupeNonNegativeInteger(options.ttlMs, 0);
152-
const memoryMaxSize = resolveDedupeNonNegativeInteger(options.memoryMaxSize, 0);
153-
const fileMaxEntries = Math.max(1, resolveDedupeNonNegativeInteger(options.fileMaxEntries, 1));
152+
const ttlMs = resolveNonNegativeIntegerOption(options.ttlMs, 0);
153+
const memoryMaxSize = resolveNonNegativeIntegerOption(options.memoryMaxSize, 0);
154+
const fileMaxEntries = Math.max(1, resolveNonNegativeIntegerOption(options.fileMaxEntries, 1));
154155
const lockOptions = mergeLockOptions(options.lockOptions);
155156
const memory = createDedupeCache({ ttlMs, maxSize: memoryMaxSize });
156157
const inflight = new Map<string, Promise<boolean>>();
@@ -324,15 +325,15 @@ function createReleasedClaimError(scopedKey: string): Error {
324325

325326
/** Create a claim/commit/release dedupe guard backed by memory and optional persistent storage. */
326327
export function createClaimableDedupe(options: ClaimableDedupeOptions): ClaimableDedupe {
327-
const ttlMs = resolveDedupeNonNegativeInteger(options.ttlMs, 0);
328-
const memoryMaxSize = resolveDedupeNonNegativeInteger(options.memoryMaxSize, 0);
328+
const ttlMs = resolveNonNegativeIntegerOption(options.ttlMs, 0);
329+
const memoryMaxSize = resolveNonNegativeIntegerOption(options.memoryMaxSize, 0);
329330
const memory = createDedupeCache({ ttlMs, maxSize: memoryMaxSize });
330331
const persistent =
331332
options.resolveFilePath != null
332333
? createPersistentDedupe({
333334
ttlMs,
334335
memoryMaxSize,
335-
fileMaxEntries: Math.max(1, resolveDedupeNonNegativeInteger(options.fileMaxEntries, 1)),
336+
fileMaxEntries: Math.max(1, resolveNonNegativeIntegerOption(options.fileMaxEntries, 1)),
336337
resolveFilePath: options.resolveFilePath,
337338
lockOptions: options.lockOptions,
338339
onDiskError: options.onDiskError,

0 commit comments

Comments
 (0)