1/**
2 * Represents exact, non-negative, integral byte counts.
3 *
4 * Decimal units use powers of 1,000 and binary units use powers of 1,024.
5 *
6 * @since 4.0.0
7 */
8import * as BI from "./BigInt.ts"
9import type * as Brand from "./Brand.ts"
10import type * as Combiner from "./Combiner.ts"
11import type * as Equ from "./Equivalence.ts"
12import { dual } from "./Function.ts"
13import * as Option from "./Option.ts"
14import type * as order from "./Order.ts"
15import type * as Reducer from "./Reducer.ts"
16
17const TypeId = "~effect/ByteSize"
18
19const bigint0 = BigInt(0)
20const bigint1 = BigInt(1)
21const decimalBase = BigInt(1000)
22const binaryBase = BigInt(1024)
23
24/**
25 * Represents an exact, non-negative number of bytes.
26 *
27 * @category models
28 * @since 4.0.0
29 */
30export type ByteSize = Brand.Branded<bigint, typeof TypeId>
31
32/**
33 * Values accepted by byte-size decoding operations.
34 *
35 * **Details**
36 *
37 * String inputs require a non-negative decimal integer without leading zeros,
38 * followed by a canonical unit symbol or lowercase unit name, optionally
39 * separated by one space. Numeric inputs are validated at runtime.
40 *
41 * @see {@link fromString} for external strings and fractional quantities
42 * @see {@link fromStringUnsafe} for throwing string validation
43 * @category models
44 * @since 4.0.0
45 */
46export type Input = ByteSize | bigint | number | `${IntegerQuantity}${"" | " "}${InputUnit}`
47
48// Combining these constraints excludes signs, radix prefixes, leading zeros,
49// fractions, and exponent notation without limiting the number of digits.
50type IntegerQuantity = "0" | (`${bigint}` & `${"1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9"}${string}`)
51
52type InputUnit =
53 | Unit
54 | `${
55 | ""
56 | "kilo"
57 | "mega"
58 | "giga"
59 | "tera"
60 | "peta"
61 | "exa"
62 | "zetta"
63 | "yotta"
64 | "ronna"
65 | "quetta"
66 | "kibi"
67 | "mebi"
68 | "gibi"
69 | "tebi"
70 | "pebi"
71 | "exbi"
72 | "zebi"
73 | "yobi"}byte${"" | "s"}`
74
75/**
76 * Canonical decimal byte unit symbols.
77 *
78 * @category models
79 * @since 4.0.0
80 */
81export type DecimalUnit =
82 | "B"
83 | "kB"
84 | "MB"
85 | "GB"
86 | "TB"
87 | "PB"
88 | "EB"
89 | "ZB"
90 | "YB"
91 | "RB"
92 | "QB"
93
94/**
95 * Canonical binary byte unit symbols.
96 *
97 * @category models
98 * @since 4.0.0
99 */
100export type BinaryUnit =
101 | "B"
102 | "KiB"
103 | "MiB"
104 | "GiB"
105 | "TiB"
106 | "PiB"
107 | "EiB"
108 | "ZiB"
109 | "YiB"
110
111/**
112 * Canonical decimal and binary byte unit symbols.
113 *
114 * @category models
115 * @since 4.0.0
116 */
117export type Unit = DecimalUnit | BinaryUnit
118
119/**
120 * Options controlling compact byte-size formatting.
121 *
122 * @category models
123 * @since 4.0.0
124 */
125export type FormatOptions =
126 & {
127 readonly precision?: number | undefined
128 readonly trailingZeros?: boolean | undefined
129 }
130 & (
131 | {
132 readonly system?: "decimal" | undefined
133 readonly unit?: DecimalUnit | undefined
134 }
135 | {
136 readonly system?: "binary" | undefined
137 readonly unit?: BinaryUnit | undefined
138 }
139 )
140
141interface UnitInfo {
142 readonly symbol: Unit
143 readonly factor: bigint
144 readonly names: ReadonlyArray<string>
145}
146
147const decimalUnits: ReadonlyArray<UnitInfo> = [
148 { symbol: "B", factor: bigint1, names: ["B", "byte", "bytes"] },
149 { symbol: "kB", factor: decimalBase, names: ["kB", "kilobyte", "kilobytes"] },
150 { symbol: "MB", factor: decimalBase ** BigInt(2), names: ["MB", "megabyte", "megabytes"] },
151 { symbol: "GB", factor: decimalBase ** BigInt(3), names: ["GB", "gigabyte", "gigabytes"] },
152 { symbol: "TB", factor: decimalBase ** BigInt(4), names: ["TB", "terabyte", "terabytes"] },
153 { symbol: "PB", factor: decimalBase ** BigInt(5), names: ["PB", "petabyte", "petabytes"] },
154 { symbol: "EB", factor: decimalBase ** BigInt(6), names: ["EB", "exabyte", "exabytes"] },
155 { symbol: "ZB", factor: decimalBase ** BigInt(7), names: ["ZB", "zettabyte", "zettabytes"] },
156 { symbol: "YB", factor: decimalBase ** BigInt(8), names: ["YB", "yottabyte", "yottabytes"] },
157 { symbol: "RB", factor: decimalBase ** BigInt(9), names: ["RB", "ronnabyte", "ronnabytes"] },
158 { symbol: "QB", factor: decimalBase ** BigInt(10), names: ["QB", "quettabyte", "quettabytes"] }
159]
160
161const binaryUnits: ReadonlyArray<UnitInfo> = [
162 decimalUnits[0],
163 { symbol: "KiB", factor: binaryBase, names: ["KiB", "kibibyte", "kibibytes"] },
164 { symbol: "MiB", factor: binaryBase ** BigInt(2), names: ["MiB", "mebibyte", "mebibytes"] },
165 { symbol: "GiB", factor: binaryBase ** BigInt(3), names: ["GiB", "gibibyte", "gibibytes"] },
166 { symbol: "TiB", factor: binaryBase ** BigInt(4), names: ["TiB", "tebibyte", "tebibytes"] },
167 { symbol: "PiB", factor: binaryBase ** BigInt(5), names: ["PiB", "pebibyte", "pebibytes"] },
168 { symbol: "EiB", factor: binaryBase ** BigInt(6), names: ["EiB", "exbibyte", "exbibytes"] },
169 { symbol: "ZiB", factor: binaryBase ** BigInt(7), names: ["ZiB", "zebibyte", "zebibytes"] },
170 { symbol: "YiB", factor: binaryBase ** BigInt(8), names: ["YiB", "yobibyte", "yobibytes"] }
171]
172
173const allUnits = [...decimalUnits, ...binaryUnits.slice(1)]
174const unitsByName = new Map(allUnits.flatMap((unit) => unit.names.map((name) => [name, unit] as const)))
175const unitsBySymbol = new Map(allUnits.map((unit) => [unit.symbol, unit] as const))
176
177const make = (value: bigint): ByteSize => value as ByteSize
178
179/**
180 * The byte size containing zero bytes.
181 *
182 * @category constants
183 * @since 4.0.0
184 */
185export const zero: ByteSize = make(bigint0)
186
187const invalid = (message: string): never => {
188 throw new Error(`Invalid ByteSize: ${message}`)
189}
190
191const fromNumber = (input: number): ByteSize => {
192 if (!Number.isSafeInteger(input) || input < 0) {
193 return invalid(`expected a non-negative safe integer, received ${input}`)
194 }
195 return make(BigInt(input))
196}
197
198const fromQuantity = (quantity: number | bigint, unit: UnitInfo): ByteSize => {
199 if (typeof quantity === "bigint") {
200 if (quantity < bigint0) return invalid(`expected a non-negative quantity, received ${quantity}`)
201 return make(quantity * unit.factor)
202 }
203 const value = quantity * Number(unit.factor)
204 if (!Number.isSafeInteger(value) || value < 0) {
205 return invalid(`expected an exact non-negative safe-integer byte result, received ${quantity} ${unit.symbol}`)
206 }
207 return make(BigInt(value))
208}
209
210/**
211 * Parses a byte-size string and throws for invalid syntax, unsupported units,
212 * or quantities that do not represent non-negative integral bytes.
213 *
214 * **Details**
215 *
216 * Accepts decimal fractions, canonical SI and IEC symbols, lowercase unit
217 * names, and whitespace around the quantity and unit. Parsing uses exact
218 * bigint arithmetic, including above the safe integer range.
219 *
220 * @see {@link fromString} for non-throwing validation
221 * @category constructors
222 * @since 4.0.0
223 */
224export const fromStringUnsafe = (input: string): ByteSize => {
225 const match = /^\s*(\d+)(?:\.(\d+))?\s*([A-Za-z]+)\s*$/.exec(input)
226 if (match === null) return invalid(`unsupported syntax ${JSON.stringify(input)}`)
227 const unit = unitsByName.get(match[3])
228 if (unit === undefined) return invalid(`unsupported unit ${JSON.stringify(match[3])}`)
229 const fraction = match[2] ?? ""
230 const scale = BigInt(10) ** BigInt(fraction.length)
231 const numerator = BigInt(match[1] + fraction) * unit.factor
232 if (numerator % scale !== bigint0) {
233 return invalid(`${JSON.stringify(input)} does not represent an integral number of bytes`)
234 }
235 return make(numerator / scale)
236}
237
238/**
239 * Parses a byte-size string, returning `None` for invalid syntax, unsupported
240 * units, or quantities that do not represent non-negative integral bytes.
241 *
242 * **Details**
243 *
244 * Accepts external strings and fractional quantities using the same syntax
245 * as `fromStringUnsafe`.
246 *
247 * @see {@link fromStringUnsafe} for throwing validation
248 * @category constructors
249 * @since 4.0.0
250 */
251export const fromString: (input: string) => Option.Option<ByteSize> = Option.liftThrowable(fromStringUnsafe)
252
253/**
254 * Decodes a trusted input into a byte size and throws for invalid input.
255 *
256 * @category constructors
257 * @since 4.0.0
258 */
259export const fromInputUnsafe = (input: Input): ByteSize => {
260 switch (typeof input) {
261 case "bigint":
262 if (input < bigint0) return invalid(`expected a non-negative bigint, received ${input}`)
263 return make(input)
264 case "number":
265 return fromNumber(input)
266 case "string":
267 return fromStringUnsafe(input)
268 }
269 return invalid(`unsupported input ${input}`)
270}
271
272/**
273 * Decodes an input into a byte size, returning `None` for invalid input.
274 *
275 * @category constructors
276 * @since 4.0.0
277 */
278export const fromInput: (input: Input) => Option.Option<ByteSize> = Option.liftThrowable(fromInputUnsafe)
279
280/**
281 * Creates a byte size from a non-negative byte count.
282 *
283 * @category constructors
284 * @since 4.0.0
285 */
286export const bytes = (value: number | bigint): ByteSize =>
287 typeof value === "bigint" ? fromInputUnsafe(value) : fromNumber(value)
288
289const unitConstructor = (symbol: Unit) => (value: number | bigint): ByteSize =>
290 fromQuantity(value, unitsBySymbol.get(symbol)!)
291
292/**
293 * Creates a decimal kilobyte value.
294 *
295 * @category constructors
296 * @since 4.0.0
297 */
298export const kilobytes: (value: number | bigint) => ByteSize = unitConstructor("kB")
299/**
300 * Creates a decimal megabyte value.
301 *
302 * @category constructors
303 * @since 4.0.0
304 */
305export const megabytes: (value: number | bigint) => ByteSize = unitConstructor("MB")
306/**
307 * Creates a decimal gigabyte value.
308 *
309 * @category constructors
310 * @since 4.0.0
311 */
312export const gigabytes: (value: number | bigint) => ByteSize = unitConstructor("GB")
313/**
314 * Creates a decimal terabyte value.
315 *
316 * @category constructors
317 * @since 4.0.0
318 */
319export const terabytes: (value: number | bigint) => ByteSize = unitConstructor("TB")
320/**
321 * Creates a decimal petabyte value.
322 *
323 * @category constructors
324 * @since 4.0.0
325 */
326export const petabytes: (value: number | bigint) => ByteSize = unitConstructor("PB")
327/**
328 * Creates a decimal exabyte value.
329 *
330 * @category constructors
331 * @since 4.0.0
332 */
333export const exabytes: (value: number | bigint) => ByteSize = unitConstructor("EB")
334/**
335 * Creates a decimal zettabyte value.
336 *
337 * @category constructors
338 * @since 4.0.0
339 */
340export const zettabytes: (value: number | bigint) => ByteSize = unitConstructor("ZB")
341/**
342 * Creates a decimal yottabyte value.
343 *
344 * @category constructors
345 * @since 4.0.0
346 */
347export const yottabytes: (value: number | bigint) => ByteSize = unitConstructor("YB")
348/**
349 * Creates a decimal ronnabyte value.
350 *
351 * @category constructors
352 * @since 4.0.0
353 */
354export const ronnabytes: (value: number | bigint) => ByteSize = unitConstructor("RB")
355/**
356 * Creates a decimal quettabyte value.
357 *
358 * @category constructors
359 * @since 4.0.0
360 */
361export const quettabytes: (value: number | bigint) => ByteSize = unitConstructor("QB")
362/**
363 * Creates a binary kibibyte value.
364 *
365 * @category constructors
366 * @since 4.0.0
367 */
368export const kibibytes: (value: number | bigint) => ByteSize = unitConstructor("KiB")
369/**
370 * Creates a binary mebibyte value.
371 *
372 * @category constructors
373 * @since 4.0.0
374 */
375export const mebibytes: (value: number | bigint) => ByteSize = unitConstructor("MiB")
376/**
377 * Creates a binary gibibyte value.
378 *
379 * @category constructors
380 * @since 4.0.0
381 */
382export const gibibytes: (value: number | bigint) => ByteSize = unitConstructor("GiB")
383/**
384 * Creates a binary tebibyte value.
385 *
386 * @category constructors
387 * @since 4.0.0
388 */
389export const tebibytes: (value: number | bigint) => ByteSize = unitConstructor("TiB")
390/**
391 * Creates a binary pebibyte value.
392 *
393 * @category constructors
394 * @since 4.0.0
395 */
396export const pebibytes: (value: number | bigint) => ByteSize = unitConstructor("PiB")
397/**
398 * Creates a binary exbibyte value.
399 *
400 * @category constructors
401 * @since 4.0.0
402 */
403export const exbibytes: (value: number | bigint) => ByteSize = unitConstructor("EiB")
404/**
405 * Creates a binary zebibyte value.
406 *
407 * @category constructors
408 * @since 4.0.0
409 */
410export const zebibytes: (value: number | bigint) => ByteSize = unitConstructor("ZiB")
411/**
412 * Creates a binary yobibyte value.
413 *
414 * @category constructors
415 * @since 4.0.0
416 */
417export const yobibytes: (value: number | bigint) => ByteSize = unitConstructor("YiB")
418
419/**
420 * Checks whether a value is a byte size.
421 *
422 * @category guards
423 * @since 4.0.0
424 */
425export const isByteSize = (input: unknown): input is ByteSize => typeof input === "bigint" && input >= bigint0
426
427/**
428 * Checks whether a byte size is zero.
429 *
430 * @category predicates
431 * @since 4.0.0
432 */
433export const isZero = (self: ByteSize): boolean => self === bigint0
434
435/**
436 * Returns the exact byte count as a bigint.
437 *
438 * @category getters
439 * @since 4.0.0
440 */
441export const toBigInt = (self: ByteSize): bigint => self
442
443/**
444 * Converts a byte size to a safe integer, returning `None` when it is too large.
445 *
446 * @category converting
447 * @since 4.0.0
448 */
449export const toNumber: (self: ByteSize) => Option.Option<number> = BI.toNumber
450
451/**
452 * Converts a byte size to a safe integer and throws when it is too large.
453 *
454 * @category unsafe
455 * @since 4.0.0
456 */
457export const toNumberUnsafe = (self: ByteSize): number =>
458 Option.getOrThrowWith(toNumber(self), () => new Error(`ByteSize exceeds Number.MAX_SAFE_INTEGER: ${self}`))
459
460/**
461 * Converts a byte size to an approximate number of the specified unit.
462 *
463 * @category converting
464 * @since 4.0.0
465 */
466export const toUnit: {
467 (unit: Unit): (self: ByteSize) => number
468 (self: ByteSize, unit: Unit): number
469} = dual(2, (self: ByteSize, unit: Unit) => Number(self) / Number(unitsBySymbol.get(unit)!.factor))
470
471/**
472 * Provides an order for byte sizes.
473 *
474 * @category instances
475 * @since 4.0.0
476 */
477export const Order: order.Order<ByteSize> = BI.Order
478
479/**
480 * Provides an equivalence for byte sizes.
481 *
482 * @category instances
483 * @since 4.0.0
484 */
485export const Equivalence: Equ.Equivalence<ByteSize> = BI.Equivalence
486
487/**
488 * Returns whether a byte size is in an inclusive range.
489 *
490 * @category predicates
491 * @since 4.0.0
492 */
493export const between: {
494 (options: { minimum: ByteSize; maximum: ByteSize }): (self: ByteSize) => boolean
495 (self: ByteSize, options: { minimum: ByteSize; maximum: ByteSize }): boolean
496} = BI.between
497
498/**
499 * Returns the smaller byte size.
500 *
501 * @category ordering
502 * @since 4.0.0
503 */
504export const min: {
505 (that: ByteSize): (self: ByteSize) => ByteSize
506 (self: ByteSize, that: ByteSize): ByteSize
507} = BI.min as any
508
509/**
510 * Returns the larger byte size.
511 *
512 * @category ordering
513 * @since 4.0.0
514 */
515export const max: {
516 (that: ByteSize): (self: ByteSize) => ByteSize
517 (self: ByteSize, that: ByteSize): ByteSize
518} = BI.max as any
519
520/**
521 * Constrains a byte size to an inclusive range.
522 *
523 * @category ordering
524 * @since 4.0.0
525 */
526export const clamp: {
527 (options: { minimum: ByteSize; maximum: ByteSize }): (self: ByteSize) => ByteSize
528 (self: ByteSize, options: { minimum: ByteSize; maximum: ByteSize }): ByteSize
529} = BI.clamp as any
530
531/**
532 * Checks whether the first byte size is less than the second.
533 *
534 * @category predicates
535 * @since 4.0.0
536 */
537export const isLessThan: {
538 (that: ByteSize): (self: ByteSize) => boolean
539 (self: ByteSize, that: ByteSize): boolean
540} = BI.isLessThan
541
542/**
543 * Checks whether the first byte size is at most the second.
544 *
545 * @category predicates
546 * @since 4.0.0
547 */
548export const isLessThanOrEqualTo: {
549 (that: ByteSize): (self: ByteSize) => boolean
550 (self: ByteSize, that: ByteSize): boolean
551} = BI.isLessThanOrEqualTo
552
553/**
554 * Checks whether the first byte size is greater than the second.
555 *
556 * @category predicates
557 * @since 4.0.0
558 */
559export const isGreaterThan: {
560 (that: ByteSize): (self: ByteSize) => boolean
561 (self: ByteSize, that: ByteSize): boolean
562} = BI.isGreaterThan
563
564/**
565 * Checks whether the first byte size is at least the second.
566 *
567 * @category predicates
568 * @since 4.0.0
569 */
570export const isGreaterThanOrEqualTo: {
571 (that: ByteSize): (self: ByteSize) => boolean
572 (self: ByteSize, that: ByteSize): boolean
573} = BI.isGreaterThanOrEqualTo
574
575/**
576 * Checks whether two byte sizes contain the same count.
577 *
578 * @category predicates
579 * @since 4.0.0
580 */
581export const equals: {
582 (that: ByteSize): (self: ByteSize) => boolean
583 (self: ByteSize, that: ByteSize): boolean
584} = dual(2, Equivalence)
585
586/**
587 * Adds two byte sizes exactly.
588 *
589 * @category math
590 * @since 4.0.0
591 */
592export const sum: {
593 (that: ByteSize): (self: ByteSize) => ByteSize
594 (self: ByteSize, that: ByteSize): ByteSize
595} = BI.sum as any
596
597/**
598 * Subtracts byte sizes, returning `None` on underflow.
599 *
600 * @category math
601 * @since 4.0.0
602 */
603export const subtract: {
604 (that: ByteSize): (self: ByteSize) => Option.Option<ByteSize>
605 (self: ByteSize, that: ByteSize): Option.Option<ByteSize>
606} = dual(
607 2,
608 (self: ByteSize, that: ByteSize) => self < that ? Option.none() : Option.some(make(self - that))
609)
610
611/**
612 * Subtracts byte sizes and throws on underflow.
613 *
614 * @category unsafe
615 * @since 4.0.0
616 */
617export const subtractUnsafe: {
618 (that: ByteSize): (self: ByteSize) => ByteSize
619 (self: ByteSize, that: ByteSize): ByteSize
620} = dual(2, (self: ByteSize, that: ByteSize) => {
621 if (self < that) throw new Error(`ByteSize subtraction underflow: ${self} - ${that}`)
622 return make(self - that)
623})
624
625const scalar = (input: number | bigint, positive: boolean): bigint | undefined => {
626 if (typeof input === "bigint") return input >= (positive ? bigint1 : bigint0) ? input : undefined
627 return Number.isSafeInteger(input) && input >= (positive ? 1 : 0) ? BigInt(input) : undefined
628}
629
630/**
631 * Multiplies a byte size by a non-negative integer scalar.
632 *
633 * @category math
634 * @since 4.0.0
635 */
636export const times: {
637 (multiplier: number | bigint): (self: ByteSize) => Option.Option<ByteSize>
638 (self: ByteSize, multiplier: number | bigint): Option.Option<ByteSize>
639} = dual(2, (self: ByteSize, multiplier: number | bigint) => {
640 const value = scalar(multiplier, false)
641 return value === undefined ? Option.none() : Option.some(make(self * value))
642})
643
644/**
645 * Divides a byte size by a positive integer, discarding any remainder.
646 *
647 * @category math
648 * @since 4.0.0
649 */
650export const divide: {
651 (divisor: number | bigint): (self: ByteSize) => Option.Option<ByteSize>
652 (self: ByteSize, divisor: number | bigint): Option.Option<ByteSize>
653} = dual(2, (self: ByteSize, divisor: number | bigint) => {
654 const value = scalar(divisor, true)
655 return value === undefined ? Option.none() : Option.some(make(self / value))
656})
657
658const validatePrecision = (precision: number): number => {
659 if (!Number.isSafeInteger(precision) || precision < 0 || precision > 20) {
660 throw new Error(`ByteSize format precision must be an integer from 0 to 20, received ${precision}`)
661 }
662 return precision
663}
664
665const formatWithUnit = (value: bigint, unit: UnitInfo, precision: number, trailingZeros: boolean): string => {
666 const scale = BigInt(10) ** BigInt(precision)
667 const rounded = (value * scale * BigInt(2) + unit.factor) / (unit.factor * BigInt(2))
668 const whole = rounded / scale
669 if (precision === 0) return `${whole} ${unit.symbol}`
670 let fraction = `${rounded % scale}`.padStart(precision, "0")
671 if (!trailingZeros) fraction = fraction.replace(/0+$/, "")
672 return `${whole}${fraction.length === 0 ? "" : `.${fraction}`} ${unit.symbol}`
673}
674
675/**
676 * Formats a byte size with canonical decimal or binary unit symbols.
677 *
678 * @category converting
679 * @since 4.0.0
680 */
681export const format = (self: ByteSize, options: FormatOptions = {}): string => {
682 const precision = validatePrecision(options.precision ?? 2)
683 const trailingZeros = options.trailingZeros ?? false
684 if (options.unit !== undefined) {
685 return formatWithUnit(self, unitsBySymbol.get(options.unit)!, precision, trailingZeros)
686 }
687 const units = options.system === "decimal" ? decimalUnits : binaryUnits
688 if (self === bigint0) return "0 B"
689 let index = units.length - 1
690 while (index > 0 && self < units[index].factor) index--
691 if (index < units.length - 1) {
692 const scale = BigInt(10) ** BigInt(precision)
693 const rounded = (self * scale * BigInt(2) + units[index].factor) / (units[index].factor * BigInt(2))
694 const base = options.system === "decimal" ? decimalBase : binaryBase
695 if (rounded >= base * scale) index++
696 }
697 return formatWithUnit(self, units[index], precision, trailingZeros)
698}
699
700/**
701 * Reducer that sums byte sizes from zero.
702 *
703 * @category math
704 * @since 4.0.0
705 */
706export const ReducerSum: Reducer.Reducer<ByteSize> = BI.ReducerSum as any
707
708/**
709 * Combiner that keeps the largest byte size.
710 *
711 * @category math
712 * @since 4.0.0
713 */
714export const CombinerMax: Combiner.Combiner<ByteSize> = BI.CombinerMax as any
715
716/**
717 * Combiner that keeps the smallest byte size.
718 *
719 * @category math
720 * @since 4.0.0
721 */
722export const CombinerMin: Combiner.Combiner<ByteSize> = BI.CombinerMin as any