zerobucket 0.4.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +87 -20
- package/dist/bin.js +2506 -0
- package/dist/{chunk-W2C37XYV.js → chunk-SCPC3QDR.js} +1 -1
- package/dist/{client-WZ4VRCPi.d.cts → client-C7xs1dlr.d.cts} +49 -1
- package/dist/{client-WZ4VRCPi.d.ts → client-C7xs1dlr.d.ts} +49 -1
- package/dist/http.d.cts +1 -1
- package/dist/http.d.ts +1 -1
- package/dist/http.js +1 -1
- package/dist/index.cjs +456 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -2
- package/dist/index.d.ts +6 -2
- package/dist/index.js +450 -13
- package/dist/index.js.map +1 -1
- package/package.json +16 -4
- /package/dist/{chunk-W2C37XYV.js.map → chunk-SCPC3QDR.js.map} +0 -0
package/README.md
CHANGED
|
@@ -10,14 +10,16 @@ This is the Node.js/TypeScript sibling of the [Python package](https://pypi.org/
|
|
|
10
10
|
**It uses the exact same table**, so a Node service and a Python service can share one database
|
|
11
11
|
and read each other's images.
|
|
12
12
|
|
|
13
|
-
> **Status: early (0.
|
|
14
|
-
> object-storage tiering
|
|
13
|
+
> **Status: early (0.6.x).** PostgreSQL **and SQLite**, each in classic and dedup modes, with S3-compatible
|
|
14
|
+
> object-storage tiering, HEIC (iPhone photo) support and a `zerobucket` command line tool. MySQL is on the
|
|
15
|
+
> roadmap (see [What's not here yet](#whats-not-here-yet)).
|
|
15
16
|
|
|
16
17
|
## Install
|
|
17
18
|
|
|
18
19
|
```bash
|
|
19
20
|
npm install zerobucket pg sharp # PostgreSQL
|
|
20
21
|
npm install zerobucket better-sqlite3 sharp # or SQLite (see below): no database server at all
|
|
22
|
+
npm install libheif-js # optional: accept HEIC (iPhone) photos, see below
|
|
21
23
|
```
|
|
22
24
|
|
|
23
25
|
`pg` (the Postgres driver), `better-sqlite3` (SQLite) and `sharp` (image decoding/optimizing) are peer dependencies, so you
|
|
@@ -176,6 +178,69 @@ await images.put(pdfBuf, { validator: new PDFValidator() }); // any ContentValid
|
|
|
176
178
|
Optimize strips metadata, applies EXIF orientation first, never upscales, and re-validates its own
|
|
177
179
|
output. Implement `ContentValidator` (`validate(data, { maxBytes })`) to store other content types.
|
|
178
180
|
|
|
181
|
+
## HEIC (iPhone photos)
|
|
182
|
+
|
|
183
|
+
iPhones upload HEIC by default, and most validators reject it. With the optional `libheif-js` installed, HEIC/HEIF
|
|
184
|
+
is accepted like any other format: validated by really decoding it, stored **as uploaded** (`image/heic`), and
|
|
185
|
+
convertible with `optimize`.
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
npm install libheif-js
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
const id = await images.put(iphonePhoto); // stored unchanged, mimeType "image/heic"
|
|
193
|
+
const id2 = await images.put(iphonePhoto, {
|
|
194
|
+
optimize: { format: "webp", maxWidth: 1600 },
|
|
195
|
+
}); // convert on the way in
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
- **Convert for the web.** HEIC is only displayable in Safari. Serve a web format by uploading with
|
|
199
|
+
`optimize: { format: "webp" | "jpeg" | "png" }`, as above. HEIC orientation is applied during conversion.
|
|
200
|
+
- **Decode only, by design.** There is no HEVC _encoder_ for Node, so `optimize` can convert **from** HEIC but not **to** it:
|
|
201
|
+
`optimize: { format: "heic" }` throws a clear error (Python's `pillow-heif` can encode), and `optimize: true` on a HEIC
|
|
202
|
+
needs an explicit target format instead of silently choosing one.
|
|
203
|
+
- **Never blocks your server.** Decoding is ~2 s of CPU for a 12-megapixel photo. It runs in a dedicated worker thread, so
|
|
204
|
+
other requests keep being served (tested), and the worker never keeps your process from exiting.
|
|
205
|
+
- **Safe on hostile input.** Pixel bombs are refused from the file header _before_ any decoding (`maxPixels`), truncated or
|
|
206
|
+
corrupt files are rejected with the decoder's own diagnosis, and a missing `libheif-js` is reported as "install it", never as "corrupt image".
|
|
207
|
+
- Recognised by content (`ftyp` brands `heic heix hevc heim heis mif1 msf1`), never by file extension: same rules as the Python package, and both
|
|
208
|
+
accept and reject the same files (checked by the conformance harness against `pillow-heif`).
|
|
209
|
+
- **License:** `libheif-js` is LGPL-3.0. It is an optional peer dependency that **you** install; it is not bundled with or redistributed by
|
|
210
|
+
this MIT-licensed package. If you use it, review its license for your distribution model.
|
|
211
|
+
- If a file contains several images, the first top-level image is used.
|
|
212
|
+
|
|
213
|
+
## Command line
|
|
214
|
+
|
|
215
|
+
The package installs a `zerobucket` command (`npx zerobucket ...`, or install globally with `npm i -g zerobucket`).
|
|
216
|
+
It has the same commands, flags, messages and exit codes as the Python package's CLI, and additionally works with
|
|
217
|
+
**SQLite** and **dedup mode**, and understands images that were tiered to object storage.
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
export ZEROBUCKET_DATABASE_URL=postgresql://user:pass@localhost/mydb # or: --database-url URL
|
|
221
|
+
# SQLite instead: export ZEROBUCKET_SQLITE_PATH=./images.db # or: --sqlite PATH
|
|
222
|
+
|
|
223
|
+
zerobucket init # create the schema (add --dedup for the dedup tables)
|
|
224
|
+
zerobucket info # image count, sizes, on-disk size, breakdown by format (+ dedup savings / tiered count)
|
|
225
|
+
zerobucket verify # re-checksum every image; exit 1 and list the ids if anything is corrupted
|
|
226
|
+
zerobucket verify --sample 200 # ...or just a random sample (good for a nightly cron job)
|
|
227
|
+
zerobucket tier IMAGE_ID --bucket my-images # move one image to S3
|
|
228
|
+
zerobucket tier --older-than 90 --min-size 500000 --bucket my-images --dry-run # preview a bulk move
|
|
229
|
+
zerobucket tier --all --limit 1000 --bucket my-images --endpoint-url https://<acct>.r2.cloudflarestorage.com
|
|
230
|
+
zerobucket migrate --to-dedup # PostgreSQL: copy classic images into the dedup tables (non-destructive, re-runnable)
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
- **Exit codes:** `0` success, `1` problems found or an operation failed, `2` usage or connection error. That makes
|
|
234
|
+
`zerobucket verify && deploy` and cron/CI checks work as you would hope.
|
|
235
|
+
- `info` and `verify` never create anything: looking at a database that does not exist (or has no schema yet) reports it
|
|
236
|
+
and exits 1 instead of silently creating an empty one.
|
|
237
|
+
- `verify` checksums one image at a time (memory stays flat on big tables). Images tiered to object storage are
|
|
238
|
+
skipped with a note unless you also pass `--bucket` (and credentials), in which case they are downloaded and verified too.
|
|
239
|
+
- `tier` processes images one at a time and reports each failure; a failed upload leaves that image untouched in the database.
|
|
240
|
+
Credentials come from the AWS SDK's standard chain, or `--aws-access-key-id` / `--aws-secret-access-key`.
|
|
241
|
+
- `tier` is not available with `--dedup`. `tier`, and `verify --bucket`, need `@aws-sdk/client-s3`; every other command works without it.
|
|
242
|
+
- The same operations are available from code: `new ZeroBucket({...}).tierToObjectStorage(id)`, `migrateClassicToDedup()`, etc.
|
|
243
|
+
|
|
179
244
|
## Reliability and observability
|
|
180
245
|
|
|
181
246
|
- Pooling: `poolMinSize` (1), `poolMaxSize` (5), `poolTimeoutMs` (10000), or pass your own `pool`
|
|
@@ -301,27 +366,29 @@ same corpus of valid and corrupt files identically.
|
|
|
301
366
|
|
|
302
367
|
## Differences from the Python package
|
|
303
368
|
|
|
304
|
-
| | Python | npm
|
|
305
|
-
| ---------------------------------------------- | -------------------------------------------------------------------------- |
|
|
306
|
-
| API | sync + async clients | one async client
|
|
307
|
-
| Malformed id (`"abc"`) | `StorageError` from Postgres | treated as not found (`ImageNotFoundError`, no DB round trip), friendlier for `/images/:id` routes
|
|
308
|
-
| `optimize` | Pillow; EXIF stripped, not applied | sharp; EXIF **applied then stripped**; transparency → **white** for JPEG
|
|
309
|
-
| `putMany` inserts | pipelined | one transaction, statements run sequentially
|
|
310
|
-
| Formats | JPEG, PNG, WebP, HEIC (extra)
|
|
311
|
-
|
|
|
312
|
-
| `
|
|
313
|
-
|
|
|
314
|
-
|
|
|
315
|
-
| SQLite `
|
|
316
|
-
| SQLite
|
|
317
|
-
|
|
|
318
|
-
|
|
|
319
|
-
|
|
|
320
|
-
|
|
|
369
|
+
| | Python | npm |
|
|
370
|
+
| ---------------------------------------------- | -------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
371
|
+
| API | sync + async clients | one async client |
|
|
372
|
+
| Malformed id (`"abc"`) | `StorageError` from Postgres | treated as not found (`ImageNotFoundError`, no DB round trip), friendlier for `/images/:id` routes |
|
|
373
|
+
| `optimize` | Pillow; EXIF stripped, not applied | sharp; EXIF **applied then stripped**; transparency → **white** for JPEG |
|
|
374
|
+
| `putMany` inserts | pipelined | one transaction, statements run sequentially |
|
|
375
|
+
| Formats | JPEG, PNG, WebP, HEIC (`[heic]` extra) | JPEG, PNG, WebP, HEIC (optional `libheif-js`) |
|
|
376
|
+
| Convert TO HEIC with `optimize` | yes (pillow-heif) | no (no HEVC encoder for Node); converting FROM HEIC works; `optimize: true` on a HEIC needs an explicit `format` |
|
|
377
|
+
| Event duration | `duration_seconds` | `durationMs` |
|
|
378
|
+
| `migrateClassicToDedup` | loads the whole table into memory; fails if re-run or if any row is tiered | batched, safe to re-run, refuses tiered rows with a clear message |
|
|
379
|
+
| Dedup `putMany` / `deleteMany` locking | arbitrary order (overlapping batches can deadlock) | sorted checksum order (cannot deadlock) |
|
|
380
|
+
| SQLite API | synchronous `SQLiteBackend` + separate async backend | one async client; the sync driver runs on the event loop, lock waits are async (never block it) |
|
|
381
|
+
| SQLite `onOperation` / busy retry | none | `onOperation` events and async `SQLITE_BUSY` retry (`busyTimeoutMs`) |
|
|
382
|
+
| SQLite stream deleted mid-read | sync adapter keeps reading a WAL snapshot (async adapter raises) | raises `StorageError`, like Postgres; use `connection` in a transaction for a snapshot |
|
|
383
|
+
| CLI | PostgreSQL, classic mode only; `verify` crashes on tiered rows | PostgreSQL **and** SQLite, classic **and** dedup; `info`/`verify` understand tiered rows; `migrate --to-dedup` |
|
|
384
|
+
| `connection=` without an open transaction | multi-statement ops rely on the driver's implicit transaction | always atomic (own transaction or savepoint) |
|
|
385
|
+
| `delete(id, connection=...)` on a tiered image | deletes the S3 object immediately | leaves the object until you delete it after commit (a rolled-back transaction can never lose data) |
|
|
386
|
+
| S3 delete failure after `delete()` | raises after the row is gone | never fails the delete; reported via `onOperation` |
|
|
387
|
+
| Options | snake_case kwargs | camelCase options object |
|
|
321
388
|
|
|
322
389
|
## What's not here yet
|
|
323
390
|
|
|
324
|
-
Planned
|
|
391
|
+
Planned: **MySQL/MariaDB**.
|
|
325
392
|
Tiering and dedup cannot be combined (same rule as the Python package).
|
|
326
393
|
|
|
327
394
|
## Runtime notes
|