zerobucket 0.6.0 → 0.7.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/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as StorageBackend, O as OperationEvent, a as ObjectStorageLike, P as PreparedRow, C as CallOptions, b as StoredRecord, c as StoredRecordMetadata, d as StreamOptions, e as StorageInfo, I as ImageListOptions, f as ImageListEntry, g as ContentValidator, V as ValidatedContent } from './client-C7xs1dlr.cjs';
2
- export { B as BatchDeleteResult, h as BatchGetResult, i as BatchPutResult, j as BeforeGetHook, k as BeforePutHook, l as Connection, D as DEFAULT_MAX_BYTES, G as GetOptions, m as Image, n as ImageInput, o as ImageMetadata, p as ObjectStorage, q as ObjectStorageOptions, r as OptimizationResult, s as OptimizeOptions, t as PutManyOptions, u as PutOptions, Q as Queryable, v as SqliteDatabaseLike, w as StreamOptions, W as WritableLike, Z as ZeroBucket, x as ZeroBucketOptions, y as optimizeImage, z as readInput } from './client-C7xs1dlr.cjs';
1
+ import { S as StorageBackend, O as OperationEvent, a as ObjectStorageLike, P as PreparedRow, C as CallOptions, b as StoredRecord, c as StoredRecordMetadata, d as StreamOptions, e as StorageInfo, I as ImageListOptions, f as ImageListEntry, M as MysqlPoolLike, g as ContentValidator, V as ValidatedContent } from './client-XQ22b1l3.cjs';
2
+ export { B as BatchDeleteResult, h as BatchGetResult, i as BatchPutResult, j as BeforeGetHook, k as BeforePutHook, l as Connection, D as DEFAULT_MAX_BYTES, G as GetOptions, m as Image, n as ImageInput, o as ImageMetadata, p as MysqlPoolConnectionLike, q as MysqlQueryable, r as ObjectStorage, s as ObjectStorageOptions, t as OptimizationResult, u as OptimizeOptions, v as PutManyOptions, w as PutOptions, Q as Queryable, x as SqliteDatabaseLike, y as StreamOptions, W as WritableLike, Z as ZeroBucket, z as ZeroBucketOptions, A as optimizeImage, E as readInput } from './client-XQ22b1l3.cjs';
3
3
  import { Pool } from 'pg';
4
4
 
