• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

PeculiarVentures / pvtsutils / 25105967637

29 Apr 2026 11:21AM UTC coverage: 92.053% (+1.1%) from 90.909%
25105967637

push

github

web-flow
Merge pull request #17 from PeculiarVentures:v2-dev

Release @peculiar/utils v2

293 of 374 branches covered (78.34%)

556 of 604 new or added lines in 17 files covered. (92.05%)

556 of 604 relevant lines covered (92.05%)

17.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.78
/src/bytes/sequence.ts
1
declare const TextEncoder: new() => { encode(text: string): Uint8Array };
2

3
import { toUint8Array, toUint8ArrayCopy } from "./buffer-source.js";
4
import type { BufferSourceLike } from "./types.js";
5

6
/** String or byte pattern accepted by the byte search helpers. */
7
export type BytePattern = BufferSourceLike | string;
8

9
/** Options shared by the stateless byte search helpers. */
10
export interface ByteSearchOptions {
11
  /**
12
   * Start offset for forward search.
13
   * For reverse search this is the upper bound / starting point.
14
   */
15
  start?: number;
16

17
  /**
18
   * End offset, exclusive.
19
   * For reverse search this is the lower bound.
20
   */
21
  end?: number;
22

23
  /**
24
   * Encoding used when pattern is a string.
25
   * Defaults to ASCII for marker-style byte searches.
26
   */
27
  encoding?: "ascii" | "utf8";
28
}
29

30
function clampIndex(value: number | undefined, fallback: number, length: number): number {
31
  const normalized = Number.isFinite(value) ? Math.trunc(value as number) : fallback;
42✔
32
  if (normalized <= 0) {
42✔
33
    return 0;
18✔
34
  }
35
  if (normalized >= length) {
24✔
36
    return length;
19✔
37
  }
38
  return normalized;
5✔
39
}
40

41
function normalizeForwardRange(length: number, options?: ByteSearchOptions): [number, number] {
42
  const start = clampIndex(options?.start, 0, length);
13✔
43
  const end = clampIndex(options?.end, length, length);
13✔
44
  return end >= start ? [start, end] : [start, start];
13!
45
}
46

47
function normalizeReverseRange(length: number, options?: ByteSearchOptions): [number, number] {
48
  const start = clampIndex(options?.start, length, length);
8✔
49
  const end = clampIndex(options?.end, 0, length);
8✔
50
  return start >= end ? [end, start] : [start, start];
8!
51
}
52

53
function normalizeSliceIndex(value: number | undefined, fallback: number, length: number): number {
54
  const normalized = Number.isFinite(value) ? Math.trunc(value as number) : fallback;
8✔
55
  if (normalized < 0) {
8✔
56
    return Math.max(length + normalized, 0);
3✔
57
  }
58
  if (normalized > length) {
5!
NEW
59
    return length;
×
60
  }
61
  return normalized;
5✔
62
}
63

64
function encodeAscii(text: string): Uint8Array {
65
  const bytes = new Uint8Array(text.length);
6✔
66
  for (let i = 0; i < text.length; i++) {
6✔
67
    bytes[i] = text.charCodeAt(i) & 0xff;
33✔
68
  }
69
  return bytes;
6✔
70
}
71

72
function encodeUtf8(text: string): Uint8Array {
73
  return new TextEncoder().encode(text) as Uint8Array;
4✔
74
}
75

76
function toPatternBytes(pattern: BytePattern, options?: Pick<ByteSearchOptions, "encoding">): Uint8Array {
77
  if (typeof pattern === "string") {
32✔
78
    return options?.encoding === "utf8" ? encodeUtf8(pattern) : encodeAscii(pattern);
10✔
79
  }
80
  return toUint8Array(pattern);
22✔
81
}
82

83
function bytesEqualAt(data: Uint8Array, pattern: Uint8Array, offset: number): boolean {
84
  for (let index = 0; index < pattern.byteLength; index++) {
39✔
85
    if (data[offset + index] !== pattern[index]) {
80✔
86
      return false;
18✔
87
    }
88
  }
89
  return true;
21✔
90
}
91

92
/**
93
 * Searches for the first occurrence of a byte pattern within the requested range.
94
 * Returns the absolute byte offset or `-1` when the pattern is not found.
95
 */
96
export function indexOf(data: BufferSourceLike, pattern: BytePattern, options?: ByteSearchOptions): number {
97
  const bytes = toUint8Array(data);
13✔
98
  const needle = toPatternBytes(pattern, options);
13✔
99
  const [start, end] = normalizeForwardRange(bytes.byteLength, options);
13✔
100

101
  if (needle.byteLength === 0) {
13✔
102
    return start;
1✔
103
  }
104

105
  const lastOffset = end - needle.byteLength;
12✔
106
  if (lastOffset < start) {
12✔
107
    return -1;
1✔
108
  }
109

110
  for (let offset = start; offset <= lastOffset; offset++) {
11✔
111
    if (bytesEqualAt(bytes, needle, offset)) {
20✔
112
      return offset;
8✔
113
    }
114
  }
115

116
  return -1;
3✔
117
}
118

