@versatiles/container 1.4.2 → 1.5.1
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 +61 -2
- package/dist/index.d.ts +12 -2
- package/dist/index.js +44 -27
- package/dist/lib/interfaces.d.ts +20 -1
- package/dist/lib/reader_file.js +11 -1
- package/dist/lib/reader_http.d.ts +3 -1
- package/dist/lib/reader_http.js +36 -11
- package/package.json +15 -12
package/README.md
CHANGED
|
@@ -10,9 +10,11 @@ A client library for [VersaTiles containers](https://github.com/versatiles-org/v
|
|
|
10
10
|
|
|
11
11
|
`npm i @versatiles/container`
|
|
12
12
|
|
|
13
|
-
# Usage
|
|
13
|
+
# Usage Examples
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
## Basic: read a tile and write to disk
|
|
16
|
+
|
|
17
|
+
```ts
|
|
16
18
|
import { Container } from "@versatiles/container";
|
|
17
19
|
import fs from "fs";
|
|
18
20
|
|
|
@@ -20,6 +22,63 @@ const container = new Container("https://example.org/planet.versatiles");
|
|
|
20
22
|
const header = await container.getHeader();
|
|
21
23
|
const tile = await container.getTileUncompressed(z, x, y);
|
|
22
24
|
fs.writeFileSync("tile." + header.tileFormat, tile);
|
|
25
|
+
|
|
26
|
+
// don't forget to close file-backed containers
|
|
27
|
+
await container.close();
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Inspect tile metadata
|
|
31
|
+
|
|
32
|
+
VersaTiles containers can carry JSON metadata describing vector tile layers:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { Container } from "@versatiles/container";
|
|
36
|
+
|
|
37
|
+
const container = new Container("/path/to/layers.versatiles");
|
|
38
|
+
|
|
39
|
+
const header = await container.getHeader();
|
|
40
|
+
console.log("Format:", header.tileFormat); // e.g. "pbf"
|
|
41
|
+
console.log("Compression:", header.tileCompression);
|
|
42
|
+
console.log("Zoom range:", header.zoomMin, "→", header.zoomMax);
|
|
43
|
+
console.log("Bounding box:", header.bbox);
|
|
44
|
+
|
|
45
|
+
const metadata = await container.getMetadata();
|
|
46
|
+
if (metadata) {
|
|
47
|
+
const parsed = JSON.parse(metadata);
|
|
48
|
+
console.log("Vector layers:", parsed.vector_layers?.length);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
await container.close();
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Handle missing tiles
|
|
55
|
+
|
|
56
|
+
Coordinates must lie within the zoom level's grid (`0 <= x, y < 2 ** z`), otherwise `getTile` throws a `RangeError`. For a valid coordinate that simply has no data, the container returns `null`:
|
|
57
|
+
|
|
58
|
+
```ts
|
|
59
|
+
const tile = await container.getTileUncompressed(12, 100, 4000);
|
|
60
|
+
if (!tile) {
|
|
61
|
+
console.log("Tile does not exist in this container");
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Custom reader (e.g. S3, in-memory)
|
|
66
|
+
|
|
67
|
+
For custom storage backends, implement the `Reader` interface:
|
|
68
|
+
|
|
69
|
+
```ts
|
|
70
|
+
import type { Reader } from "@versatiles/container";
|
|
71
|
+
import { Container } from "@versatiles/container";
|
|
72
|
+
|
|
73
|
+
const myReader: Reader = async (offset, length) => {
|
|
74
|
+
// e.g. fetch from an S3 bucket or read from a typed array
|
|
75
|
+
const buffer = Buffer.alloc(length);
|
|
76
|
+
// … fill buffer with data starting at offset …
|
|
77
|
+
return buffer;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const container = new Container(myReader);
|
|
81
|
+
const header = await container.getHeader();
|
|
23
82
|
```
|
|
24
83
|
|
|
25
84
|
# API
|
package/dist/index.d.ts
CHANGED
|
@@ -96,7 +96,7 @@ export declare class Container {
|
|
|
96
96
|
* @returns A promise that resolves with the block index map.
|
|
97
97
|
* @protected
|
|
98
98
|
*/
|
|
99
|
-
getBlockIndex(): Promise<Map<string, Block>>;
|
|
99
|
+
protected getBlockIndex(): Promise<Map<string, Block>>;
|
|
100
100
|
/**
|
|
101
101
|
* Asynchronously retrieves the tile index for a specified block. This is an internal method used to
|
|
102
102
|
* maintain a lookup for every tile within a block.
|
|
@@ -105,7 +105,17 @@ export declare class Container {
|
|
|
105
105
|
* @returns A promise that resolves with the tile index.
|
|
106
106
|
* @protected
|
|
107
107
|
*/
|
|
108
|
-
getTileIndex(block: Block): Promise<TileIndex>;
|
|
108
|
+
protected getTileIndex(block: Block): Promise<TileIndex>;
|
|
109
|
+
/**
|
|
110
|
+
* Releases any resources held by the underlying reader, such as an open file
|
|
111
|
+
* descriptor when reading from a local file. Readers that hold no resources
|
|
112
|
+
* (e.g. the HTTP reader or a custom `Reader` function without a `close`
|
|
113
|
+
* method) are unaffected. After calling `close`, no further methods should
|
|
114
|
+
* be called on this container.
|
|
115
|
+
*
|
|
116
|
+
* @returns A promise that resolves once the reader has been closed.
|
|
117
|
+
*/
|
|
118
|
+
close(): Promise<void>;
|
|
109
119
|
/**
|
|
110
120
|
* A protected method to read a chunk of data from the source based on the specified offset and length.
|
|
111
121
|
*
|
package/dist/index.js
CHANGED
|
@@ -37,25 +37,6 @@ import HttpReader from './lib/reader_http.js';
|
|
|
37
37
|
import FileReader from './lib/reader_file.js';
|
|
38
38
|
import { decompress } from './lib/decompress.js';
|
|
39
39
|
const FORMATS = {
|
|
40
|
-
c01: [
|
|
41
|
-
'png',
|
|
42
|
-
'jpg',
|
|
43
|
-
'webp',
|
|
44
|
-
null,
|
|
45
|
-
null,
|
|
46
|
-
null,
|
|
47
|
-
null,
|
|
48
|
-
null,
|
|
49
|
-
null,
|
|
50
|
-
null,
|
|
51
|
-
null,
|
|
52
|
-
null,
|
|
53
|
-
null,
|
|
54
|
-
null,
|
|
55
|
-
null,
|
|
56
|
-
null,
|
|
57
|
-
'pbf',
|
|
58
|
-
],
|
|
59
40
|
v01: ['png', 'jpg', 'webp', 'pbf'],
|
|
60
41
|
v02: [
|
|
61
42
|
'bin',
|
|
@@ -243,17 +224,31 @@ export class Container {
|
|
|
243
224
|
* @returns A promise that resolves with the tile data as a Buffer, or null if the tile cannot be found.
|
|
244
225
|
*/
|
|
245
226
|
async getTile(z, x, y) {
|
|
227
|
+
// validate coordinates: must be non-negative integers within the zoom level
|
|
228
|
+
if (!Number.isInteger(z) || !Number.isInteger(x) || !Number.isInteger(y)) {
|
|
229
|
+
throw new RangeError(`Tile coordinates must be integers, got z=${z}, x=${x}, y=${y}`);
|
|
230
|
+
}
|
|
231
|
+
// zoom is capped at 30: a tile coordinate at higher zoom would exceed 2^30
|
|
232
|
+
// and no longer be an exact integer under the arithmetic below
|
|
233
|
+
if (z < 0 || z > 30) {
|
|
234
|
+
throw new RangeError(`Zoom level must be between 0 and 30, got z=${z}`);
|
|
235
|
+
}
|
|
236
|
+
// x and y must lie inside the 2^z × 2^z tile grid for this zoom level
|
|
237
|
+
const size = 2 ** z;
|
|
238
|
+
if (x < 0 || x >= size || y < 0 || y >= size) {
|
|
239
|
+
throw new RangeError(`Tile coordinates out of range for zoom ${z}: x and y must be in [0, ${size}), got x=${x}, y=${y}`);
|
|
240
|
+
}
|
|
246
241
|
// when y index is inverted
|
|
247
242
|
if (this.#options.tms)
|
|
248
|
-
y =
|
|
243
|
+
y = size - y - 1;
|
|
249
244
|
// ensure block index is loaded
|
|
250
245
|
const blockIndex = await this.getBlockIndex();
|
|
251
|
-
// block xy
|
|
252
|
-
const bx = x
|
|
253
|
-
const by = y
|
|
246
|
+
// block xy (avoid bitwise ops: they coerce to int32 and break above zoom 30)
|
|
247
|
+
const bx = Math.floor(x / 256);
|
|
248
|
+
const by = Math.floor(y / 256);
|
|
254
249
|
// tile xy (within block)
|
|
255
|
-
const tx = x
|
|
256
|
-
const ty = y
|
|
250
|
+
const tx = x % 256;
|
|
251
|
+
const ty = y % 256;
|
|
257
252
|
// check if block containing tile is within bounds
|
|
258
253
|
const blockKey = `${z},${bx},${by}`;
|
|
259
254
|
const block = blockIndex.get(blockKey);
|
|
@@ -309,7 +304,6 @@ export class Container {
|
|
|
309
304
|
// read index from buffer
|
|
310
305
|
const blocks = [];
|
|
311
306
|
switch (header.version) {
|
|
312
|
-
case 'c01':
|
|
313
307
|
case 'v01':
|
|
314
308
|
// check block index length
|
|
315
309
|
if (data.length % 29 !== 0)
|
|
@@ -364,15 +358,38 @@ export class Container {
|
|
|
364
358
|
return block.tileIndex;
|
|
365
359
|
let buffer = await this.read(block.tileIndexOffset, block.tileIndexLength);
|
|
366
360
|
buffer = await decompress(buffer, 'br');
|
|
361
|
+
// each tile entry is 12 bytes (8-byte offset + 4-byte length); guard against
|
|
362
|
+
// a truncated/corrupt index before indexing into the buffer below
|
|
363
|
+
if (buffer.length < block.tileCount * 12) {
|
|
364
|
+
throw new Error('invalid tile index');
|
|
365
|
+
}
|
|
367
366
|
const offsets = new Float64Array(block.tileCount);
|
|
368
367
|
const lengths = new Float64Array(block.tileCount);
|
|
369
368
|
for (let i = 0; i < block.tileCount; i++) {
|
|
370
|
-
|
|
369
|
+
const offset = Number(buffer.readBigUInt64BE(12 * i)) + block.blockOffset;
|
|
370
|
+
// offsets are addressed as `number`; beyond 2^53 they can no longer be
|
|
371
|
+
// represented exactly, so surface it instead of reading corrupt data
|
|
372
|
+
if (!Number.isSafeInteger(offset)) {
|
|
373
|
+
throw new Error('invalid tile index: tile offset exceeds safe integer range (2^53)');
|
|
374
|
+
}
|
|
375
|
+
offsets[i] = offset;
|
|
371
376
|
lengths[i] = buffer.readUInt32BE(12 * i + 8);
|
|
372
377
|
}
|
|
373
378
|
block.tileIndex = { offsets, lengths };
|
|
374
379
|
return block.tileIndex;
|
|
375
380
|
}
|
|
381
|
+
/**
|
|
382
|
+
* Releases any resources held by the underlying reader, such as an open file
|
|
383
|
+
* descriptor when reading from a local file. Readers that hold no resources
|
|
384
|
+
* (e.g. the HTTP reader or a custom `Reader` function without a `close`
|
|
385
|
+
* method) are unaffected. After calling `close`, no further methods should
|
|
386
|
+
* be called on this container.
|
|
387
|
+
*
|
|
388
|
+
* @returns A promise that resolves once the reader has been closed.
|
|
389
|
+
*/
|
|
390
|
+
async close() {
|
|
391
|
+
await this.#reader.close?.();
|
|
392
|
+
}
|
|
376
393
|
/**
|
|
377
394
|
* A protected method to read a chunk of data from the source based on the specified offset and length.
|
|
378
395
|
*
|
package/dist/lib/interfaces.d.ts
CHANGED
|
@@ -20,6 +20,9 @@ export type Decompressor = (data: Buffer, compression: Compression) => Promise<B
|
|
|
20
20
|
*
|
|
21
21
|
* This is useful for implementing new container readers, e.g. reading over other network protocols.
|
|
22
22
|
*
|
|
23
|
+
* A reader may optionally expose a `close` method to release any underlying
|
|
24
|
+
* resources (e.g. a file descriptor). {@link Container.close} calls it if present.
|
|
25
|
+
*
|
|
23
26
|
* @param {number} position - The byte offset at which to start reading.
|
|
24
27
|
* @param {number} length - The number of bytes to read.
|
|
25
28
|
* @returns {Promise<Buffer>} A promise that resolves with the data read as a Buffer.
|
|
@@ -27,7 +30,15 @@ export type Decompressor = (data: Buffer, compression: Compression) => Promise<B
|
|
|
27
30
|
* @throws {RangeError} If the sum of `position` and `length` exceeds the size of the content (filesize).
|
|
28
31
|
* @throws {Error} If there is any filesystem or network error such as the content not being accessible or readable.
|
|
29
32
|
*/
|
|
30
|
-
export
|
|
33
|
+
export interface Reader {
|
|
34
|
+
(position: number, length: number): Promise<Buffer>;
|
|
35
|
+
/**
|
|
36
|
+
* Releases any underlying resources held by the reader (e.g. an open file
|
|
37
|
+
* descriptor). Optional; readers that hold no resources may omit it.
|
|
38
|
+
* After `close` resolves, the reader must not be called again.
|
|
39
|
+
*/
|
|
40
|
+
close?: () => Promise<void>;
|
|
41
|
+
}
|
|
31
42
|
/**
|
|
32
43
|
* Interface for the metadata header of a `*.Versatiles` container.
|
|
33
44
|
*
|
|
@@ -92,6 +103,14 @@ export interface Block {
|
|
|
92
103
|
/**
|
|
93
104
|
* Interface for the index structure used for tiles within a block.
|
|
94
105
|
*
|
|
106
|
+
* Byte offsets are stored as `Float64Array`, which represents integers exactly
|
|
107
|
+
* up to `Number.MAX_SAFE_INTEGER` (2^53 − 1, ~9 PB). This is not a limitation of
|
|
108
|
+
* this type specifically: the {@link Reader} contract addresses data by `number`
|
|
109
|
+
* position, and both `fs.read` and HTTP byte-range requests are likewise
|
|
110
|
+
* `Number`-based, so 2^53 is the effective addressing limit of the whole reader
|
|
111
|
+
* pipeline. Storing offsets as `BigInt` here would therefore not extend the
|
|
112
|
+
* reachable range.
|
|
113
|
+
*
|
|
95
114
|
* @property {Float64Array} offsets - Array indicating the start byte positions of tiles within the block.
|
|
96
115
|
* @property {Float64Array} lengths - Array specifying the byte lengths of the tiles. A length of 0 means the tile is not stored.
|
|
97
116
|
*/
|
package/dist/lib/reader_file.js
CHANGED
|
@@ -37,7 +37,7 @@ export default function getFileReader(filename) {
|
|
|
37
37
|
* the file descriptor becomes invalid due to the file being closed, the
|
|
38
38
|
* promise will be rejected with an error.
|
|
39
39
|
*/
|
|
40
|
-
|
|
40
|
+
const read = async function read(position, length) {
|
|
41
41
|
if (position < 0) {
|
|
42
42
|
throw new RangeError(`Invalid read position: ${position}. The read position must be a non-negative integer.`);
|
|
43
43
|
}
|
|
@@ -61,4 +61,14 @@ export default function getFileReader(filename) {
|
|
|
61
61
|
});
|
|
62
62
|
});
|
|
63
63
|
};
|
|
64
|
+
/**
|
|
65
|
+
* Closes the underlying file descriptor. After this resolves, the reader
|
|
66
|
+
* must not be called again.
|
|
67
|
+
*/
|
|
68
|
+
read.close = async function close() {
|
|
69
|
+
await new Promise((resolve, reject) => {
|
|
70
|
+
fs.close(fd, (err) => (err ? reject(err) : resolve()));
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
return read;
|
|
64
74
|
}
|
|
@@ -5,6 +5,8 @@ import type { Reader } from './interfaces.js';
|
|
|
5
5
|
* operations such as streaming or handling large data in segments.
|
|
6
6
|
*
|
|
7
7
|
* @param url - The URL from which data will be read.
|
|
8
|
+
* @param timeout - Idle timeout in milliseconds applied both while waiting for the
|
|
9
|
+
* response headers and between body chunks. Defaults to {@link DEFAULT_TIMEOUT}.
|
|
8
10
|
* @returns A `Reader` function that asynchronously reads a specified chunk of data from the URL.
|
|
9
11
|
*/
|
|
10
|
-
export default function getHTTPReader(url: string): Reader;
|
|
12
|
+
export default function getHTTPReader(url: string, timeout?: number): Reader;
|
package/dist/lib/reader_http.js
CHANGED
|
@@ -14,9 +14,11 @@ const clients = {
|
|
|
14
14
|
* operations such as streaming or handling large data in segments.
|
|
15
15
|
*
|
|
16
16
|
* @param url - The URL from which data will be read.
|
|
17
|
+
* @param timeout - Idle timeout in milliseconds applied both while waiting for the
|
|
18
|
+
* response headers and between body chunks. Defaults to {@link DEFAULT_TIMEOUT}.
|
|
17
19
|
* @returns A `Reader` function that asynchronously reads a specified chunk of data from the URL.
|
|
18
20
|
*/
|
|
19
|
-
export default function getHTTPReader(url) {
|
|
21
|
+
export default function getHTTPReader(url, timeout = DEFAULT_TIMEOUT) {
|
|
20
22
|
const protocol = new URL(url).protocol.slice(0, -1);
|
|
21
23
|
if (!(protocol in clients)) {
|
|
22
24
|
throw new Error(`Unsupported protocol: ${protocol}`);
|
|
@@ -49,13 +51,13 @@ export default function getHTTPReader(url) {
|
|
|
49
51
|
const watchdog = setTimeout(() => {
|
|
50
52
|
req.destroy();
|
|
51
53
|
reject(new Error('Request timed out'));
|
|
52
|
-
},
|
|
54
|
+
}, timeout);
|
|
53
55
|
const req = clients[protocol].client
|
|
54
56
|
.request(url, {
|
|
55
57
|
method: 'GET',
|
|
56
58
|
agent: clients[protocol].agent,
|
|
57
59
|
headers,
|
|
58
|
-
timeout
|
|
60
|
+
timeout,
|
|
59
61
|
})
|
|
60
62
|
.on('response', (response) => {
|
|
61
63
|
clearTimeout(watchdog);
|
|
@@ -68,38 +70,61 @@ export default function getHTTPReader(url) {
|
|
|
68
70
|
})
|
|
69
71
|
.end();
|
|
70
72
|
});
|
|
71
|
-
|
|
73
|
+
/**
|
|
74
|
+
* Destroys the response (draining the socket / freeing the keep-alive
|
|
75
|
+
* connection) and then throws the given error. Every validation failure
|
|
76
|
+
* below must go through this so an unread response body never leaks a
|
|
77
|
+
* connection back to the keep-alive agent's pool.
|
|
78
|
+
*/
|
|
79
|
+
function fail(error) {
|
|
72
80
|
message.destroy();
|
|
73
|
-
throw
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
if (message.statusCode == null || Math.floor(message.statusCode / 100) !== 2) {
|
|
84
|
+
fail(new Error(`Server responded with status code: ${message.statusCode} `));
|
|
74
85
|
}
|
|
75
86
|
const contentRange = message.headers['content-range'];
|
|
76
87
|
if (contentRange == null)
|
|
77
|
-
|
|
88
|
+
fail(new Error('The response header does not contain "content-range"'));
|
|
78
89
|
const parts = /^bytes (\d+)-(\d+)\/(\d+)/i.exec(contentRange);
|
|
79
90
|
if (parts == null)
|
|
80
|
-
|
|
91
|
+
fail(new Error('"content-range" in response header is malformed'));
|
|
81
92
|
if (position !== parseInt(parts[1], 10))
|
|
82
|
-
|
|
93
|
+
fail(new Error(`requested position (${position}) and returned offset (${parts[1]}) must be equal`));
|
|
83
94
|
if (position + length > parseInt(parts[3], 10)) {
|
|
84
|
-
|
|
95
|
+
fail(new RangeError(`Read range out of bounds: The requested range ends at position ${position + length}, which exceeds the file's limit of ${parts[3]} bytes.`));
|
|
85
96
|
}
|
|
86
97
|
const returnedLength = parseInt(parts[2], 10) + 1 - position;
|
|
87
98
|
if (length !== returnedLength) {
|
|
88
|
-
|
|
99
|
+
fail(new Error(`Returned length (${returnedLength}) is not requested length (${length}).`));
|
|
89
100
|
}
|
|
90
101
|
/**
|
|
91
102
|
* Collects and concatenates response data chunks into a buffer.
|
|
103
|
+
*
|
|
104
|
+
* An idle watchdog guards the download: it aborts the request if no data
|
|
105
|
+
* chunk arrives within `timeout` ms. The timer is reset on every chunk,
|
|
106
|
+
* so a slow-but-progressing transfer is not killed, while a stalled socket is.
|
|
92
107
|
* @type {Buffer}
|
|
93
108
|
*/
|
|
94
109
|
const body = await new Promise((resolve, reject) => {
|
|
95
110
|
const buffers = [];
|
|
111
|
+
const watchdog = setTimeout(onTimeout, timeout);
|
|
112
|
+
function onTimeout() {
|
|
113
|
+
message.destroy();
|
|
114
|
+
reject(new Error('Request timed out'));
|
|
115
|
+
}
|
|
96
116
|
message
|
|
97
|
-
.on('data', (chunk) =>
|
|
117
|
+
.on('data', (chunk) => {
|
|
118
|
+
watchdog.refresh();
|
|
119
|
+
buffers.push(chunk);
|
|
120
|
+
})
|
|
98
121
|
.on('error', (err) => {
|
|
122
|
+
clearTimeout(watchdog);
|
|
99
123
|
message.destroy();
|
|
100
124
|
reject(err);
|
|
101
125
|
})
|
|
102
126
|
.once('end', () => {
|
|
127
|
+
clearTimeout(watchdog);
|
|
103
128
|
resolve(Buffer.concat(buffers));
|
|
104
129
|
});
|
|
105
130
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@versatiles/container",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
4
4
|
"description": "A client library for reading VersaTiles containers",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"build": "rm -rf dist && tsc -p tsconfig.build.json && npm run doc-graph",
|
|
@@ -54,18 +54,21 @@
|
|
|
54
54
|
],
|
|
55
55
|
"devDependencies": {
|
|
56
56
|
"@eslint/js": "^10.0.1",
|
|
57
|
-
"@types/node": "^
|
|
58
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
59
|
-
"@typescript-eslint/parser": "^8.
|
|
60
|
-
"@versatiles/release-tool": "^2.
|
|
61
|
-
"@vitest/coverage-v8": "^4.1.
|
|
62
|
-
"eslint": "^10.
|
|
57
|
+
"@types/node": "^26.2.0",
|
|
58
|
+
"@typescript-eslint/eslint-plugin": "^8.67.0",
|
|
59
|
+
"@typescript-eslint/parser": "^8.67.0",
|
|
60
|
+
"@versatiles/release-tool": "^2.9.0",
|
|
61
|
+
"@vitest/coverage-v8": "^4.1.11",
|
|
62
|
+
"eslint": "^10.8.1",
|
|
63
63
|
"eslint-config-prettier": "^10.1.8",
|
|
64
|
-
"prettier": "^3.
|
|
65
|
-
"
|
|
66
|
-
"tsx": "^4.22.0",
|
|
64
|
+
"prettier": "^3.9.6",
|
|
65
|
+
"tsx": "^4.23.12",
|
|
67
66
|
"typescript": "^6.0.3",
|
|
68
|
-
"typescript-eslint": "^8.
|
|
69
|
-
"vitest": "^4.1.
|
|
67
|
+
"typescript-eslint": "^8.67.0",
|
|
68
|
+
"vitest": "^4.1.11"
|
|
69
|
+
},
|
|
70
|
+
"allowScripts": {
|
|
71
|
+
"esbuild": true,
|
|
72
|
+
"fsevents": true
|
|
70
73
|
}
|
|
71
74
|
}
|