5
5
  /**
@@ -275,6 +275,113 @@ declare class SQLiteBackend implements StorageBackend {
275
275
  tierToObjectStorage(id: string, { connection }?: CallOptions): Promise<boolean | null>;
276
276
  }
277
277
 
278
+ interface MySQLBackendOptions {
279
+ /** `mysql://user:password@host:3306/database` (`mariadb://` is accepted too). Required unless `pool` is given. */
280
+ connectionString?: string;
281
+ /** Bring your own `mysql2/promise` Pool (not closed by `close()`). Pool sizing options are then ignored. */
282
+ pool?: MysqlPoolLike;
283
+ /** Create the schema on first use. Default true. */
284
+ autoMigrate?: boolean;
285
+ /** Retries for transient errors on pooled calls. Default 3; 0 disables. */
286
+ maxRetries?: number;
287
+ /** Base for exponential backoff with jitter (capped at 2s). Default 100. */
288
+ retryBaseDelayMs?: number;
289
+ /** Accepted for parity with the other backends. mysql2 opens connections on demand, so there is no minimum to hold. */
290
+ poolMinSize?: number;
291
+ /** Maximum simultaneous connections. Default 5. */
292
+ poolMaxSize?: number;
293
+ /** Max wait for a pooled connection (also the TCP connect timeout). Default 10000. */
294
+ poolTimeoutMs?: number;
295
+ onOperation?: (event: OperationEvent) => void;
296
+ /** Enables transparent reads of tiered rows and `tierToObjectStorage()`. */
297
+ objectStorage?: ObjectStorageLike;
298
+ /** Not available for MySQL yet; passing `true` throws. */
299
+ dedup?: boolean;
300
+ }
301
+ declare class MySQLBackend implements StorageBackend {
302
+ private readonly opts;
303
+ private pool;
304
+ private readonly ownsPool;
305
+ private readyPromise;
306
+ private closed;
307
+ private readonly maxRetries;
308
+ private readonly retryBaseDelayMs;
309
+ private readonly poolTimeoutMs;
310
+ private readonly autoMigrate;
311
+ constructor(opts: MySQLBackendOptions);
312
+ ready(): Promise<void>;
313
+ private init;
314
+ /**
315
+ * Create the schema if missing, under a server-side named lock so concurrent first callers (in this
316
+ * or any other process) run the DDL one at a time. Every step checks before it changes anything, so
317
+ * re-running is a no-op.
318
+ */
319
+ private migrate;
320
+ close(): Promise<void>;
321
+ private emit;
322
+ /** Wait for a pool slot, but never longer than poolTimeoutMs. */
323
+ private acquire;
324
+ /**
325
+ * Run `work` on a pooled connection. `tx` wraps it in a transaction. Transient failures retry the
326
+ * WHOLE closure on a fresh connection after a full rollback.
327
+ */
328
+ private runPooled;
329
+ /**
330
+ * Run `work` and return its result, emitting exactly one OperationEvent.
331
+ *
332
+ * connection given -> run once on it; no retry, and no commit/rollback of the caller's transaction.
333
+ * connection absent -> pooled, retried (see runPooled).
334
+ */
335
+ private run;
336
+ /**
337
+ * Make multi-statement work all-or-nothing on a CALLER-supplied connection.
338
+ *
339
+ * Is a transaction open? SAVEPOINT followed by ROLLBACK TO SAVEPOINT succeeds only inside one (in
340
+ * autocommit mode the savepoint is gone before the next statement and the rollback fails with 1305).
341
+ * - Open -> reuse that SAVEPOINT: a failure undoes only our work; success joins the caller's transaction.
342
+ * - None -> START TRANSACTION and COMMIT our own, so a failure can never leave half-applied state.
343
+ */
344
+ private atomic;
345
+ put(row: PreparedRow, { connection }?: CallOptions): Promise<string>;
346
+ putMany(rows: PreparedRow[], { connection }?: CallOptions): Promise<string[]>;
347
+ get(id: string, { connection }?: CallOptions): Promise<StoredRecord | null>;
348
+ getMany(ids: string[], { connection }?: CallOptions): Promise<StoredRecord[]>;
349
+ getMetadata(id: string, { connection }?: CallOptions): Promise<StoredRecordMetadata | null>;
350
+ getStream(id: string, opts: StreamOptions): Promise<AsyncIterable<Buffer> | null>;
351
+ delete(id: string, { connection }?: CallOptions): Promise<boolean>;
352
+ deleteMany(ids: string[], { connection }?: CallOptions): Promise<string[]>;
353
+ exists(id: string, { connection }?: CallOptions): Promise<boolean>;
354
+ private hydrate;
355
+ /**
356
+ * Remove tiered objects AFTER their rows are gone: once the row is deleted the image is correctly
357
+ * "not found" to everyone, so a failed object delete only leaves a harmless orphan, whereas deleting
358
+ * the object first and then failing the row delete would leave a row pointing at nothing.
359
+ *
360
+ * Never runs inside a caller-owned transaction (`connection`): we cannot know whether it will
361
+ * commit. In that case the object stays until you remove it yourself after committing. Failures are
362
+ * reported through onOperation and never fail the delete itself.
363
+ */
364
+ private cleanupObject;
365
+ /**
366
+ * Move an image's bytes out of the database into object storage, replacing the row's `data` with a
367
+ * pointer. Returns null (no such image), false (already tiered: a safe no-op, so backfills can be
368
+ * re-run) or true (tiered just now).
369
+ *
370
+ * SAFETY: the upload happens INSIDE the same transaction as the row lock (SELECT ... FOR UPDATE)
371
+ * and the UPDATE that flips the row. If the upload fails the transaction rolls back and the row is
372
+ * untouched: there is no moment when the bytes exist nowhere, or when a row claims to be tiered
373
+ * without a completed upload.
374
+ *
375
+ * TRADEOFF: InnoDB row-locks only THIS row for the duration of the upload. Concurrent writes to this
376
+ * image wait (up to innodb_lock_wait_timeout, 50s by default); every other row is unaffected and
377
+ * plain reads are never blocked. Meant for explicit maintenance, not hot paths.
378
+ *
379
+ * Retry is safe: a transient error replays the whole closure, and the deterministic key (the image
380
+ * id) makes the re-upload an idempotent overwrite.
381
+ */
382
+ tierToObjectStorage(id: string, { connection }?: CallOptions): Promise<boolean | null>;
383
+ }
384
+
278
385
  declare const SUPPORTED_FORMATS: readonly string[];
279
386
  /** Reject images that would decode to more pixels than this, regardless of compressed size (~89 MP). */
280
387
  declare const DEFAULT_MAX_PIXELS = 89000000;
@@ -308,4 +415,4 @@ declare class PDFValidator implements ContentValidator {
308
415
  }): ValidatedContent;
309
416
  }
310
417
 
311
- export { AccessDeniedError, CallOptions, ContentValidationError, ContentValidator, CorruptedImageError, DEFAULT_MAX_PIXELS, DEFAULT_STREAM_CHUNK_SIZE, ImageNotFoundError, ImageTooLargeError, ImageValidationError, ObjectStorageLike, OperationEvent, PDFValidator, PostgresBackend, type PostgresBackendOptions, PreparedRow, SQLiteBackend, type SQLiteBackendOptions, SUPPORTED_FORMATS, StorageBackend, StorageError, StoredRecord, StoredRecordMetadata, UnsupportedFormatError, ValidatedContent, type ValidatedImage, ZeroBucketError, validateImage };
418
+ export { AccessDeniedError, CallOptions, ContentValidationError, ContentValidator, CorruptedImageError, DEFAULT_MAX_PIXELS, DEFAULT_STREAM_CHUNK_SIZE, ImageNotFoundError, ImageTooLargeError, ImageValidationError, MySQLBackend, type MySQLBackendOptions, MysqlPoolLike, ObjectStorageLike, OperationEvent, PDFValidator, PostgresBackend, type PostgresBackendOptions, PreparedRow, SQLiteBackend, type SQLiteBackendOptions, SUPPORTED_FORMATS, StorageBackend, StorageError, StoredRecord, StoredRecordMetadata, UnsupportedFormatError, ValidatedContent, type ValidatedImage, ZeroBucketError, validateImage };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as StorageBackend, O as OperationEvent, a as ObjectStorageLike, P as PreparedRow, C as CallOptions, b as StoredRecord, c as StoredRecordMetadata, d as StreamOptions, e as StorageInfo, I as ImageListOptions, f as ImageListEntry, g as ContentValidator, V as ValidatedContent } from './client-C7xs1dlr.js';
2
- export { B as BatchDeleteResult, h as BatchGetResult, i as BatchPutResult, j as BeforeGetHook, k as BeforePutHook, l as Connection, D as DEFAULT_MAX_BYTES, G as GetOptions, m as Image, n as ImageInput, o as ImageMetadata, p as ObjectStorage, q as ObjectStorageOptions, r as OptimizationResult, s as OptimizeOptions, t as PutManyOptions, u as PutOptions, Q as Queryable, v as SqliteDatabaseLike, w as StreamOptions, W as WritableLike, Z as ZeroBucket, x as ZeroBucketOptions, y as optimizeImage, z as readInput } from './client-C7xs1dlr.js';
1
+ import { S as StorageBackend, O as OperationEvent, a as ObjectStorageLike, P as PreparedRow, C as CallOptions, b as StoredRecord, c as StoredRecordMetadata, d as StreamOptions, e as StorageInfo, I as ImageListOptions, f as ImageListEntry, M as MysqlPoolLike, g as ContentValidator, V as ValidatedContent } from './client-XQ22b1l3.js';
2
+ export { B as BatchDeleteResult, h as BatchGetResult, i as BatchPutResult, j as BeforeGetHook, k as BeforePutHook, l as Connection, D as DEFAULT_MAX_BYTES, G as GetOptions, m as Image, n as ImageInput, o as ImageMetadata, p as MysqlPoolConnectionLike, q as MysqlQueryable, r as ObjectStorage, s as ObjectStorageOptions, t as OptimizationResult, u as OptimizeOptions, v as PutManyOptions, w as PutOptions, Q as Queryable, x as SqliteDatabaseLike, y as StreamOptions, W as WritableLike, Z as ZeroBucket, z as ZeroBucketOptions, A as optimizeImage, E as readInput } from './client-XQ22b1l3.js';
3
3
  import { Pool } from 'pg';
