@statewalker/webrun-files-sqlite 0.10.0 → 0.10.3

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.
Files changed (2) hide show
  1. package/README.md +173 -135
  2. package/package.json +13 -8
package/README.md CHANGED
@@ -1,179 +1,221 @@
1
1
  # @statewalker/webrun-files-sqlite
2
2
 
3
- Two `FilesApi` implementations over SQLite, sharing one small SQL port with drivers for
4
- `node:sqlite`, Cloudflare Durable Object storage and Cloudflare D1.
3
+ ## What it is
4
+
5
+ Two implementations of `FilesApi` from `@statewalker/webrun-files` that store files in a SQLite
6
+ database, behind one small SQL driver interface with drivers for `node:sqlite`, Cloudflare Durable
7
+ Object storage and Cloudflare D1:
8
+
9
+ - `SqliteFilesApi` streams content as fixed-size blocks (1 MiB by default, optionally deflated per
10
+ block), shares identical content between paths, and makes every metadata change one transaction.
11
+ Files of any size.
12
+ - `SqlarFilesApi` stores each file as one row of a [SQLite Archive](https://sqlite.org/sqlar.html),
13
+ readable by `sqlite3 -A`. Whole-file reads and writes.
14
+
15
+ ## Why it exists
16
+
17
+ Durable Objects and D1 offer SQLite and nothing like a file system, and a single SQLite file is a
18
+ convenient, transactional store elsewhere too. Storing a file as one blob breaks there: Durable
19
+ Objects and D1 cap a row at 2 MB, and a blob is read and written whole. `SqliteFilesApi` cuts
20
+ content into blocks that stay under the cap and can be streamed and range-read. `SqlarFilesApi`
21
+ exists for the opposite need: an archive other SQLAR tools can open, in runtimes without a row
22
+ limit.
5
23
 
6
24
  | | `SqliteFilesApi` | `SqlarFilesApi` |
7
25
  | --- | --- | --- |
8
- | Storage | paths → contents → blocks of ≤ 1 MiB | one row per file, [SQLite Archive](https://sqlite.org/sqlar.html) format |
26
+ | Storage | paths -> contents -> blocks of at most 1.5 MiB | one row per file, SQLite Archive format |
9
27
  | File size | unbounded; streamed block by block | one blob, read and written whole |
10
- | Durable Objects / D1 | yes — every row stays under the 2 MB limit | only files whose stored row is under 2 MB |
28
+ | Durable Objects / D1 | yes, every row stays under the 2 MB limit | only files whose stored row is under 2 MB |
11
29
  | Readable by `sqlite3 -A` | no | yes |
12
30
  | Identical content | stored once, shared by every path | stored per path |
13
31
 
14
- Use `SqliteFilesApi` by default, and `SqlarFilesApi` where the archive must be portable to other
15
- SQLAR tools, in a runtime without a row limit (Node, Deno, Bun, a browser SQLite).
32
+ Use `SqliteFilesApi` by default.
16
33
 
17
- ## Installation
34
+ ## How to use
18
35
 
19
36
  ```bash
20
- npm install @statewalker/webrun-files-sqlite @statewalker/webrun-files
37
+ pnpm add @statewalker/webrun-files-sqlite @statewalker/webrun-files
21
38
  ```
22
39
 
23
40
  `pako` is not a dependency: pass the module to `pakoCodec` / `pakoDeflateCodec` if you use them.
24
41
 
25
- ## Drivers
26
-
27
- Both implementations take a `SqlDriver`. The package imports none of the handles below — they are
28
- passed in — so it bundles for any runtime.
42
+ One entry point, `@statewalker/webrun-files-sqlite`: ESM (`dist/esm/index.js`), CommonJS
43
+ (`dist/cjs/index.cjs`), types (`dist/index.d.ts`); sources in `src/`. The package imports no
44
+ database binding itself; you pass the handle in, so it bundles for any runtime.
29
45
 
30
46
  ```typescript
31
- import { D1SqlDriver, DoSqlDriver, NodeSqlDriver } from "@statewalker/webrun-files-sqlite";
47
+ import { D1SqlDriver, DoSqlDriver, NodeSqlDriver, SqliteFilesApi } from '@statewalker/webrun-files-sqlite';
32
48
 
33
- new NodeSqlDriver(new DatabaseSync("files.db")); // node:sqlite
34
- new DoSqlDriver(this.ctx.storage); // inside a Durable Object: ctx.storage, not ctx.storage.sql
35
- new D1SqlDriver(env.DB); // a D1 binding
49
+ new NodeSqlDriver(new DatabaseSync('files.db')); // node:sqlite
50
+ new DoSqlDriver(this.ctx.storage); // in a Durable Object: ctx.storage, not ctx.storage.sql
51
+ new D1SqlDriver(env.DB); // a D1 binding
52
+
53
+ const files = new SqliteFilesApi(driver, { blockSize: 1024 * 1024 });
54
+ await files.init(); // creates tables and indexes if missing; await it before any other call
36
55
  ```
37
56
 
38
- Any other SQLite binding works by implementing the port:
57
+ ### `SqliteFilesApi` options
39
58
 
40
- ```typescript
41
- interface SqlStatement {
42
- sql: string;
43
- params?: unknown[];
44
- }
59
+ | Option | Default | |
60
+ | --- | --- | --- |
61
+ | `compression` | `webDeflateCodec()` where `CompressionStream` exists, else none | `null` stores uncompressed; `pakoDeflateCodec(pako)` for runtimes without Compression Streams |
62
+ | `tablePrefix` | `"fs_"` | several file systems in one database |
63
+ | `blockSize` | 1 MiB | uncompressed bytes per block for new writes, 1 byte to 1.5 MiB |
45
64
 
46
- interface SqlDriver {
47
- /** Rows as objects keyed by column name. */
48
- all<T>(sql: string, ...params: unknown[]): T[] | Promise<T[]>;
49
- /** One statement, for its effect. */
50
- run(sql: string, ...params: unknown[]): void | Promise<void>;
51
- /**
52
- * The statements in order, as one transaction: all apply or none do. Returns the number of rows
53
- * each statement changed. No statement may depend on another's result.
54
- */
55
- transaction(statements: SqlStatement[]): number[] | Promise<number[]>;
56
- }
57
- ```
65
+ ### `SqlarFilesApi` codecs (`codec` option)
58
66
 
59
- The built-in drivers implement `transaction` with `BEGIN IMMEDIATE … COMMIT` (node:sqlite),
60
- `ctx.storage.transactionSync` (Durable Objects, where `sql.exec` rejects `BEGIN`) and `batch()` (D1).
61
- Blobs come back as `Uint8Array`, `ArrayBuffer` or `number[]` depending on the runtime; both
62
- implementations accept all three.
67
+ | Codec | Uses | When |
68
+ | --- | --- | --- |
69
+ | `defaultCodec()` | `webCodec()` or `rawCodec()` | the default: `webCodec()` where `CompressionStream` exists, `rawCodec()` elsewhere |
70
+ | `webCodec({ minSize? })` | `CompressionStream("deflate")` | browsers, Workers, Node, Deno, Bun |
71
+ | `pakoCodec(pako, { minSize?, level? })` | an injected `pako` module | runtimes without Compression Streams |
72
+ | `rawCodec()` | nothing | never compresses, and refuses to read compressed rows |
63
73
 
64
- ## SqliteFilesApi
74
+ ## Examples
75
+
76
+ ### Stream a large file through `SqliteFilesApi`
65
77
 
66
78
  ```typescript
67
- import { DatabaseSync } from "node:sqlite";
68
- import { NodeSqlDriver, SqliteFilesApi } from "@statewalker/webrun-files-sqlite";
79
+ import { DatabaseSync } from 'node:sqlite';
80
+ import { NodeSqlDriver, SqliteFilesApi } from '@statewalker/webrun-files-sqlite';
69
81
 
70
- const files = new SqliteFilesApi(new NodeSqlDriver(new DatabaseSync("files.db")));
71
- await files.init(); // creates fs_paths, fs_files and fs_blocks if missing
82
+ const files = new SqliteFilesApi(new NodeSqlDriver(new DatabaseSync('files.db')));
83
+ await files.init();
72
84
 
73
- await files.write("/video.mp4", response.body); // any Iterable or AsyncIterable of Uint8Array
74
- for await (const chunk of files.read("/video.mp4", { start: 10_000_000, length: 65_536 })) {
85
+ await files.write('/video.mp4', chunks); // any Iterable or AsyncIterable of Uint8Array
86
+ for await (const chunk of files.read('/video.mp4', { start: 10_000_000, length: 65_536 })) {
75
87
  // only the block holding this range is fetched and inflated
76
88
  }
77
89
  ```
78
90
 
79
- `init()` creates the tables and indexes if they are missing; await it before any other call.
91
+ ### Inside a Durable Object
80
92
 
81
- ### How content is stored
93
+ ```typescript
94
+ import { DoSqlDriver, SqliteFilesApi } from '@statewalker/webrun-files-sqlite';
82
95
 
83
- - `fs_paths(pid, path UNIQUE, fid, mtime)` — a path points at a content, or is a directory (`fid` NULL).
84
- - `fs_files(fid, size, compression, block_size, hash, updated)` — an immutable content: its
85
- uncompressed size, `"none"` or `"deflate"`, the block size it was written with, the SHA-256 of its
86
- bytes, and when it last changed (`size` and `hash` are NULL while it is being written).
87
- - `fs_blocks(fid, shift, block)`, keyed by `(fid, shift)` — the content cut into blocks of
88
- `block_size` uncompressed bytes (only the last one shorter); `shift` is the block's offset in the
89
- uncompressed content, always a multiple of `block_size`. Each block of a `deflate` content is its
90
- own zlib stream, so a range read fetches and inflates exactly the blocks the range touches, found
91
- by key.
96
+ const files = new SqliteFilesApi(new DoSqlDriver(this.ctx.storage));
97
+ await files.init();
98
+ ```
92
99
 
93
- `copy` adds path rows pointing at the same content; a write whose bytes match an existing content
94
- (same SHA-256, size, compression and block size) shares it too. A content and its blocks are deleted when the
95
- last path pointing at it is removed or overwritten.
100
+ ### Clean up interrupted writes
96
101
 
97
- ### Streaming
102
+ ```typescript
103
+ const removed = await files.sweep({ olderThan: 60 * 60_000 }); // contents untouched for an hour
104
+ ```
98
105
 
99
- `write` pulls the source only while it builds the current block and stores each block before
100
- pulling for the next; `read` fetches a block only when its consumer asks for bytes beyond the
101
- previous one. Memory is bounded by one block plus one source chunk, and the tests measure both
102
- bounds. An `AbortSignal` passed to `read` is checked before every block.
106
+ ### A portable archive with `SqlarFilesApi`
103
107
 
104
- `list` is streamed too: it reads `fs_paths` in pages of 256 by key, in the index's order — which is
105
- the path order every `FilesApi` lists in — and `{ after }` is simply where the first page starts.
108
+ ```typescript
109
+ import { DatabaseSync } from 'node:sqlite';
110
+ import * as pako from 'pako';
111
+ import { readText, writeText } from '@statewalker/webrun-files';
112
+ import { NodeSqlDriver, pakoCodec, SqlarFilesApi } from '@statewalker/webrun-files-sqlite';
106
113
 
107
- ### Options
114
+ const files = new SqlarFilesApi(new NodeSqlDriver(new DatabaseSync('site.sqlar')), {
115
+ codec: pakoCodec(pako, { level: 9 }),
116
+ });
117
+ await files.init(); // creates the sqlar table if missing
108
118
 
109
- | Option | Default | |
110
- | --- | --- | --- |
111
- | `compression` | `webDeflateCodec()` where `CompressionStream` exists, else none | `null` stores uncompressed; `pakoDeflateCodec(pako)` for runtimes without Compression Streams |
112
- | `tablePrefix` | `"fs_"` | several file systems in one database |
113
- | `blockSize` | 1 MiB | uncompressed bytes per block for new writes, 1 to 1.5 MiB (the cap keeps a deflated block under the 2 MB row limit). Larger means fewer rows — better where statements are costly, as on D1; smaller means cheaper small range reads. Each content records its own size, so changing this never affects existing files. |
119
+ await writeText(files, '/docs/index.md', '# Hello');
120
+ console.log(await readText(files, '/docs/index.md'));
114
121
 
115
- ### Semantics worth knowing
122
+ await files.symlink('/latest.md', './docs/index.md');
123
+ await files.readlink('/latest.md'); // "./docs/index.md"
124
+ ```
116
125
 
117
- - `copy` and `move` replace the target's subtree rather than merging into it, and throw when one
118
- path contains the other.
119
- - A write is invisible until complete: if it fails for any reason, the path keeps its previous
120
- content and the partly written content is deleted.
121
- - A read whose content is removed underneath it throws `changed during read`. A block whose
122
- uncompressed length is not what its position implies throws `block at <shift> holds … bytes`,
123
- before any wrong byte reaches the reader.
126
+ ### Your own SQLite binding
124
127
 
125
- ### Interruptions and `sweep`
128
+ Implement the driver interface:
126
129
 
127
- Streaming happens outside transactions; every step that changes what a path shows is one
128
- transaction (`BEGIN IMMEDIATE` on node:sqlite, `transactionSync` on Durable Objects, `batch()` on D1):
129
- storing each block, finishing a write, `remove`, `copy` and `move`. A failure inside any of them
130
- leaves the database exactly as it was — `move` onto an existing target never loses the target.
130
+ ```typescript
131
+ import type { SqlDriver, SqlStatement } from '@statewalker/webrun-files-sqlite';
131
132
 
132
- A write stays invisible until it finishes. If the process dies mid-stream, the partly written
133
- content is left behind unreferenced; remove such leftovers with
133
+ interface SqlStatement {
134
+ sql: string;
135
+ params?: unknown[];
136
+ }
134
137
 
135
- ```typescript
136
- const removed = await files.sweep({ olderThan: 60 * 60_000 }); // contents untouched for an hour
138
+ interface SqlDriver {
139
+ /** Rows as objects keyed by column name. */
140
+ all<T>(sql: string, ...params: unknown[]): T[] | Promise<T[]>;
141
+ /** One statement, for its effect. */
142
+ run(sql: string, ...params: unknown[]): void | Promise<void>;
143
+ /** The statements in order, as one transaction: all apply or none do. Returns rows changed per
144
+ * statement. No statement may depend on another's result. */
145
+ transaction(statements: SqlStatement[]): number[] | Promise<number[]>;
146
+ }
137
147
  ```
138
148
 
139
- `sweep` deletes contents that no path references and that have not been updated for `olderThan`
140
- milliseconds, with their blocks. A write refreshes its content with every block, so only a write
141
- stalled longer than `olderThan` can be swept — and it then fails rather than creating a broken path.
142
- Choose a threshold above the longest pause a source may take. `sweep` never runs on its own.
149
+ ## Internals
143
150
 
144
- ## SqlarFilesApi
151
+ ### The drivers and their transactions
145
152
 
146
- ```typescript
147
- import { DatabaseSync } from "node:sqlite";
148
- import { readText, writeText } from "@statewalker/webrun-files";
149
- import { NodeSqlDriver, SqlarFilesApi } from "@statewalker/webrun-files-sqlite";
153
+ The built-in drivers implement `transaction` with `BEGIN IMMEDIATE ... COMMIT` (node:sqlite),
154
+ `ctx.storage.transactionSync` (Durable Objects, where `sql.exec` rejects `BEGIN`) and `batch()` (D1).
155
+ Blobs come back as `Uint8Array`, `ArrayBuffer` or `number[]` depending on the runtime; both
156
+ implementations accept all three. The handle types are exported as `NodeSqliteDatabase`,
157
+ `DoStorage` and `D1Database`.
150
158
 
151
- const files = new SqlarFilesApi(new NodeSqlDriver(new DatabaseSync("site.sqlar")));
152
- await files.init(); // creates the sqlar table if missing — await before anything else
159
+ ### How `SqliteFilesApi` stores content
153
160
 
154
- await writeText(files, "/docs/index.md", "# Hello");
155
- console.log(await readText(files, "/docs/index.md"));
161
+ ```
162
+ fs_paths(pid, path UNIQUE, fid, mtime) a path -> a content, or a directory (fid NULL)
163
+ |
164
+ v
165
+ fs_files(fid, size, compression, block_size, an immutable content: size, "none"/"deflate",
166
+ hash, updated) block size, SHA-256, last change
167
+ |
168
+ v
169
+ fs_blocks(fid, shift, block) the content in blocks of block_size bytes;
170
+ shift = offset in the uncompressed content
156
171
  ```
157
172
 
158
- It takes the same drivers (see *Drivers*), subject to the row limit below.
173
+ Each block of a `deflate` content is its own zlib stream, so a range read fetches and inflates
174
+ exactly the blocks it touches, found by key. `copy` adds path rows pointing at the same content; a
175
+ write whose bytes match an existing content (same SHA-256, size, compression and block size) shares
176
+ it too. A content and its blocks are deleted when the last path pointing at it goes.
159
177
 
160
- ### Compression codecs
178
+ ### Why the block size is capped at 1.5 MiB
161
179
 
162
- | Codec | Uses | When |
163
- | --- | --- | --- |
164
- | `webCodec()` | `CompressionStream("deflate")` | Default wherever `CompressionStream` exists |
165
- | `pakoCodec(pako, { level })` | an injected `pako` module | Runtimes without Compression Streams |
166
- | `rawCodec()` | nothing | Default elsewhere; never compresses, and refuses to read compressed rows |
180
+ A deflated block can be slightly larger than its input. 1.5 MiB keeps even incompressible input
181
+ under the 2 MB row limit of Durable Objects and D1. Larger blocks mean fewer rows, which matters
182
+ where every statement costs (D1); smaller blocks make small range reads cheaper. Each content
183
+ records its own block size, so changing `blockSize` never affects existing files.
167
184
 
168
- ```typescript
169
- import * as pako from "pako";
170
- const files = new SqlarFilesApi(driver, { codec: pakoCodec(pako) });
171
- ```
185
+ ### Streaming and memory
172
186
 
173
- All codecs read and write the same zlib format, so an archive written with one is read by another.
174
- Inputs shorter than `minSize` (64 bytes by default) are stored without attempting compression.
187
+ `write` pulls the source only while it builds the current block and stores each block before
188
+ pulling the next; `read` fetches a block only when its consumer asks for bytes beyond the previous
189
+ one. Memory is bounded by one block plus one source chunk; the tests measure both bounds. An
190
+ `AbortSignal` passed to `read` is checked before every block. `list` reads `fs_paths` in pages of
191
+ 256 by key, in index order, which is the `FilesApi` path order, and `{ after }` is where the first
192
+ page starts.
193
+
194
+ ### Atomic steps, and why `sweep` exists
195
+
196
+ Streaming happens outside transactions. Every step that changes what a path shows is one
197
+ transaction: storing each block, finishing a write, `remove`, `copy` and `move`. A failure inside
198
+ any of them leaves the database as it was; `move` onto an existing target never loses the target.
199
+ A write stays invisible until it finishes: if it fails, the path keeps its previous content and the
200
+ partial content is deleted.
201
+
202
+ If the process dies mid-stream, the partial content is left behind, unreferenced. `sweep` deletes
203
+ contents that no path references and that have not been updated for `olderThan` milliseconds, with
204
+ their blocks. A write refreshes its content with every block, so only a write stalled longer than
205
+ `olderThan` can be swept, and it then fails rather than creating a broken path. Choose a threshold
206
+ above the longest pause a source may take. `sweep` never runs on its own.
207
+
208
+ ### What `SqliteFilesApi` throws
209
+
210
+ - The constructor throws `SqliteFilesApi: blockSize must be an integer from 1 to 1572864, got ...`
211
+ and `SqliteFilesApi: tablePrefix must be a plain SQL identifier, got ...`.
212
+ - `copy` and `move` replace the target's subtree rather than merging into it, and throw when one
213
+ path contains the other.
214
+ - A read whose content is removed underneath it throws `changed during read`.
215
+ - A block whose uncompressed length does not match its position throws `block at <shift> holds ...
216
+ bytes`, before any wrong byte reaches the reader.
175
217
 
176
- ### How entries are stored
218
+ ### How `SqlarFilesApi` stores entries
177
219
 
178
220
  | Entry | `name` | `sz` | `data` |
179
221
  | --- | --- | --- | --- |
@@ -182,30 +224,26 @@ Inputs shorter than `minSize` (64 bytes by default) are stored without attemptin
182
224
  | Directory | | `0` | `NULL` |
183
225
  | Symlink | | `-1` | target as text |
184
226
 
185
- `mtime` is stored in seconds; `lastModified` reports it in milliseconds. Archives written by other
186
- tools without directory rows still behave as trees: a path that is only a prefix of other names is a
187
- directory.
227
+ `mtime` is stored in seconds; `lastModified` reports milliseconds. Inputs shorter than `minSize`
228
+ (64 bytes by default) are stored without trying to compress. All codecs read and write the same
229
+ zlib format. Archives written by other tools without directory rows still behave as trees: a path
230
+ that is only a prefix of other names is a directory.
188
231
 
189
- #### Symlinks
232
+ `FileKind` has no symlink, so `stats`, `exists`, `read` and `list` follow links as POSIX `stat()`
233
+ does. A dangling link, or a chain longer than 8, behaves as a missing path. `remove`, `copy` and
234
+ `move` act on the link itself. Only the final path component is resolved through symlinks.
190
235
 
191
- `FileKind` has no symlink, so `stats`, `exists`, `read` and `list` follow links the way POSIX
192
- `stat()` does. A dangling link, or a chain of more than 8, behaves as a missing path. `remove`,
193
- `copy` and `move` act on the link itself. Two extension methods give access to links:
236
+ ### `SqlarFilesApi` limits
194
237
 
195
- ```typescript
196
- await files.symlink("/latest.js", "./v2/index.js");
197
- await files.readlink("/latest.js"); // "./v2/index.js"
198
- ```
238
+ - A file is one blob: `write` buffers it, `read` inflates it whole and yields one chunk.
239
+ - On Durable Objects and D1 the stored (possibly compressed) size of one file must stay under 2 MB.
240
+ - Multi-row mutations (`copy`, `move`, `remove` of a tree) are not wrapped in a transaction.
241
+ - `list` loads every row under the directory, then sorts the entries into path order before
242
+ applying `after`.
199
243
 
200
- ### Limitations
244
+ ### Dependencies
201
245
 
202
- - A file is one blob: `write` buffers its content, `read` inflates it whole and yields one chunk.
203
- - On Durable Objects and D1 a row may not exceed 2 MB, so the *stored* (possibly compressed) size
204
- of one file is capped there. Use `SqliteFilesApi` in those runtimes.
205
- - Multi-row mutations (`copy`, `move`, `remove` of a tree) are not wrapped in a transaction.
206
- - Only the final path component is resolved through symlinks.
207
- - `list` loads every row under the directory, then sorts the entries into path order (implicit
208
- directories and resolved links do not sit where the rows do) before applying `after`.
246
+ `@statewalker/webrun-files` only. SQLite handles and `pako` are passed in by the caller.
209
247
 
210
248
  ## License
211
249
 
package/package.json CHANGED
@@ -1,10 +1,13 @@
1
1
  {
2
2
  "name": "@statewalker/webrun-files-sqlite",
3
- "version": "0.10.0",
3
+ "version": "0.10.3",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "SQLite-backed FilesApi implementations",
7
- "homepage": "https://github.com/statewalker/webrun-files",
7
+ "bugs": {
8
+ "url": "https://github.com/statewalker/webrun-files/issues"
9
+ },
10
+ "homepage": "https://github.com/statewalker/webrun-files/tree/main/packages/webrun-files-sqlite#readme",
8
11
  "author": {
9
12
  "name": "Mikhail Kotelnikov",
10
13
  "email": "mikhail.kotelnikov@gmail.com"
@@ -12,13 +15,15 @@
12
15
  "license": "MIT",
13
16
  "repository": {
14
17
  "type": "git",
15
- "url": "git@github.com:statewalker/webrun-files.git"
18
+ "url": "git+https://github.com/statewalker/webrun-files.git",
19
+ "directory": "packages/webrun-files-sqlite"
16
20
  },
17
21
  "main": "./dist/cjs/index.cjs",
18
22
  "module": "./dist/esm/index.js",
19
23
  "types": "./dist/index.d.ts",
20
24
  "exports": {
21
25
  ".": {
26
+ "source": "./src/index.ts",
22
27
  "types": "./dist/index.d.ts",
23
28
  "import": "./dist/esm/index.js",
24
29
  "require": "./dist/cjs/index.cjs"
@@ -29,16 +34,16 @@
29
34
  "src"
30
35
  ],
31
36
  "dependencies": {
32
- "@statewalker/webrun-files": "0.10.0"
37
+ "@statewalker/webrun-files": "^0.10.2"
33
38
  },
34
39
  "devDependencies": {
35
- "@types/node": "^26.2.0",
40
+ "@types/node": "^26.6.4",
36
41
  "pako": "^3.0.2",
37
42
  "rimraf": "^6.1.3",
38
- "rolldown": "^1.2.4",
43
+ "rolldown": "^1.2.12",
39
44
  "typescript": "^7.0.2",
40
- "vitest": "^4.1.10",
41
- "@statewalker/webrun-files-tests": "3.0.0"
45
+ "vitest": "^5.0.3",
46
+ "@statewalker/webrun-files-tests": "^3.0.0"
42
47
  },
43
48
  "sideEffects": false,
44
49
  "publishConfig": {