Skip to content

Commit 341b309

Browse files
committed
feat: Add safe-stable-stringify for deterministic object serialization
1 parent 7af67b9 commit 341b309

3 files changed

Lines changed: 22 additions & 54 deletions

File tree

packages/connectivity/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,8 @@
4747
"async-retry": "^1.3.3",
4848
"axios": "^1.15.0",
4949
"jks-js": "^1.1.6",
50-
"jsonwebtoken": "^9.0.3"
50+
"jsonwebtoken": "^9.0.3",
51+
"safe-stable-stringify": "^2.5.0"
5152
},
5253
"devDependencies": {
5354
"mock-fs": "^5.5.0",

packages/connectivity/src/scp-cf/cache.ts

Lines changed: 19 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { createHash } from 'node:crypto';
2+
import { stringify } from 'safe-stable-stringify';
23

34
interface CacheInterface<T> {
45
hasKey(key: string): boolean;
@@ -81,17 +82,23 @@ export class Cache<T> implements CacheInterface<T> {
8182
* @returns The corresponding entry to the provided key if it is still valid, returns `undefined` otherwise.
8283
*/
8384
get(key: string | undefined): T | undefined {
84-
const entry = key ? this.cache.get(key) : undefined;
85-
if (entry) {
86-
if (isExpired(entry)) {
87-
this.cache.delete(key!);
88-
return undefined;
89-
}
90-
// LRU cache: Move accessed entry to the end of the Map to mark it as recently used
91-
if (this.maxSize !== Infinity) {
92-
this.cache.delete(key!);
93-
this.cache.set(key!, entry);
94-
}
85+
if (!key) {
86+
return undefined;
87+
}
88+
const entry = this.cache.get(key);
89+
if (!entry) {
90+
return undefined;
91+
}
92+
93+
if (isExpired(entry)) {
94+
this.cache.delete(key!);
95+
return undefined;
96+
}
97+
98+
// LRU cache: Move accessed entry to the end of the Map to mark it as recently used
99+
if (this.maxSize !== Infinity) {
100+
this.cache.delete(key!);
101+
this.cache.set(key!, entry);
95102
}
96103
return entry?.entry;
97104
}
@@ -149,54 +156,14 @@ export class Cache<T> implements CacheInterface<T> {
149156
}
150157
}
151158

152-
/**
153-
* Stringifies a given object in a deterministic way, so that the same objects always yield the same string representation.
154-
* @param toSerialize - The object to stringify.
155-
* @returns A string representation of the given object.
156-
*/
157-
function stringifyJsonSafe(toSerialize: Record<string, unknown>): string {
158-
return JSON.stringify(toSerialize, (_key, value) => {
159-
if (value instanceof Map) {
160-
return [
161-
['dataType', 'Map'],
162-
['value', Array.from(value.entries()).sort()]
163-
];
164-
}
165-
if (value instanceof Set) {
166-
return [
167-
['dataType', 'Set'],
168-
['value', Array.from(value.values()).sort()]
169-
];
170-
}
171-
if (typeof value === 'function') {
172-
return [
173-
['dataType', 'Function'],
174-
['value', value.toString()]
175-
];
176-
}
177-
if (value instanceof Object && !Array.isArray(value)) {
178-
return [
179-
['dataType', 'Object'],
180-
[
181-
'value',
182-
Object.entries(value).sort(([keyA], [keyB]) =>
183-
keyA.localeCompare(keyB)
184-
)
185-
]
186-
];
187-
}
188-
return value;
189-
});
190-
}
191-
192159
/**
193160
* Hashes the given value to create a cache key.
194161
* @internal
195162
* @param value - The value to hash.
196163
* @returns A hash of the given value using a cryptographic hash function.
197164
*/
198165
export function hashCacheKey(value: Record<string, unknown>): string {
199-
const serialized = stringifyJsonSafe(value);
166+
const serialized = stringify(value);
200167
return createHash('blake2s256').update(serialized).digest('hex');
201168
}
202169

yarn.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8446,7 +8446,7 @@ safe-regex-test@^1.1.0:
84468446
es-errors "^1.3.0"
84478447
is-regex "^1.2.1"
84488448

8449-
safe-stable-stringify@^2.3.1:
8449+
safe-stable-stringify@^2.3.1, safe-stable-stringify@^2.5.0:
84508450
version "2.5.0"
84518451
resolved "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz#4ca2f8e385f2831c432a719b108a3bf7af42a1dd"
84528452
integrity sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==

0 commit comments

Comments
 (0)