Skip to content

Commit 549ab4a

Browse files
committed
perf(devframe): replace destr with local safe-parse
Storage's only JSON parsing need is reading back its own previously written state (node/storage.ts), where destr's lenient (non-strict) handling of bare keywords/quoted strings never applies - the file is always a JSON object literal. Inline a ~15-line JSON.parse + reviver that mirrors destr's core __proto__/constructor.prototype prototype-pollution guard, and drop the ~36 KB destr runtime dependency. @devframes/hub also depends on destr (client/remote.ts) - left alone in this PR per the plan's decided scope.
1 parent 97cbe1d commit 549ab4a

3 files changed

Lines changed: 15 additions & 6 deletions

File tree

packages/devframe/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,6 @@
9595
"@standard-schema/spec": "catalog:deps",
9696
"birpc": "catalog:deps",
9797
"crossws": "catalog:deps",
98-
"destr": "catalog:deps",
9998
"h3": "catalog:deps",
10099
"mrmime": "catalog:deps",
101100
"nostics": "catalog:deps",

packages/devframe/src/node/storage.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import fs from 'node:fs'
22
import process from 'node:process'
3-
import { destr } from 'destr'
43
import { createSharedState } from 'devframe/utils/shared-state'
54
import { dirname } from 'pathe'
65
import { debounce } from 'perfect-debounce'
@@ -13,6 +12,20 @@ export interface CreateStorageOptions<T extends object> {
1312
debounce?: number
1413
}
1514

15+
// `JSON.parse` with a reviver that drops `__proto__`/`constructor.prototype`
16+
// keys, mirroring destr's core prototype-pollution guard. Storage only ever
17+
// reads back JSON it wrote itself via `JSON.stringify`, so destr's lenient
18+
// (non-strict) parsing of bare keywords/quoted strings never applies here -
19+
// this file is always a JSON object literal, and invalid JSON should throw
20+
// (caught below) rather than fall back to the raw string.
21+
function safeJsonParse<T>(text: string): T {
22+
return JSON.parse(text, (key, value) => {
23+
if (key === '__proto__' || (key === 'constructor' && value && typeof value === 'object' && 'prototype' in value))
24+
return undefined
25+
return value
26+
})
27+
}
28+
1629
export function createStorage<T extends object>(options: CreateStorageOptions<T>) {
1730
const {
1831
mergeInitialValue = (initialValue, savedValue) => ({ ...initialValue, ...savedValue }),
@@ -22,7 +35,7 @@ export function createStorage<T extends object>(options: CreateStorageOptions<T>
2235
let initialValue: T = options.initialValue
2336
if (fs.existsSync(options.filepath)) {
2437
try {
25-
const savedValue = destr<T>(fs.readFileSync(options.filepath, 'utf-8'), { strict: true })
38+
const savedValue = safeJsonParse<T>(fs.readFileSync(options.filepath, 'utf-8'))
2639
initialValue = mergeInitialValue ? mergeInitialValue(options.initialValue, savedValue) : savedValue
2740
}
2841
catch (error) {

pnpm-lock.yaml

Lines changed: 0 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)