119
/**
120
 * Searches backwards for the last occurrence of a byte pattern within the requested range.
121
 * Returns the absolute byte offset or `-1` when the pattern is not found.
122
 */
123
export function lastIndexOf(data: BufferSourceLike, pattern: BytePattern, options?: ByteSearchOptions): number {
124
  const bytes = toUint8Array(data);
8✔
125
  const needle = toPatternBytes(pattern, options);
8✔
126
  const [end, start] = normalizeReverseRange(bytes.byteLength, options);
8✔
127

128
  if (needle.byteLength === 0) {
8✔
129
    return start;
1✔
130
  }
131

132
  const firstOffset = start - needle.byteLength;
7✔
133
  if (firstOffset < end) {
7✔
134
    return -1;
1✔
135
  }
136

137
  for (let offset = firstOffset; offset >= end; offset--) {
6✔
138
    if (bytesEqualAt(bytes, needle, offset)) {
9✔
139
      return offset;
5✔
140
    }
141
  }
142

143
  return -1;
1✔
144
}
145

146
/** Returns `true` when the pattern exists anywhere in the requested range. */
147
export function includes(data: BufferSourceLike, pattern: BytePattern, options?: ByteSearchOptions): boolean {
148
  return indexOf(data, pattern, options) !== -1;
4✔
149
}
150

151
/** Returns `true` when the byte sequence starts with the requested pattern. */
152
export function startsWith(
153
  data: BufferSourceLike,
154
  pattern: BytePattern,
155
  options?: Pick<ByteSearchOptions, "encoding">,
156
): boolean {
157
  const bytes = toUint8Array(data);
5✔
158
  const needle = toPatternBytes(pattern, options);
5✔
159

160
  if (needle.byteLength > bytes.byteLength) {
5!
NEW
161
    return false;
×
162
  }
163

164
  return bytesEqualAt(bytes, needle, 0);
5✔
165
}
166

167
/** Returns `true` when the byte sequence ends with the requested pattern. */
168
export function endsWith(
169
  data: BufferSourceLike,
170
  pattern: BytePattern,
171
  options?: Pick<ByteSearchOptions, "encoding">,
172
): boolean {
173
  const bytes = toUint8Array(data);
6✔
174
  const needle = toPatternBytes(pattern, options);
6✔
175

176
  if (needle.byteLength > bytes.byteLength) {
6✔
177
    return false;
1✔
178
  }
179

180
  return bytesEqualAt(bytes, needle, bytes.byteLength - needle.byteLength);
5✔
181
}
182

183
/**
184
 * Returns a Uint8Array view over the requested byte range.
185
 * Negative indexes follow `Array.prototype.slice` semantics.
186
 */
187
export function slice(data: BufferSourceLike, start?: number, end?: number): Uint8Array {
188
  const bytes = toUint8Array(data);
4✔
189
  const normalizedStart = normalizeSliceIndex(start, 0, bytes.byteLength);
4✔
190
  const normalizedEnd = normalizeSliceIndex(end, bytes.byteLength, bytes.byteLength);
4✔
191

192
  if (normalizedEnd <= normalizedStart) {
4✔
193
    return bytes.subarray(normalizedStart, normalizedStart);
1✔
194
  }
195

196
  return bytes.subarray(normalizedStart, normalizedEnd);
3✔
197
}
198

199
/** Returns the last `length` bytes of the input as a Uint8Array view. */
200
export function tail(data: BufferSourceLike, length: number): Uint8Array {
201
  const bytes = toUint8Array(data);
4✔
202
  const normalizedLength = Number.isFinite(length) ? Math.max(0, Math.trunc(length)) : 0;
4!
203

204
  if (normalizedLength >= bytes.byteLength) {
4✔
205
    return bytes;
2✔
206
  }
207

208
  return bytes.subarray(bytes.byteLength - normalizedLength);
2✔
209
}
210

211
/** Returns a new Uint8Array copy that never shares memory with the input. */
212
export function copy(data: BufferSourceLike): Uint8Array {
213
  return toUint8ArrayCopy(data);
2✔
214
}
215

216
/** Compares two byte sequences lexicographically. */
217
export function compare(a: BufferSourceLike, b: BufferSourceLike): -1 | 0 | 1 {
218
  const left = toUint8Array(a);
5✔
219
  const right = toUint8Array(b);
5✔
220
  const limit = Math.min(left.byteLength, right.byteLength);
5✔
221

222
  for (let index = 0; index < limit; index++) {
5✔
223
    if (left[index] < right[index]) {
10✔
224
      return -1;
1✔
225
    }
226
    if (left[index] > right[index]) {
9✔
227
      return 1;
1✔
228
    }
229
  }
230

231
  if (left.byteLength < right.byteLength) {
3✔
232
    return -1;
1✔
233
  }
234
  if (left.byteLength > right.byteLength) {
2✔
235
    return 1;
1✔
236
  }
237
  return 0;
1✔
238
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc