web-jszipp 1.0.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 +21 -0
- package/README.md +955 -0
- package/dist/cr61ff58/jszipp.cjs +1 -0
- package/dist/cr61ff58/jszipp.mjs +1 -0
- package/dist/cr61ff58/jszipp.reader.umd.js +1 -0
- package/dist/cr61ff58/jszipp.umd.js +1 -0
- package/dist/cr61ff58/jszipp.writer.umd.js +1 -0
- package/dist/cr86ff68/jszipp.cjs +1 -0
- package/dist/cr86ff68/jszipp.mjs +1 -0
- package/dist/cr86ff68/jszipp.reader.umd.js +1 -0
- package/dist/cr86ff68/jszipp.umd.js +1 -0
- package/dist/cr86ff68/jszipp.writer.umd.js +1 -0
- package/dist/index.d.ts +135 -0
- package/dist/jszipp.cjs +1 -0
- package/dist/jszipp.mjs +1 -0
- package/dist/jszipp.reader.umd.js +1 -0
- package/dist/jszipp.umd.js +1 -0
- package/dist/jszipp.writer.umd.js +1 -0
- package/dist/reader.d.ts +7 -0
- package/dist/types.d.ts +1 -0
- package/dist/writer.d.ts +7 -0
- package/package.json +82 -0
package/README.md
ADDED
|
@@ -0,0 +1,955 @@
|
|
|
1
|
+
# JSZipp
|
|
2
|
+
|
|
3
|
+
**The ZIP library for browser apps that need safety, streaming, and archive fidelity.**
|
|
4
|
+
|
|
5
|
+
JSZipp is a tiny, dependency-free ZIP reader and writer for modern browser apps.
|
|
6
|
+
It combines safe defaults, Web Streams integration, ZIP64, full archive metadata,
|
|
7
|
+
correct filename decoding, TypeScript types, and practical output shapes
|
|
8
|
+
(`Blob`, `Response`, `ReadableStream`, `Uint8Array`, `ArrayBuffer`) in one
|
|
9
|
+
focused package.
|
|
10
|
+
|
|
11
|
+
Reach for JSZipp when your app handles **ZIP archives in the browser** — file
|
|
12
|
+
uploads, downloadable exports, `.docx` / `.xlsx` / `.epub` inspection, plugin
|
|
13
|
+
bundles, templates, CI artifacts, generated reports, or package-like archives —
|
|
14
|
+
and you want the default path to be safe and productive.
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { ZipWriter, openZip } from "web-jszipp";
|
|
18
|
+
|
|
19
|
+
// Create a browser-downloadable ZIP.
|
|
20
|
+
const writer = new ZipWriter({ outputAs: "blob" });
|
|
21
|
+
await writer.add({ path: "hello.txt", data: "Hello from JSZipp" });
|
|
22
|
+
const download = await writer.close();
|
|
23
|
+
|
|
24
|
+
// Open an untrusted upload with the strict package profile.
|
|
25
|
+
const zip = await openZip(fileInput.files![0], {
|
|
26
|
+
pathMode: "strict-package",
|
|
27
|
+
maxArchiveSize: 50 * 1024 * 1024,
|
|
28
|
+
maxEntrySize: 10 * 1024 * 1024
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
console.log(await zip.get("hello.txt")?.text());
|
|
32
|
+
await zip.close();
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Why JSZipp?
|
|
36
|
+
|
|
37
|
+
Most ZIP libraries make the happy path easy. JSZipp is designed to make the
|
|
38
|
+
**safe browser happy path** easy.
|
|
39
|
+
|
|
40
|
+
| General-user need | Why it matters | JSZipp's answer |
|
|
41
|
+
| --- | --- | --- |
|
|
42
|
+
| Accept user ZIP uploads | ZIP filenames are attacker-controlled paths, not harmless labels. | `openZip()` rejects unsafe paths by default; `strict-package` adds package-grade collision and local/central consistency checks. |
|
|
43
|
+
| Avoid zip-bomb surprises | A small upload can claim one size and expand into much more data. | `maxArchiveSize` bounds the archive and `maxEntrySize` is enforced while inflating. |
|
|
44
|
+
| Ship less JavaScript | Browser apps pay for every byte and every dependency. | Zero dependencies, native `DecompressionStream` for reading deflated entries, and tree-shakeable reader/writer entry points. |
|
|
45
|
+
| Work with real browser APIs | Downloads, fetch responses, service workers, and pipelines already speak Web APIs. | `ZipWriter` can return `Blob`, `Response`, `ReadableStream`, `Uint8Array`, or `ArrayBuffer`; `ZipTransformStream` is a native `TransformStream`. |
|
|
46
|
+
| Preserve real archive data | Archives are more than compressed bytes: comments, modes, timestamps, names, and ZIP64 matter. | ZIP64, comments, extra fields, Unix mode bits, DOS + UTC timestamps, CRC-32, CP437, `TextDecoder` fallbacks, and Info-ZIP Unicode Path support. |
|
|
47
|
+
| Keep the app code simple | Most teams do not want to write their own ZIP safety and metadata layer. | Random-access `entries` / `get(path)` plus `text()` / `bytes()` / `arrayBuffer()` / `stream()` helpers and full TypeScript types. |
|
|
48
|
+
|
|
49
|
+
The practical win is not that JSZipp beats every library at every benchmark. It
|
|
50
|
+
is that common browser ZIP tasks need fewer adapters, fewer security footguns,
|
|
51
|
+
and fewer project-specific validation rules.
|
|
52
|
+
|
|
53
|
+
## Highlights
|
|
54
|
+
|
|
55
|
+
- **Safe by default.** `openZip` rejects Zip Slip (`..`), absolute, drive-letter,
|
|
56
|
+
drive-relative, backslash, and NUL-byte paths out of the box. It also
|
|
57
|
+
cross-checks each entry's local header against the central directory for
|
|
58
|
+
filename, security-flag, and reused-offset consistency, so a scanner and an
|
|
59
|
+
extractor cannot be shown two different file trees.
|
|
60
|
+
- **A stricter profile for untrusted packages.** `pathMode: "strict-package"`
|
|
61
|
+
adds local/central size cross-checks and rejects duplicate, case-only
|
|
62
|
+
(`Readme.txt` vs `README.TXT`), and Unicode NFC/NFD path collisions — the
|
|
63
|
+
parser-differential tricks that appear when one tool validates an archive and
|
|
64
|
+
another extracts it.
|
|
65
|
+
- **Anti-zip-bomb caps.** `maxArchiveSize` and `maxEntrySize` bound input and
|
|
66
|
+
per-entry output. `maxEntrySize` is enforced *during* inflate, so a header that
|
|
67
|
+
lies about its uncompressed size cannot expand past the cap before JSZipp
|
|
68
|
+
notices.
|
|
69
|
+
- **Browser-native output.** Create a ZIP byte stream by default, or ask for a
|
|
70
|
+
`Blob` for downloads, a `Response` for fetch-like APIs, or raw bytes for
|
|
71
|
+
storage and tests. `AbortSignal` and progress callbacks are first-class.
|
|
72
|
+
- **Stream-shaped APIs.** `ZipTransformStream` drops into Web Streams pipelines,
|
|
73
|
+
while `readZipStream` gives you a `for await...of` reader for archive entries.
|
|
74
|
+
- **Full-fidelity ZIP handling.** ZIP64 (auto/force/off), store + deflate,
|
|
75
|
+
per-file and archive comments, extra fields, Unix mode bits, DOS + UTC
|
|
76
|
+
(`0x5455`) timestamps, CRC-32 integrity, and EOCD-by-content detection that
|
|
77
|
+
resists comment/append forgery.
|
|
78
|
+
- **Correct filenames.** UTF-8 with the UTF-8 flag, a built-in CP437 decoder,
|
|
79
|
+
`TextDecoder` fallbacks (`shift_jis`, `gbk`, `big5`, …), and CRC-verified
|
|
80
|
+
Info-ZIP Unicode Path (`0x7075`) support.
|
|
81
|
+
- **Ergonomic and typed.** Random-access `entries` / `get(path)`, reusable
|
|
82
|
+
`text()` / `bytes()` / `arrayBuffer()` / `stream()` helpers, synchronous
|
|
83
|
+
in-memory writing, and full TypeScript types.
|
|
84
|
+
|
|
85
|
+
## JSZipp vs JSZip vs fflate
|
|
86
|
+
|
|
87
|
+
There are excellent ZIP libraries already. The honest summary:
|
|
88
|
+
|
|
89
|
+
- **JSZip** is a mature, friendly, general-purpose ZIP toolkit with a large
|
|
90
|
+
ecosystem and a familiar API.
|
|
91
|
+
- **fflate** is a best-in-class JavaScript compression engine with fast raw
|
|
92
|
+
DEFLATE/GZIP/Zlib/ZIP primitives and callback-style streaming tools.
|
|
93
|
+
- **JSZipp** focuses on *safe, browser-native, full-fidelity ZIP archive handling*
|
|
94
|
+
for apps that read or write archives crossing a trust boundary.
|
|
95
|
+
|
|
96
|
+
| | **JSZipp** | JSZip | fflate |
|
|
97
|
+
| --- | --- | --- | --- |
|
|
98
|
+
| Best fit | Browser ZIP handling with safety defaults | Mature general ZIP toolkit | Fastest/smallest compression engine |
|
|
99
|
+
| Read unsafe paths | Rejects by default; `sanitize` / `unsafe` are opt-in | Sanitizes relative path traversal; strict rejection policy is app-defined | App-defined |
|
|
100
|
+
| Package hardening | `strict-package` collision + local/central checks | No strict-package profile | No strict-package profile |
|
|
101
|
+
| Parser-differential defenses | Filename, security flags, reused offsets; size checks in `strict-package` | Not the primary focus | Not the primary focus |
|
|
102
|
+
| Anti-zip-bomb caps | Built in (`maxArchiveSize`, bounded `maxEntrySize`) | App-defined | App-defined/filter-based |
|
|
103
|
+
| Browser Web Streams | Native `ReadableStream` + `TransformStream` shapes | Promise/StreamHelper/Node stream oriented | Callback stream APIs |
|
|
104
|
+
| Browser output targets | `ReadableStream`, `Blob`, `Response`, `Uint8Array`, `ArrayBuffer` | Common byte/blob outputs | Byte arrays/callback chunks |
|
|
105
|
+
| Random-access convenience | `entries`, `get(path)`, reusable entry readers | Yes, mature object API | Mostly lower-level ZIP primitives |
|
|
106
|
+
| Full archive metadata | Comments, extra fields, modes, timestamps, ZIP64 | Common metadata, but some input data is discarded on rewrite | Focused on compression/archive primitives |
|
|
107
|
+
| Filename encodings | UTF-8, CP437, `TextDecoder` fallbacks, Unicode Path extra | UTF-8 plus custom decode hooks | UTF-8-oriented API |
|
|
108
|
+
| Dependencies | None | None | None |
|
|
109
|
+
| Raw compression speed | Good | Moderate | Best-in-class |
|
|
110
|
+
|
|
111
|
+
Competitor cells are deliberately high-level and may change by version. Verify
|
|
112
|
+
library-specific behavior against the release you use.
|
|
113
|
+
|
|
114
|
+
### Choosing between them
|
|
115
|
+
|
|
116
|
+
- **Pick JSZipp** when you read ZIP uploads, inspect package-like archives, create
|
|
117
|
+
downloadable ZIPs in a browser, need Web Streams or `Response` output, care
|
|
118
|
+
about metadata, or want safe defaults instead of writing your own path,
|
|
119
|
+
collision, and zip-bomb guardrails.
|
|
120
|
+
- **Pick JSZip** when you already rely on its API or ecosystem, want the most
|
|
121
|
+
familiar general-purpose ZIP object model, and do not need JSZipp's stricter
|
|
122
|
+
trust-boundary profile or browser-native stream shapes.
|
|
123
|
+
- **Pick fflate** when raw compression/decompression speed, worker-based
|
|
124
|
+
throughput, or the smallest compression-focused primitive is the deciding
|
|
125
|
+
factor, and you are comfortable building your own archive policy, metadata
|
|
126
|
+
layer, and app-specific validation.
|
|
127
|
+
|
|
128
|
+
If your decision question is **"can my browser app safely open this ZIP
|
|
129
|
+
upload?"**, JSZipp is built for that job: use `openZip` with
|
|
130
|
+
`pathMode: "strict-package"` plus explicit `maxArchiveSize` and `maxEntrySize`
|
|
131
|
+
caps, and reject archives that do not meet the profile.
|
|
132
|
+
|
|
133
|
+
## Runtime
|
|
134
|
+
|
|
135
|
+
- ECMAScript 2019 output
|
|
136
|
+
- Modern browsers with `ReadableStream`, `TransformStream`, `Blob`, and
|
|
137
|
+
`DecompressionStream` for reading deflated entries
|
|
138
|
+
- Intended browser baseline: Chrome 80+ and Firefox 113+ class browsers
|
|
139
|
+
- Node.js can run the tests, but the library is designed for browser APIs
|
|
140
|
+
|
|
141
|
+
## Error Messages
|
|
142
|
+
|
|
143
|
+
JSZipp keeps exception classes and DOMException names stable across builds
|
|
144
|
+
(`RangeError`, `TypeError`, `SecurityError`, `InvalidStateError`,
|
|
145
|
+
`NotSupportedError`, and so on). Production bundles shorten `error.message` to
|
|
146
|
+
codes such as `E_PATH`, `E_LIMIT`, and `E_STRUCTURE`; source/dev execution keeps
|
|
147
|
+
the detailed diagnostic messages used by the test suite.
|
|
148
|
+
|
|
149
|
+
## Install
|
|
150
|
+
|
|
151
|
+
```sh
|
|
152
|
+
pnpm add web-jszipp
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
import JSZipp, {
|
|
157
|
+
ZipWriter,
|
|
158
|
+
ZipTransformStream,
|
|
159
|
+
openZip,
|
|
160
|
+
readZipStream,
|
|
161
|
+
TimestampMode
|
|
162
|
+
} from "web-jszipp";
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`JSZipp` is the default namespace export and includes the same runtime values:
|
|
166
|
+
`ZipWriter`, `ZipTransformStream`, `openZip`, `readZipStream`, and
|
|
167
|
+
`TimestampMode`. Named exports are usually more convenient in application code.
|
|
168
|
+
|
|
169
|
+
Browser-legacy builds are opt-in npm subpaths for apps that must target older
|
|
170
|
+
browser pairs. They expose the same public API as the main entry point, but ship
|
|
171
|
+
extra compatibility code:
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
import { ZipWriter, openZip } from "web-jszipp/browser-legacy/cr61ff58";
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
import { ZipWriter, openZip } from "web-jszipp/browser-legacy/cr86ff68";
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
If you prefer CDN script tags, use one of the following UMD builds:
|
|
182
|
+
|
|
183
|
+
```html
|
|
184
|
+
<!-- Modern UMD default -->
|
|
185
|
+
|
|
186
|
+
<script src="https://unpkg.com/web-jszipp"></script>
|
|
187
|
+
|
|
188
|
+
<script src="https://cdn.jsdelivr.net/npm/web-jszipp"></script>
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
<!-- Chrome 61 / Firefox 58 compatible UMD -->
|
|
192
|
+
|
|
193
|
+
<script src="https://unpkg.com/web-jszipp/dist/cr61ff58/jszipp.umd.js"></script>
|
|
194
|
+
|
|
195
|
+
<script src="https://cdn.jsdelivr.net/npm/web-jszipp/dist/cr61ff58/jszipp.umd.js"></script>
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
<!-- Chrome 86 / Firefox 68 compatible UMD -->
|
|
199
|
+
|
|
200
|
+
<script src="https://unpkg.com/jszipp/dist/cr86ff68/jszipp.umd.js"></script>
|
|
201
|
+
|
|
202
|
+
<script src="https://cdn.jsdelivr.net/npm/jszipp/dist/cr86ff68/jszipp.umd.js"></script>
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Which API Should I Use?
|
|
206
|
+
|
|
207
|
+
| Your app needs to | Use | Why |
|
|
208
|
+
| --- | --- | --- |
|
|
209
|
+
| Create a ZIP Blob for download or upload | `new ZipWriter({ outputAs: "blob" })` | Easiest option for most browser apps. |
|
|
210
|
+
| Create a ZIP HTTP response | `new ZipWriter({ outputAs: "response" })` | Returns a native `Response` wrapper. |
|
|
211
|
+
| Create a ZIP byte stream | `new ZipWriter()` | Default mode returns `ReadableStream<Uint8Array>`. |
|
|
212
|
+
| Create raw ZIP bytes | `new ZipWriter({ outputAs: "uint8array" })` | Returns browser byte containers directly. |
|
|
213
|
+
| Insert ZIP creation into an existing Web Streams pipeline | `ZipTransformStream` | It is a native `TransformStream`. |
|
|
214
|
+
| Open a user-selected `.zip` file and read files by name | `openZip` | Best random-access API for `Blob`, `File`, `Uint8Array`, or `ArrayBuffer`. |
|
|
215
|
+
| Open an untrusted upload or package | `openZip(file, { pathMode: "strict-package", maxArchiveSize, maxEntrySize })` | Applies the strongest reader policy with explicit size caps. |
|
|
216
|
+
| List every entry in archive order, including duplicate names from foreign archives | `openZip(...).entries` | Preserves the archive's true entry order. |
|
|
217
|
+
| Get JSZipp's selected file for a path when duplicates exist | `openZip(...).get(path)` | Returns the last matching central-directory entry; external extractors vary. |
|
|
218
|
+
| Consume a ZIP as an async iterator | `readZipStream` | Forward-style iteration with single-use entry tokens. |
|
|
219
|
+
| Read a file more than once or concurrently | `openZip` | Random-access entries create independent streams. |
|
|
220
|
+
| Create a small in-memory ZIP synchronously | `writer.writeSync()` + `writer.closeSync()` | Useful for tests, fixtures, and already-in-memory data. |
|
|
221
|
+
|
|
222
|
+
Most browser apps should use:
|
|
223
|
+
|
|
224
|
+
- `ZipWriter` for creating archives
|
|
225
|
+
- `openZip` for reading archives selected by the user
|
|
226
|
+
|
|
227
|
+
Use `ZipTransformStream` only when you already think in Web Streams. Use
|
|
228
|
+
`readZipStream` when async iteration is a better fit than path lookup.
|
|
229
|
+
|
|
230
|
+
## Create A ZIP
|
|
231
|
+
|
|
232
|
+
`ZipWriter` is the simplest way to create an archive. For browser downloads,
|
|
233
|
+
ask it to return a `Blob`.
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
import { ZipWriter } from "web-jszipp";
|
|
237
|
+
|
|
238
|
+
const writer = new ZipWriter({ level: 6, outputAs: "blob" });
|
|
239
|
+
|
|
240
|
+
await writer.add({ path: "hello.txt", data: "Hello from JSZipp" });
|
|
241
|
+
await writer.add({ path: "docs/readme.md", data: "# Readme\n" });
|
|
242
|
+
const zipBlob = await writer.close();
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Save it from the browser:
|
|
246
|
+
|
|
247
|
+
```ts
|
|
248
|
+
const url = URL.createObjectURL(zipBlob);
|
|
249
|
+
const link = document.createElement("a");
|
|
250
|
+
link.href = url;
|
|
251
|
+
link.download = "archive.zip";
|
|
252
|
+
link.click();
|
|
253
|
+
URL.revokeObjectURL(url);
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Add Different Data Types
|
|
257
|
+
|
|
258
|
+
`ZipInputEntry.data` accepts `string`, `Uint8Array`, `ArrayBuffer`, `Blob`, or
|
|
259
|
+
`ReadableStream<Uint8Array>`.
|
|
260
|
+
|
|
261
|
+
```ts
|
|
262
|
+
const writer = new ZipWriter({ level: 6, outputAs: "blob" });
|
|
263
|
+
|
|
264
|
+
await writer.add({ path: "text.txt", data: "plain text" });
|
|
265
|
+
await writer.add({ path: "bytes.bin", data: new Uint8Array([1, 2, 3]) });
|
|
266
|
+
await writer.add({ path: "buffer.bin", data: new Uint8Array([4, 5, 6]).buffer });
|
|
267
|
+
await writer.add({ path: "photo.jpg", data: fileInput.files![0] });
|
|
268
|
+
await writer.add({ path: "folder/", data: "" });
|
|
269
|
+
await writer.add({
|
|
270
|
+
path: "stream.txt",
|
|
271
|
+
data: new Blob(["streamed content"]).stream()
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
const zipBlob = await writer.close();
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## Add Metadata
|
|
278
|
+
|
|
279
|
+
Each entry can include a comment, timestamps, Unix permissions, DOS attributes,
|
|
280
|
+
or low-level ZIP metadata. Writer options can also include an archive-level ZIP
|
|
281
|
+
comment.
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
const writer = new ZipWriter({
|
|
285
|
+
outputAs: "blob",
|
|
286
|
+
comment: "Generated by JSZipp"
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
await writer.add({
|
|
290
|
+
path: "report.txt",
|
|
291
|
+
data: "Quarterly report",
|
|
292
|
+
meta: {
|
|
293
|
+
comment: "Generated in the browser",
|
|
294
|
+
modifiedAt: new Date("2026-05-31T12:00:00Z"),
|
|
295
|
+
unixPermissions: 0o644
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
await writer.add({
|
|
300
|
+
path: "scripts/build.sh",
|
|
301
|
+
data: "#!/bin/sh\npnpm build\n",
|
|
302
|
+
meta: { unixPermissions: 0o755 }
|
|
303
|
+
});
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## Compression Options
|
|
307
|
+
|
|
308
|
+
```ts
|
|
309
|
+
new ZipWriter({
|
|
310
|
+
level: 6,
|
|
311
|
+
zip64: "auto",
|
|
312
|
+
outputAs: "blob"
|
|
313
|
+
});
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
`level`:
|
|
317
|
+
|
|
318
|
+
- `0`: store files without compression
|
|
319
|
+
- `1` to `9`: use DEFLATE compression with real level control
|
|
320
|
+
- default: `6`
|
|
321
|
+
|
|
322
|
+
`zip64`:
|
|
323
|
+
|
|
324
|
+
- `"auto"`: emit ZIP64 records only when standard ZIP limits are exceeded. This is the default.
|
|
325
|
+
- `"force"`: always emit ZIP64-compatible records.
|
|
326
|
+
- `"off"`: write standard ZIP records and throw if ZIP64 would be required.
|
|
327
|
+
|
|
328
|
+
`outputAs`:
|
|
329
|
+
|
|
330
|
+
- `"stream"`: `close()` returns `ReadableStream<Uint8Array>`. This is the default.
|
|
331
|
+
- `"blob"`: `close()` returns a native `Blob`.
|
|
332
|
+
- `"response"`: `close()` returns a native `Response`.
|
|
333
|
+
- `"uint8array"`: `close()` returns a `Uint8Array`.
|
|
334
|
+
- `"arraybuffer"`: `close()` returns an `ArrayBuffer`.
|
|
335
|
+
|
|
336
|
+
Use `level: 6` for text, JSON, CSV, HTML, and similar files. JSZipp will store
|
|
337
|
+
an entry automatically when the default DEFLATE attempt would not make it
|
|
338
|
+
smaller. Use `level: 0` or `method: "store"` when you want to skip compression
|
|
339
|
+
work entirely for already-compressed files such as JPEG, PNG, MP4, or PDF.
|
|
340
|
+
|
|
341
|
+
You can override compression per entry:
|
|
342
|
+
|
|
343
|
+
```ts
|
|
344
|
+
await writer.add({ path: "photo.jpg", data: photoFile, method: "store" });
|
|
345
|
+
await writer.add({ path: "data/report.json", data: jsonText, method: "deflate" });
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
`method: "store"` skips compression for that entry. `method: "deflate"` forces
|
|
349
|
+
JSZipp's in-repo raw DEFLATE writer. When no per-entry method is set, JSZipp
|
|
350
|
+
uses DEFLATE but stores the entry instead if the compressed payload would be no
|
|
351
|
+
smaller than the source. Entry-level `level` overrides the writer default for
|
|
352
|
+
that file, so you can use lower levels for faster files and higher levels for
|
|
353
|
+
deeper match search.
|
|
354
|
+
|
|
355
|
+
Generated archives use ZIP method `0x0000` for stored entries, ZIP method
|
|
356
|
+
`0x0008` for deflated entries, and general-purpose bit flags `0x0800` to mark
|
|
357
|
+
filenames/comments as UTF-8. For the ZIP-format distinction between compression
|
|
358
|
+
method values and general-purpose bit flags, see
|
|
359
|
+
[ZIP metadata traps](docs/zip-metadata-traps.md#compression-method-and-general-purpose-bit-flags).
|
|
360
|
+
|
|
361
|
+
## Choose The Output Type
|
|
362
|
+
|
|
363
|
+
Default streaming output:
|
|
364
|
+
|
|
365
|
+
```ts
|
|
366
|
+
const writer = new ZipWriter();
|
|
367
|
+
|
|
368
|
+
await writer.add({ path: "log.txt", data: "stream me" });
|
|
369
|
+
const stream = await writer.close();
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
Blob output for downloads, file uploads, or `openZip`:
|
|
373
|
+
|
|
374
|
+
```ts
|
|
375
|
+
const writer = new ZipWriter({ outputAs: "blob" });
|
|
376
|
+
|
|
377
|
+
await writer.add({ path: "report.txt", data: "download me" });
|
|
378
|
+
const blob = await writer.close();
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
Response output for service workers, route handlers, and fetch-like APIs:
|
|
382
|
+
|
|
383
|
+
```ts
|
|
384
|
+
const writer = new ZipWriter({ outputAs: "response" });
|
|
385
|
+
|
|
386
|
+
await writer.add({ path: "api.txt", data: "response body" });
|
|
387
|
+
const response = await writer.close();
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
Custom response MIME type:
|
|
391
|
+
|
|
392
|
+
```ts
|
|
393
|
+
const writer = new ZipWriter({
|
|
394
|
+
outputAs: "response",
|
|
395
|
+
mimeType: "application/x-zip-compressed"
|
|
396
|
+
});
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
Raw byte output:
|
|
400
|
+
|
|
401
|
+
```ts
|
|
402
|
+
const bytes = await new ZipWriter({ outputAs: "uint8array" }).close();
|
|
403
|
+
const buffer = await new ZipWriter({ outputAs: "arraybuffer" }).close();
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
## Synchronous In-Memory Writing
|
|
407
|
+
|
|
408
|
+
Use `writeSync()` / `closeSync()` for tests, fixtures, small generated archives,
|
|
409
|
+
or code paths where all entry data is already in memory. The synchronous API
|
|
410
|
+
accepts `string`, `Uint8Array`, and `ArrayBuffer` data. Use async `add()` for
|
|
411
|
+
`Blob` and `ReadableStream` input.
|
|
412
|
+
|
|
413
|
+
```ts
|
|
414
|
+
const writer = new ZipWriter({ outputAs: "uint8array" });
|
|
415
|
+
|
|
416
|
+
writer.writeSync({ path: "manifest.json", data: JSON.stringify({ ok: true }) });
|
|
417
|
+
writer.writeSync({ path: "data.bin", data: new Uint8Array([1, 2, 3]) });
|
|
418
|
+
|
|
419
|
+
const zipBytes = writer.closeSync();
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
Do not mix sync and async writes on the same writer. JSZipp rejects mixed usage
|
|
423
|
+
so entries are not accidentally routed to different output paths.
|
|
424
|
+
|
|
425
|
+
## Read A ZIP By File Name
|
|
426
|
+
|
|
427
|
+
Use `openZip` when the ZIP is a `Blob`, `File`, `Uint8Array`, or `ArrayBuffer`,
|
|
428
|
+
such as a file chosen from an `<input type="file">`.
|
|
429
|
+
|
|
430
|
+
```ts
|
|
431
|
+
import { openZip } from "web-jszipp";
|
|
432
|
+
|
|
433
|
+
const file = fileInput.files![0];
|
|
434
|
+
const reader = await openZip(file);
|
|
435
|
+
|
|
436
|
+
const readme = reader.get("docs/readme.md");
|
|
437
|
+
if (readme) {
|
|
438
|
+
console.log(await readme.text());
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
await reader.close();
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
By default, `openZip()` rejects unsafe entry paths that could escape an
|
|
445
|
+
extraction root, including `..`, absolute paths, drive-letter paths (including
|
|
446
|
+
drive-relative names like `C:name`), backslash-separated paths, and paths
|
|
447
|
+
containing a NUL byte. Use `pathMode: "sanitize"` to normalize unsafe names
|
|
448
|
+
instead, or `pathMode: "unsafe"` only when you need raw archive names and will
|
|
449
|
+
handle extraction safety yourself.
|
|
450
|
+
|
|
451
|
+
```ts
|
|
452
|
+
const reader = await openZip(file, { pathMode: "sanitize" });
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
### Strict Package Mode
|
|
456
|
+
|
|
457
|
+
For archives that cross a trust boundary — uploads, software packages, CI
|
|
458
|
+
artifacts, document bundles — use `pathMode: "strict-package"`. It applies all
|
|
459
|
+
the `"strict"` path checks above and adds two cross-entry checks the default
|
|
460
|
+
deliberately leaves off (so the default can preserve duplicate paths and defer
|
|
461
|
+
size integrity to read time):
|
|
462
|
+
|
|
463
|
+
- the local file header and central directory sizes must agree (for non-streaming
|
|
464
|
+
entries), and
|
|
465
|
+
- no two entries may collide after Unicode (NFC) and case normalization — this
|
|
466
|
+
rejects exact duplicates, case-only twins (`Readme.txt` vs `README.TXT`), and
|
|
467
|
+
NFC/NFD twins.
|
|
468
|
+
|
|
469
|
+
```ts
|
|
470
|
+
try {
|
|
471
|
+
// A hostile package with duplicate, case-colliding, or size-spoofing entries
|
|
472
|
+
// throws here instead of being silently accepted.
|
|
473
|
+
const reader = await openZip(untrustedUpload, { pathMode: "strict-package" });
|
|
474
|
+
for (const entry of reader.entries) {
|
|
475
|
+
// ... safe to process
|
|
476
|
+
}
|
|
477
|
+
} catch (error) {
|
|
478
|
+
// Reject the upload: it does not meet the strict package profile.
|
|
479
|
+
}
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
The default reader (`pathMode: "strict"`) is unchanged: it still preserves
|
|
483
|
+
duplicate paths and verifies size/CRC integrity at read time.
|
|
484
|
+
|
|
485
|
+
Writers reject duplicate normalized entry paths. If you need to replace an entry,
|
|
486
|
+
choose the final payload before calling `add()` or `writeSync()`.
|
|
487
|
+
|
|
488
|
+
## List Every Entry
|
|
489
|
+
|
|
490
|
+
`reader.entries` preserves the real order inside the archive. This matters for
|
|
491
|
+
ZIP files from other tools that contain duplicate paths.
|
|
492
|
+
|
|
493
|
+
```ts
|
|
494
|
+
const reader = await openZip(zipBlob);
|
|
495
|
+
|
|
496
|
+
for (const entry of reader.entries) {
|
|
497
|
+
console.log({
|
|
498
|
+
path: entry.path,
|
|
499
|
+
size: entry.size,
|
|
500
|
+
compressedSize: entry.compressedSize,
|
|
501
|
+
crc32: entry.crc32,
|
|
502
|
+
isDirectory: entry.isDirectory,
|
|
503
|
+
comment: entry.comment,
|
|
504
|
+
modifiedAt: entry.modifiedAt,
|
|
505
|
+
externalAttributes: entry.externalAttributes,
|
|
506
|
+
unixFileAttributes: entry.externalAttributes !== undefined ? entry.externalAttributes >>> 16 : undefined,
|
|
507
|
+
dosAttributeByte: entry.externalAttributes !== undefined ? entry.externalAttributes & 0xff : undefined
|
|
508
|
+
});
|
|
509
|
+
}
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
## Duplicate File Names
|
|
513
|
+
|
|
514
|
+
ZIP archives can contain the same path more than once. `entries` shows all of
|
|
515
|
+
them. `get(path)` returns the latest matching entry.
|
|
516
|
+
|
|
517
|
+
```ts
|
|
518
|
+
const reader = await openZip(zipBlob);
|
|
519
|
+
|
|
520
|
+
const allCopies = reader.entries.filter((entry) => entry.path === "data.json");
|
|
521
|
+
const latest = reader.get("data.json");
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
## Read Entry Data
|
|
525
|
+
|
|
526
|
+
Random-access entries from `openZip` are reusable. You can call `stream()` or
|
|
527
|
+
`text()` many times.
|
|
528
|
+
|
|
529
|
+
```ts
|
|
530
|
+
const entry = reader.get("data.json");
|
|
531
|
+
|
|
532
|
+
if (entry) {
|
|
533
|
+
const text = await entry.text();
|
|
534
|
+
const bytes = await entry.bytes();
|
|
535
|
+
const buffer = await entry.arrayBuffer();
|
|
536
|
+
}
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
## Read Legacy File Names
|
|
540
|
+
|
|
541
|
+
If an archive does not mark names as UTF-8, `openZip` can use a fallback
|
|
542
|
+
encoding.
|
|
543
|
+
|
|
544
|
+
```ts
|
|
545
|
+
const reader = await openZip(file, {
|
|
546
|
+
filenameEncoding: "shift_jis",
|
|
547
|
+
pathMode: "strict"
|
|
548
|
+
});
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
Supported fallback values:
|
|
552
|
+
|
|
553
|
+
- `"cp437"`
|
|
554
|
+
- any charset label supported by `TextDecoder`, such as `"utf-8"`,
|
|
555
|
+
`"shift_jis"`, or `"windows-1252"`
|
|
556
|
+
|
|
557
|
+
See [Filename Charset Handling](docs/charset.md) for
|
|
558
|
+
details on ZIP filename charset behavior and choosing a fallback.
|
|
559
|
+
|
|
560
|
+
## Stream Pipeline Writing
|
|
561
|
+
|
|
562
|
+
Use `ZipTransformStream` when another part of your app already writes
|
|
563
|
+
`ZipInputEntry` objects into a stream.
|
|
564
|
+
|
|
565
|
+
```ts
|
|
566
|
+
import { ZipTransformStream } from "web-jszipp";
|
|
567
|
+
|
|
568
|
+
const zipStream = new ZipTransformStream({ level: 6 });
|
|
569
|
+
const archivePromise = new Response(zipStream.readable).blob();
|
|
570
|
+
const writer = zipStream.writable.getWriter();
|
|
571
|
+
|
|
572
|
+
await writer.write({ path: "a.txt", data: "A" });
|
|
573
|
+
await writer.write({ path: "b.txt", data: "B" });
|
|
574
|
+
await writer.close();
|
|
575
|
+
|
|
576
|
+
const zipBlob = await archivePromise;
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
## Async Iterator Reading
|
|
580
|
+
|
|
581
|
+
Use `readZipStream` when you want a `for await...of` style reader.
|
|
582
|
+
|
|
583
|
+
```ts
|
|
584
|
+
import { readZipStream } from "web-jszipp";
|
|
585
|
+
|
|
586
|
+
for await (const entry of readZipStream(zipBlob.stream())) {
|
|
587
|
+
if (entry.isDirectory) {
|
|
588
|
+
await entry.skip();
|
|
589
|
+
continue;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
if (entry.path.endsWith(".txt")) {
|
|
593
|
+
console.log(entry.path, await entry.text());
|
|
594
|
+
} else {
|
|
595
|
+
await entry.skip();
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
`ZipStreamEntry` payloads are single-use. For each entry, call exactly one of:
|
|
601
|
+
|
|
602
|
+
- `entry.stream()`
|
|
603
|
+
- `entry.text()`
|
|
604
|
+
- `entry.bytes()`
|
|
605
|
+
- `entry.arrayBuffer()`
|
|
606
|
+
- `entry.skip()`
|
|
607
|
+
|
|
608
|
+
If you need to read the same entry more than once, use `openZip` instead.
|
|
609
|
+
|
|
610
|
+
## API Reference
|
|
611
|
+
|
|
612
|
+
### `new ZipWriter(options?)`
|
|
613
|
+
|
|
614
|
+
High-level ZIP writer.
|
|
615
|
+
|
|
616
|
+
```ts
|
|
617
|
+
const writer = new ZipWriter({
|
|
618
|
+
level: 6,
|
|
619
|
+
zip64: "auto",
|
|
620
|
+
outputAs: "blob"
|
|
621
|
+
});
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
Properties and methods:
|
|
625
|
+
|
|
626
|
+
- `writer.output: ReadableStream<Uint8Array>`
|
|
627
|
+
- `writer.add(entry: ZipInputEntry): Promise<void>`
|
|
628
|
+
- `writer.writeSync(entry: ZipSyncInputEntry): void`
|
|
629
|
+
- `writer.close(): Promise<ReadableStream<Uint8Array> | Blob | Response | Uint8Array | ArrayBuffer>`
|
|
630
|
+
- `writer.closeSync(): ReadableStream<Uint8Array> | Blob | Response | Uint8Array | ArrayBuffer`
|
|
631
|
+
|
|
632
|
+
The writer rejects duplicate normalized entry paths. It does not emit archives
|
|
633
|
+
where two records target the same path.
|
|
634
|
+
|
|
635
|
+
`close()` returns a more specific type when `outputAs` is known:
|
|
636
|
+
|
|
637
|
+
```ts
|
|
638
|
+
const stream = await new ZipWriter().close();
|
|
639
|
+
const blob = await new ZipWriter({ outputAs: "blob" }).close();
|
|
640
|
+
const response = await new ZipWriter({ outputAs: "response" }).close();
|
|
641
|
+
const bytes = await new ZipWriter({ outputAs: "uint8array" }).close();
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
Options:
|
|
645
|
+
|
|
646
|
+
```ts
|
|
647
|
+
interface ZipWriterOptions {
|
|
648
|
+
level?: number;
|
|
649
|
+
zip64?: "auto" | "force" | "off";
|
|
650
|
+
comment?: string;
|
|
651
|
+
timestamps?: number; // bitmask of TimestampMode flags (Dos=1, Unix=2, Ntfs=4)
|
|
652
|
+
pathMode?: "strict" | "sanitize" | "unsafe" | "strict-package";
|
|
653
|
+
signal?: AbortSignal;
|
|
654
|
+
onProgress?: (progress: ZipProgress) => void;
|
|
655
|
+
explicitDirectoryEntries?: boolean;
|
|
656
|
+
outputAs?: "stream" | "blob" | "response" | "uint8array" | "arraybuffer";
|
|
657
|
+
mimeType?: string;
|
|
658
|
+
}
|
|
659
|
+
```
|
|
660
|
+
|
|
661
|
+
### `new ZipTransformStream(options?)`
|
|
662
|
+
|
|
663
|
+
Native transform stream from `ZipInputEntry` objects to ZIP bytes.
|
|
664
|
+
|
|
665
|
+
```ts
|
|
666
|
+
const stream = new ZipTransformStream({ level: 0, zip64: "off" });
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
It extends:
|
|
670
|
+
|
|
671
|
+
```ts
|
|
672
|
+
TransformStream<ZipInputEntry, Uint8Array>
|
|
673
|
+
```
|
|
674
|
+
|
|
675
|
+
### `openZip(source, options?)`
|
|
676
|
+
|
|
677
|
+
Random-access reader for `Blob`, `File`, `Uint8Array`, or `ArrayBuffer`.
|
|
678
|
+
|
|
679
|
+
```ts
|
|
680
|
+
const reader = await openZip(file, {
|
|
681
|
+
filenameEncoding: "utf-8",
|
|
682
|
+
pathMode: "strict-package",
|
|
683
|
+
maxArchiveSize: 50 * 1024 * 1024,
|
|
684
|
+
maxEntrySize: 10 * 1024 * 1024
|
|
685
|
+
});
|
|
686
|
+
```
|
|
687
|
+
|
|
688
|
+
Options:
|
|
689
|
+
|
|
690
|
+
```ts
|
|
691
|
+
interface ZipReadOptions {
|
|
692
|
+
filenameEncoding?: "cp437" | StandardFilenameEncoding | {
|
|
693
|
+
encoding: string;
|
|
694
|
+
fatal: boolean;
|
|
695
|
+
ignoreBOM: boolean;
|
|
696
|
+
decode(bytes: Uint8Array): string;
|
|
697
|
+
};
|
|
698
|
+
pathMode?: "strict" | "sanitize" | "unsafe" | "strict-package";
|
|
699
|
+
maxArchiveSize?: number;
|
|
700
|
+
maxEntrySize?: number;
|
|
701
|
+
signal?: AbortSignal;
|
|
702
|
+
onProgress?: (progress: ZipProgress) => void;
|
|
703
|
+
}
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
Returns:
|
|
707
|
+
|
|
708
|
+
```ts
|
|
709
|
+
interface ZipRandomAccessReader {
|
|
710
|
+
readonly comment?: string;
|
|
711
|
+
readonly entries: readonly ZipRandomAccessEntry[];
|
|
712
|
+
get(path: string): ZipRandomAccessEntry | undefined;
|
|
713
|
+
close(): Promise<void>;
|
|
714
|
+
}
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
### `readZipStream(zipStream, options?)`
|
|
718
|
+
|
|
719
|
+
Async iterable reader.
|
|
720
|
+
|
|
721
|
+
```ts
|
|
722
|
+
for await (const entry of readZipStream(zipBlob.stream())) {
|
|
723
|
+
await entry.skip();
|
|
724
|
+
}
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
Returns:
|
|
728
|
+
|
|
729
|
+
```ts
|
|
730
|
+
AsyncIterable<ZipStreamEntry>
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
### `ZipInputEntry`
|
|
734
|
+
|
|
735
|
+
```ts
|
|
736
|
+
interface ZipInputEntry {
|
|
737
|
+
path: string;
|
|
738
|
+
data: string | Uint8Array | ArrayBuffer | Blob | ReadableStream<Uint8Array>;
|
|
739
|
+
method?: "store" | "deflate";
|
|
740
|
+
level?: number;
|
|
741
|
+
meta?: ZipEntryMeta;
|
|
742
|
+
}
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
### `ZipSyncInputEntry`
|
|
746
|
+
|
|
747
|
+
```ts
|
|
748
|
+
interface ZipSyncInputEntry extends Omit<ZipInputEntry, "data"> {
|
|
749
|
+
data: string | Uint8Array | ArrayBuffer;
|
|
750
|
+
}
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
### `ZipEntryMeta`
|
|
754
|
+
|
|
755
|
+
```ts
|
|
756
|
+
interface ZipEntryMeta {
|
|
757
|
+
comment?: string; // per-entry comment (informational)
|
|
758
|
+
extraField?: Uint8Array; // raw, well-formed ZIP extra-field bytes — ⚠ unchecked override
|
|
759
|
+
modifiedAt?: Date; // mtime; defaults to write time; must be a valid Date ≥ 1970
|
|
760
|
+
createdAt?: Date; // defaults to modifiedAt when timestamps includes TimestampMode.Ntfs
|
|
761
|
+
lastAccess?: Date; // defaults to modifiedAt when timestamps includes TimestampMode.Ntfs
|
|
762
|
+
unixPermissions?: number; // Unix permission bits 0o000–0o777; needs the Unix timestamp mode
|
|
763
|
+
dosAttributes?: number; // MS-DOS attribute byte 0x00–0xff; 0x10 must match entry kind; not allowed in Dos|Unix
|
|
764
|
+
externalAttributes?: number; // raw 32-bit external attributes — ⚠ unchecked override
|
|
765
|
+
}
|
|
766
|
+
```
|
|
767
|
+
|
|
768
|
+
`comment` is an informational per-entry comment. It does not affect extraction.
|
|
769
|
+
|
|
770
|
+
`modifiedAt` is the main entry timestamp and defaults to the current write time
|
|
771
|
+
when omitted. `createdAt` and `lastAccess` are stored only when the `timestamps`
|
|
772
|
+
mode includes `TimestampMode.Ntfs`; in that mode, omitted creation/access times
|
|
773
|
+
default to `modifiedAt`.
|
|
774
|
+
|
|
775
|
+
`unixPermissions` stores the permission portion of a Unix mode, such as `0o644`
|
|
776
|
+
for a regular file or `0o755` for a script or directory. JSZipp adds the
|
|
777
|
+
file-type bits from the entry kind. Use `unixPermissions: 0o755` when that
|
|
778
|
+
permission should survive extraction.
|
|
779
|
+
|
|
780
|
+
`dosAttributes` stores the MS-DOS attribute byte, such as read-only, hidden,
|
|
781
|
+
archive, or directory flags. Use it when you need Windows/DOS-style attributes;
|
|
782
|
+
for ordinary Unix permission restoration, prefer `unixPermissions`.
|
|
783
|
+
|
|
784
|
+
`externalAttributes` is the raw 32-bit Central Directory attribute field behind
|
|
785
|
+
Unix permissions and DOS attributes. Set it only when you need to round-trip an
|
|
786
|
+
exact value from another archive; it overrides the higher-level permission fields.
|
|
787
|
+
|
|
788
|
+
`extraField` appends raw ZIP extra-field records for callers that already know
|
|
789
|
+
the ZIP extra format. It is useful for exact metadata preservation, but most
|
|
790
|
+
callers should leave it unset.
|
|
791
|
+
|
|
792
|
+
`externalAttributes` and `extraField` are unchecked manual overrides. JSZipp
|
|
793
|
+
writes them as supplied and cannot detect every conflict with the entry kind or
|
|
794
|
+
with generated metadata, so prefer `unixPermissions`, `dosAttributes`, and the
|
|
795
|
+
`timestamps` option for normal writes.
|
|
796
|
+
|
|
797
|
+
For field validation and timestamp-mode interactions, see the
|
|
798
|
+
[API reference](pages/api.html#entry-meta). For ZIP-format background on what
|
|
799
|
+
metadata adds bytes, see [ZIP optional metadata](docs/zip-optional-metadata.md).
|
|
800
|
+
|
|
801
|
+
### `ZipRandomAccessEntry`
|
|
802
|
+
|
|
803
|
+
```ts
|
|
804
|
+
interface ZipRandomAccessEntry extends ZipEntryMeta {
|
|
805
|
+
readonly path: string;
|
|
806
|
+
readonly size: number;
|
|
807
|
+
readonly compressedSize: number;
|
|
808
|
+
readonly crc32: number;
|
|
809
|
+
readonly isDirectory: boolean;
|
|
810
|
+
stream(): ReadableStream<Uint8Array>;
|
|
811
|
+
text(): Promise<string>;
|
|
812
|
+
bytes(): Promise<Uint8Array>;
|
|
813
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
814
|
+
}
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
### `ZipStreamEntry`
|
|
818
|
+
|
|
819
|
+
```ts
|
|
820
|
+
interface ZipStreamEntry extends ZipEntryMeta {
|
|
821
|
+
readonly path: string;
|
|
822
|
+
readonly size: number | null;
|
|
823
|
+
readonly compressedSize: number | null;
|
|
824
|
+
readonly crc32: number | null;
|
|
825
|
+
readonly isDirectory: boolean;
|
|
826
|
+
stream(): ReadableStream<Uint8Array>;
|
|
827
|
+
text(): Promise<string>;
|
|
828
|
+
bytes(): Promise<Uint8Array>;
|
|
829
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
830
|
+
skip(): Promise<void>;
|
|
831
|
+
}
|
|
832
|
+
```
|
|
833
|
+
|
|
834
|
+
## Timestamp Modes and Archive Size
|
|
835
|
+
|
|
836
|
+
ZIP stores timestamps in more than one place, and JSZipp lets you choose which
|
|
837
|
+
with the `timestamps` bitmask (`TimestampMode.Dos` = 1, `Unix` = 2, `Ntfs` = 4;
|
|
838
|
+
default `Dos | Unix`; values outside `0`–`7` are rejected). The legacy MS-DOS
|
|
839
|
+
date/time pair lives in the normal ZIP headers and is **always** written.
|
|
840
|
+
|
|
841
|
+
Every ZIP entry already has two per-entry metadata locations: a local file header
|
|
842
|
+
before the file data, and a Central Directory header near the end of the archive.
|
|
843
|
+
The byte counts below are the **additional timestamp extra-field bytes** JSZipp
|
|
844
|
+
writes into those existing locations. They do not include the base local header,
|
|
845
|
+
Central Directory header, filename bytes, comments, ZIP64 records, EOCD records,
|
|
846
|
+
or compressed file data. For a broader breakdown of ZIP metadata size, see
|
|
847
|
+
[ZIP optional metadata](docs/zip-optional-metadata.md).
|
|
848
|
+
|
|
849
|
+
- **`Dos` (always on).** Two bytes of date plus two of time are already reserved
|
|
850
|
+
in every local header and Central Directory header, so it adds no extra bytes
|
|
851
|
+
beyond the normal per-entry ZIP headers. The tradeoff is fidelity: 2-second
|
|
852
|
+
granularity, no time zone (interpreted as local wall-clock), and a representable
|
|
853
|
+
range of 1980–2107. Dates before 1980 clamp upward; the writer rejects pre-1970
|
|
854
|
+
(negative) dates outright.
|
|
855
|
+
- **`Unix` (`0x5455` Extended Timestamp).** Whole-second UTC mtime. JSZipp writes
|
|
856
|
+
a 9-byte extra-field record in both the local header and Central Directory
|
|
857
|
+
header (**+18 timestamp bytes per entry**). It also lets you set
|
|
858
|
+
`unixPermissions` and makes the archive advertise the Unix host. Skipped for
|
|
859
|
+
dates outside the unsigned 32-bit Unix range (then only DOS applies).
|
|
860
|
+
- **`Ntfs` (`0x000a` NTFS extra).** 100-nanosecond UTC modification, access, and
|
|
861
|
+
creation times. JSZipp writes a 36-byte extra-field record in both headers
|
|
862
|
+
(**+72 timestamp bytes per entry**). When this flag is set, a missing
|
|
863
|
+
`createdAt` or `lastAccess` defaults to `modifiedAt`. It also lets you set
|
|
864
|
+
`dosAttributes`.
|
|
865
|
+
|
|
866
|
+
| `timestamps` | Extra timestamp bytes/entry | mtime precision | createdAt / lastAccess | `unixPermissions` | `dosAttributes` |
|
|
867
|
+
| --- | --- | --- | --- | --- | --- |
|
|
868
|
+
| `Dos` | 0 | 2 s, local | not stored | rejected | allowed |
|
|
869
|
+
| `Dos \| Unix` (default) | +18 | 1 s, UTC | not stored | allowed | rejected |
|
|
870
|
+
| `Dos \| Ntfs` | +72 | 100 ns, UTC | stored (default to mtime) | rejected | allowed |
|
|
871
|
+
| `Dos \| Unix \| Ntfs` | +90 | 100 ns, UTC | stored (default to mtime) | allowed | allowed |
|
|
872
|
+
|
|
873
|
+
`dosAttributes` is rejected for `Dos | Unix` specifically: a Unix-host archive
|
|
874
|
+
that also carried DOS attribute bits would confuse Unix-oriented tools. On read,
|
|
875
|
+
an NTFS extra carrying both creation and last-access times is authoritative;
|
|
876
|
+
otherwise JSZipp prefers the `0x5455` mtime and falls back to the DOS fields. For
|
|
877
|
+
the smallest archive use `Dos` alone; for portable UTC mtime use the default
|
|
878
|
+
`Dos | Unix`; reach for `Ntfs` only when you need sub-second or creation/access
|
|
879
|
+
times, since it is the largest of the three.
|
|
880
|
+
|
|
881
|
+
## Important Notes
|
|
882
|
+
|
|
883
|
+
- `ZipWriter` defaults to `outputAs: "stream"`. Use `outputAs: "blob"` for the
|
|
884
|
+
easiest browser download flow.
|
|
885
|
+
- `writer.output` is still available for advanced streaming integrations, but
|
|
886
|
+
most apps should use the value returned by `writer.close()`.
|
|
887
|
+
- `ZipWriter`, `ZipTransformStream`, and `readZipStream` expose Web Streams
|
|
888
|
+
shapes but currently consume each entry payload, compression result, and read
|
|
889
|
+
archive into memory before emitting the next ZIP structure.
|
|
890
|
+
- `ZipWriter`, `openZip`, and `readZipStream` accept `AbortSignal`; large
|
|
891
|
+
operations can also report coarse progress with `onProgress`.
|
|
892
|
+
- Encrypted ZIP files are not supported.
|
|
893
|
+
- Unsupported compression methods are rejected.
|
|
894
|
+
- ZIP64 records are supported with JavaScript `number` precision limits.
|
|
895
|
+
- Modification times are always written to the legacy DOS fields. The
|
|
896
|
+
`timestamps` option controls which UTC timestamp extras are added; see
|
|
897
|
+
[Timestamp Modes and Archive Size](#timestamp-modes-and-archive-size) and
|
|
898
|
+
[docs/timezone.md](./docs/timezone.md) for the detailed timezone model.
|
|
899
|
+
- `explicitDirectoryEntries` (default `false`) controls whether the writer
|
|
900
|
+
materializes a standalone entry for every parent directory implied by an
|
|
901
|
+
entry's path (`a/b/c.txt` also emits `a/` and `a/b/`). The default keeps the
|
|
902
|
+
historical behavior — only the directory entries you add yourself are written.
|
|
903
|
+
JSZipp never scans for empty directories, so an empty folder must still be added
|
|
904
|
+
explicitly regardless of this flag.
|
|
905
|
+
- All options that affect the ZIP file specification itself — `level`, `zip64`,
|
|
906
|
+
`comment`, `timestamps`, `pathMode`, and `explicitDirectoryEntries` — live on
|
|
907
|
+
`ZipEncoderOptions`, shared by `ZipWriter` and `ZipTransformStream`. Only the
|
|
908
|
+
output-shaping options (`outputAs`, `mimeType`) are `ZipWriter`-specific.
|
|
909
|
+
- `openZip` and `readZipStream` reject a negative or non-finite `maxArchiveSize`
|
|
910
|
+
or `maxEntrySize`.
|
|
911
|
+
- `readZipStream` currently exposes the forward-iteration API by collecting the
|
|
912
|
+
input stream and parsing the Central Directory first.
|
|
913
|
+
|
|
914
|
+
See [CONTRACT.md](./CONTRACT.md) for the detailed implementation contract and
|
|
915
|
+
current runtime boundaries. See [docs/timezone.md](./docs/timezone.md) for the
|
|
916
|
+
timestamp timezone model.
|
|
917
|
+
|
|
918
|
+
## Build
|
|
919
|
+
|
|
920
|
+
```sh
|
|
921
|
+
pnpm install
|
|
922
|
+
pnpm test
|
|
923
|
+
pnpm build
|
|
924
|
+
```
|
|
925
|
+
|
|
926
|
+
The npm package points at generated files under `dist/`. `prepack` runs the
|
|
927
|
+
build and test suite before `pnpm pack` / `pnpm publish`, so the published
|
|
928
|
+
tarball contains those generated artifacts even if the source repository omits
|
|
929
|
+
them.
|
|
930
|
+
|
|
931
|
+
Build output:
|
|
932
|
+
|
|
933
|
+
- `dist/jszipp.mjs`
|
|
934
|
+
- `dist/jszipp.cjs`
|
|
935
|
+
- `dist/jszipp.umd.js`
|
|
936
|
+
- `dist/jszipp.writer.umd.js`
|
|
937
|
+
- `dist/jszipp.reader.umd.js`
|
|
938
|
+
- `dist/cr61ff58/jszipp.mjs`
|
|
939
|
+
- `dist/cr61ff58/jszipp.cjs`
|
|
940
|
+
- `dist/cr61ff58/jszipp.umd.js`
|
|
941
|
+
- `dist/cr61ff58/jszipp.reader.umd.js`
|
|
942
|
+
- `dist/cr61ff58/jszipp.writer.umd.js`
|
|
943
|
+
- `dist/cr86ff68/jszipp.mjs`
|
|
944
|
+
- `dist/cr86ff68/jszipp.cjs`
|
|
945
|
+
- `dist/cr86ff68/jszipp.umd.js`
|
|
946
|
+
- `dist/cr86ff68/jszipp.reader.umd.js`
|
|
947
|
+
- `dist/cr86ff68/jszipp.writer.umd.js`
|
|
948
|
+
- `dist/index.d.ts`
|
|
949
|
+
- `dist/types.d.ts`
|
|
950
|
+
- `dist/writer.d.ts`
|
|
951
|
+
- `dist/reader.d.ts`
|
|
952
|
+
|
|
953
|
+
## License
|
|
954
|
+
|
|
955
|
+
MIT
|