4
4
 
5
5
  /**
@@ -275,6 +275,113 @@ declare class SQLiteBackend implements StorageBackend {
275
275
  tierToObjectStorage(id: string, { connection }?: CallOptions): Promise<boolean | null>;
276
276
  }
277
277
 
278
+ interface MySQLBackendOptions {
279
+ /** `mysql://user:password@host:3306/database` (`mariadb://` is accepted too). Required unless `pool` is given. */
280
+ connectionString?: string;
281
+ /** Bring your own `mysql2/promise` Pool (not closed by `close()`). Pool sizing options are then ignored. */
282
+ pool?: MysqlPoolLike;
283
+ /** Create the schema on first use. Default true. */
284
+ autoMigrate?: boolean;
285
+ /** Retries for transient errors on pooled calls. Default 3; 0 disables. */
286
+ maxRetries?: number;
287
+ /** Base for exponential backoff with jitter (capped at 2s). Default 100. */
288
+ retryBaseDelayMs?: number;
289
+ /** Accepted for parity with the other backends. mysql2 opens connections on demand, so there is no minimum to hold. */
290
+ poolMinSize?: number;
291
+ /** Maximum simultaneous connections. Default 5. */
292
+ poolMaxSize?: number;
293
+ /** Max wait for a pooled connection (also the TCP connect timeout). Default 10000. */
294
+ poolTimeoutMs?: number;
295
+ onOperation?: (event: OperationEvent) => void;
296
+ /** Enables transparent reads of tiered rows and `tierToObjectStorage()`. */
297
+ objectStorage?: ObjectStorageLike;
298
+ /** Not available for MySQL yet; passing `true` throws. */
299
+ dedup?: boolean;
300
+ }
301
+ declare class MySQLBackend implements StorageBackend {
302
+ private readonly opts;
303
+ private pool;
304
+ private readonly ownsPool;
305
+ private readyPromise;
306
+ private closed;
307
+ private readonly maxRetries;
308
+ private readonly retryBaseDelayMs;
309
+ private readonly poolTimeoutMs;
310
+ private readonly autoMigrate;
311
+ constructor(opts: MySQLBackendOptions);
312
+ ready(): Promise<void>;
313
+ private init;
314
+ /**
315
+ * Create the schema if missing, under a server-side named lock so concurrent first callers (in this
316
+ * or any other process) run the DDL one at a time. Every step checks before it changes anything, so
317
+ * re-running is a no-op.
318
+ */
319
+ private migrate;
320
+ close(): Promise<void>;
321
+ private emit;
322
+ /** Wait for a pool slot, but never longer than poolTimeoutMs. */
323
+ private acquire;
324
+ /**
325
+ * Run `work` on a pooled connection. `tx` wraps it in a transaction. Transient failures retry the
326
+ * WHOLE closure on a fresh connection after a full rollback.
327
+ */
328
+ private runPooled;
329
+ /**
330
+ * Run `work` and return its result, emitting exactly one OperationEvent.
331
+ *
332
+ * connection given -> run once on it; no retry, and no commit/rollback of the caller's transaction.
333
+ * connection absent -> pooled, retried (see runPooled).
334
+ */
335
+ private run;
336
+ /**
337
+ * Make multi-statement work all-or-nothing on a CALLER-supplied connection.
338
+ *
339
+ * Is a transaction open? SAVEPOINT followed by ROLLBACK TO SAVEPOINT succeeds only inside one (in
340
+ * autocommit mode the savepoint is gone before the next statement and the rollback fails with 1305).
341
+ * - Open -> reuse that SAVEPOINT: a failure undoes only our work; success joins the caller's transaction.
342
+ * - None -> START TRANSACTION and COMMIT our own, so a failure can never leave half-applied state.
343
+ */
344
+ private atomic;
345
+ put(row: PreparedRow, { connection }?: CallOptions): Promise<string>;
346
+ putMany(rows: PreparedRow[], { connection }?: CallOptions): Promise<string[]>;
347
+ get(id: string, { connection }?: CallOptions): Promise<StoredRecord | null>;
348
+ getMany(ids: string[], { connection }?: CallOptions): Promise<StoredRecord[]>;
349
+ getMetadata(id: string, { connection }?: CallOptions): Promise<StoredRecordMetadata | null>;
350
+ getStream(id: string, opts: StreamOptions): Promise<AsyncIterable<Buffer> | null>;
351
+ delete(id: string, { connection }?: CallOptions): Promise<boolean>;
352
+ deleteMany(ids: string[], { connection }?: CallOptions): Promise<string[]>;
353
+ exists(id: string, { connection }?: CallOptions): Promise<boolean>;
354
+ private hydrate;
355
+ /**
356
+ * Remove tiered objects AFTER their rows are gone: once the row is deleted the image is correctly
357
+ * "not found" to everyone, so a failed object delete only leaves a harmless orphan, whereas deleting
358
+ * the object first and then failing the row delete would leave a row pointing at nothing.
359
+ *
360
+ * Never runs inside a caller-owned transaction (`connection`): we cannot know whether it will
361
+ * commit. In that case the object stays until you remove it yourself after committing. Failures are
362
+ * reported through onOperation and never fail the delete itself.
363
+ */
364
+ private cleanupObject;
365
+ /**
366
+ * Move an image's bytes out of the database into object storage, replacing the row's `data` with a
367
+ * pointer. Returns null (no such image), false (already tiered: a safe no-op, so backfills can be
368
+ * re-run) or true (tiered just now).
369
+ *
370
+ * SAFETY: the upload happens INSIDE the same transaction as the row lock (SELECT ... FOR UPDATE)
371
+ * and the UPDATE that flips the row. If the upload fails the transaction rolls back and the row is
372
+ * untouched: there is no moment when the bytes exist nowhere, or when a row claims to be tiered
373
+ * without a completed upload.
374
+ *
375
+ * TRADEOFF: InnoDB row-locks only THIS row for the duration of the upload. Concurrent writes to this
376
+ * image wait (up to innodb_lock_wait_timeout, 50s by default); every other row is unaffected and
377
+ * plain reads are never blocked. Meant for explicit maintenance, not hot paths.
378
+ *
379
+ * Retry is safe: a transient error replays the whole closure, and the deterministic key (the image
380
+ * id) makes the re-upload an idempotent overwrite.
381
+ */
382
+ tierToObjectStorage(id: string, { connection }?: CallOptions): Promise<boolean | null>;
383
+ }
384
+
278
385
  declare const SUPPORTED_FORMATS: readonly string[];
