@@ -87,89 +87,223 @@ export function BigUint(v?: BigUintCompat | string): biguint {
8787 return BigUintCls . fromCompat ( v ) . asAlgoTs ( )
8888}
8989
90+ type ToFixedBytesOptions < TLength extends uint64 = uint64 > = {
91+ /**
92+ * The length for the bounded type
93+ */
94+ length : TLength
95+ /**
96+ * The strategy to use for converting to a fixed length bytes type (default: 'assert-length')
97+ *
98+ * - 'assert-length': Asserts that the byte sequence has the specified length and fails if it differs
99+ * - 'unsafe-cast': Reinterprets the byte sequence as a fixed length type without any checks. This will succeed even if the value
100+ * is not of the specified length but will result in undefined behaviour for any code that makes use of this value.
101+ *
102+ */
103+ strategy ?: 'assert-length' | 'unsafe-cast'
104+ }
105+
90106/**
91- * @internal
92107 * Create a byte array from a string interpolation template and compatible replacements
93108 * @param value
94109 * @param replacements
95110 */
96- export function Bytes ( value : TemplateStringsArray , ...replacements : BytesCompat [ ] ) : bytes
111+ export function Bytes ( value : TemplateStringsArray , ...replacements : BytesCompat [ ] ) : bytes < uint64 >
97112/**
98- * @internal
99113 * Create a byte array from a utf8 string
100114 */
101- export function Bytes ( value : string ) : bytes
115+ export function Bytes ( value : string ) : bytes < uint64 >
116+ /**
117+ * Create a byte array from a utf8 string
118+ */
119+ export function Bytes < TLength extends uint64 > ( value : string , options : ToFixedBytesOptions < TLength > ) : bytes < TLength >
102120/**
103- * @internal
104121 * No op, returns the provided byte array.
105122 */
106- export function Bytes ( value : bytes ) : bytes
123+ export function Bytes ( value : bytes ) : bytes < uint64 >
124+ /**
125+ * No op, returns the provided byte array.
126+ */
127+ export function Bytes < TLength extends uint64 > ( value : bytes , options : ToFixedBytesOptions < TLength > ) : bytes < TLength >
107128/**
108- * @internal
109129 * Create a byte array from a biguint value encoded as a variable length big-endian number
110130 */
111- export function Bytes ( value : biguint ) : bytes
131+ export function Bytes ( value : biguint ) : bytes < uint64 >
112132/**
113- * @internal
114- * Create a byte array from a uint64 value encoded as a fixed length 64-bit number
133+ * Create a byte array from a biguint value encoded as a variable length big-endian number
115134 */
116- export function Bytes ( value : uint64 ) : bytes
135+ export function Bytes < TLength extends uint64 > ( value : biguint , options : ToFixedBytesOptions < TLength > ) : bytes < TLength >
136+ /**
137+ * Create a byte array from a uint64 value encoded as a a variable length 64-bit number
138+ */
139+ export function Bytes ( value : uint64 ) : bytes < uint64 >
140+ /**
141+ * Create a byte array from a uint64 value encoded as a a variable length 64-bit number
142+ */
143+ export function Bytes < TLength extends uint64 = 8 > ( value : uint64 , options : ToFixedBytesOptions < TLength > ) : bytes < TLength >
117144/**
118- * @internal
119145 * Create a byte array from an Iterable<uint64> where each item is interpreted as a single byte and must be between 0 and 255 inclusively
120146 */
121- export function Bytes ( value : Iterable < uint64 > ) : bytes
147+ export function Bytes ( value : Iterable < uint64 > ) : bytes < uint64 >
148+ /**
149+ * Create a byte array from an Iterable<uint64> where each item is interpreted as a single byte and must be between 0 and 255 inclusively
150+ */
151+ export function Bytes < TLength extends uint64 > ( value : Iterable < uint64 > , options : ToFixedBytesOptions < TLength > ) : bytes < TLength >
122152/**
123- * @internal
124153 * Create an empty byte array
125154 */
126- export function Bytes ( ) : bytes
127- export function Bytes (
128- value ?: BytesCompat | TemplateStringsArray | biguint | uint64 | Iterable < number > ,
129- ...replacements : BytesCompat [ ]
130- ) : bytes {
131- if ( isTemplateStringsArray ( value ) ) {
132- return BytesCls . fromInterpolation ( value , replacements ) . asAlgoTs ( )
133- } else if ( typeof value === 'bigint' || value instanceof BigUintCls ) {
134- return BigUintCls . fromCompat ( value ) . toBytes ( ) . asAlgoTs ( )
135- } else if ( typeof value === 'number' || value instanceof Uint64Cls ) {
136- return Uint64Cls . fromCompat ( value ) . toBytes ( ) . asAlgoTs ( )
137- } else if ( typeof value === 'object' && Symbol . iterator in value ) {
138- const valueItems = Array . from ( value ) . map ( ( v ) => getNumber ( v ) )
139- const invalidValue = valueItems . find ( ( v ) => v < 0 && v > 255 )
140- if ( invalidValue ) {
141- throw new CodeError ( `Cannot convert ${ invalidValue } to a byte` )
142- }
143- return new BytesCls ( new Uint8Array ( value ) ) . asAlgoTs ( )
144- } else {
145- return BytesCls . fromCompat ( value ) . asAlgoTs ( )
146- }
155+ export function Bytes ( ) : bytes < uint64 >
156+ /**
157+ * Create an empty byte array
158+ */
159+ export function Bytes < TLength extends uint64 = uint64 > ( options : ToFixedBytesOptions < TLength > ) : bytes < TLength >
160+ export function Bytes < TLength extends uint64 = uint64 > (
161+ value ?: BytesCompat | TemplateStringsArray | biguint | uint64 | Iterable < number > | ToFixedBytesOptions < TLength > ,
162+ ...replacements : [ ToFixedBytesOptions < TLength > ] | BytesCompat [ ] | undefined [ ]
163+ ) : bytes < TLength > {
164+ // Handle the case where only options are provided (empty bytes with fixed length)
165+ if ( isOptionsOnly ( value ) ) {
166+ const options = value as ToFixedBytesOptions < TLength >
167+ const emptyBytes = new BytesCls ( new Uint8Array ( options . length ) )
168+ return emptyBytes . toFixed ( options )
169+ }
170+
171+ // Convert the input value to a BytesCls instance
172+ const result = convertValueToBytes ( value , replacements )
173+
174+ // Extract options from replacements if provided
175+ const options = isTemplateStringsArray ( value ) ? undefined : extractOptionsFromReplacements ( replacements )
176+
177+ // Return either fixed-length or variable-length bytes
178+ return options ? result . toFixed ( options ) : ( result . asAlgoTs ( ) as bytes < TLength > )
147179}
148180
149181/**
150182 * @internal
151183 * Create a new bytes value from a hexadecimal encoded string
152184 * @param hex
153185 */
154- Bytes . fromHex = ( hex : string ) : bytes => {
155- return BytesCls . fromHex ( hex ) . asAlgoTs ( )
186+ Bytes . fromHex = < TLength extends uint64 = uint64 > ( hex : string , options ?: ToFixedBytesOptions < TLength > ) : bytes < TLength > => {
187+ return options ? BytesCls . fromHex ( hex ) . toFixed ( options ) : ( BytesCls . fromHex ( hex ) . asAlgoTs ( ) as bytes < TLength > )
156188}
157189/**
158190 * @internal
159191 * Create a new bytes value from a base 64 encoded string
160192 * @param b64
161193 */
162- Bytes . fromBase64 = ( b64 : string ) : bytes => {
163- return BytesCls . fromBase64 ( b64 ) . asAlgoTs ( )
194+ Bytes . fromBase64 = < TLength extends uint64 = uint64 > ( b64 : string , options ?: ToFixedBytesOptions < TLength > ) : bytes < TLength > => {
195+ return options ? BytesCls . fromBase64 ( b64 ) . toFixed ( options ) : ( BytesCls . fromBase64 ( b64 ) . asAlgoTs ( ) as bytes < TLength > )
164196}
165197
166198/**
167199 * @internal
168200 * Create a new bytes value from a base 32 encoded string
169201 * @param b32
170202 */
171- Bytes . fromBase32 = ( b32 : string ) : bytes => {
172- return BytesCls . fromBase32 ( b32 ) . asAlgoTs ( )
203+ Bytes . fromBase32 = < TLength extends uint64 = uint64 > ( b32 : string , options ?: ToFixedBytesOptions < TLength > ) : bytes < TLength > => {
204+ return options ? BytesCls . fromBase32 ( b32 ) . toFixed ( options ) : ( BytesCls . fromBase32 ( b32 ) . asAlgoTs ( ) as bytes < TLength > )
205+ }
206+
207+ /**
208+ * Helper function to check if the value parameter is options-only (for empty bytes with fixed length)
209+ */
210+ function isOptionsOnly < TLength extends uint64 > (
211+ value ?: BytesCompat | TemplateStringsArray | biguint | uint64 | Iterable < number > | ToFixedBytesOptions < TLength > ,
212+ ) : value is ToFixedBytesOptions < TLength > {
213+ return (
214+ value !== null &&
215+ typeof value === 'object' &&
216+ ! Array . isArray ( value ) &&
217+ ! isTemplateStringsArray ( value ) &&
218+ ! ( Symbol . iterator in value ) &&
219+ ! ( value instanceof BigUintCls ) &&
220+ ! ( value instanceof Uint64Cls ) &&
221+ ! ( value instanceof BytesCls ) &&
222+ ! ( value instanceof Uint8Array ) &&
223+ Object . keys ( value ) . length <= 2 &&
224+ Object . keys ( value ) . includes ( 'length' )
225+ )
226+ }
227+
228+ /**
229+ * Helper function to convert various input types to BytesCls
230+ */
231+ function convertValueToBytes < TLength extends uint64 > (
232+ value ?: BytesCompat | TemplateStringsArray | biguint | uint64 | Iterable < number > | ToFixedBytesOptions < TLength > ,
233+ replacements ?: [ ToFixedBytesOptions < TLength > ] | BytesCompat [ ] | undefined [ ] ,
234+ ) : BytesCls {
235+ if ( value === undefined ) {
236+ return new BytesCls ( new Uint8Array ( 0 ) )
237+ }
238+
239+ if ( isTemplateStringsArray ( value ) ) {
240+ return BytesCls . fromInterpolation ( value , replacements as BytesCompat [ ] )
241+ }
242+
243+ if ( typeof value === 'bigint' || value instanceof BigUintCls ) {
244+ return BigUintCls . fromCompat ( value ) . toBytes ( )
245+ }
246+
247+ if ( typeof value === 'number' || value instanceof Uint64Cls ) {
248+ return Uint64Cls . fromCompat ( value ) . toBytes ( )
249+ }
250+
251+ if ( isIterable ( value ) ) {
252+ return convertIterableToBytes ( value )
253+ }
254+
255+ // Default case: treat as BytesCompat
256+ return BytesCls . fromCompat ( value as BytesCompat )
257+ }
258+
259+ /**
260+ * Helper function to check if a value is iterable (but not string or Uint8Array)
261+ */
262+ function isIterable ( value : unknown ) : value is Iterable < number > {
263+ return (
264+ value !== null &&
265+ typeof value === 'object' &&
266+ Symbol . iterator in value &&
267+ ! isTemplateStringsArray ( value ) &&
268+ ! ( value instanceof Uint8Array ) &&
269+ ! ( value instanceof BytesCls )
270+ )
271+ }
272+
273+ /**
274+ * Helper function to convert an iterable of numbers to BytesCls
275+ */
276+ function convertIterableToBytes ( value : Iterable < number > ) : BytesCls {
277+ const valueItems = Array . from ( value ) . map ( ( v ) => getNumber ( v ) )
278+ const invalidValue = valueItems . find ( ( v ) => v < 0 || v > 255 )
279+ if ( invalidValue !== undefined ) {
280+ throw new CodeError ( `Cannot convert ${ invalidValue } to a byte` )
281+ }
282+ return new BytesCls ( new Uint8Array ( valueItems ) )
283+ }
284+
285+ /**
286+ * Helper function to extract options from the replacements parameter
287+ */
288+ function extractOptionsFromReplacements < TLength extends uint64 > (
289+ replacements : [ ToFixedBytesOptions < TLength > ] | BytesCompat [ ] | undefined [ ] ,
290+ ) : ToFixedBytesOptions < TLength > | undefined {
291+ if ( ! replacements || replacements . length !== 1 ) {
292+ return undefined
293+ }
294+
295+ const potentialOptions = replacements [ 0 ]
296+ // Check if the replacement looks like options
297+ if (
298+ typeof potentialOptions === 'object' &&
299+ potentialOptions !== null &&
300+ Object . keys ( potentialOptions ) . length <= 2 &&
301+ Object . keys ( potentialOptions ) . includes ( 'length' )
302+ ) {
303+ return potentialOptions as ToFixedBytesOptions < TLength >
304+ }
305+
306+ return undefined
173307}
174308
175309/**
0 commit comments