zerobucket 0.8.0 → 0.8.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 +52 -25
- package/dist/bin.js +26 -12
- package/dist/{client-XQ22b1l3.d.cts → client-l4jPIFJI.d.cts} +21 -8
- package/dist/{client-XQ22b1l3.d.ts → client-l4jPIFJI.d.ts} +21 -8
- package/dist/http.d.cts +1 -1
- package/dist/http.d.ts +1 -1
- package/dist/index.cjs +16 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -13
- package/dist/index.d.ts +8 -13
- package/dist/index.js +16 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -236,6 +236,7 @@ zerobucket tier IMAGE_ID --bucket my-images # move one i
|
|
|
236
236
|
zerobucket tier --older-than 90 --min-size 500000 --bucket my-images --dry-run # preview a bulk move
|
|
237
237
|
zerobucket tier --all --limit 1000 --bucket my-images --endpoint-url https://<acct>.r2.cloudflarestorage.com
|
|
238
238
|
zerobucket migrate --to-dedup # PostgreSQL: copy classic images into the dedup tables (non-destructive, re-runnable)
|
|
239
|
+
zerobucket migrate --to-dedup --skip-tiered # ...skipping images tiered to object storage instead of refusing
|
|
239
240
|
```
|
|
240
241
|
|
|
241
242
|
- **Exit codes:** `0` success, `1` problems found or an operation failed, `2` usage or connection error. That makes
|
|
@@ -390,7 +391,11 @@ await images.delete(b); // last reference gone: blob removed
|
|
|
390
391
|
|
|
391
392
|
**Moving existing data in:** `await images.migrateClassicToDedup()` copies every classic image into the dedup tables,
|
|
392
393
|
keeping each id, and never modifies the classic table. It runs in small batches (bounded memory), is safe to re-run
|
|
393
|
-
(already-migrated ids are skipped and counted), and refuses up front if any classic image is tiered to object storage
|
|
394
|
+
(already-migrated ids are skipped and counted), and refuses up front if any classic image is tiered to object storage
|
|
395
|
+
(their bytes are not in the database to copy, and dedup mode has no tiering), changing nothing. If you would rather
|
|
396
|
+
migrate everything else, pass `{ skipTiered: true }` (CLI: `--skip-tiered`): tiered images are left untouched in the
|
|
397
|
+
classic table, counted in `tieredSkipped`, and **will not exist in the dedup tables**, so only use it if you can live
|
|
398
|
+
without them there.
|
|
394
399
|
|
|
395
400
|
## Object-storage tiering
|
|
396
401
|
|
|
@@ -440,30 +445,52 @@ same corpus of valid and corrupt files identically.
|
|
|
440
445
|
|
|
441
446
|
## Differences from the Python package
|
|
442
447
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
|
447
|
-
|
|
|
448
|
-
|
|
|
449
|
-
|
|
|
450
|
-
|
|
|
451
|
-
|
|
|
452
|
-
|
|
|
453
|
-
|
|
|
454
|
-
|
|
|
455
|
-
|
|
|
456
|
-
|
|
|
457
|
-
|
|
|
458
|
-
| `
|
|
459
|
-
|
|
|
460
|
-
|
|
|
461
|
-
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
448
|
+
Python column = zerobucket on PyPI **0.25.1 or later** (several of its behaviours below were fixed in 0.25.1; older versions
|
|
449
|
+
differ).
|
|
450
|
+
|
|
451
|
+
| | Python | npm |
|
|
452
|
+
| -------------------------------------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
453
|
+
| API | sync + async clients | one async client |
|
|
454
|
+
| Malformed id (`"abc"`) | `StorageError` from Postgres | treated as not found (`ImageNotFoundError`, no DB round trip), friendlier for `/images/:id` routes |
|
|
455
|
+
| `optimize` | Pillow; EXIF stripped, not applied | sharp; EXIF **applied then stripped**; transparency → **white** for JPEG |
|
|
456
|
+
| `putMany` inserts | pipelined | one transaction, statements run sequentially |
|
|
457
|
+
| Formats | JPEG, PNG, WebP, HEIC (`[heic]` extra) | JPEG, PNG, WebP, HEIC (optional `libheif-js`) |
|
|
458
|
+
| 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` |
|
|
459
|
+
| Event duration | `duration_seconds` | `durationMs` |
|
|
460
|
+
| `migrateClassicToDedup`, tiered classic rows | always **skips** them and reports `tiered_rows_skipped` | **refuses** by default (changes nothing); `skipTiered: true` / `--skip-tiered` skips and reports like Python |
|
|
461
|
+
| `migrateClassicToDedup`, other behaviour | batched, safe to re-run (server-side cursor) | batched, safe to re-run (keyset paging, advisory lock so concurrent runs cannot race) |
|
|
462
|
+
| 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) |
|
|
463
|
+
| SQLite `onOperation` / busy retry | none | `onOperation` events and async `SQLITE_BUSY` retry (`busyTimeoutMs`) |
|
|
464
|
+
| 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 |
|
|
465
|
+
| MySQL `onOperation` / retry | sync `MySQLBackend` yes; `AsyncMySQLBackend` none | yes (same client for everything) |
|
|
466
|
+
| CLI | PostgreSQL, classic mode only; `verify` skips tiered rows with a note | PostgreSQL, SQLite **and** MySQL/MariaDB, classic **and** dedup; `verify --bucket` also checks tiered images; `migrate --to-dedup` |
|
|
467
|
+
| `connection=` without an open transaction | multi-statement ops rely on the driver's implicit transaction | always atomic (own transaction or savepoint) |
|
|
468
|
+
| S3 delete failure after `delete()` | raises after the row is gone | never fails the delete; reported via `onOperation` |
|
|
469
|
+
| Options | snake_case kwargs | camelCase options object |
|
|
470
|
+
|
|
471
|
+
Both packages now leave a tiered image's object in place on `delete(id, connection=...)` (a rolled-back transaction can
|
|
472
|
+
never lose data), take dedup batch locks in sorted checksum order (overlapping batches cannot deadlock), and skip the
|
|
473
|
+
MySQL `ALTER TABLE` at startup when the schema is current.
|
|
474
|
+
|
|
475
|
+
## Limitations (read before using in production)
|
|
476
|
+
|
|
477
|
+
- **Images live in your database.** That is the point (one backup, one transaction, no extra service), but it means
|
|
478
|
+
this is not built for very large files or high-volume media: every image goes through the database and counts toward
|
|
479
|
+
its size, its backups and its replication. The default size limit is 8 MiB (`maxBytes`). For large video or
|
|
480
|
+
millions of big files, use object storage directly.
|
|
481
|
+
- **Backups and space need attention.** A whole-database dump now includes every image, and deleting images does not
|
|
482
|
+
give disk space back on its own (PostgreSQL needs autovacuum, MySQL `OPTIMIZE TABLE`, SQLite `VACUUM`). SQLite
|
|
483
|
+
files must be backed up with `.backup`, not copied. See the
|
|
484
|
+
[Operations Guide](https://github.com/KedarGhadyalji/ZeroBucket/blob/main/docs/OPERATIONS.md).
|
|
485
|
+
- **Dedup and tiering cannot be combined** (a shared blob has many owners): `dedup: true` with `objectStorage` throws.
|
|
486
|
+
- **`migrate --to-dedup` is PostgreSQL-only**, as in the Python package. Tiered classic images cannot be migrated (see
|
|
487
|
+
Deduplication for `skipTiered`).
|
|
488
|
+
- **HEIC can be read and converted from, but not written.** There is no HEVC encoder for Node, so `optimize` cannot
|
|
489
|
+
produce HEIC.
|
|
490
|
+
- **SQLite allows one writer at a time**; lock waits are async and never block the event loop, but write-heavy
|
|
491
|
+
workloads belong on PostgreSQL or MySQL/MariaDB.
|
|
492
|
+
- **Tested versions:** CI runs PostgreSQL 16 and MariaDB 10.11; MySQL 8.0.46 was also run by hand. Other versions are
|
|
493
|
+
expected to behave the same but have not been run.
|
|
467
494
|
|
|
468
495
|
## Runtime notes
|
|
469
496
|
|
package/dist/bin.js
CHANGED
|
@@ -875,13 +875,13 @@ var PostgresBackend = class {
|
|
|
875
875
|
* dedup instance's tables, preserving every id (external references keep working) and
|
|
876
876
|
* deduplicating identical content on the way. The classic table is never modified.
|
|
877
877
|
*
|
|
878
|
-
*
|
|
879
|
-
*
|
|
880
|
-
*
|
|
881
|
-
*
|
|
878
|
+
* Rows are copied in small batches (bounded memory instead of loading the whole table);
|
|
879
|
+
* re-running is safe (already-migrated ids are skipped and reported, not an error). Classic rows
|
|
880
|
+
* already tiered to object storage are refused up front with a clear message (their bytes are
|
|
881
|
+
* not in the database to copy) unless `skipTiered: true`, which skips and counts them instead.
|
|
882
882
|
* Still all-or-nothing: one transaction.
|
|
883
883
|
*/
|
|
884
|
-
async migrateClassicToDedup() {
|
|
884
|
+
async migrateClassicToDedup(opts = {}) {
|
|
885
885
|
if (!this.dedup) {
|
|
886
886
|
throw new StorageError(
|
|
887
887
|
"migrateClassicToDedup() must be called on an instance created with dedup: true."
|
|
@@ -900,6 +900,7 @@ var PostgresBackend = class {
|
|
|
900
900
|
"No classic zerobucket_images table found in this database -- nothing to migrate."
|
|
901
901
|
);
|
|
902
902
|
}
|
|
903
|
+
let tieredSkipped = 0;
|
|
903
904
|
const hasTierCol = (await client.query(
|
|
904
905
|
"SELECT 1 FROM information_schema.columns WHERE table_name = 'zerobucket_images' AND column_name = 'storage_backend'"
|
|
905
906
|
)).rowCount;
|
|
@@ -907,12 +908,14 @@ var PostgresBackend = class {
|
|
|
907
908
|
const tiered = (await client.query(
|
|
908
909
|
"SELECT count(*)::int AS n FROM zerobucket_images WHERE storage_backend <> 'postgres'"
|
|
909
910
|
)).rows[0].n;
|
|
910
|
-
if (tiered > 0) {
|
|
911
|
+
if (tiered > 0 && !opts.skipTiered) {
|
|
911
912
|
throw new StorageError(
|
|
912
|
-
`${tiered} classic image(s) are tiered to object storage, so their bytes are not in the database to migrate. Dedup mode does not support tiering. Migrate only an un-tiered table,
|
|
913
|
+
`${tiered} classic image(s) are tiered to object storage, so their bytes are not in the database to migrate. Dedup mode does not support tiering. Migrate only an un-tiered table, bring those images back first, or pass skipTiered: true (CLI: --skip-tiered) to skip them and migrate the rest.`
|
|
913
914
|
);
|
|
914
915
|
}
|
|
916
|
+
tieredSkipped = tiered;
|
|
915
917
|
}
|
|
918
|
+
const onlyInDb = hasTierCol && tieredSkipped > 0;
|
|
916
919
|
const seen = /* @__PURE__ */ new Set();
|
|
917
920
|
let migrated = 0;
|
|
918
921
|
let already = 0;
|
|
@@ -921,11 +924,11 @@ var PostgresBackend = class {
|
|
|
921
924
|
for (; ; ) {
|
|
922
925
|
const page = cursor ? await client.query(
|
|
923
926
|
`SELECT id, data, mime_type, original_filename, size_bytes, width, height, checksum_sha256, created_at::text AS created_at_txt
|
|
924
|
-
FROM zerobucket_images WHERE (created_at, id) > ($1::timestamptz, $2::uuid) ORDER BY created_at, id LIMIT ${BATCH}`,
|
|
927
|
+
FROM zerobucket_images WHERE (created_at, id) > ($1::timestamptz, $2::uuid)${onlyInDb ? " AND storage_backend = 'postgres'" : ""} ORDER BY created_at, id LIMIT ${BATCH}`,
|
|
925
928
|
[cursor.createdAt, cursor.id]
|
|
926
929
|
) : await client.query(
|
|
927
930
|
`SELECT id, data, mime_type, original_filename, size_bytes, width, height, checksum_sha256, created_at::text AS created_at_txt
|
|
928
|
-
FROM zerobucket_images ORDER BY created_at, id LIMIT ${BATCH}`
|
|
931
|
+
FROM zerobucket_images${onlyInDb ? " WHERE storage_backend = 'postgres'" : ""} ORDER BY created_at, id LIMIT ${BATCH}`
|
|
929
932
|
);
|
|
930
933
|
if (page.rows.length === 0) break;
|
|
931
934
|
const last = page.rows[page.rows.length - 1];
|
|
@@ -975,7 +978,8 @@ var PostgresBackend = class {
|
|
|
975
978
|
imagesMigrated: migrated,
|
|
976
979
|
distinctBlobsCreated: seen.size,
|
|
977
980
|
duplicateReferencesFound: migrated - seen.size,
|
|
978
|
-
alreadyMigrated: already
|
|
981
|
+
alreadyMigrated: already,
|
|
982
|
+
tieredSkipped
|
|
979
983
|
};
|
|
980
984
|
} catch (exc) {
|
|
981
985
|
try {
|
|
@@ -2995,6 +2999,7 @@ Commands:
|
|
|
2995
2999
|
init Create the schema (tables and indexes) if missing.
|
|
2996
3000
|
migrate Apply schema migrations (currently: same as init).
|
|
2997
3001
|
--to-dedup copy every classic image into the dedup tables (PostgreSQL only; non-destructive)
|
|
3002
|
+
--skip-tiered with --to-dedup: skip images tiered to object storage instead of refusing
|
|
2998
3003
|
info Show storage stats: count, size, breakdown by format.
|
|
2999
3004
|
verify Re-checksum stored images to detect corruption.
|
|
3000
3005
|
--sample N check a random sample of N images instead of every image
|
|
@@ -3024,6 +3029,7 @@ var OPTIONS = {
|
|
|
3024
3029
|
help: { type: "boolean", short: "h" },
|
|
3025
3030
|
version: { type: "boolean", short: "v" },
|
|
3026
3031
|
"to-dedup": { type: "boolean" },
|
|
3032
|
+
"skip-tiered": { type: "boolean" },
|
|
3027
3033
|
sample: { type: "string" },
|
|
3028
3034
|
bucket: { type: "string" },
|
|
3029
3035
|
"endpoint-url": { type: "string" },
|
|
@@ -3038,7 +3044,7 @@ var OPTIONS = {
|
|
|
3038
3044
|
};
|
|
3039
3045
|
var COMMAND_OPTIONS = {
|
|
3040
3046
|
init: [],
|
|
3041
|
-
migrate: ["to-dedup"],
|
|
3047
|
+
migrate: ["to-dedup", "skip-tiered"],
|
|
3042
3048
|
info: [],
|
|
3043
3049
|
verify: [
|
|
3044
3050
|
"sample",
|
|
@@ -3155,6 +3161,8 @@ async function cmdInit(v, env, io) {
|
|
|
3155
3161
|
return 0;
|
|
3156
3162
|
}
|
|
3157
3163
|
async function cmdMigrate(v, env, io) {
|
|
3164
|
+
if (v["skip-tiered"] && !v["to-dedup"])
|
|
3165
|
+
throw usageError("Error: --skip-tiered only applies with --to-dedup.");
|
|
3158
3166
|
if (v["to-dedup"]) {
|
|
3159
3167
|
const target = resolveTarget(v, env);
|
|
3160
3168
|
if (target.kind !== "postgres")
|
|
@@ -3162,7 +3170,9 @@ async function cmdMigrate(v, env, io) {
|
|
|
3162
3170
|
const backend = openBackend(target, { dedup: true, autoMigrate: true });
|
|
3163
3171
|
await connect(backend, "connect");
|
|
3164
3172
|
try {
|
|
3165
|
-
const r = await backend.migrateClassicToDedup(
|
|
3173
|
+
const r = await backend.migrateClassicToDedup({
|
|
3174
|
+
skipTiered: Boolean(v["skip-tiered"])
|
|
3175
|
+
});
|
|
3166
3176
|
io.out(
|
|
3167
3177
|
`Migrated ${num(r.imagesMigrated)} image(s) into ${num(r.distinctBlobsCreated)} distinct blob(s).`
|
|
3168
3178
|
);
|
|
@@ -3170,6 +3180,10 @@ async function cmdMigrate(v, env, io) {
|
|
|
3170
3180
|
` Duplicate references found: ${num(r.duplicateReferencesFound)}`
|
|
3171
3181
|
);
|
|
3172
3182
|
io.out(` Already migrated (skipped): ${num(r.alreadyMigrated)}`);
|
|
3183
|
+
if (r.tieredSkipped > 0)
|
|
3184
|
+
io.out(
|
|
3185
|
+
` Tiered to object storage (skipped, NOT in the dedup tables): ${num(r.tieredSkipped)}`
|
|
3186
|
+
);
|
|
3173
3187
|
io.out("The classic table was not modified.");
|
|
3174
3188
|
return 0;
|
|
3175
3189
|
} catch (exc) {
|
|
@@ -191,6 +191,24 @@ interface StorageInfo {
|
|
|
191
191
|
storedBytes: number;
|
|
192
192
|
};
|
|
193
193
|
}
|
|
194
|
+
interface MigrateToDedupOptions {
|
|
195
|
+
/**
|
|
196
|
+
* Classic rows already tiered to object storage cannot be migrated (their bytes are not in the
|
|
197
|
+
* database, and dedup mode has no tiering). By default the migration REFUSES, changing nothing.
|
|
198
|
+
* With `skipTiered: true` it skips them instead, leaves them untouched in the classic table, and
|
|
199
|
+
* reports the count in `tieredSkipped`. Skipped images will not exist in the dedup tables.
|
|
200
|
+
*/
|
|
201
|
+
skipTiered?: boolean;
|
|
202
|
+
}
|
|
203
|
+
interface MigrateToDedupResult {
|
|
204
|
+
imagesMigrated: number;
|
|
205
|
+
distinctBlobsCreated: number;
|
|
206
|
+
duplicateReferencesFound: number;
|
|
207
|
+
/** Ids already present in the dedup tables (a re-run): skipped, never double-counted. */
|
|
208
|
+
alreadyMigrated: number;
|
|
209
|
+
/** Tiered classic rows skipped because `skipTiered` was set; always 0 otherwise. */
|
|
210
|
+
tieredSkipped: number;
|
|
211
|
+
}
|
|
194
212
|
interface StorageBackend {
|
|
195
213
|
/** Resolve once the backend is connected and migrated. Safe to call repeatedly. */
|
|
196
214
|
ready(): Promise<void>;
|
|
@@ -213,12 +231,7 @@ interface StorageBackend {
|
|
|
213
231
|
/** null = no such image, false = already tiered (no-op), true = tiered now. Needs object storage configured. */
|
|
214
232
|
tierToObjectStorage(id: string, opts?: CallOptions): Promise<boolean | null>;
|
|
215
233
|
/** Dedup-mode instances only: copy the classic table into the dedup tables (non-destructive). */
|
|
216
|
-
migrateClassicToDedup?(): Promise<
|
|
217
|
-
imagesMigrated: number;
|
|
218
|
-
distinctBlobsCreated: number;
|
|
219
|
-
duplicateReferencesFound: number;
|
|
220
|
-
alreadyMigrated: number;
|
|
221
|
-
}>;
|
|
234
|
+
migrateClassicToDedup?(opts?: MigrateToDedupOptions): Promise<MigrateToDedupResult>;
|
|
222
235
|
/** Maintenance: storage statistics, or null if the tables do not exist yet. */
|
|
223
236
|
getInfo?(): Promise<StorageInfo | null>;
|
|
224
237
|
/** Maintenance: list ids matching the filters (oldest first, or random when sampling). */
|
|
@@ -487,7 +500,7 @@ declare class ZeroBucket<C = unknown> {
|
|
|
487
500
|
* One-time, non-destructive copy of classic-mode images into a dedup instance's tables, keeping every
|
|
488
501
|
* id. Call it on an instance created with `dedup: true`. Safe to re-run. See PostgresBackend for details.
|
|
489
502
|
*/
|
|
490
|
-
migrateClassicToDedup():
|
|
503
|
+
migrateClassicToDedup(opts?: MigrateToDedupOptions): Promise<MigrateToDedupResult>;
|
|
491
504
|
/** Release the pool (if ZeroBucket created it). */
|
|
492
505
|
close(): Promise<void>;
|
|
493
506
|
}
|
|
@@ -502,4 +515,4 @@ declare function readInput(image: ImageInput, filename: string | undefined, maxB
|
|
|
502
515
|
filename: string | undefined;
|
|
503
516
|
}>;
|
|
504
517
|
|
|
505
|
-
export {
|
|
518
|
+
export { type StreamOptions as A, type BatchDeleteResult as B, type CallOptions as C, DEFAULT_MAX_BYTES as D, type ZeroBucketOptions as E, optimizeImage as F, type GetOptions as G, readInput as H, type ImageListOptions as I, type MigrateToDedupOptions as M, type OperationEvent as O, type PreparedRow as P, type Queryable as Q, type StorageBackend as S, type ValidatedContent as V, type WritableLike as W, ZeroBucket as Z, type ObjectStorageLike as a, type StoredRecord as b, type StoredRecordMetadata as c, type StreamOptions$1 as d, type StorageInfo as e, type ImageListEntry as f, type MigrateToDedupResult as g, type MysqlPoolLike as h, type ContentValidator as i, type BatchGetResult as j, type BatchPutResult as k, type BeforeGetHook as l, type BeforePutHook as m, type Connection as n, type Image as o, type ImageInput as p, type ImageMetadata as q, type MysqlPoolConnectionLike as r, type MysqlQueryable as s, ObjectStorage as t, type ObjectStorageOptions as u, type OptimizationResult as v, type OptimizeOptions as w, type PutManyOptions as x, type PutOptions as y, type SqliteDatabaseLike as z };
|
|
@@ -191,6 +191,24 @@ interface StorageInfo {
|
|
|
191
191
|
storedBytes: number;
|
|
192
192
|
};
|
|
193
193
|
}
|
|
194
|
+
interface MigrateToDedupOptions {
|
|
195
|
+
/**
|
|
196
|
+
* Classic rows already tiered to object storage cannot be migrated (their bytes are not in the
|
|
197
|
+
* database, and dedup mode has no tiering). By default the migration REFUSES, changing nothing.
|
|
198
|
+
* With `skipTiered: true` it skips them instead, leaves them untouched in the classic table, and
|
|
199
|
+
* reports the count in `tieredSkipped`. Skipped images will not exist in the dedup tables.
|
|
200
|
+
*/
|
|
201
|
+
skipTiered?: boolean;
|
|
202
|
+
}
|
|
203
|
+
interface MigrateToDedupResult {
|
|
204
|
+
imagesMigrated: number;
|
|
205
|
+
distinctBlobsCreated: number;
|
|
206
|
+
duplicateReferencesFound: number;
|
|
207
|
+
/** Ids already present in the dedup tables (a re-run): skipped, never double-counted. */
|
|
208
|
+
alreadyMigrated: number;
|
|
209
|
+
/** Tiered classic rows skipped because `skipTiered` was set; always 0 otherwise. */
|
|
210
|
+
tieredSkipped: number;
|
|
211
|
+
}
|
|
194
212
|
interface StorageBackend {
|
|
195
213
|
/** Resolve once the backend is connected and migrated. Safe to call repeatedly. */
|
|
196
214
|
ready(): Promise<void>;
|
|
@@ -213,12 +231,7 @@ interface StorageBackend {
|
|
|
213
231
|
/** null = no such image, false = already tiered (no-op), true = tiered now. Needs object storage configured. */
|
|
214
232
|
tierToObjectStorage(id: string, opts?: CallOptions): Promise<boolean | null>;
|
|
215
233
|
/** Dedup-mode instances only: copy the classic table into the dedup tables (non-destructive). */
|
|
216
|
-
migrateClassicToDedup?(): Promise<
|
|
217
|
-
imagesMigrated: number;
|
|
218
|
-
distinctBlobsCreated: number;
|
|
219
|
-
duplicateReferencesFound: number;
|
|
220
|
-
alreadyMigrated: number;
|
|
221
|
-
}>;
|
|
234
|
+
migrateClassicToDedup?(opts?: MigrateToDedupOptions): Promise<MigrateToDedupResult>;
|
|
222
235
|
/** Maintenance: storage statistics, or null if the tables do not exist yet. */
|
|
223
236
|
getInfo?(): Promise<StorageInfo | null>;
|
|
224
237
|
/** Maintenance: list ids matching the filters (oldest first, or random when sampling). */
|
|
@@ -487,7 +500,7 @@ declare class ZeroBucket<C = unknown> {
|
|
|
487
500
|
* One-time, non-destructive copy of classic-mode images into a dedup instance's tables, keeping every
|
|
488
501
|
* id. Call it on an instance created with `dedup: true`. Safe to re-run. See PostgresBackend for details.
|
|
489
502
|
*/
|
|
490
|
-
migrateClassicToDedup():
|
|
503
|
+
migrateClassicToDedup(opts?: MigrateToDedupOptions): Promise<MigrateToDedupResult>;
|
|
491
504
|
/** Release the pool (if ZeroBucket created it). */
|
|
492
505
|
close(): Promise<void>;
|
|
493
506
|
}
|
|
@@ -502,4 +515,4 @@ declare function readInput(image: ImageInput, filename: string | undefined, maxB
|
|
|
502
515
|
filename: string | undefined;
|
|
503
516
|
}>;
|
|
504
517
|
|
|
505
|
-
export {
|
|
518
|
+
export { type StreamOptions as A, type BatchDeleteResult as B, type CallOptions as C, DEFAULT_MAX_BYTES as D, type ZeroBucketOptions as E, optimizeImage as F, type GetOptions as G, readInput as H, type ImageListOptions as I, type MigrateToDedupOptions as M, type OperationEvent as O, type PreparedRow as P, type Queryable as Q, type StorageBackend as S, type ValidatedContent as V, type WritableLike as W, ZeroBucket as Z, type ObjectStorageLike as a, type StoredRecord as b, type StoredRecordMetadata as c, type StreamOptions$1 as d, type StorageInfo as e, type ImageListEntry as f, type MigrateToDedupResult as g, type MysqlPoolLike as h, type ContentValidator as i, type BatchGetResult as j, type BatchPutResult as k, type BeforeGetHook as l, type BeforePutHook as m, type Connection as n, type Image as o, type ImageInput as p, type ImageMetadata as q, type MysqlPoolConnectionLike as r, type MysqlQueryable as s, ObjectStorage as t, type ObjectStorageOptions as u, type OptimizationResult as v, type OptimizeOptions as w, type PutManyOptions as x, type PutOptions as y, type SqliteDatabaseLike as z };
|
package/dist/http.d.cts
CHANGED
package/dist/http.d.ts
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -980,13 +980,13 @@ var PostgresBackend = class {
|
|
|
980
980
|
* dedup instance's tables, preserving every id (external references keep working) and
|
|
981
981
|
* deduplicating identical content on the way. The classic table is never modified.
|
|
982
982
|
*
|
|
983
|
-
*
|
|
984
|
-
*
|
|
985
|
-
*
|
|
986
|
-
*
|
|
983
|
+
* Rows are copied in small batches (bounded memory instead of loading the whole table);
|
|
984
|
+
* re-running is safe (already-migrated ids are skipped and reported, not an error). Classic rows
|
|
985
|
+
* already tiered to object storage are refused up front with a clear message (their bytes are
|
|
986
|
+
* not in the database to copy) unless `skipTiered: true`, which skips and counts them instead.
|
|
987
987
|
* Still all-or-nothing: one transaction.
|
|
988
988
|
*/
|
|
989
|
-
async migrateClassicToDedup() {
|
|
989
|
+
async migrateClassicToDedup(opts = {}) {
|
|
990
990
|
if (!this.dedup) {
|
|
991
991
|
throw new StorageError(
|
|
992
992
|
"migrateClassicToDedup() must be called on an instance created with dedup: true."
|
|
@@ -1005,6 +1005,7 @@ var PostgresBackend = class {
|
|
|
1005
1005
|
"No classic zerobucket_images table found in this database -- nothing to migrate."
|
|
1006
1006
|
);
|
|
1007
1007
|
}
|
|
1008
|
+
let tieredSkipped = 0;
|
|
1008
1009
|
const hasTierCol = (await client.query(
|
|
1009
1010
|
"SELECT 1 FROM information_schema.columns WHERE table_name = 'zerobucket_images' AND column_name = 'storage_backend'"
|
|
1010
1011
|
)).rowCount;
|
|
@@ -1012,12 +1013,14 @@ var PostgresBackend = class {
|
|
|
1012
1013
|
const tiered = (await client.query(
|
|
1013
1014
|
"SELECT count(*)::int AS n FROM zerobucket_images WHERE storage_backend <> 'postgres'"
|
|
1014
1015
|
)).rows[0].n;
|
|
1015
|
-
if (tiered > 0) {
|
|
1016
|
+
if (tiered > 0 && !opts.skipTiered) {
|
|
1016
1017
|
throw new StorageError(
|
|
1017
|
-
`${tiered} classic image(s) are tiered to object storage, so their bytes are not in the database to migrate. Dedup mode does not support tiering. Migrate only an un-tiered table,
|
|
1018
|
+
`${tiered} classic image(s) are tiered to object storage, so their bytes are not in the database to migrate. Dedup mode does not support tiering. Migrate only an un-tiered table, bring those images back first, or pass skipTiered: true (CLI: --skip-tiered) to skip them and migrate the rest.`
|
|
1018
1019
|
);
|
|
1019
1020
|
}
|
|
1021
|
+
tieredSkipped = tiered;
|
|
1020
1022
|
}
|
|
1023
|
+
const onlyInDb = hasTierCol && tieredSkipped > 0;
|
|
1021
1024
|
const seen = /* @__PURE__ */ new Set();
|
|
1022
1025
|
let migrated = 0;
|
|
1023
1026
|
let already = 0;
|
|
@@ -1026,11 +1029,11 @@ var PostgresBackend = class {
|
|
|
1026
1029
|
for (; ; ) {
|
|
1027
1030
|
const page = cursor ? await client.query(
|
|
1028
1031
|
`SELECT id, data, mime_type, original_filename, size_bytes, width, height, checksum_sha256, created_at::text AS created_at_txt
|
|
1029
|
-
FROM zerobucket_images WHERE (created_at, id) > ($1::timestamptz, $2::uuid) ORDER BY created_at, id LIMIT ${BATCH}`,
|
|
1032
|
+
FROM zerobucket_images WHERE (created_at, id) > ($1::timestamptz, $2::uuid)${onlyInDb ? " AND storage_backend = 'postgres'" : ""} ORDER BY created_at, id LIMIT ${BATCH}`,
|
|
1030
1033
|
[cursor.createdAt, cursor.id]
|
|
1031
1034
|
) : await client.query(
|
|
1032
1035
|
`SELECT id, data, mime_type, original_filename, size_bytes, width, height, checksum_sha256, created_at::text AS created_at_txt
|
|
1033
|
-
FROM zerobucket_images ORDER BY created_at, id LIMIT ${BATCH}`
|
|
1036
|
+
FROM zerobucket_images${onlyInDb ? " WHERE storage_backend = 'postgres'" : ""} ORDER BY created_at, id LIMIT ${BATCH}`
|
|
1034
1037
|
);
|
|
1035
1038
|
if (page.rows.length === 0) break;
|
|
1036
1039
|
const last = page.rows[page.rows.length - 1];
|
|
@@ -1080,7 +1083,8 @@ var PostgresBackend = class {
|
|
|
1080
1083
|
imagesMigrated: migrated,
|
|
1081
1084
|
distinctBlobsCreated: seen.size,
|
|
1082
1085
|
duplicateReferencesFound: migrated - seen.size,
|
|
1083
|
-
alreadyMigrated: already
|
|
1086
|
+
alreadyMigrated: already,
|
|
1087
|
+
tieredSkipped
|
|
1084
1088
|
};
|
|
1085
1089
|
} catch (exc) {
|
|
1086
1090
|
try {
|
|
@@ -3739,13 +3743,13 @@ var ZeroBucket = class _ZeroBucket {
|
|
|
3739
3743
|
* One-time, non-destructive copy of classic-mode images into a dedup instance's tables, keeping every
|
|
3740
3744
|
* id. Call it on an instance created with `dedup: true`. Safe to re-run. See PostgresBackend for details.
|
|
3741
3745
|
*/
|
|
3742
|
-
async migrateClassicToDedup() {
|
|
3746
|
+
async migrateClassicToDedup(opts) {
|
|
3743
3747
|
if (!this.backend.migrateClassicToDedup) {
|
|
3744
3748
|
throw new StorageError(
|
|
3745
3749
|
"This backend does not support migrateClassicToDedup()."
|
|
3746
3750
|
);
|
|
3747
3751
|
}
|
|
3748
|
-
return this.backend.migrateClassicToDedup();
|
|
3752
|
+
return this.backend.migrateClassicToDedup(opts);
|
|
3749
3753
|
}
|
|
3750
3754
|
/** Release the pool (if ZeroBucket created it). */
|
|
3751
3755
|
close() {
|