v0.13.0
This release applies the remaining items from the v0.12.0 deep review (cross-cutting concerns + nits) plus a few new findings from the implementation pass. The headline change is opt-in support for cloud disks in the FilesystemTracker via Cache-backed locks, alongside built-in idempotency for chunk POSTs and a metrics-callback surface for observability integrations.
Added
chunky.lock_driver = 'cache'— Cache::lock-backed locking forFilesystemTrackermutations and the batch counter. Required when running against S3, GCS, or any non-local Flysystem disk; works with any Laravel cache driver that supports atomic locks (Redis, Memcached, DB, DynamoDB). Defaults toflockfor backward compatibility. Tunable viachunky.lock_ttl_seconds(30) andchunky.lock_wait_seconds(5).- Idempotent chunk POSTs. The
POST /upload/{uploadId}/chunksendpoint now caches its response by(uploadId, chunkIndex, Idempotency-Key OR checksum)forchunky.idempotency_ttl_seconds(default 300). A network retry of a chunk the server already accepted replays the cached payload byte-for-byte instead of double-firingChunkUploadedevents and double-dispatchingAssembleFileJob. The frontendChunkUploaderautomatically attaches anIdempotency-Keyheader ({uploadId}:{chunkIndex}). - Observability hooks (
chunky.metrics). Five lifecycle events fire throughNETipar\Chunky\Support\Metrics::emit():chunk_uploaded,chunk_upload_failed,assembly_started,assembly_completed,assembly_failed. Wire any of them to Datadog / Prometheus / StatsD via a callable in config. Callback exceptions are swallowed so an observability bug cannot break the upload pipeline. UploadStatus::isTerminal()andBatchStatus::isTerminal()helpers, replacing 5+ inlinein_array($status, [Completed, Failed, …])lists across the codebase. Single source of truth for "is this state final?" semantics.chunky.staging_directoryconfig — local filesystem directory used while assembling chunks. Defaults tosys_get_temp_dir(). Set to a path on a volume with enough free space when accepting uploads larger than your/tmppartition (cloud-disk targets buffer the full file locally before upload).chunky.metadata.max_keysconfig (default 50) — bounded user-supplied metadata, so a misbehaving client can't balloon DB rows or broadcast payloads with megabyte-sized metadata blobs.chunky.broadcasting.expose_internal_paths— opt-in flag to include the storagediskand absolutefinalPathin theUploadCompleted/UploadFailedbroadcast payloads. Defaults tofalseso server-internal paths don't leak over WebSocket. The Livewire component honours the same flag.CompletionWatcherpoll backoff and progress-aware timeout. New options:pollMaxIntervalMs(default 30000),pollBackoffFactor(default 1.5), andextendTimeoutOnProgressMs(default 0 = static deadline). The poll cadence now grows over time so a long-running batch doesn't hammer the status endpoint at the initial cadence; withextendTimeoutOnProgressMs > 0, observed progress extends the wall-clock deadline.useBatchCompletion(Vue 3)debounceMsoption (default 50) — protects againstbatchIdflapping on rapid route param changes, which would otherwise teardown/setup an Echo subscription on every tick.- React
useChunkUpload/useBatchUploadand Alpine wrappers exposedestroy()for explicit teardown. Brings parity with the Vue 3 wrappers shipped in v0.12.0.
Fixed
ChunkUploader.upload()no longer falls back to a 1MB chunk size silently. A missingchunk_sizefrom the server (and no client override) now throws — guessing the slice size produces silently corrupted output if the guess disagrees with the backend.ChunkUploader.uploadChunks()is O(N) instead of O(N²) on the pending list. ASetreplaces the previousArray.filter()rebuild on every chunk completion. For a 10000-chunk file that's ~50M ops saved.BatchUploader.aggregateProgress()uses aforloop instead ofArray.reduce— same time complexity, less closure overhead per progress event.CompletionWatcherno longer hammers a 401/403/404 endpoint. All three status codes are now treated as fatal alongside 404 (added in v0.12.0); previously 401/403 looped at 2-second intervals until the wall-clock timeout.ChunkyContext::name()empty string is rejected at registration time rather than producing a silently broken$contexts['']entry.simple()save-callback applies a defence-in-depthbasename()to the destination, mirroring the assembler's path-traversal guard.Livewire/ChunkUpload::completeUpload()now performs an Authorizer ownership check on the upload before broadcasting the completion event — closes the same IDOR surface that v0.12.0 closed for the HTTP layer.
Changed
UploadCompletedandUploadFailedbroadcast payloads no longer includediskorfinalPathby default. Setchunky.broadcasting.expose_internal_paths = trueto opt back in if a consumer depends on these.Models\ChunkedUpload::markChunkUploaded()dropped the unused?string $checksumparameter (already nullable; never persisted).- Vue 3 wrapper
onBeforeUnmountlifecycle hooks are nowonScopeDisposeeverywhere (already partially done in v0.12.0; the rest converted in this release).
npm packages
- All packages bumped to
0.13.0(core, vue3, react, alpine).
Migration notes
- The new
chunky.broadcasting.expose_internal_pathsdefault offalseis technically a wire-format change forUploadCompleted/UploadFailed. Frontends that readevent.finalPathorevent.diskfrom broadcast payloads need to either set the flag back totrueinconfig/chunky.phpor fetch those fields fromGET /api/chunky/upload/{uploadId}server-side instead. - Switching to
chunky.lock_driver = 'cache'requires a Laravel cache driver that supportsCache::lock().arrayandfiledrivers do not — use Redis, Memcached, DB, or DynamoDB.