279
386
  /** Reject images that would decode to more pixels than this, regardless of compressed size (~89 MP). */
280
387
  declare const DEFAULT_MAX_PIXELS = 89000000;
@@ -308,4 +415,4 @@ declare class PDFValidator implements ContentValidator {
308
415
  }): ValidatedContent;
309
416
  }
310
417
 
311
- export { AccessDeniedError, CallOptions, ContentValidationError, ContentValidator, CorruptedImageError, DEFAULT_MAX_PIXELS, DEFAULT_STREAM_CHUNK_SIZE, ImageNotFoundError, ImageTooLargeError, ImageValidationError, ObjectStorageLike, OperationEvent, PDFValidator, PostgresBackend, type PostgresBackendOptions, PreparedRow, SQLiteBackend, type SQLiteBackendOptions, SUPPORTED_FORMATS, StorageBackend, StorageError, StoredRecord, StoredRecordMetadata, UnsupportedFormatError, ValidatedContent, type ValidatedImage, ZeroBucketError, validateImage };
418
+ export { AccessDeniedError, CallOptions, ContentValidationError, ContentValidator, CorruptedImageError, DEFAULT_MAX_PIXELS, DEFAULT_STREAM_CHUNK_SIZE, ImageNotFoundError, ImageTooLargeError, ImageValidationError, MySQLBackend, type MySQLBackendOptions, MysqlPoolLike, ObjectStorageLike, OperationEvent, PDFValidator, PostgresBackend, type PostgresBackendOptions, PreparedRow, SQLiteBackend, type SQLiteBackendOptions, SUPPORTED_FORMATS, StorageBackend, StorageError, StoredRecord, StoredRecordMetadata, UnsupportedFormatError, ValidatedContent, type ValidatedImage, ZeroBucketError, validateImage };