@@ -4,9 +4,132 @@ import { ExecutionSnapshot } from '@/executor/execution/snapshot'
44import type { ExecutionMetadata , SerializableExecutionState } from '@/executor/execution/types'
55import type { ExecutionContext , SerializedSnapshot } from '@/executor/types'
66
7+ const JSON_SYNTAX_BYTES = {
8+ QUOTE : 1 ,
9+ COLON : 1 ,
10+ COMMA : 1 ,
11+ ARRAY_BRACKETS : 2 ,
12+ OBJECT_BRACES : 2 ,
13+ NULL : 4 ,
14+ } as const
15+
16+ function getEscapedJsonStringByteLength ( value : string ) : number {
17+ let bytes = JSON_SYNTAX_BYTES . QUOTE * 2
18+ for ( let index = 0 ; index < value . length ; index ++ ) {
19+ const code = value . charCodeAt ( index )
20+ if ( code === 0x22 || code === 0x5c ) {
21+ bytes += 2
22+ } else if ( code === 0x08 || code === 0x09 || code === 0x0a || code === 0x0c || code === 0x0d ) {
23+ bytes += 2
24+ } else if ( code < 0x20 ) {
25+ bytes += 6
26+ } else if ( code >= 0xd800 && code <= 0xdbff ) {
27+ const next = value . charCodeAt ( index + 1 )
28+ if ( next >= 0xdc00 && next <= 0xdfff ) {
29+ bytes += 4
30+ index ++
31+ } else {
32+ bytes += 6
33+ }
34+ } else if ( code >= 0xdc00 && code <= 0xdfff ) {
35+ bytes += 6
36+ } else if ( code < 0x80 ) {
37+ bytes += 1
38+ } else if ( code < 0x800 ) {
39+ bytes += 2
40+ } else {
41+ bytes += 3
42+ }
43+ }
44+ return bytes
45+ }
46+
47+ function getPrimitiveJsonByteLength ( value : unknown ) : number | undefined {
48+ if ( value === null ) {
49+ return JSON_SYNTAX_BYTES . NULL
50+ }
51+ if ( typeof value === 'string' ) {
52+ return getEscapedJsonStringByteLength ( value )
53+ }
54+ if ( typeof value === 'number' ) {
55+ return Number . isFinite ( value )
56+ ? Buffer . byteLength ( String ( value ) , 'utf8' )
57+ : JSON_SYNTAX_BYTES . NULL
58+ }
59+ if ( typeof value === 'boolean' ) {
60+ return value ? 4 : 5
61+ }
62+ if ( typeof value === 'bigint' ) {
63+ throw new TypeError ( 'Do not know how to serialize a BigInt' )
64+ }
65+ return undefined
66+ }
67+
68+ function getBoundedJsonByteLength (
69+ value : unknown ,
70+ maxBytes : number ,
71+ seen = new WeakSet < object > ( )
72+ ) : number | undefined {
73+ const primitiveSize = getPrimitiveJsonByteLength ( value )
74+ if ( primitiveSize !== undefined ) {
75+ return primitiveSize
76+ }
77+
78+ if ( value === undefined || typeof value === 'function' || typeof value === 'symbol' ) {
79+ return undefined
80+ }
81+
82+ if ( ! value || typeof value !== 'object' ) {
83+ return undefined
84+ }
85+
86+ if ( seen . has ( value ) ) {
87+ throw new TypeError ( 'Converting circular structure to JSON' )
88+ }
89+ seen . add ( value )
90+
91+ let bytes = Array . isArray ( value )
92+ ? JSON_SYNTAX_BYTES . ARRAY_BRACKETS
93+ : JSON_SYNTAX_BYTES . OBJECT_BRACES
94+ if ( Array . isArray ( value ) ) {
95+ for ( let index = 0 ; index < value . length ; index ++ ) {
96+ if ( index > 0 ) bytes += JSON_SYNTAX_BYTES . COMMA
97+ const itemSize = getBoundedJsonByteLength ( value [ index ] , maxBytes - bytes , seen )
98+ bytes += itemSize ?? JSON_SYNTAX_BYTES . NULL
99+ if ( bytes > maxBytes ) return bytes
100+ }
101+ seen . delete ( value )
102+ return bytes
103+ }
104+
105+ let hasEntries = false
106+ for ( const key of Object . keys ( value ) ) {
107+ const entryValue = ( value as Record < string , unknown > ) [ key ]
108+ if (
109+ entryValue === undefined ||
110+ typeof entryValue === 'function' ||
111+ typeof entryValue === 'symbol'
112+ ) {
113+ continue
114+ }
115+ if ( hasEntries ) bytes += JSON_SYNTAX_BYTES . COMMA
116+ bytes += getEscapedJsonStringByteLength ( key ) + JSON_SYNTAX_BYTES . COLON
117+ const entrySize = getBoundedJsonByteLength ( entryValue , maxBytes - bytes , seen )
118+ if ( entrySize === undefined ) {
119+ continue
120+ }
121+ bytes += entrySize
122+ hasEntries = true
123+ if ( bytes > maxBytes ) return bytes
124+ }
125+
126+ seen . delete ( value )
127+ return bytes
128+ }
129+
7130function assertSnapshotValueIsCompact ( value : unknown , label : string ) : void {
8- const json = JSON . stringify ( value )
9- if ( json && Buffer . byteLength ( json , 'utf8' ) > LARGE_VALUE_THRESHOLD_BYTES ) {
131+ const byteLength = getBoundedJsonByteLength ( value , LARGE_VALUE_THRESHOLD_BYTES )
132+ if ( byteLength !== undefined && byteLength > LARGE_VALUE_THRESHOLD_BYTES ) {
10133 throw new Error ( `Cannot serialize pause snapshot with oversized ${ label } ; compact it first.` )
11134 }
12135}
0 commit comments