strtok3 10.3.4 → 10.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +21 -21
- package/README.md +561 -399
- package/lib/AbortSignal.d.ts +2 -0
- package/lib/AbortSignal.js +7 -0
- package/lib/AbstractTokenizer.d.ts +41 -3
- package/lib/AbstractTokenizer.js +105 -17
- package/lib/BlobTokenizer.d.ts +1 -1
- package/lib/BlobTokenizer.js +14 -11
- package/lib/BufferSizeError.d.ts +19 -0
- package/lib/BufferSizeError.js +27 -0
- package/lib/BufferTokenizer.d.ts +1 -1
- package/lib/BufferTokenizer.js +6 -8
- package/lib/FileTokenizer.d.ts +4 -3
- package/lib/FileTokenizer.js +22 -6
- package/lib/ReadStreamTokenizer.d.ts +3 -0
- package/lib/ReadStreamTokenizer.js +32 -9
- package/lib/core.d.ts +7 -6
- package/lib/core.js +11 -4
- package/lib/index.d.ts +4 -4
- package/lib/index.js +2 -2
- package/lib/stream/AbstractStreamReader.js +5 -3
- package/lib/stream/Errors.js +3 -3
- package/lib/stream/StreamReader.d.ts +4 -3
- package/lib/stream/StreamReader.js +40 -24
- package/lib/stream/WebStreamByobReader.js +5 -3
- package/lib/stream/WebStreamDefaultReader.d.ts +1 -1
- package/lib/stream/WebStreamDefaultReader.js +9 -4
- package/lib/stream/WebStreamReader.d.ts +1 -1
- package/lib/stream/WebStreamReader.js +1 -1
- package/lib/stream/WebStreamReaderFactory.js +1 -1
- package/lib/stream/index.d.ts +1 -1
- package/lib/types.d.ts +7 -0
- package/package.json +95 -94
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ITokenizer, IFileInfo, IReadChunkOptions, ITokenizerOptions } from './types.js';
|
|
2
1
|
import type { IGetToken, IToken } from '@tokenizer/token';
|
|
2
|
+
import type { IFileInfo, IReadChunkOptions, ITokenizer, ITokenizerOptions } from './types.js';
|
|
3
3
|
interface INormalizedReadChunkOptions extends IReadChunkOptions {
|
|
4
4
|
length: number;
|
|
5
5
|
position: number;
|
|
@@ -10,7 +10,10 @@ interface INormalizedReadChunkOptions extends IReadChunkOptions {
|
|
|
10
10
|
*/
|
|
11
11
|
export declare abstract class AbstractTokenizer implements ITokenizer {
|
|
12
12
|
private onClose?;
|
|
13
|
+
private abortSignal?;
|
|
14
|
+
private abortListener?;
|
|
13
15
|
private numBuffer;
|
|
16
|
+
protected readonly maxBufferSize: number;
|
|
14
17
|
abstract fileInfo: IFileInfo;
|
|
15
18
|
/**
|
|
16
19
|
* Tokenizer-stream position
|
|
@@ -22,6 +25,8 @@ export declare abstract class AbstractTokenizer implements ITokenizer {
|
|
|
22
25
|
* @protected
|
|
23
26
|
*/
|
|
24
27
|
protected constructor(options?: ITokenizerOptions);
|
|
28
|
+
private addAbortListener;
|
|
29
|
+
protected removeAbortListener(): void;
|
|
25
30
|
abstract supportsRandomAccess(): boolean;
|
|
26
31
|
/**
|
|
27
32
|
* Read buffer from tokenizer
|
|
@@ -50,7 +55,7 @@ export declare abstract class AbstractTokenizer implements ITokenizer {
|
|
|
50
55
|
* @param position - Offset where to begin reading within the file. If position is null, data will be read from the current file position.
|
|
51
56
|
* @returns Promise with token data
|
|
52
57
|
*/
|
|
53
|
-
peekToken<Value>(token: IGetToken<Value>, position?: number): Promise<Value>;
|
|
58
|
+
peekToken<Value>(token: IGetToken<Value>, position?: number | null): Promise<Value>;
|
|
54
59
|
/**
|
|
55
60
|
* Read a numeric token from the stream
|
|
56
61
|
* @param token - Numeric token
|
|
@@ -65,11 +70,44 @@ export declare abstract class AbstractTokenizer implements ITokenizer {
|
|
|
65
70
|
peekNumber(token: IToken<number>): Promise<number>;
|
|
66
71
|
/**
|
|
67
72
|
* Ignore number of bytes, advances the pointer in under tokenizer-stream.
|
|
68
|
-
* @param length - Number of bytes to ignore
|
|
73
|
+
* @param length - Number of bytes to ignore. Must be ≥ 0.
|
|
69
74
|
* @return resolves the number of bytes ignored, equals length if this available, otherwise the number of bytes available
|
|
70
75
|
*/
|
|
71
76
|
ignore(length: number): Promise<number>;
|
|
72
77
|
close(): Promise<void>;
|
|
78
|
+
/**
|
|
79
|
+
* Validate a byte length or position before using it in buffer calculations.
|
|
80
|
+
* @param value - Value that must be a non-negative safe integer
|
|
81
|
+
* @param name - Label used in the validation error
|
|
82
|
+
* @throws RangeError if the value is negative or not a safe integer
|
|
83
|
+
*/
|
|
84
|
+
protected validateLength(value: number, name: string): void;
|
|
85
|
+
/**
|
|
86
|
+
* Validate a requested buffer size against the configured allocation limit.
|
|
87
|
+
* @param length - Requested buffer size in bytes
|
|
88
|
+
* @throws RangeError if the length is negative or not a safe integer
|
|
89
|
+
* @throws BufferSizeError if the length exceeds maxBufferSize
|
|
90
|
+
*/
|
|
91
|
+
protected validateBufferSize(length: number): void;
|
|
92
|
+
/**
|
|
93
|
+
* Validate a token request before allocating a buffer or consuming input.
|
|
94
|
+
* @param length - Token length in bytes
|
|
95
|
+
* @param position - Byte offset where the token starts
|
|
96
|
+
* @param peek - Whether to include skipped stream bytes in the lookahead limit
|
|
97
|
+
* @throws RangeError if a length or position is negative or not a safe integer
|
|
98
|
+
* @throws BufferSizeError if the token buffer or stream lookahead exceeds maxBufferSize
|
|
99
|
+
* @throws Error if a stream request starts before the current position
|
|
100
|
+
* @throws EndOfStreamError if the token extends beyond the known input size
|
|
101
|
+
*/
|
|
102
|
+
private validateTokenRead;
|
|
103
|
+
/**
|
|
104
|
+
* Apply defaults and validate buffer read or peek options.
|
|
105
|
+
* @param uint8Array - Target buffer whose length is the default read length
|
|
106
|
+
* @param options - Requested length, position and partial-read behavior
|
|
107
|
+
* @returns Validated options with an explicit length, position and mayBeLess flag
|
|
108
|
+
* @throws RangeError if a length or position is invalid, or the length exceeds the target buffer
|
|
109
|
+
* @throws Error if a stream request starts before the current position
|
|
110
|
+
*/
|
|
73
111
|
protected normalizeOptions(uint8Array: Uint8Array, options?: IReadChunkOptions): INormalizedReadChunkOptions;
|
|
74
112
|
abort(): Promise<void>;
|
|
75
113
|
}
|
package/lib/AbstractTokenizer.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { throwIfAborted } from './AbortSignal.js';
|
|
2
|
+
import { BufferSizeError, getMaxBufferSize } from './BufferSizeError.js';
|
|
1
3
|
import { EndOfStreamError } from './stream/index.js';
|
|
2
4
|
/**
|
|
3
5
|
* Core tokenizer
|
|
@@ -14,13 +16,29 @@ export class AbstractTokenizer {
|
|
|
14
16
|
* Tokenizer-stream position
|
|
15
17
|
*/
|
|
16
18
|
this.position = 0;
|
|
19
|
+
this.maxBufferSize = getMaxBufferSize(options);
|
|
20
|
+
throwIfAborted(options?.abortSignal);
|
|
17
21
|
this.onClose = options?.onClose;
|
|
18
22
|
if (options?.abortSignal) {
|
|
19
|
-
options.abortSignal
|
|
20
|
-
this.abort()
|
|
23
|
+
this.addAbortListener(options.abortSignal, () => {
|
|
24
|
+
void this.abort().catch(() => {
|
|
25
|
+
// Signal events cannot propagate cancellation errors; explicit abort/close calls still reject.
|
|
26
|
+
});
|
|
21
27
|
});
|
|
22
28
|
}
|
|
23
29
|
}
|
|
30
|
+
addAbortListener(signal, listener) {
|
|
31
|
+
this.abortSignal = signal;
|
|
32
|
+
this.abortListener = listener;
|
|
33
|
+
signal.addEventListener('abort', listener, { once: true });
|
|
34
|
+
}
|
|
35
|
+
removeAbortListener() {
|
|
36
|
+
if (this.abortListener) {
|
|
37
|
+
this.abortSignal?.removeEventListener('abort', this.abortListener);
|
|
38
|
+
}
|
|
39
|
+
this.abortSignal = undefined;
|
|
40
|
+
this.abortListener = undefined;
|
|
41
|
+
}
|
|
24
42
|
/**
|
|
25
43
|
* Read a token from the tokenizer-stream
|
|
26
44
|
* @param token - The token to read
|
|
@@ -28,10 +46,12 @@ export class AbstractTokenizer {
|
|
|
28
46
|
* @returns Promise with token data
|
|
29
47
|
*/
|
|
30
48
|
async readToken(token, position = this.position) {
|
|
49
|
+
this.validateTokenRead(token.len, position);
|
|
31
50
|
const uint8Array = new Uint8Array(token.len);
|
|
32
51
|
const len = await this.readBuffer(uint8Array, { position });
|
|
33
|
-
if (len < token.len)
|
|
52
|
+
if (len < token.len) {
|
|
34
53
|
throw new EndOfStreamError();
|
|
54
|
+
}
|
|
35
55
|
return token.get(uint8Array, 0);
|
|
36
56
|
}
|
|
37
57
|
/**
|
|
@@ -41,10 +61,13 @@ export class AbstractTokenizer {
|
|
|
41
61
|
* @returns Promise with token data
|
|
42
62
|
*/
|
|
43
63
|
async peekToken(token, position = this.position) {
|
|
64
|
+
const peekPosition = position ?? this.position;
|
|
65
|
+
this.validateTokenRead(token.len, peekPosition, true);
|
|
44
66
|
const uint8Array = new Uint8Array(token.len);
|
|
45
|
-
const len = await this.peekBuffer(uint8Array, { position });
|
|
46
|
-
if (len < token.len)
|
|
67
|
+
const len = await this.peekBuffer(uint8Array, { position: peekPosition });
|
|
68
|
+
if (len < token.len) {
|
|
47
69
|
throw new EndOfStreamError();
|
|
70
|
+
}
|
|
48
71
|
return token.get(uint8Array, 0);
|
|
49
72
|
}
|
|
50
73
|
/**
|
|
@@ -54,8 +77,9 @@ export class AbstractTokenizer {
|
|
|
54
77
|
*/
|
|
55
78
|
async readNumber(token) {
|
|
56
79
|
const len = await this.readBuffer(this.numBuffer, { length: token.len });
|
|
57
|
-
if (len < token.len)
|
|
80
|
+
if (len < token.len) {
|
|
58
81
|
throw new EndOfStreamError();
|
|
82
|
+
}
|
|
59
83
|
return token.get(this.numBuffer, 0);
|
|
60
84
|
}
|
|
61
85
|
/**
|
|
@@ -65,16 +89,22 @@ export class AbstractTokenizer {
|
|
|
65
89
|
*/
|
|
66
90
|
async peekNumber(token) {
|
|
67
91
|
const len = await this.peekBuffer(this.numBuffer, { length: token.len });
|
|
68
|
-
if (len < token.len)
|
|
92
|
+
if (len < token.len) {
|
|
69
93
|
throw new EndOfStreamError();
|
|
94
|
+
}
|
|
70
95
|
return token.get(this.numBuffer, 0);
|
|
71
96
|
}
|
|
72
97
|
/**
|
|
73
98
|
* Ignore number of bytes, advances the pointer in under tokenizer-stream.
|
|
74
|
-
* @param length - Number of bytes to ignore
|
|
99
|
+
* @param length - Number of bytes to ignore. Must be ≥ 0.
|
|
75
100
|
* @return resolves the number of bytes ignored, equals length if this available, otherwise the number of bytes available
|
|
76
101
|
*/
|
|
77
102
|
async ignore(length) {
|
|
103
|
+
if (length < 0) {
|
|
104
|
+
throw new RangeError('ignore length must be ≥ 0 bytes');
|
|
105
|
+
}
|
|
106
|
+
this.validateLength(length, 'ignore length');
|
|
107
|
+
this.validateLength(this.position + length, 'end position');
|
|
78
108
|
if (this.fileInfo.size !== undefined) {
|
|
79
109
|
const bytesLeft = this.fileInfo.size - this.position;
|
|
80
110
|
if (length > bytesLeft) {
|
|
@@ -86,23 +116,81 @@ export class AbstractTokenizer {
|
|
|
86
116
|
return length;
|
|
87
117
|
}
|
|
88
118
|
async close() {
|
|
119
|
+
this.removeAbortListener();
|
|
89
120
|
await this.abort();
|
|
90
121
|
await this.onClose?.();
|
|
91
122
|
}
|
|
123
|
+
/**
|
|
124
|
+
* Validate a byte length or position before using it in buffer calculations.
|
|
125
|
+
* @param value - Value that must be a non-negative safe integer
|
|
126
|
+
* @param name - Label used in the validation error
|
|
127
|
+
* @throws RangeError if the value is negative or not a safe integer
|
|
128
|
+
*/
|
|
129
|
+
validateLength(value, name) {
|
|
130
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
131
|
+
throw new RangeError(`${name} must be a non-negative safe integer`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Validate a requested buffer size against the configured allocation limit.
|
|
136
|
+
* @param length - Requested buffer size in bytes
|
|
137
|
+
* @throws RangeError if the length is negative or not a safe integer
|
|
138
|
+
* @throws BufferSizeError if the length exceeds maxBufferSize
|
|
139
|
+
*/
|
|
140
|
+
validateBufferSize(length) {
|
|
141
|
+
this.validateLength(length, 'buffer length');
|
|
142
|
+
if (length > this.maxBufferSize) {
|
|
143
|
+
throw new BufferSizeError(length, this.maxBufferSize);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Validate a token request before allocating a buffer or consuming input.
|
|
148
|
+
* @param length - Token length in bytes
|
|
149
|
+
* @param position - Byte offset where the token starts
|
|
150
|
+
* @param peek - Whether to include skipped stream bytes in the lookahead limit
|
|
151
|
+
* @throws RangeError if a length or position is negative or not a safe integer
|
|
152
|
+
* @throws BufferSizeError if the token buffer or stream lookahead exceeds maxBufferSize
|
|
153
|
+
* @throws Error if a stream request starts before the current position
|
|
154
|
+
* @throws EndOfStreamError if the token extends beyond the known input size
|
|
155
|
+
*/
|
|
156
|
+
validateTokenRead(length, position, peek = false) {
|
|
157
|
+
this.validateBufferSize(length);
|
|
158
|
+
this.validateLength(position, 'position');
|
|
159
|
+
this.validateLength(position + length, 'end position');
|
|
160
|
+
if (!this.supportsRandomAccess() && position < this.position) {
|
|
161
|
+
throw new Error('`options.position` must be equal or greater than `tokenizer.position`');
|
|
162
|
+
}
|
|
163
|
+
if (peek && !this.supportsRandomAccess()) {
|
|
164
|
+
this.validateBufferSize(length + position - this.position);
|
|
165
|
+
}
|
|
166
|
+
if (this.fileInfo.size !== undefined && length > this.fileInfo.size - position) {
|
|
167
|
+
throw new EndOfStreamError();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Apply defaults and validate buffer read or peek options.
|
|
172
|
+
* @param uint8Array - Target buffer whose length is the default read length
|
|
173
|
+
* @param options - Requested length, position and partial-read behavior
|
|
174
|
+
* @returns Validated options with an explicit length, position and mayBeLess flag
|
|
175
|
+
* @throws RangeError if a length or position is invalid, or the length exceeds the target buffer
|
|
176
|
+
* @throws Error if a stream request starts before the current position
|
|
177
|
+
*/
|
|
92
178
|
normalizeOptions(uint8Array, options) {
|
|
93
|
-
|
|
179
|
+
const length = options?.length ?? uint8Array.length;
|
|
180
|
+
const position = options?.position ?? this.position;
|
|
181
|
+
this.validateLength(length, 'length');
|
|
182
|
+
this.validateLength(position, 'position');
|
|
183
|
+
this.validateLength(position + length, 'end position');
|
|
184
|
+
if (length > uint8Array.length) {
|
|
185
|
+
throw new RangeError('length must not exceed the target buffer length');
|
|
186
|
+
}
|
|
187
|
+
if (!this.supportsRandomAccess() && position < this.position) {
|
|
94
188
|
throw new Error('`options.position` must be equal or greater than `tokenizer.position`');
|
|
95
189
|
}
|
|
96
|
-
return {
|
|
97
|
-
...{
|
|
98
|
-
mayBeLess: false,
|
|
99
|
-
offset: 0,
|
|
100
|
-
length: uint8Array.length,
|
|
101
|
-
position: this.position
|
|
102
|
-
}, ...options
|
|
103
|
-
};
|
|
190
|
+
return { length, position, mayBeLess: options?.mayBeLess ?? false };
|
|
104
191
|
}
|
|
105
192
|
abort() {
|
|
193
|
+
this.removeAbortListener();
|
|
106
194
|
return Promise.resolve(); // Ignore abort signal
|
|
107
195
|
}
|
|
108
196
|
}
|
package/lib/BlobTokenizer.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ITokenizerOptions, IReadChunkOptions, IRandomAccessFileInfo, IRandomAccessTokenizer } from './types.js';
|
|
2
1
|
import { AbstractTokenizer } from './AbstractTokenizer.js';
|
|
2
|
+
import type { IRandomAccessFileInfo, IRandomAccessTokenizer, IReadChunkOptions, ITokenizerOptions } from './types.js';
|
|
3
3
|
export declare class BlobTokenizer extends AbstractTokenizer implements IRandomAccessTokenizer {
|
|
4
4
|
private blob;
|
|
5
5
|
fileInfo: IRandomAccessFileInfo;
|
package/lib/BlobTokenizer.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { EndOfStreamError } from './stream/index.js';
|
|
2
1
|
import { AbstractTokenizer } from './AbstractTokenizer.js';
|
|
2
|
+
import { EndOfStreamError } from './stream/index.js';
|
|
3
3
|
export class BlobTokenizer extends AbstractTokenizer {
|
|
4
4
|
/**
|
|
5
5
|
* Construct BufferTokenizer
|
|
@@ -9,7 +9,7 @@ export class BlobTokenizer extends AbstractTokenizer {
|
|
|
9
9
|
constructor(blob, options) {
|
|
10
10
|
super(options);
|
|
11
11
|
this.blob = blob;
|
|
12
|
-
this.fileInfo = { ...options?.fileInfo ?? {}, ...{ size: blob.size, mimeType: blob.type } };
|
|
12
|
+
this.fileInfo = { ...(options?.fileInfo ?? {}), ...{ size: blob.size, mimeType: blob.type } };
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
15
|
* Read buffer from tokenizer
|
|
@@ -18,11 +18,9 @@ export class BlobTokenizer extends AbstractTokenizer {
|
|
|
18
18
|
* @returns {Promise<number>}
|
|
19
19
|
*/
|
|
20
20
|
async readBuffer(uint8Array, options) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const bytesRead = await this.peekBuffer(uint8Array, options);
|
|
25
|
-
this.position += bytesRead;
|
|
21
|
+
const normOptions = this.normalizeOptions(uint8Array, options);
|
|
22
|
+
const bytesRead = await this.peekBuffer(uint8Array, normOptions);
|
|
23
|
+
this.position = normOptions.position + bytesRead;
|
|
26
24
|
return bytesRead;
|
|
27
25
|
}
|
|
28
26
|
/**
|
|
@@ -33,12 +31,17 @@ export class BlobTokenizer extends AbstractTokenizer {
|
|
|
33
31
|
*/
|
|
34
32
|
async peekBuffer(buffer, options) {
|
|
35
33
|
const normOptions = this.normalizeOptions(buffer, options);
|
|
36
|
-
const bytes2read = Math.min(this.blob.size - normOptions.position, normOptions.length);
|
|
37
|
-
if (
|
|
34
|
+
const bytes2read = Math.max(0, Math.min(this.blob.size - normOptions.position, normOptions.length));
|
|
35
|
+
if (!normOptions.mayBeLess && bytes2read < normOptions.length) {
|
|
38
36
|
throw new EndOfStreamError();
|
|
39
37
|
}
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
for (let offset = 0; offset < bytes2read;) {
|
|
39
|
+
const chunkLength = Math.min(this.maxBufferSize, bytes2read - offset);
|
|
40
|
+
const position = normOptions.position + offset;
|
|
41
|
+
const arrayBuffer = await this.blob.slice(position, position + chunkLength).arrayBuffer();
|
|
42
|
+
buffer.set(new Uint8Array(arrayBuffer), offset);
|
|
43
|
+
offset += chunkLength;
|
|
44
|
+
}
|
|
42
45
|
return bytes2read;
|
|
43
46
|
}
|
|
44
47
|
close() {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ITokenizerOptions } from './types.js';
|
|
2
|
+
/** Thrown before a dynamically sized tokenizer buffer exceeds its configured limit. */
|
|
3
|
+
export declare class BufferSizeError extends RangeError {
|
|
4
|
+
readonly requestedSize: number;
|
|
5
|
+
readonly maxBufferSize: number;
|
|
6
|
+
/**
|
|
7
|
+
* Create an error describing an allocation that exceeds the configured limit.
|
|
8
|
+
* @param requestedSize - Requested buffer size in bytes
|
|
9
|
+
* @param maxBufferSize - Configured maximum buffer size in bytes
|
|
10
|
+
*/
|
|
11
|
+
constructor(requestedSize: number, maxBufferSize: number);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Validate options before acquiring file handles or stream readers.
|
|
15
|
+
* @param options - Tokenizer options containing the optional allocation limit
|
|
16
|
+
* @returns Configured buffer limit in bytes, or Infinity when the limit is disabled
|
|
17
|
+
* @throws RangeError if maxBufferSize is neither a positive safe integer nor Infinity
|
|
18
|
+
*/
|
|
19
|
+
export declare function getMaxBufferSize(options?: ITokenizerOptions): number;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Thrown before a dynamically sized tokenizer buffer exceeds its configured limit. */
|
|
2
|
+
export class BufferSizeError extends RangeError {
|
|
3
|
+
/**
|
|
4
|
+
* Create an error describing an allocation that exceeds the configured limit.
|
|
5
|
+
* @param requestedSize - Requested buffer size in bytes
|
|
6
|
+
* @param maxBufferSize - Configured maximum buffer size in bytes
|
|
7
|
+
*/
|
|
8
|
+
constructor(requestedSize, maxBufferSize) {
|
|
9
|
+
super(`Requested buffer size ${requestedSize} exceeds maxBufferSize ${maxBufferSize}`);
|
|
10
|
+
this.requestedSize = requestedSize;
|
|
11
|
+
this.maxBufferSize = maxBufferSize;
|
|
12
|
+
this.name = 'BufferSizeError';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Validate options before acquiring file handles or stream readers.
|
|
17
|
+
* @param options - Tokenizer options containing the optional allocation limit
|
|
18
|
+
* @returns Configured buffer limit in bytes, or Infinity when the limit is disabled
|
|
19
|
+
* @throws RangeError if maxBufferSize is neither a positive safe integer nor Infinity
|
|
20
|
+
*/
|
|
21
|
+
export function getMaxBufferSize(options) {
|
|
22
|
+
const limit = options?.maxBufferSize ?? Number.POSITIVE_INFINITY;
|
|
23
|
+
if (limit !== Number.POSITIVE_INFINITY && (!Number.isSafeInteger(limit) || limit <= 0)) {
|
|
24
|
+
throw new RangeError('maxBufferSize must be a positive safe integer or Infinity');
|
|
25
|
+
}
|
|
26
|
+
return limit;
|
|
27
|
+
}
|
package/lib/BufferTokenizer.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { ITokenizerOptions, IReadChunkOptions, IRandomAccessFileInfo, IRandomAccessTokenizer } from './types.js';
|
|
2
1
|
import { AbstractTokenizer } from './AbstractTokenizer.js';
|
|
2
|
+
import type { IRandomAccessFileInfo, IRandomAccessTokenizer, IReadChunkOptions, ITokenizerOptions } from './types.js';
|
|
3
3
|
export declare class BufferTokenizer extends AbstractTokenizer implements IRandomAccessTokenizer {
|
|
4
4
|
private uint8Array;
|
|
5
5
|
fileInfo: IRandomAccessFileInfo;
|
package/lib/BufferTokenizer.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { EndOfStreamError } from './stream/index.js';
|
|
2
1
|
import { AbstractTokenizer } from './AbstractTokenizer.js';
|
|
2
|
+
import { EndOfStreamError } from './stream/index.js';
|
|
3
3
|
export class BufferTokenizer extends AbstractTokenizer {
|
|
4
4
|
/**
|
|
5
5
|
* Construct BufferTokenizer
|
|
@@ -9,7 +9,7 @@ export class BufferTokenizer extends AbstractTokenizer {
|
|
|
9
9
|
constructor(uint8Array, options) {
|
|
10
10
|
super(options);
|
|
11
11
|
this.uint8Array = uint8Array;
|
|
12
|
-
this.fileInfo = { ...options?.fileInfo ?? {}, ...{ size: uint8Array.length } };
|
|
12
|
+
this.fileInfo = { ...(options?.fileInfo ?? {}), ...{ size: uint8Array.length } };
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
15
|
* Read buffer from tokenizer
|
|
@@ -18,11 +18,9 @@ export class BufferTokenizer extends AbstractTokenizer {
|
|
|
18
18
|
* @returns {Promise<number>}
|
|
19
19
|
*/
|
|
20
20
|
async readBuffer(uint8Array, options) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const bytesRead = await this.peekBuffer(uint8Array, options);
|
|
25
|
-
this.position += bytesRead;
|
|
21
|
+
const normOptions = this.normalizeOptions(uint8Array, options);
|
|
22
|
+
const bytesRead = await this.peekBuffer(uint8Array, normOptions);
|
|
23
|
+
this.position = normOptions.position + bytesRead;
|
|
26
24
|
return bytesRead;
|
|
27
25
|
}
|
|
28
26
|
/**
|
|
@@ -34,7 +32,7 @@ export class BufferTokenizer extends AbstractTokenizer {
|
|
|
34
32
|
async peekBuffer(uint8Array, options) {
|
|
35
33
|
const normOptions = this.normalizeOptions(uint8Array, options);
|
|
36
34
|
const bytes2read = Math.min(this.uint8Array.length - normOptions.position, normOptions.length);
|
|
37
|
-
if (
|
|
35
|
+
if (!normOptions.mayBeLess && bytes2read < normOptions.length) {
|
|
38
36
|
throw new EndOfStreamError();
|
|
39
37
|
}
|
|
40
38
|
uint8Array.set(this.uint8Array.subarray(normOptions.position, normOptions.position + bytes2read));
|
package/lib/FileTokenizer.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { AbstractTokenizer } from './AbstractTokenizer.js';
|
|
2
|
-
import type { IRandomAccessTokenizer, IRandomAccessFileInfo, IReadChunkOptions, ITokenizerOptions } from './types.js';
|
|
3
1
|
import { type FileHandle } from 'node:fs/promises';
|
|
2
|
+
import { AbstractTokenizer } from './AbstractTokenizer.js';
|
|
3
|
+
import type { IRandomAccessFileInfo, IRandomAccessTokenizer, IReadChunkOptions, ITokenizerOptions } from './types.js';
|
|
4
4
|
interface IFileTokenizerOptions extends ITokenizerOptions {
|
|
5
5
|
/**
|
|
6
6
|
* Pass additional file information to the tokenizer
|
|
@@ -13,8 +13,9 @@ export declare class FileTokenizer extends AbstractTokenizer implements IRandomA
|
|
|
13
13
|
/**
|
|
14
14
|
* Create tokenizer from provided file path
|
|
15
15
|
* @param sourceFilePath File path
|
|
16
|
+
* @param options Tokenizer options
|
|
16
17
|
*/
|
|
17
|
-
static fromFile(sourceFilePath: string): Promise<FileTokenizer>;
|
|
18
|
+
static fromFile(sourceFilePath: string, options?: ITokenizerOptions): Promise<FileTokenizer>;
|
|
18
19
|
protected constructor(fileHandle: FileHandle, options: IFileTokenizerOptions);
|
|
19
20
|
/**
|
|
20
21
|
* Read buffer from file
|
package/lib/FileTokenizer.js
CHANGED
|
@@ -1,15 +1,29 @@
|
|
|
1
|
+
import { open as fsOpen } from 'node:fs/promises';
|
|
2
|
+
import { throwIfAborted } from './AbortSignal.js';
|
|
1
3
|
import { AbstractTokenizer } from './AbstractTokenizer.js';
|
|
4
|
+
import { getMaxBufferSize } from './BufferSizeError.js';
|
|
2
5
|
import { EndOfStreamError } from './stream/index.js';
|
|
3
|
-
import { open as fsOpen } from 'node:fs/promises';
|
|
4
6
|
export class FileTokenizer extends AbstractTokenizer {
|
|
5
7
|
/**
|
|
6
8
|
* Create tokenizer from provided file path
|
|
7
9
|
* @param sourceFilePath File path
|
|
10
|
+
* @param options Tokenizer options
|
|
8
11
|
*/
|
|
9
|
-
static async fromFile(sourceFilePath) {
|
|
12
|
+
static async fromFile(sourceFilePath, options) {
|
|
13
|
+
getMaxBufferSize(options);
|
|
14
|
+
throwIfAborted(options?.abortSignal);
|
|
10
15
|
const fileHandle = await fsOpen(sourceFilePath, 'r');
|
|
11
|
-
|
|
12
|
-
|
|
16
|
+
try {
|
|
17
|
+
const stat = await fileHandle.stat();
|
|
18
|
+
return new FileTokenizer(fileHandle, {
|
|
19
|
+
...options,
|
|
20
|
+
fileInfo: { ...options?.fileInfo, path: sourceFilePath, size: stat.size }
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
await fileHandle.close();
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
13
27
|
}
|
|
14
28
|
constructor(fileHandle, options) {
|
|
15
29
|
super(options);
|
|
@@ -25,8 +39,9 @@ export class FileTokenizer extends AbstractTokenizer {
|
|
|
25
39
|
async readBuffer(uint8Array, options) {
|
|
26
40
|
const normOptions = this.normalizeOptions(uint8Array, options);
|
|
27
41
|
this.position = normOptions.position;
|
|
28
|
-
if (normOptions.length === 0)
|
|
42
|
+
if (normOptions.length === 0) {
|
|
29
43
|
return 0;
|
|
44
|
+
}
|
|
30
45
|
const res = await this.fileHandle.read(uint8Array, 0, normOptions.length, normOptions.position);
|
|
31
46
|
this.position += res.bytesRead;
|
|
32
47
|
if (res.bytesRead < normOptions.length && (!options || !options.mayBeLess)) {
|
|
@@ -43,12 +58,13 @@ export class FileTokenizer extends AbstractTokenizer {
|
|
|
43
58
|
async peekBuffer(uint8Array, options) {
|
|
44
59
|
const normOptions = this.normalizeOptions(uint8Array, options);
|
|
45
60
|
const res = await this.fileHandle.read(uint8Array, 0, normOptions.length, normOptions.position);
|
|
46
|
-
if (
|
|
61
|
+
if (!normOptions.mayBeLess && res.bytesRead < normOptions.length) {
|
|
47
62
|
throw new EndOfStreamError();
|
|
48
63
|
}
|
|
49
64
|
return res.bytesRead;
|
|
50
65
|
}
|
|
51
66
|
async close() {
|
|
67
|
+
this.removeAbortListener();
|
|
52
68
|
await this.fileHandle.close();
|
|
53
69
|
return super.close();
|
|
54
70
|
}
|
|
@@ -24,6 +24,9 @@ export declare class ReadStreamTokenizer extends AbstractTokenizer {
|
|
|
24
24
|
* @returns Promise with number of bytes peeked
|
|
25
25
|
*/
|
|
26
26
|
peekBuffer(uint8Array: Uint8Array, options?: IReadChunkOptions): Promise<number>;
|
|
27
|
+
/**
|
|
28
|
+
* @param length Number of bytes to ignore. Must be ≥ 0.
|
|
29
|
+
*/
|
|
27
30
|
ignore(length: number): Promise<number>;
|
|
28
31
|
abort(): Promise<void>;
|
|
29
32
|
close(): Promise<void>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AbstractTokenizer } from './AbstractTokenizer.js';
|
|
2
2
|
import { EndOfStreamError } from './stream/index.js';
|
|
3
|
-
const
|
|
3
|
+
const ignoreChunkSize = 256000;
|
|
4
4
|
export class ReadStreamTokenizer extends AbstractTokenizer {
|
|
5
5
|
/**
|
|
6
6
|
* Constructor
|
|
@@ -31,9 +31,17 @@ export class ReadStreamTokenizer extends AbstractTokenizer {
|
|
|
31
31
|
if (normOptions.length === 0) {
|
|
32
32
|
return 0;
|
|
33
33
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
34
|
+
let bytesRead = 0;
|
|
35
|
+
while (bytesRead < normOptions.length) {
|
|
36
|
+
const chunkLength = Math.min(this.maxBufferSize, normOptions.length - bytesRead);
|
|
37
|
+
const count = await this.streamReader.read(uint8Array.subarray(bytesRead, bytesRead + chunkLength), normOptions.mayBeLess);
|
|
38
|
+
bytesRead += count;
|
|
39
|
+
this.position += count;
|
|
40
|
+
if (count < chunkLength) {
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (!normOptions.mayBeLess && bytesRead < normOptions.length) {
|
|
37
45
|
throw new EndOfStreamError();
|
|
38
46
|
}
|
|
39
47
|
return bytesRead;
|
|
@@ -46,14 +54,20 @@ export class ReadStreamTokenizer extends AbstractTokenizer {
|
|
|
46
54
|
*/
|
|
47
55
|
async peekBuffer(uint8Array, options) {
|
|
48
56
|
const normOptions = this.normalizeOptions(uint8Array, options);
|
|
57
|
+
if (normOptions.length === 0) {
|
|
58
|
+
return 0;
|
|
59
|
+
}
|
|
60
|
+
const lookaheadSize = normOptions.length + normOptions.position - this.position;
|
|
61
|
+
this.validateBufferSize(lookaheadSize);
|
|
49
62
|
let bytesRead = 0;
|
|
50
63
|
if (normOptions.position) {
|
|
51
64
|
const skipBytes = normOptions.position - this.position;
|
|
52
65
|
if (skipBytes > 0) {
|
|
53
66
|
const skipBuffer = new Uint8Array(normOptions.length + skipBytes);
|
|
54
67
|
bytesRead = await this.peekBuffer(skipBuffer, { mayBeLess: normOptions.mayBeLess });
|
|
55
|
-
|
|
56
|
-
|
|
68
|
+
const bytesPeeked = Math.max(0, bytesRead - skipBytes);
|
|
69
|
+
uint8Array.set(skipBuffer.subarray(skipBytes, skipBytes + bytesPeeked));
|
|
70
|
+
return bytesPeeked;
|
|
57
71
|
}
|
|
58
72
|
if (skipBytes < 0) {
|
|
59
73
|
throw new Error('Cannot peek from a negative offset in a stream');
|
|
@@ -69,15 +83,22 @@ export class ReadStreamTokenizer extends AbstractTokenizer {
|
|
|
69
83
|
}
|
|
70
84
|
throw err;
|
|
71
85
|
}
|
|
72
|
-
if (
|
|
86
|
+
if (!normOptions.mayBeLess && bytesRead < normOptions.length) {
|
|
73
87
|
throw new EndOfStreamError();
|
|
74
88
|
}
|
|
75
89
|
}
|
|
76
90
|
return bytesRead;
|
|
77
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* @param length Number of bytes to ignore. Must be ≥ 0.
|
|
94
|
+
*/
|
|
78
95
|
async ignore(length) {
|
|
79
|
-
|
|
80
|
-
|
|
96
|
+
if (length < 0) {
|
|
97
|
+
throw new RangeError('ignore length must be ≥ 0 bytes');
|
|
98
|
+
}
|
|
99
|
+
this.validateLength(length, 'ignore length');
|
|
100
|
+
this.validateLength(this.position + length, 'end position');
|
|
101
|
+
const bufSize = Math.min(ignoreChunkSize, this.maxBufferSize, length);
|
|
81
102
|
const buf = new Uint8Array(bufSize);
|
|
82
103
|
let totBytesRead = 0;
|
|
83
104
|
while (totBytesRead < length) {
|
|
@@ -91,9 +112,11 @@ export class ReadStreamTokenizer extends AbstractTokenizer {
|
|
|
91
112
|
return totBytesRead;
|
|
92
113
|
}
|
|
93
114
|
abort() {
|
|
115
|
+
this.removeAbortListener();
|
|
94
116
|
return this.streamReader.abort();
|
|
95
117
|
}
|
|
96
118
|
async close() {
|
|
119
|
+
this.removeAbortListener();
|
|
97
120
|
return this.streamReader.close();
|
|
98
121
|
}
|
|
99
122
|
supportsRandomAccess() {
|
package/lib/core.d.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import type { Readable } from 'node:stream';
|
|
2
|
-
import {
|
|
3
|
-
import { ReadStreamTokenizer } from './ReadStreamTokenizer.js';
|
|
2
|
+
import { BlobTokenizer } from './BlobTokenizer.js';
|
|
4
3
|
import { BufferTokenizer } from './BufferTokenizer.js';
|
|
4
|
+
import { ReadStreamTokenizer } from './ReadStreamTokenizer.js';
|
|
5
|
+
import { type AnyWebByteStream } from './stream/index.js';
|
|
5
6
|
import type { ITokenizerOptions } from './types.js';
|
|
6
|
-
|
|
7
|
-
export { EndOfStreamError, AbortError, type AnyWebByteStream } from './stream/index.js';
|
|
8
|
-
export type { ITokenizer, IRandomAccessTokenizer, IFileInfo, IRandomAccessFileInfo, ITokenizerOptions, IReadChunkOptions, OnClose } from './types.js';
|
|
9
|
-
export type { IToken, IGetToken } from '@tokenizer/token';
|
|
7
|
+
export type { IGetToken, IToken } from '@tokenizer/token';
|
|
10
8
|
export { AbstractTokenizer } from './AbstractTokenizer.js';
|
|
9
|
+
export { BufferSizeError } from './BufferSizeError.js';
|
|
10
|
+
export { AbortError, type AnyWebByteStream, EndOfStreamError } from './stream/index.js';
|
|
11
|
+
export type { IFileInfo, IRandomAccessFileInfo, IRandomAccessTokenizer, IReadChunkOptions, ITokenizer, ITokenizerOptions, OnClose } from './types.js';
|
|
11
12
|
/**
|
|
12
13
|
* Construct ReadStreamTokenizer from given Stream.
|
|
13
14
|
* Will set fileSize, if provided given Stream has set the .path property/
|