@travetto/runtime 7.1.3 → 8.0.0-alpha.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/README.md +42 -39
- package/__index__.ts +3 -1
- package/package.json +5 -4
- package/src/binary-metadata.ts +174 -0
- package/src/binary.ts +162 -101
- package/src/codec.ts +105 -0
- package/src/console.ts +0 -1
- package/src/context.ts +11 -2
- package/src/error.ts +8 -45
- package/src/exec.ts +13 -22
- package/src/file-loader.ts +18 -19
- package/src/function.ts +16 -20
- package/src/global.d.ts +29 -0
- package/src/json.ts +132 -51
- package/src/queue.ts +5 -5
- package/src/shutdown.ts +1 -1
- package/src/time.ts +62 -76
- package/src/types.ts +10 -40
- package/src/util.ts +32 -4
- package/src/watch.ts +2 -2
- package/support/polyfill.js +9 -0
- package/support/transformer/metadata.ts +4 -4
package/README.md
CHANGED
|
@@ -19,6 +19,8 @@ Runtime is the foundation of all [Travetto](https://travetto.dev) applications.
|
|
|
19
19
|
* Standard Error Support
|
|
20
20
|
* Console Management
|
|
21
21
|
* Resource Access
|
|
22
|
+
* Encoding and Decoding Utilities
|
|
23
|
+
* Binary Utilities
|
|
22
24
|
* JSON Utilities
|
|
23
25
|
* Common Utilities
|
|
24
26
|
* Time Utilities
|
|
@@ -67,6 +69,8 @@ class $Runtime {
|
|
|
67
69
|
getImport(handle: Function): string;
|
|
68
70
|
/** Import from a given path */
|
|
69
71
|
async importFrom<T = unknown>(location?: string): Promise<T>;
|
|
72
|
+
/** Get an install command for a given npm module */
|
|
73
|
+
getInstallCommand(pkg: string, production = false): string;
|
|
70
74
|
}
|
|
71
75
|
```
|
|
72
76
|
|
|
@@ -161,9 +165,9 @@ export class EnvProp<T> {
|
|
|
161
165
|
```
|
|
162
166
|
|
|
163
167
|
## Standard Error Support
|
|
164
|
-
While the framework is 100 % compatible with standard `Error` instances, there are cases in which additional functionality is desired. Within the framework we use [
|
|
168
|
+
While the framework is 100 % compatible with standard `Error` instances, there are cases in which additional functionality is desired. Within the framework we use [RuntimeError](https://github.com/travetto/travetto/tree/main/module/runtime/src/error.ts#L17) (or its derivatives) to represent framework errors. This class is available for use in your own projects. Some of the additional benefits of using this class is enhanced error reporting, as well as better integration with other modules (e.g. the [Web API](https://github.com/travetto/travetto/tree/main/module/web#readme "Declarative support for creating Web Applications") module and HTTP status codes).
|
|
165
169
|
|
|
166
|
-
The [
|
|
170
|
+
The [RuntimeError](https://github.com/travetto/travetto/tree/main/module/runtime/src/error.ts#L17) takes in a message, and an optional payload and / or error classification. The currently supported error classifications are:
|
|
167
171
|
* `general` - General purpose errors
|
|
168
172
|
* `system` - Synonym for `general`
|
|
169
173
|
* `data` - Data format, content, etc are incorrect. Generally correlated to bad input.
|
|
@@ -247,25 +251,26 @@ $ DEBUG=express:*,@travetto/web npx trv run web
|
|
|
247
251
|
## Resource Access
|
|
248
252
|
The primary access patterns for resources, is to directly request a file, and to resolve that file either via file-system look up or leveraging the [Manifest](https://github.com/travetto/travetto/tree/main/module/manifest#readme "Support for project indexing, manifesting, along with file watching")'s data for what resources were found at manifesting time.
|
|
249
253
|
|
|
250
|
-
The [FileLoader](https://github.com/travetto/travetto/tree/main/module/runtime/src/file-loader.ts#
|
|
254
|
+
The [FileLoader](https://github.com/travetto/travetto/tree/main/module/runtime/src/file-loader.ts#L11) allows for accessing information about the resources, and subsequently reading the file as text/binary or to access the resource as a `Readable` stream. If a file is not found, it will throw an [RuntimeError](https://github.com/travetto/travetto/tree/main/module/runtime/src/error.ts#L17) with a category of 'notfound'.
|
|
251
255
|
|
|
252
|
-
The [FileLoader](https://github.com/travetto/travetto/tree/main/module/runtime/src/file-loader.ts#
|
|
256
|
+
The [FileLoader](https://github.com/travetto/travetto/tree/main/module/runtime/src/file-loader.ts#L11) also supports tying itself to [Env](https://github.com/travetto/travetto/tree/main/module/runtime/src/env.ts#L114)'s `TRV_RESOURCES` information on where to attempt to find a requested resource.
|
|
253
257
|
|
|
254
|
-
##
|
|
255
|
-
The
|
|
256
|
-
|
|
257
|
-
*
|
|
258
|
-
*
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
258
|
+
## Encoding and Decoding Utilities
|
|
259
|
+
The [CodecUtil](https://github.com/travetto/travetto/tree/main/module/runtime/src/codec.ts#L15) class provides a variety of static methods for encoding and decoding data. When working with JSON data, it also provide security checks to prevent prototype pollution. The utility supports the following formats:
|
|
260
|
+
* Hex
|
|
261
|
+
* Base64
|
|
262
|
+
* UTF8
|
|
263
|
+
* UTT8 Encoded JSON
|
|
264
|
+
* Base64 Encoded JSON
|
|
265
|
+
* New Line Delimited UTF8
|
|
262
266
|
|
|
263
267
|
## Common Utilities
|
|
264
|
-
Common utilities used throughout the framework. Currently [Util](https://github.com/travetto/travetto/tree/main/module/runtime/src/util.ts#
|
|
268
|
+
Common utilities used throughout the framework. Currently [Util](https://github.com/travetto/travetto/tree/main/module/runtime/src/util.ts#L14) includes:
|
|
265
269
|
* `uuid(len: number)` generates a simple uuid for use within the application.
|
|
266
270
|
* `allowDenyMatcher(rules[])` builds a matching function that leverages the rules as an allow/deny list, where order of the rules matters. Negative rules are prefixed by '!'.
|
|
267
271
|
* `hash(text: string, size?: number)` produces a full sha512 hash.
|
|
268
272
|
* `resolvablePromise()` produces a `Promise` instance with the `resolve` and `reject` methods attached to the instance. This is extremely useful for integrating promises into async iterations, or any other situation in which the promise creation and the execution flow don't always match up.
|
|
273
|
+
* `bufferedFileWrite(file:string, content: string)` will write the file, using a temporary buffer file to ensure that the entire file is written before being moved to the final location. This helps minimize file watch noise when writing files.
|
|
269
274
|
|
|
270
275
|
**Code: Sample makeTemplate Usage**
|
|
271
276
|
```typescript
|
|
@@ -275,53 +280,51 @@ tpl`{{age:20}} {{name: 'bob'}}</>;
|
|
|
275
280
|
'**age: 20** **name: bob**'
|
|
276
281
|
```
|
|
277
282
|
|
|
283
|
+
## Binary Utilities
|
|
284
|
+
The [BinaryUtil](https://github.com/travetto/travetto/tree/main/module/runtime/src/binary.ts#L59) class provides a unified interface for working with binary data across different formats, especially bridging the gap between Node.js specific types (`Buffer`, `Stream`) and Web Standard types (`Blob`, `ArrayBuffer`). The framework leverages this to allow for seamless handling of binary data, regardless of the source.
|
|
285
|
+
|
|
286
|
+
## JSON Utilities
|
|
287
|
+
The [JSONUtil](https://github.com/travetto/travetto/tree/main/module/runtime/src/json.ts#L31) class provides a comprehensive set of utilities for working with JSON data, including serialization, deserialization, encoding, and deep cloning capabilities. The utility handles special types like `Date`, `BigInt`, and `Error` objects seamlessly. Key features include:
|
|
288
|
+
* `fromUTF8(input, config?)` - Parse JSON from a UTF-8 string
|
|
289
|
+
* `toUTF8(value, config?)` - Serialize a value to JSON string
|
|
290
|
+
* `toUTF8Pretty(value)` - Serialize with pretty formatting (2-space indent)
|
|
291
|
+
* `fromBinaryArray(input)` - Parse JSON from binary array
|
|
292
|
+
* `toBinaryArray(value, config?)` - Serialize to binary array (UTF-8 encoded)
|
|
293
|
+
* `toBase64(value)` - Encode JSON as base64 string
|
|
294
|
+
* `fromBase64(input)` - Decode JSON from base64 string
|
|
295
|
+
* `clone(input, config?)` - Deep clone objects with optional transformations
|
|
296
|
+
* `cloneForTransmit(input)` - Clone for transmission with error serialization
|
|
297
|
+
* `cloneFromTransmit(input)` - Clone from transmission with type restoration
|
|
298
|
+
|
|
299
|
+
The `TRANSMIT_REVIVER` automatically restores `Date` objects and `BigInt` values during deserialization, making it ideal for transmitting complex data structures across network boundaries.
|
|
300
|
+
|
|
278
301
|
## Time Utilities
|
|
279
|
-
[TimeUtil](https://github.com/travetto/travetto/tree/main/module/runtime/src/time.ts#
|
|
302
|
+
[TimeUtil](https://github.com/travetto/travetto/tree/main/module/runtime/src/time.ts#L21) contains general helper methods, created to assist with time-based inputs via environment variables, command line interfaces, and other string-heavy based input.
|
|
280
303
|
|
|
281
304
|
**Code: Time Utilities**
|
|
282
305
|
```typescript
|
|
283
306
|
export class TimeUtil {
|
|
284
307
|
/**
|
|
285
308
|
* Test to see if a string is valid for relative time
|
|
286
|
-
* @param val
|
|
287
309
|
*/
|
|
288
310
|
static isTimeSpan(value: string): value is TimeSpan;
|
|
289
311
|
/**
|
|
290
|
-
*
|
|
291
|
-
* @param amount Number of units to extend
|
|
292
|
-
* @param unit Time unit to extend ('ms', 's', 'm', 'h', 'd', 'w', 'y')
|
|
293
|
-
*/
|
|
294
|
-
static asMillis(amount: Date | number | TimeSpan, unit?: TimeUnit): number;
|
|
295
|
-
/**
|
|
296
|
-
* Returns the time converted to seconds
|
|
297
|
-
* @param date The date to convert
|
|
298
|
-
*/
|
|
299
|
-
static asSeconds(date: Date | number | TimeSpan, unit?: TimeUnit): number;
|
|
300
|
-
/**
|
|
301
|
-
* Returns the time converted to a Date
|
|
302
|
-
* @param date The date to convert
|
|
303
|
-
*/
|
|
304
|
-
static asDate(date: Date | number | TimeSpan, unit?: TimeUnit): Date;
|
|
305
|
-
/**
|
|
306
|
-
* Resolve time or span to possible time
|
|
312
|
+
* Exposes the ability to create a duration succinctly
|
|
307
313
|
*/
|
|
308
|
-
static
|
|
314
|
+
static duration(input: TimeSpan | number | string, outputUnit: TimeUnit): number;
|
|
309
315
|
/**
|
|
310
316
|
* Returns a new date with `amount` units into the future
|
|
311
|
-
* @param amount Number of units to extend
|
|
312
|
-
* @param unit Time unit to extend ('ms', 's', 'm', 'h', 'd', 'w', 'y')
|
|
313
317
|
*/
|
|
314
|
-
static fromNow(
|
|
318
|
+
static fromNow(input: TimeSpan | number | string): Date;
|
|
315
319
|
/**
|
|
316
320
|
* Returns a pretty timestamp
|
|
317
|
-
* @param time Time in milliseconds
|
|
318
321
|
*/
|
|
319
|
-
static asClock(
|
|
322
|
+
static asClock(input: TimeSpan | number | string): string;
|
|
320
323
|
}
|
|
321
324
|
```
|
|
322
325
|
|
|
323
326
|
## Process Execution
|
|
324
|
-
[ExecUtil](https://github.com/travetto/travetto/tree/main/module/runtime/src/exec.ts#
|
|
327
|
+
[ExecUtil](https://github.com/travetto/travetto/tree/main/module/runtime/src/exec.ts#L41) exposes `getResult` as a means to wrap [child_process](https://nodejs.org/api/child_process.html)'s process object. This wrapper allows for a promise-based resolution of the subprocess with the ability to capture the stderr/stdout.
|
|
325
328
|
|
|
326
329
|
A simple example would be:
|
|
327
330
|
|
package/__index__.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import type { } from './src/global.d.ts';
|
|
2
2
|
import type { } from './src/trv.d.ts';
|
|
3
3
|
export * from './src/binary.ts';
|
|
4
|
+
export * from './src/binary-metadata.ts';
|
|
4
5
|
export * from './src/console.ts';
|
|
5
6
|
export * from './src/context.ts';
|
|
6
7
|
export * from './src/debug.ts';
|
|
7
8
|
export * from './src/error.ts';
|
|
8
9
|
export * from './src/exec.ts';
|
|
10
|
+
export * from './src/codec.ts';
|
|
9
11
|
export * from './src/env.ts';
|
|
10
12
|
export * from './src/file-loader.ts';
|
|
11
13
|
export * from './src/function.ts';
|
|
@@ -17,4 +19,4 @@ export * from './src/shutdown.ts';
|
|
|
17
19
|
export * from './src/time.ts';
|
|
18
20
|
export * from './src/types.ts';
|
|
19
21
|
export * from './src/watch.ts';
|
|
20
|
-
export * from './src/util.ts';
|
|
22
|
+
export * from './src/util.ts';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/runtime",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.0.0-alpha.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Runtime for travetto applications.",
|
|
6
6
|
"keywords": [
|
|
@@ -26,12 +26,13 @@
|
|
|
26
26
|
"directory": "module/runtime"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@travetto/manifest": "^
|
|
29
|
+
"@travetto/manifest": "^8.0.0-alpha.0",
|
|
30
30
|
"@types/debug": "^4.1.12",
|
|
31
|
-
"debug": "^4.4.3"
|
|
31
|
+
"debug": "^4.4.3",
|
|
32
|
+
"temporal-polyfill-lite": "^0.2.2"
|
|
32
33
|
},
|
|
33
34
|
"peerDependencies": {
|
|
34
|
-
"@travetto/transformer": "^
|
|
35
|
+
"@travetto/transformer": "^8.0.0-alpha.0"
|
|
35
36
|
},
|
|
36
37
|
"peerDependenciesMeta": {
|
|
37
38
|
"@travetto/transformer": {
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { createReadStream, ReadStream } from 'node:fs';
|
|
5
|
+
|
|
6
|
+
import { BinaryUtil, type BinaryArray, type BinaryContainer, type BinaryStream, type BinaryType } from './binary.ts';
|
|
7
|
+
import { RuntimeError } from './error.ts';
|
|
8
|
+
import { CodecUtil } from './codec.ts';
|
|
9
|
+
|
|
10
|
+
type BlobInput = BinaryType | (() => (BinaryType | Promise<BinaryType>));
|
|
11
|
+
|
|
12
|
+
/** Range of bytes, inclusive */
|
|
13
|
+
export type ByteRange = { start: number, end?: number };
|
|
14
|
+
|
|
15
|
+
export interface BinaryMetadata {
|
|
16
|
+
/** Size of binary data */
|
|
17
|
+
size?: number;
|
|
18
|
+
/** Mime type of the content */
|
|
19
|
+
contentType?: string;
|
|
20
|
+
/** Hash of binary data contents */
|
|
21
|
+
hash?: string;
|
|
22
|
+
/** The original base filename of the file */
|
|
23
|
+
filename?: string;
|
|
24
|
+
/** Filenames title, optional for elements like images, audio, videos */
|
|
25
|
+
title?: string;
|
|
26
|
+
/** Content encoding */
|
|
27
|
+
contentEncoding?: string;
|
|
28
|
+
/** Content language */
|
|
29
|
+
contentLanguage?: string;
|
|
30
|
+
/** Cache control */
|
|
31
|
+
cacheControl?: string;
|
|
32
|
+
/** Byte range for binary data */
|
|
33
|
+
range?: Required<ByteRange>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type HashConfig = {
|
|
37
|
+
length?: number;
|
|
38
|
+
hashAlgorithm?: 'sha1' | 'sha256' | 'sha512' | 'md5';
|
|
39
|
+
outputEncoding?: crypto.BinaryToTextEncoding;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const BinaryMetaSymbol = Symbol();
|
|
43
|
+
|
|
44
|
+
export class BinaryMetadataUtil {
|
|
45
|
+
/** Set metadata for a binary type */
|
|
46
|
+
static write(input: BinaryType, metadata: BinaryMetadata): BinaryMetadata {
|
|
47
|
+
const withMeta: BinaryType & { [BinaryMetaSymbol]?: BinaryMetadata } = input;
|
|
48
|
+
return withMeta[BinaryMetaSymbol] = metadata;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Read metadata for a binary type, if available */
|
|
52
|
+
static read(input: BinaryType): BinaryMetadata {
|
|
53
|
+
const withMeta: BinaryType & { [BinaryMetaSymbol]?: BinaryMetadata } = input;
|
|
54
|
+
return withMeta[BinaryMetaSymbol] ?? {};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Generate a hash from an input value * @param input The seed value to build the hash from
|
|
58
|
+
* @param length The optional length of the hash to generate
|
|
59
|
+
* @param hashAlgorithm The hash algorithm to use
|
|
60
|
+
* @param outputEncoding The output encoding format
|
|
61
|
+
*/
|
|
62
|
+
static hash(input: string | BinaryArray, config?: HashConfig): string;
|
|
63
|
+
static hash(input: BinaryStream | BinaryContainer, config?: HashConfig): Promise<string>;
|
|
64
|
+
static hash(input: string | BinaryType, config?: HashConfig): string | Promise<string> {
|
|
65
|
+
const hashAlgorithm = config?.hashAlgorithm ?? 'sha512';
|
|
66
|
+
const outputEncoding = config?.outputEncoding ?? 'hex';
|
|
67
|
+
const length = config?.length;
|
|
68
|
+
const hash = crypto.createHash(hashAlgorithm).setEncoding(outputEncoding);
|
|
69
|
+
|
|
70
|
+
if (typeof input === 'string') {
|
|
71
|
+
input = CodecUtil.fromUTF8String(input);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (BinaryUtil.isBinaryArray(input)) {
|
|
75
|
+
hash.update(BinaryUtil.binaryArrayToUint8Array(input));
|
|
76
|
+
return hash.digest(outputEncoding).substring(0, length);
|
|
77
|
+
} else {
|
|
78
|
+
return BinaryUtil.pipeline(input, hash).then(() =>
|
|
79
|
+
hash.digest(outputEncoding).substring(0, length)
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Compute the length of the binary data to be returned */
|
|
85
|
+
static readLength(metadata: BinaryMetadata): number | undefined {
|
|
86
|
+
return metadata.range ? (metadata.range.end - metadata.range.start + 1) : metadata.size;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Compute metadata for a given binary input */
|
|
90
|
+
static async compute(input: BlobInput, base: BinaryMetadata = {}): Promise<BinaryMetadata> {
|
|
91
|
+
if (typeof input === 'function') {
|
|
92
|
+
input = await input();
|
|
93
|
+
}
|
|
94
|
+
const metadata = { ...BinaryMetadataUtil.read(input), ...base };
|
|
95
|
+
|
|
96
|
+
if (BinaryUtil.isBinaryContainer(input)) {
|
|
97
|
+
metadata.size ??= input.size;
|
|
98
|
+
metadata.contentType ??= input.type;
|
|
99
|
+
if (input instanceof File) {
|
|
100
|
+
metadata.filename ??= input.name;
|
|
101
|
+
}
|
|
102
|
+
} else if (BinaryUtil.isBinaryArray(input)) {
|
|
103
|
+
metadata.size ??= input.byteLength;
|
|
104
|
+
metadata.hash ??= this.hash(input, { hashAlgorithm: 'sha256' });
|
|
105
|
+
} else if (input instanceof ReadStream) {
|
|
106
|
+
const location = input.path.toString();
|
|
107
|
+
metadata.filename ??= path.basename(location);
|
|
108
|
+
metadata.contentEncoding ??= input.readableEncoding!;
|
|
109
|
+
metadata.size ??= (await fs.stat(location)).size;
|
|
110
|
+
metadata.hash ??= await this.hash(createReadStream(location), { hashAlgorithm: 'sha256' });
|
|
111
|
+
} else if (input && typeof input === 'object' && 'readableEncoding' in input && typeof input.readableEncoding === 'string') {
|
|
112
|
+
metadata.contentEncoding ??= input.readableEncoding!;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return metadata;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Rewrite a blob to support metadata, and provide a dynamic input source
|
|
120
|
+
*/
|
|
121
|
+
static defineBlob<T extends Blob>(target: T, input: BlobInput, metadata: BinaryMetadata = {}): typeof target {
|
|
122
|
+
const inputFn = async (): Promise<BinaryType> => typeof input === 'function' ? await input() : input;
|
|
123
|
+
this.write(target, metadata);
|
|
124
|
+
|
|
125
|
+
Object.defineProperties(target, {
|
|
126
|
+
size: { get() { return BinaryMetadataUtil.readLength(metadata); } },
|
|
127
|
+
type: { get() { return metadata.contentType; } },
|
|
128
|
+
name: { get() { return metadata.filename; } },
|
|
129
|
+
arrayBuffer: { value: () => inputFn().then(BinaryUtil.toArrayBuffer) },
|
|
130
|
+
stream: { value: () => BinaryUtil.toReadableStream(BinaryUtil.toSynchronous(input)) },
|
|
131
|
+
bytes: { value: () => inputFn().then(BinaryUtil.toBuffer) },
|
|
132
|
+
text: { value: () => inputFn().then(BinaryUtil.toBinaryArray).then(CodecUtil.toUTF8String) },
|
|
133
|
+
slice: {
|
|
134
|
+
value: (start?: number, end?: number, _contentType?: string) => {
|
|
135
|
+
const result = target instanceof File ? new File([], '') : new Blob([]);
|
|
136
|
+
return BinaryMetadataUtil.defineBlob(result,
|
|
137
|
+
() => inputFn().then(BinaryUtil.toBinaryArray).then(data => BinaryUtil.sliceByteArray(data, start, end)),
|
|
138
|
+
{
|
|
139
|
+
...metadata,
|
|
140
|
+
range: { start: start ?? 0, end: end ?? metadata.size! - 1 },
|
|
141
|
+
}
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
return target;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Make a blob that contains the appropriate metadata
|
|
151
|
+
*/
|
|
152
|
+
static makeBlob(source: BlobInput, metadata?: BinaryMetadata): Blob {
|
|
153
|
+
return this.defineBlob(new Blob([]), source, metadata);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Enforce byte range for stream stream/file of a certain size
|
|
158
|
+
*/
|
|
159
|
+
static enforceRange(range: ByteRange, metadata?: BinaryMetadata): Required<ByteRange> {
|
|
160
|
+
if (!metadata || metadata.size === undefined) {
|
|
161
|
+
throw new RuntimeError('Cannot enforce range on data with unknown size', { category: 'data' });
|
|
162
|
+
}
|
|
163
|
+
const size = metadata.size;
|
|
164
|
+
|
|
165
|
+
// End is inclusive
|
|
166
|
+
const [start, end] = [range.start, Math.min(range.end ?? (size - 1), size - 1)];
|
|
167
|
+
|
|
168
|
+
if (Number.isNaN(start) || Number.isNaN(end) || !Number.isFinite(start) || start >= size || start < 0 || start > end) {
|
|
169
|
+
throw new RuntimeError('Invalid position, out of range', { category: 'data', details: { start, end, size } });
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return { start, end };
|
|
173
|
+
}
|
|
174
|
+
}
|