@tranquilload/core 0.1.0 → 0.1.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/.turbo/turbo-build.log +68 -68
- package/CHANGELOG.md +67 -0
- package/dist/errors.cjs +3 -1
- package/dist/errors.d.cts +2 -2
- package/dist/errors.d.mts +2 -2
- package/dist/errors.mjs +2 -2
- package/dist/index-BaeUV_fj.d.cts +139 -0
- package/dist/index-BaeUV_fj.d.cts.map +1 -0
- package/dist/index-bpWq6tje.d.mts +139 -0
- package/dist/index-bpWq6tje.d.mts.map +1 -0
- package/dist/multipart.cjs +80 -12
- package/dist/multipart.cjs.map +1 -1
- package/dist/multipart.d.cts +2 -2
- package/dist/multipart.d.mts +2 -2
- package/dist/multipart.mjs +80 -12
- package/dist/multipart.mjs.map +1 -1
- package/dist/{normalize-callback-BNBZZ1jT.cjs → normalize-callback-BdLtk9jb.cjs} +2 -2
- package/dist/{normalize-callback-BNBZZ1jT.cjs.map → normalize-callback-BdLtk9jb.cjs.map} +1 -1
- package/dist/{normalize-callback-DQ6C4gaV.mjs → normalize-callback-tcZ_nyq5.mjs} +2 -2
- package/dist/{normalize-callback-DQ6C4gaV.mjs.map → normalize-callback-tcZ_nyq5.mjs.map} +1 -1
- package/dist/oneshot.cjs +2 -2
- package/dist/oneshot.d.cts +2 -2
- package/dist/oneshot.d.mts +2 -2
- package/dist/oneshot.mjs +2 -2
- package/dist/progress.d.cts +2 -2
- package/dist/progress.d.mts +2 -2
- package/dist/{upload-error-BUexBh08.cjs → upload-error-BG1dOOl3.cjs} +26 -1
- package/dist/upload-error-BG1dOOl3.cjs.map +1 -0
- package/dist/{upload-error-B2ISUc_k.d.cts → upload-error-DTYVNlaJ.d.cts} +19 -3
- package/dist/{upload-error-B2ISUc_k.d.cts.map → upload-error-DTYVNlaJ.d.cts.map} +1 -1
- package/dist/{upload-error-zDvpxT9X.mjs → upload-error-Dbz_9j81.mjs} +21 -2
- package/dist/upload-error-Dbz_9j81.mjs.map +1 -0
- package/dist/{upload-error-jol-eoDW.d.mts → upload-error-jBco270d.d.mts} +19 -3
- package/dist/{upload-error-jol-eoDW.d.mts.map → upload-error-jBco270d.d.mts.map} +1 -1
- package/dist/{upload-event-C9TOVp5l.d.mts → upload-event-BT_nXgM9.d.cts} +7 -1
- package/dist/upload-event-BT_nXgM9.d.cts.map +1 -0
- package/dist/{upload-event-D77olieX.d.cts → upload-event-DOGbegxa.d.mts} +7 -1
- package/dist/upload-event-DOGbegxa.d.mts.map +1 -0
- package/package.json +1 -1
- package/src/errors/index.ts +2 -0
- package/src/errors/upload-error.test.ts +35 -0
- package/src/errors/upload-error.ts +27 -0
- package/src/multipart/index.test.ts +164 -1
- package/src/multipart/index.ts +96 -5
- package/src/multipart/upload-stream.test.ts +201 -2
- package/src/multipart/upload-stream.ts +160 -16
- package/src/progress/upload-event.ts +6 -0
- package/dist/index-Ch8xM6Xt.d.cts +0 -60
- package/dist/index-Ch8xM6Xt.d.cts.map +0 -1
- package/dist/index-DBGtgXEd.d.mts +0 -60
- package/dist/index-DBGtgXEd.d.mts.map +0 -1
- package/dist/upload-error-BUexBh08.cjs.map +0 -1
- package/dist/upload-error-zDvpxT9X.mjs.map +0 -1
- package/dist/upload-event-C9TOVp5l.d.mts.map +0 -1
- package/dist/upload-event-D77olieX.d.cts.map +0 -1
package/dist/multipart.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"multipart.mjs","names":[],"sources":["../src/multipart/circuit-breaker.ts","../src/multipart/chunk-stream.ts","../src/multipart/upload-stream.ts","../src/multipart/index.ts"],"sourcesContent":["import { Effect, Ref } from \"effect\"\nimport { CircuitOpenError } from \"../errors/upload-error.js\"\nimport type { CircuitOpen } from \"../progress/upload-event.js\"\n\nexport interface CircuitBreakerConfig {\n readonly threshold: number\n readonly cooldown: number\n}\n\ntype CircuitState =\n | { readonly _tag: \"Closed\"; readonly consecutiveFailures: number }\n | { readonly _tag: \"Open\"; readonly openedAt: number }\n | { readonly _tag: \"HalfOpen\" }\n\nexport interface CircuitBreaker {\n readonly guard: Effect.Effect<void, CircuitOpenError>\n readonly onSuccess: Effect.Effect<void>\n readonly onFailure: Effect.Effect<CircuitOpen | null>\n}\n\nexport const makeCircuitBreaker = (config: CircuitBreakerConfig): Effect.Effect<CircuitBreaker> =>\n Effect.gen(function* () {\n const refState = yield* Ref.make<CircuitState>({ _tag: \"Closed\", consecutiveFailures: 0 })\n\n const guard: Effect.Effect<void, CircuitOpenError> = Effect.gen(function* () {\n const blocked = yield* Ref.modify(refState, (state): [boolean, CircuitState] => {\n if (state._tag !== \"Open\") return [false, state]\n const elapsed = Date.now() - state.openedAt\n if (elapsed < config.cooldown) return [true, state]\n return [false, { _tag: \"HalfOpen\" as const }]\n })\n if (blocked) {\n return yield* Effect.fail(new CircuitOpenError(config.threshold))\n }\n })\n\n const onSuccess: Effect.Effect<void> = Ref.update(refState, state =>\n state._tag === \"HalfOpen\" || state._tag === \"Closed\"\n ? { _tag: \"Closed\" as const, consecutiveFailures: 0 }\n : state\n )\n\n const onFailure: Effect.Effect<CircuitOpen | null> = Ref.modify(refState, (state): [CircuitOpen | null, CircuitState] => {\n if (state._tag === \"Closed\") {\n const newFailures = state.consecutiveFailures + 1\n if (newFailures >= config.threshold) {\n const event: CircuitOpen = {\n _tag: \"CircuitOpen\",\n failedParts: newFailures,\n timestamp: Date.now(),\n }\n return [event, { _tag: \"Open\" as const, openedAt: Date.now() }]\n }\n return [null, { _tag: \"Closed\" as const, consecutiveFailures: newFailures }]\n }\n if (state._tag === \"HalfOpen\") {\n const event: CircuitOpen = {\n _tag: \"CircuitOpen\",\n failedParts: config.threshold,\n timestamp: Date.now(),\n }\n return [event, { _tag: \"Open\" as const, openedAt: Date.now() }]\n }\n return [null, state]\n })\n\n return { guard, onSuccess, onFailure }\n })\n","import { Stream } from \"effect\"\n\nexport const chunkStream = (\n stream: ReadableStream<Uint8Array>,\n chunkSize: number\n): Stream.Stream<Uint8Array, unknown> => {\n let buffer = new Uint8Array(0)\n\n const transform = new TransformStream<Uint8Array, Uint8Array>({\n transform(chunk, controller) {\n // Concatenate incoming chunk into the buffer\n const merged = new Uint8Array(buffer.length + chunk.length)\n merged.set(buffer)\n merged.set(chunk, buffer.length)\n buffer = merged\n\n // Emit every full-size chunk\n while (buffer.length >= chunkSize) {\n controller.enqueue(buffer.slice(0, chunkSize))\n buffer = buffer.slice(chunkSize)\n }\n },\n flush(controller) {\n // Emit remaining bytes (last partial chunk)\n if (buffer.length > 0) {\n controller.enqueue(buffer)\n }\n },\n })\n\n const chunked = stream.pipeThrough(transform)\n\n return Stream.fromReadableStream(\n () => chunked,\n (e) => e\n )\n}\n","import { Cause, Effect, Exit, Option, Ref, Schedule, Stream } from \"effect\"\nimport type { UploadError } from \"../errors/upload-error.js\"\nimport { CircuitOpenError, CompleteUploadError, InitiateUploadError, MaxRetriesExceededError, PartUploadError, ReconcileError } from \"../errors/upload-error.js\"\nimport type { CircuitOpen, PartCompleted, ProgressTick, UploadCompleted, UploadEvent, UploadInitiated } from \"../progress/upload-event.js\"\nimport { LoggerService } from \"../services/logger-service.js\"\nimport { fromAbortSignal } from \"../utils/abort-interop.js\"\nimport { normalizeCallback } from \"../utils/normalize-callback.js\"\nimport { makeCircuitBreaker, type CircuitBreakerConfig } from \"./circuit-breaker.js\"\nimport { chunkStream } from \"./chunk-stream.js\"\n\nexport interface CompletedPart {\n readonly partNumber: number\n readonly etag: string\n}\n\nexport interface UploadMultipartOptions {\n readonly stream: ReadableStream<Uint8Array>\n readonly chunkSize: number\n readonly uploadPart: (\n partNumber: number,\n chunk: Uint8Array\n ) => string | Promise<string> | Effect.Effect<string, UploadError>\n readonly completeUpload: (\n uploadId: string,\n parts: ReadonlyArray<CompletedPart>\n ) => void | Promise<void> | Effect.Effect<void, UploadError>\n readonly initiate?: () =>\n | { uploadId: string }\n | Promise<{ uploadId: string }>\n | Effect.Effect<{ uploadId: string }, UploadError>\n readonly reconcileCompletedParts?: () =>\n | ReadonlyArray<CompletedPart>\n | Promise<ReadonlyArray<CompletedPart>>\n | Effect.Effect<ReadonlyArray<CompletedPart>, UploadError>\n readonly maxConcurrency?: number\n readonly signal?: AbortSignal\n readonly retrySchedule?: Schedule.Schedule<unknown, PartUploadError>\n readonly circuitBreaker?: CircuitBreakerConfig\n}\n\nconst DEFAULT_MAX_CONCURRENCY = 4\n\n// 3 total attempts: 1 initial + 2 retries, with exponential backoff\nconst DEFAULT_RETRY_SCHEDULE = Schedule.exponential(\"100 millis\").pipe(\n Schedule.compose(Schedule.recurs(2))\n)\n\nexport const uploadMultipartEffect = (\n options: UploadMultipartOptions\n): Stream.Stream<UploadEvent, UploadError, LoggerService> => {\n const {\n stream,\n chunkSize,\n uploadPart,\n completeUpload,\n initiate,\n reconcileCompletedParts,\n maxConcurrency = DEFAULT_MAX_CONCURRENCY,\n signal,\n retrySchedule = DEFAULT_RETRY_SCHEDULE,\n } = options\n\n return Stream.unwrap(\n Effect.gen(function* () {\n const logger = yield* LoggerService\n const semaphore = yield* Effect.makeSemaphore(maxConcurrency)\n const refParts = yield* Ref.make<CompletedPart[]>([])\n const refBytesUploaded = yield* Ref.make(0)\n const refUploadId = yield* Ref.make(\"\")\n const breaker = options.circuitBreaker\n ? yield* makeCircuitBreaker(options.circuitBreaker)\n : null\n\n const reconciledMap: Map<number, string> = reconcileCompletedParts\n ? new Map(\n (yield* normalizeCallback(reconcileCompletedParts).pipe(\n Effect.mapError((cause): UploadError => new ReconcileError(cause))\n )).map(p => [p.partNumber, p.etag])\n )\n : new Map()\n\n const initiateStream: Stream.Stream<UploadEvent, UploadError, never> = initiate\n ? Stream.fromEffect(\n normalizeCallback(initiate).pipe(\n Effect.mapError((cause): UploadError => new InitiateUploadError(cause)),\n Effect.flatMap(({ uploadId }) =>\n Ref.set(refUploadId, uploadId).pipe(\n Effect.as({\n _tag: \"UploadInitiated\" as const,\n uploadId,\n timestamp: Date.now(),\n } satisfies UploadInitiated)\n )\n )\n )\n )\n : Stream.empty\n\n const makeUploadOne = (\n partNumber: number,\n chunk: Uint8Array\n ): Effect.Effect<PartCompleted, UploadError> =>\n Effect.gen(function* () {\n const reconciledEtag = reconciledMap.get(partNumber)\n if (reconciledEtag !== undefined) {\n const event: PartCompleted = {\n _tag: \"PartCompleted\" as const,\n partNumber,\n etag: reconciledEtag,\n bytesUploaded: chunk.length,\n timestamp: Date.now(),\n }\n yield* Ref.update(refParts, parts => [...parts, { partNumber, etag: reconciledEtag }])\n yield* Effect.sync(() => logger.log(\"info\", `Part ${partNumber} skipped (reconciled)`))\n return event\n }\n\n const refAttempts = yield* Ref.make(0)\n\n const single: Effect.Effect<string, PartUploadError> = Effect.gen(function* () {\n yield* Ref.update(refAttempts, n => n + 1)\n const attempt = yield* Ref.get(refAttempts)\n return yield* normalizeCallback(() => uploadPart(partNumber, chunk)).pipe(\n Effect.mapError(\n (cause): PartUploadError => new PartUploadError(partNumber, attempt, cause)\n )\n )\n })\n\n const etag = yield* Effect.retry(single, retrySchedule).pipe(\n Effect.catchAll(err =>\n Effect.gen(function* () {\n const totalAttempts = yield* Ref.get(refAttempts)\n if (totalAttempts <= 1) {\n return yield* Effect.fail(err)\n }\n return yield* Effect.fail(\n new MaxRetriesExceededError(partNumber, totalAttempts, err.cause)\n )\n })\n )\n )\n\n const event: PartCompleted = {\n _tag: \"PartCompleted\" as const,\n partNumber,\n etag,\n bytesUploaded: chunk.length,\n timestamp: Date.now(),\n }\n\n yield* Ref.update(refParts, parts => [...parts, { partNumber, etag }])\n yield* Effect.sync(() => logger.log(\"info\", `Part ${partNumber} completed`))\n return event\n })\n\n const partsStream: Stream.Stream<UploadEvent, UploadError, never> = chunkStream(\n stream,\n chunkSize\n ).pipe(\n Stream.mapError((cause): UploadError => new PartUploadError(0, 0, cause)),\n Stream.zipWithIndex,\n Stream.mapEffect(\n ([chunk, idx]) => {\n const partNumber = Number(idx) + 1\n\n if (!breaker) {\n const partEffect = semaphore.withPermits(1)(\n makeUploadOne(partNumber, chunk)\n )\n return signal ? Effect.raceFirst(partEffect, fromAbortSignal(signal)) : partEffect\n }\n\n const partEffect = Effect.gen(function* () {\n yield* breaker.guard\n return yield* semaphore.withPermits(1)(\n Effect.gen(function* () {\n const exit = yield* Effect.exit(makeUploadOne(partNumber, chunk))\n if (Exit.isSuccess(exit)) {\n yield* breaker.onSuccess\n return exit.value\n }\n const circuitEvent = yield* breaker.onFailure\n if (circuitEvent !== null) {\n return yield* Effect.fail(new CircuitOpenError(circuitEvent.failedParts))\n }\n return yield* Effect.fail(Cause.squash(exit.cause) as UploadError)\n })\n )\n })\n\n return signal ? Effect.raceFirst(partEffect, fromAbortSignal(signal)) : partEffect\n },\n { concurrency: \"unbounded\" }\n ),\n Stream.flatMap(\n (event): Stream.Stream<UploadEvent, UploadError, never> => {\n const tickEffect = Ref.updateAndGet(refBytesUploaded, (n) => n + event.bytesUploaded).pipe(\n Effect.map(\n (total): ProgressTick => ({\n _tag: \"ProgressTick\" as const,\n bytesUploaded: total,\n totalBytes: Option.none(),\n timestamp: Date.now(),\n })\n )\n )\n return Stream.concat(Stream.make(event), Stream.fromEffect(tickEffect))\n }\n ),\n Stream.catchAll((err: UploadError): Stream.Stream<UploadEvent, UploadError, never> => {\n if (breaker && err._tag === \"CircuitOpenError\") {\n const event: UploadEvent = {\n _tag: \"CircuitOpen\",\n failedParts: err.failedParts,\n timestamp: Date.now(),\n }\n return Stream.concat(Stream.succeed(event), Stream.fail(err))\n }\n return Stream.fail(err)\n })\n )\n\n const finalEffect: Effect.Effect<UploadEvent, UploadError, never> = Effect.gen(\n function* () {\n const uploadId = yield* Ref.get(refUploadId)\n const parts = yield* Ref.get(refParts)\n yield* normalizeCallback(() => completeUpload(uploadId, parts)).pipe(\n Effect.mapError(\n (cause): UploadError => new CompleteUploadError(cause)\n )\n )\n yield* Effect.sync(() => logger.log(\"info\", \"Multipart upload completed\"))\n return {\n _tag: \"UploadCompleted\" as const,\n uploadId,\n totalParts: parts.length,\n timestamp: Date.now(),\n } satisfies UploadCompleted\n }\n )\n\n return Stream.concat(initiateStream, partsStream.pipe(Stream.concat(Stream.fromEffect(finalEffect))))\n })\n )\n}\n","import { Cause, Effect, Exit, Option, Ref, Stream } from \"effect\"\nimport type { UploadCompleted, UploadEvent } from \"../progress/upload-event.js\"\nimport type { Transform } from \"../pipeline/middleware.js\"\nimport { CompressionServiceLive } from \"../services/compression-service.js\"\nimport { LoggerServiceLive } from \"../services/logger-service.js\"\nimport { uploadMultipartEffect, type CompletedPart, type UploadMultipartOptions } from \"./upload-stream.js\"\n\nexport type UploadResult = UploadCompleted\nexport type { CompletedPart, UploadMultipartOptions }\n\nexport interface Progress {\n readonly bytesUploaded: number\n readonly totalBytes: Option.Option<number>\n}\n\nexport interface MultipartPublicOptions extends UploadMultipartOptions {\n readonly totalBytes?: number\n readonly pipeline?: Transform | Effect.Effect<Transform, unknown, unknown>\n}\n\nexport const uploadMultipart = (\n options: MultipartPublicOptions\n): {\n events: ReadableStream<UploadEvent>\n result: Promise<UploadResult>\n getProgress: (() => Promise<Progress>) & { effect: Effect.Effect<Progress> }\n uploadId: Promise<string>\n} => {\n const refProgress = Effect.runSync(\n Ref.make<Progress>({\n bytesUploaded: 0,\n totalBytes: options.totalBytes !== undefined ? Option.some(options.totalBytes) : Option.none(),\n })\n )\n\n let resolveUploadId!: (id: string) => void\n const uploadIdPromise: Promise<string> = new Promise<string>((resolve) => {\n resolveUploadId = resolve\n })\n\n const collected: Promise<ReadonlyArray<UploadEvent>> = (async () => {\n // Step 1: resolve pipeline to get the processed stream\n let processedStream = options.stream\n if (options.pipeline !== undefined) {\n if (typeof options.pipeline === \"function\") {\n processedStream = options.pipeline(options.stream)\n } else {\n // Effect pipeline — resolve with CompressionServiceLive\n const transform = await Effect.runPromise(\n Effect.provide(\n options.pipeline as Effect.Effect<Transform, unknown, never>,\n CompressionServiceLive\n )\n )\n processedStream = transform(options.stream)\n }\n }\n\n // Step 2: run upload with processedStream\n const program = uploadMultipartEffect({ ...options, stream: processedStream }).pipe(\n Stream.tap((event) => {\n if (event._tag === \"UploadInitiated\") {\n return Effect.sync(() => resolveUploadId(event.uploadId))\n }\n if (event._tag === \"PartCompleted\") {\n return Ref.update(refProgress, (p) => ({\n ...p,\n bytesUploaded: p.bytesUploaded + event.bytesUploaded,\n }))\n }\n return Effect.void\n }),\n Stream.provideLayer(LoggerServiceLive)\n )\n\n const exit = await Stream.runCollect(program).pipe(\n Effect.map((chunk) => Array.from(chunk)),\n Effect.runPromiseExit\n )\n if (Exit.isSuccess(exit)) return exit.value\n return Promise.reject(Cause.squash(exit.cause))\n })()\n\n // Rejection is surfaced via `result`; suppress the propagated rejection from .finally()\n collected.finally(() => resolveUploadId(\"\")).catch(() => {})\n\n // events: ReadableStream built from collected array; closes cleanly on error\n const events = new ReadableStream<UploadEvent>({\n async start(controller) {\n try {\n const evts = await collected\n for (const event of evts) controller.enqueue(event)\n controller.close()\n } catch (_) {\n // Close cleanly — upload errors surface via `result` only\n controller.close()\n }\n },\n })\n\n // result: resolves with UploadCompleted, rejects with UploadError on failure\n const result: Promise<UploadResult> = collected.then((evts) => {\n const last = evts[evts.length - 1]\n if (last === undefined) {\n return Promise.reject(new Error(\"uploadMultipart: stream ended without emitting an event\"))\n }\n return last as UploadResult\n })\n\n const getProgress = Object.assign(\n (): Promise<Progress> => Effect.runPromise(Ref.get(refProgress)),\n { effect: Ref.get(refProgress) }\n )\n\n return { events, result, getProgress, uploadId: uploadIdPromise }\n}\n\n// Effect escape hatch — LoggerService layer left open for user composition\nuploadMultipart.effect = uploadMultipartEffect\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"multipart.mjs","names":[],"sources":["../src/multipart/circuit-breaker.ts","../src/multipart/chunk-stream.ts","../src/multipart/upload-stream.ts","../src/multipart/index.ts"],"sourcesContent":["import { Effect, Ref } from \"effect\"\nimport { CircuitOpenError } from \"../errors/upload-error.js\"\nimport type { CircuitOpen } from \"../progress/upload-event.js\"\n\nexport interface CircuitBreakerConfig {\n readonly threshold: number\n readonly cooldown: number\n}\n\ntype CircuitState =\n | { readonly _tag: \"Closed\"; readonly consecutiveFailures: number }\n | { readonly _tag: \"Open\"; readonly openedAt: number }\n | { readonly _tag: \"HalfOpen\" }\n\nexport interface CircuitBreaker {\n readonly guard: Effect.Effect<void, CircuitOpenError>\n readonly onSuccess: Effect.Effect<void>\n readonly onFailure: Effect.Effect<CircuitOpen | null>\n}\n\nexport const makeCircuitBreaker = (config: CircuitBreakerConfig): Effect.Effect<CircuitBreaker> =>\n Effect.gen(function* () {\n const refState = yield* Ref.make<CircuitState>({ _tag: \"Closed\", consecutiveFailures: 0 })\n\n const guard: Effect.Effect<void, CircuitOpenError> = Effect.gen(function* () {\n const blocked = yield* Ref.modify(refState, (state): [boolean, CircuitState] => {\n if (state._tag !== \"Open\") return [false, state]\n const elapsed = Date.now() - state.openedAt\n if (elapsed < config.cooldown) return [true, state]\n return [false, { _tag: \"HalfOpen\" as const }]\n })\n if (blocked) {\n return yield* Effect.fail(new CircuitOpenError(config.threshold))\n }\n })\n\n const onSuccess: Effect.Effect<void> = Ref.update(refState, state =>\n state._tag === \"HalfOpen\" || state._tag === \"Closed\"\n ? { _tag: \"Closed\" as const, consecutiveFailures: 0 }\n : state\n )\n\n const onFailure: Effect.Effect<CircuitOpen | null> = Ref.modify(refState, (state): [CircuitOpen | null, CircuitState] => {\n if (state._tag === \"Closed\") {\n const newFailures = state.consecutiveFailures + 1\n if (newFailures >= config.threshold) {\n const event: CircuitOpen = {\n _tag: \"CircuitOpen\",\n failedParts: newFailures,\n timestamp: Date.now(),\n }\n return [event, { _tag: \"Open\" as const, openedAt: Date.now() }]\n }\n return [null, { _tag: \"Closed\" as const, consecutiveFailures: newFailures }]\n }\n if (state._tag === \"HalfOpen\") {\n const event: CircuitOpen = {\n _tag: \"CircuitOpen\",\n failedParts: config.threshold,\n timestamp: Date.now(),\n }\n return [event, { _tag: \"Open\" as const, openedAt: Date.now() }]\n }\n return [null, state]\n })\n\n return { guard, onSuccess, onFailure }\n })\n","import { Stream } from \"effect\"\n\nexport const chunkStream = (\n stream: ReadableStream<Uint8Array>,\n chunkSize: number\n): Stream.Stream<Uint8Array, unknown> => {\n let buffer = new Uint8Array(0)\n\n const transform = new TransformStream<Uint8Array, Uint8Array>({\n transform(chunk, controller) {\n // Concatenate incoming chunk into the buffer\n const merged = new Uint8Array(buffer.length + chunk.length)\n merged.set(buffer)\n merged.set(chunk, buffer.length)\n buffer = merged\n\n // Emit every full-size chunk\n while (buffer.length >= chunkSize) {\n controller.enqueue(buffer.slice(0, chunkSize))\n buffer = buffer.slice(chunkSize)\n }\n },\n flush(controller) {\n // Emit remaining bytes (last partial chunk)\n if (buffer.length > 0) {\n controller.enqueue(buffer)\n }\n },\n })\n\n const chunked = stream.pipeThrough(transform)\n\n return Stream.fromReadableStream(\n () => chunked,\n (e) => e\n )\n}\n","import { Cause, Effect, Exit, Option, Ref, Schedule, Stream } from \"effect\"\nimport type { UploadError } from \"../errors/upload-error.js\"\nimport { CircuitOpenError, CompleteUploadError, InitiateUploadError, MaxRetriesExceededError, PartUploadError, ReconcileError, ResumeMismatchError } from \"../errors/upload-error.js\"\nimport type { CircuitOpen, PartCompleted, ProgressTick, UploadCompleted, UploadEvent, UploadInitiated } from \"../progress/upload-event.js\"\nimport { LoggerService } from \"../services/logger-service.js\"\nimport { fromAbortSignal } from \"../utils/abort-interop.js\"\nimport { normalizeCallback } from \"../utils/normalize-callback.js\"\nimport { makeCircuitBreaker, type CircuitBreakerConfig } from \"./circuit-breaker.js\"\nimport { chunkStream } from \"./chunk-stream.js\"\n\nexport interface CompletedPart {\n readonly partNumber: number\n readonly etag: string\n}\n\n/**\n * Opaque resume metadata returned by `uploadMultipart` and persisted by the\n * caller (typically `JSON.stringify` → localStorage). Pass it back as\n * `resumeFrom` on the next session.\n *\n * The lib validates `version`, `chunkSize`, `pipelineIdentity`, and the content\n * digest before any byte is uploaded. A mismatch fails the upload with a typed\n * `ResumeMismatchError` — preventing the silent-corruption classes documented\n * in the v0.2.x release notes.\n *\n * **Schema versioning.** The `version: 1` literal is a tripwire for schema\n * evolution: future v2 schemas widen this union, and a persisted v1 state\n * passed to a future v2 lib will fail with `ResumeMismatchError(\"version_mismatch\")`\n * rather than silently misinterpreting old fields.\n */\nexport interface ResumeState {\n readonly version: 1\n readonly uploadId: string\n readonly chunkSize: number\n readonly pipelineIdentity?: string\n readonly contentDigest?: string\n /**\n * True if the original session captured a digest. Detects persistence layers\n * that drop the `contentDigest` field (which would otherwise silently bypass\n * content-mismatch validation).\n */\n readonly contentDigestCaptured: boolean\n}\n\nexport interface UploadMultipartOptions {\n readonly stream: ReadableStream<Uint8Array>\n readonly chunkSize: number\n readonly uploadPart: (\n partNumber: number,\n chunk: Uint8Array\n ) => string | Promise<string> | Effect.Effect<string, UploadError>\n readonly completeUpload: (\n uploadId: string,\n parts: ReadonlyArray<CompletedPart>\n ) => void | Promise<void> | Effect.Effect<void, UploadError>\n readonly initiate?: () =>\n | { uploadId: string }\n | Promise<{ uploadId: string }>\n | Effect.Effect<{ uploadId: string }, UploadError>\n readonly reconcileCompletedParts?: () =>\n | ReadonlyArray<CompletedPart>\n | Promise<ReadonlyArray<CompletedPart>>\n | Effect.Effect<ReadonlyArray<CompletedPart>, UploadError>\n /**\n * Resume metadata persisted from a previous session. When set, the lib skips\n * the `initiate` callback (the `uploadId` is read from `resumeFrom`) and\n * validates `version`, `chunkSize`, `pipelineIdentity`, and `contentDigest`\n * before any byte is uploaded. A mismatch fails the upload with\n * `ResumeMismatchError`.\n *\n * Synchronous (pre-flight) validation happens at `uploadMultipart()` call\n * time — `TypeError` for an empty `uploadId`, `ResumeMismatchError` for the\n * rest. The asynchronous content-digest *value* match is verified inside\n * the Effect once the upload stream is consumed.\n */\n readonly resumeFrom?: ResumeState\n /**\n * Called once on fresh initiate to capture a digest of the source content.\n * On a subsequent resume session, called again and compared to\n * `resumeFrom.contentDigest`; a mismatch fails the upload with\n * `ResumeMismatchError(\"content_mismatch\")` before any byte is uploaded.\n *\n * **MUST be lightweight and stable across sessions.** Suggested patterns:\n * - Browser `File`: `` `${name}|${size}|${lastModified}` ``\n * - Node `Readable` from a file: `` `${path}|${stat.size}|${stat.mtimeMs}` ``\n * - Synchronous strings; avoid full-file crypto hashes on the synchronous path.\n *\n * **MUST NOT consume bytes from the source stream** (passed in\n * `options.stream`). The lib calls `getContentDigest` before any chunk is\n * pulled from the source; consuming from the source here will produce a\n * zero-byte upload because no bytes remain for `chunkStream`.\n */\n readonly getContentDigest?: () =>\n | string\n | Promise<string>\n | Effect.Effect<string, UploadError>\n /**\n * An opaque, stable identifier for the upstream pipeline composition.\n * Captured in `ResumeState` and validated strict-equality on resume.\n * **You own keeping this stable** — if you configure `compress(\"deflate-raw\")`\n * in session A, you must pass the same `pipelineIdentity` on resume.\n *\n * **Strict equality limitation:** a pipeline that is logically identical but\n * produces different identifier strings (e.g. tag bumps, version-stamped\n * strings) triggers `ResumeMismatchError(\"pipeline_mismatch\")`. Pick a stable\n * string (e.g. `\"deflate-raw-v1\"`) and only change it when the pipeline's\n * *byte-level output* changes.\n *\n * **Compression non-determinism caveat:** even with identical\n * `pipelineIdentity`, a non-deterministic pipeline (e.g. gzip with `mtime`\n * headers, encryption with random salt) produces different bytes per run.\n * Resume against the same uploaded parts only works if the pipeline is\n * byte-deterministic. Verify your pipeline's determinism before relying on\n * this.\n */\n readonly pipelineIdentity?: string\n readonly maxConcurrency?: number\n readonly signal?: AbortSignal\n readonly retrySchedule?: Schedule.Schedule<unknown, PartUploadError>\n readonly circuitBreaker?: CircuitBreakerConfig\n}\n\nconst DEFAULT_MAX_CONCURRENCY = 4\n\n// 3 total attempts: 1 initial + 2 retries, with exponential backoff\nconst DEFAULT_RETRY_SCHEDULE = Schedule.exponential(\"100 millis\").pipe(\n Schedule.compose(Schedule.recurs(2))\n)\n\nexport const uploadMultipartEffect = (\n options: UploadMultipartOptions\n): Stream.Stream<UploadEvent, UploadError, LoggerService> => {\n const {\n stream,\n chunkSize,\n uploadPart,\n completeUpload,\n initiate,\n reconcileCompletedParts,\n resumeFrom,\n getContentDigest,\n pipelineIdentity,\n maxConcurrency = DEFAULT_MAX_CONCURRENCY,\n signal,\n retrySchedule = DEFAULT_RETRY_SCHEDULE,\n } = options\n\n if (!Number.isFinite(chunkSize) || chunkSize <= 0) {\n throw new TypeError(\n `uploadMultipart: chunkSize must be a positive finite number, got ${chunkSize}`\n )\n }\n\n if (resumeFrom !== undefined) {\n if (typeof resumeFrom.uploadId !== \"string\" || resumeFrom.uploadId === \"\") {\n throw new TypeError(\n \"uploadMultipart: ResumeState.uploadId must be a non-empty string\"\n )\n }\n if (resumeFrom.version !== 1) {\n throw new ResumeMismatchError(\"version_mismatch\")\n }\n if (resumeFrom.chunkSize !== chunkSize) {\n throw new ResumeMismatchError(\"chunksize_mismatch\")\n }\n if (resumeFrom.pipelineIdentity !== pipelineIdentity) {\n throw new ResumeMismatchError(\"pipeline_mismatch\")\n }\n if (\n resumeFrom.contentDigestCaptured === true &&\n resumeFrom.contentDigest === undefined\n ) {\n throw new ResumeMismatchError(\"content_mismatch\")\n }\n }\n\n return Stream.unwrap(\n Effect.gen(function* () {\n const logger = yield* LoggerService\n const semaphore = yield* Effect.makeSemaphore(maxConcurrency)\n const refParts = yield* Ref.make<CompletedPart[]>([])\n const refBytesUploaded = yield* Ref.make(0)\n const refUploadId = yield* Ref.make(\"\")\n const refDigest = yield* Ref.make<Option.Option<string>>(Option.none())\n const breaker = options.circuitBreaker\n ? yield* makeCircuitBreaker(options.circuitBreaker)\n : null\n\n const reconciledMap: Map<number, string> = reconcileCompletedParts\n ? new Map(\n (yield* normalizeCallback(reconcileCompletedParts).pipe(\n Effect.mapError((cause): UploadError => new ReconcileError(cause))\n )).map(p => [p.partNumber, p.etag])\n )\n : new Map()\n\n const runFreshInit: Effect.Effect<UploadInitiated, UploadError> = Effect.gen(\n function* () {\n const { uploadId } = yield* normalizeCallback(initiate!).pipe(\n Effect.mapError((cause): UploadError => new InitiateUploadError(cause))\n )\n yield* Ref.set(refUploadId, uploadId)\n if (getContentDigest !== undefined) {\n const digest = yield* normalizeCallback(getContentDigest).pipe(\n Effect.mapError((cause): UploadError => new InitiateUploadError(cause))\n )\n yield* Ref.set(refDigest, Option.some(digest))\n }\n const capturedDigest = yield* Ref.get(refDigest)\n return {\n _tag: \"UploadInitiated\" as const,\n uploadId,\n contentDigest: Option.getOrUndefined(capturedDigest),\n timestamp: Date.now(),\n } satisfies UploadInitiated\n }\n )\n\n const runResumeSetup: Effect.Effect<void, UploadError> = Effect.gen(function* () {\n // `resumeFrom` is non-undefined here (checked by setupStream selector below).\n const rf = resumeFrom!\n if (rf.contentDigest !== undefined && getContentDigest !== undefined) {\n const digest = yield* normalizeCallback(getContentDigest).pipe(\n Effect.mapError(\n (cause): UploadError => new ResumeMismatchError(\"content_mismatch\", cause)\n )\n )\n if (digest !== rf.contentDigest) {\n return yield* Effect.fail(new ResumeMismatchError(\"content_mismatch\"))\n }\n yield* Ref.set(refDigest, Option.some(digest))\n }\n yield* Ref.set(refUploadId, rf.uploadId)\n })\n\n const setupStream: Stream.Stream<UploadEvent, UploadError, never> =\n resumeFrom !== undefined\n ? Stream.fromEffect(runResumeSetup).pipe(Stream.drain)\n : initiate !== undefined\n ? Stream.fromEffect(runFreshInit)\n : Stream.empty\n\n const makeUploadOne = (\n partNumber: number,\n chunk: Uint8Array\n ): Effect.Effect<PartCompleted, UploadError> =>\n Effect.gen(function* () {\n const reconciledEtag = reconciledMap.get(partNumber)\n if (reconciledEtag !== undefined) {\n const event: PartCompleted = {\n _tag: \"PartCompleted\" as const,\n partNumber,\n etag: reconciledEtag,\n bytesUploaded: chunk.length,\n timestamp: Date.now(),\n }\n yield* Ref.update(refParts, parts => [...parts, { partNumber, etag: reconciledEtag }])\n yield* Effect.sync(() => logger.log(\"info\", `Part ${partNumber} skipped (reconciled)`))\n return event\n }\n\n const refAttempts = yield* Ref.make(0)\n\n const single: Effect.Effect<string, PartUploadError> = Effect.gen(function* () {\n yield* Ref.update(refAttempts, n => n + 1)\n const attempt = yield* Ref.get(refAttempts)\n return yield* normalizeCallback(() => uploadPart(partNumber, chunk)).pipe(\n Effect.mapError(\n (cause): PartUploadError => new PartUploadError(partNumber, attempt, cause)\n )\n )\n })\n\n const etag = yield* Effect.retry(single, retrySchedule).pipe(\n Effect.catchAll(err =>\n Effect.gen(function* () {\n const totalAttempts = yield* Ref.get(refAttempts)\n if (totalAttempts <= 1) {\n return yield* Effect.fail(err)\n }\n return yield* Effect.fail(\n new MaxRetriesExceededError(partNumber, totalAttempts, err.cause)\n )\n })\n )\n )\n\n const event: PartCompleted = {\n _tag: \"PartCompleted\" as const,\n partNumber,\n etag,\n bytesUploaded: chunk.length,\n timestamp: Date.now(),\n }\n\n yield* Ref.update(refParts, parts => [...parts, { partNumber, etag }])\n yield* Effect.sync(() => logger.log(\"info\", `Part ${partNumber} completed`))\n return event\n })\n\n const partsStream: Stream.Stream<UploadEvent, UploadError, never> = chunkStream(\n stream,\n chunkSize\n ).pipe(\n Stream.mapError((cause): UploadError => new PartUploadError(0, 0, cause)),\n Stream.zipWithIndex,\n Stream.mapEffect(\n ([chunk, idx]) => {\n const partNumber = Number(idx) + 1\n\n if (!breaker) {\n const partEffect = semaphore.withPermits(1)(\n makeUploadOne(partNumber, chunk)\n )\n return signal ? Effect.raceFirst(partEffect, fromAbortSignal(signal)) : partEffect\n }\n\n const partEffect = Effect.gen(function* () {\n yield* breaker.guard\n return yield* semaphore.withPermits(1)(\n Effect.gen(function* () {\n const exit = yield* Effect.exit(makeUploadOne(partNumber, chunk))\n if (Exit.isSuccess(exit)) {\n yield* breaker.onSuccess\n return exit.value\n }\n const circuitEvent = yield* breaker.onFailure\n if (circuitEvent !== null) {\n return yield* Effect.fail(new CircuitOpenError(circuitEvent.failedParts))\n }\n return yield* Effect.fail(Cause.squash(exit.cause) as UploadError)\n })\n )\n })\n\n return signal ? Effect.raceFirst(partEffect, fromAbortSignal(signal)) : partEffect\n },\n { concurrency: \"unbounded\" }\n ),\n Stream.flatMap(\n (event): Stream.Stream<UploadEvent, UploadError, never> => {\n const tickEffect = Ref.updateAndGet(refBytesUploaded, (n) => n + event.bytesUploaded).pipe(\n Effect.map(\n (total): ProgressTick => ({\n _tag: \"ProgressTick\" as const,\n bytesUploaded: total,\n totalBytes: Option.none(),\n timestamp: Date.now(),\n })\n )\n )\n return Stream.concat(Stream.make(event), Stream.fromEffect(tickEffect))\n }\n ),\n Stream.catchAll((err: UploadError): Stream.Stream<UploadEvent, UploadError, never> => {\n if (breaker && err._tag === \"CircuitOpenError\") {\n const event: UploadEvent = {\n _tag: \"CircuitOpen\",\n failedParts: err.failedParts,\n timestamp: Date.now(),\n }\n return Stream.concat(Stream.succeed(event), Stream.fail(err))\n }\n return Stream.fail(err)\n })\n )\n\n const finalEffect: Effect.Effect<UploadEvent, UploadError, never> = Effect.gen(\n function* () {\n const uploadId = yield* Ref.get(refUploadId)\n const parts = yield* Ref.get(refParts)\n yield* normalizeCallback(() => completeUpload(uploadId, parts)).pipe(\n Effect.mapError(\n (cause): UploadError => new CompleteUploadError(cause)\n )\n )\n yield* Effect.sync(() => logger.log(\"info\", \"Multipart upload completed\"))\n return {\n _tag: \"UploadCompleted\" as const,\n uploadId,\n totalParts: parts.length,\n timestamp: Date.now(),\n } satisfies UploadCompleted\n }\n )\n\n return Stream.concat(setupStream, partsStream.pipe(Stream.concat(Stream.fromEffect(finalEffect))))\n })\n )\n}\n","import { Cause, Effect, Exit, Option, Ref, Stream } from \"effect\"\nimport type { UploadCompleted, UploadEvent } from \"../progress/upload-event.js\"\nimport type { Transform } from \"../pipeline/middleware.js\"\nimport { CompressionServiceLive } from \"../services/compression-service.js\"\nimport { LoggerServiceLive } from \"../services/logger-service.js\"\nimport { uploadMultipartEffect, type CompletedPart, type ResumeState, type UploadMultipartOptions } from \"./upload-stream.js\"\n\nexport type UploadResult = UploadCompleted\nexport type { CompletedPart, ResumeState, UploadMultipartOptions }\n\nexport interface Progress {\n readonly bytesUploaded: number\n readonly totalBytes: Option.Option<number>\n}\n\nexport interface MultipartPublicOptions extends UploadMultipartOptions {\n readonly totalBytes?: number\n readonly pipeline?: Transform | Effect.Effect<Transform, unknown, unknown>\n}\n\nconst NO_RESUME_CONTEXT_ERROR_MESSAGE =\n \"uploadMultipart: resumeState is only available when `initiate` or `resumeFrom` is provided\"\n\nexport const uploadMultipart = (\n options: MultipartPublicOptions\n): {\n events: ReadableStream<UploadEvent>\n result: Promise<UploadResult>\n getProgress: (() => Promise<Progress>) & { effect: Effect.Effect<Progress> }\n uploadId: Promise<string>\n resumeState: Promise<ResumeState>\n} => {\n // Legacy-pattern detection: warns unconditionally so first-time-after-upgrade\n // users also see the migration message (per G3 — empty reconcile would have\n // hidden the warning under the old \"warn when reconcile returned >= 1\" rule).\n if (\n options.initiate !== undefined &&\n options.reconcileCompletedParts !== undefined &&\n options.resumeFrom === undefined\n ) {\n console.warn(\n \"Tranquilload: detected legacy resume pattern. You're passing `initiate` \" +\n \"and `reconcileCompletedParts` without `resumeFrom: ResumeState`. The new \" +\n \"API requires the persisted ResumeState to validate chunkSize/pipeline/\" +\n \"digest match across sessions. See MIGRATION.md for migration steps.\"\n )\n }\n // Pipeline-without-identity: the resume validation cannot detect a pipeline\n // mismatch across sessions when the user runs a pipeline but provides no\n // identity. We warn so the user is aware their resume safety is reduced.\n if (options.pipeline !== undefined && options.pipelineIdentity === undefined) {\n console.warn(\n \"Tranquilload: pipeline is set but pipelineIdentity is not. Without an \" +\n \"identity, the resume validation cannot detect a pipeline mismatch \" +\n \"across sessions. See README → Resume Safety.\"\n )\n }\n\n const refProgress = Effect.runSync(\n Ref.make<Progress>({\n bytesUploaded: 0,\n totalBytes: options.totalBytes !== undefined ? Option.some(options.totalBytes) : Option.none(),\n })\n )\n\n let resolveUploadId!: (id: string) => void\n const uploadIdPromise: Promise<string> = new Promise<string>((resolve) => {\n resolveUploadId = resolve\n })\n\n let resolveResumeState!: (s: ResumeState) => void\n let rejectResumeState!: (e: unknown) => void\n let resumeStateSettled = false\n const resumeStatePromise: Promise<ResumeState> = new Promise<ResumeState>((resolve, reject) => {\n resolveResumeState = (s) => {\n if (resumeStateSettled) return\n resumeStateSettled = true\n resolve(s)\n }\n rejectResumeState = (e) => {\n if (resumeStateSettled) return\n resumeStateSettled = true\n reject(e)\n }\n })\n // Avoid unhandled-rejection warnings: callers may not await resumeState.\n resumeStatePromise.catch(() => {})\n\n // Resume branch: resolve uploadId AND resumeState synchronously before the\n // stream runs. uploadId per AC22; resumeState per Task 4.1 (the lib has no\n // new state to add — the user already has the value they passed in).\n if (options.resumeFrom !== undefined) {\n resolveUploadId(options.resumeFrom.uploadId)\n resolveResumeState(options.resumeFrom)\n }\n\n const collected: Promise<ReadonlyArray<UploadEvent>> = (async () => {\n // Step 1: resolve pipeline to get the processed stream\n let processedStream = options.stream\n if (options.pipeline !== undefined) {\n if (typeof options.pipeline === \"function\") {\n processedStream = options.pipeline(options.stream)\n } else {\n // Effect pipeline — resolve with CompressionServiceLive\n const transform = await Effect.runPromise(\n Effect.provide(\n options.pipeline as Effect.Effect<Transform, unknown, never>,\n CompressionServiceLive\n )\n )\n processedStream = transform(options.stream)\n }\n }\n\n // Step 2: run upload with processedStream\n const program = uploadMultipartEffect({ ...options, stream: processedStream }).pipe(\n Stream.tap((event) => {\n if (event._tag === \"UploadInitiated\") {\n // Fresh-init branch: resolve uploadId on the event, and build the\n // ResumeState from the event payload + caller-supplied fields.\n return Effect.sync(() => {\n resolveUploadId(event.uploadId)\n const state: ResumeState = {\n version: 1,\n uploadId: event.uploadId,\n chunkSize: options.chunkSize,\n ...(options.pipelineIdentity !== undefined\n ? { pipelineIdentity: options.pipelineIdentity }\n : {}),\n ...(event.contentDigest !== undefined\n ? { contentDigest: event.contentDigest }\n : {}),\n contentDigestCaptured: options.getContentDigest !== undefined,\n }\n resolveResumeState(state)\n })\n }\n if (event._tag === \"PartCompleted\") {\n return Ref.update(refProgress, (p) => ({\n ...p,\n bytesUploaded: p.bytesUploaded + event.bytesUploaded,\n }))\n }\n return Effect.void\n }),\n Stream.provideLayer(LoggerServiceLive)\n )\n\n const exit = await Stream.runCollect(program).pipe(\n Effect.map((chunk) => Array.from(chunk)),\n Effect.runPromiseExit\n )\n if (Exit.isSuccess(exit)) return exit.value\n return Promise.reject(Cause.squash(exit.cause))\n })()\n\n // Rejection is surfaced via `result`; suppress the propagated rejection from .finally()\n collected\n .then(() => {\n // Success-but-no-context: neither initiate nor resumeFrom produced state.\n if (!resumeStateSettled) {\n rejectResumeState(new Error(NO_RESUME_CONTEXT_ERROR_MESSAGE))\n }\n })\n .catch((err) => {\n if (!resumeStateSettled) {\n rejectResumeState(err)\n }\n })\n .finally(() => resolveUploadId(\"\"))\n\n // events: ReadableStream built from collected array; closes cleanly on error\n const events = new ReadableStream<UploadEvent>({\n async start(controller) {\n try {\n const evts = await collected\n for (const event of evts) controller.enqueue(event)\n controller.close()\n } catch (_) {\n // Close cleanly — upload errors surface via `result` only\n controller.close()\n }\n },\n })\n\n // result: resolves with UploadCompleted, rejects with UploadError on failure\n const result: Promise<UploadResult> = collected.then((evts) => {\n const last = evts[evts.length - 1]\n if (last === undefined) {\n return Promise.reject(new Error(\"uploadMultipart: stream ended without emitting an event\"))\n }\n return last as UploadResult\n })\n\n const getProgress = Object.assign(\n (): Promise<Progress> => Effect.runPromise(Ref.get(refProgress)),\n { effect: Ref.get(refProgress) }\n )\n\n return {\n events,\n result,\n getProgress,\n uploadId: uploadIdPromise,\n resumeState: resumeStatePromise,\n }\n}\n\n// Effect escape hatch — LoggerService layer left open for user composition\nuploadMultipart.effect = uploadMultipartEffect\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const require_upload_error = require("./upload-error-
|
|
1
|
+
const require_upload_error = require("./upload-error-BG1dOOl3.cjs");
|
|
2
2
|
let effect = require("effect");
|
|
3
3
|
//#region src/utils/abort-interop.ts
|
|
4
4
|
const fromAbortSignal = (signal) => effect.Effect.async((resume) => {
|
|
@@ -41,4 +41,4 @@ Object.defineProperty(exports, "normalizeCallback", {
|
|
|
41
41
|
}
|
|
42
42
|
});
|
|
43
43
|
|
|
44
|
-
//# sourceMappingURL=normalize-callback-
|
|
44
|
+
//# sourceMappingURL=normalize-callback-BdLtk9jb.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"normalize-callback-
|
|
1
|
+
{"version":3,"file":"normalize-callback-BdLtk9jb.cjs","names":["Effect","AbortError","Effect"],"sources":["../src/utils/abort-interop.ts","../src/utils/normalize-callback.ts"],"sourcesContent":["import { Effect } from \"effect\"\nimport { AbortError } from \"../errors/upload-error.js\"\n\nexport const fromAbortSignal = (signal?: AbortSignal): Effect.Effect<never, AbortError> =>\n Effect.async<never, AbortError>((resume) => {\n if (!signal) return\n if (signal.aborted) {\n resume(Effect.fail(new AbortError()))\n return\n }\n const handler = (): void => resume(Effect.fail(new AbortError()))\n signal.addEventListener(\"abort\", handler, { once: true })\n return Effect.sync(() => signal.removeEventListener(\"abort\", handler))\n })\n","import { Effect } from \"effect\"\n\nexport const normalizeCallback = <A, E = never>(\n fn: () => A | Promise<A> | Effect.Effect<A, E>\n): Effect.Effect<A, E | unknown> =>\n Effect.suspend((): Effect.Effect<A, E | unknown> => {\n let result: A | Promise<A> | Effect.Effect<A, E>\n try {\n result = fn()\n } catch (e) {\n return Effect.fail(e)\n }\n if (Effect.isEffect(result)) return result\n if (result instanceof Promise) {\n return Effect.tryPromise({ try: () => result as Promise<A>, catch: (e) => e })\n }\n return Effect.succeed(result)\n })\n"],"mappings":";;;AAGA,MAAa,mBAAmB,WAC9BA,OAAAA,OAAO,OAA0B,WAAW;AAC1C,KAAI,CAAC,OAAQ;AACb,KAAI,OAAO,SAAS;AAClB,SAAOA,OAAAA,OAAO,KAAK,IAAIC,qBAAAA,YAAY,CAAC,CAAC;AACrC;;CAEF,MAAM,gBAAsB,OAAOD,OAAAA,OAAO,KAAK,IAAIC,qBAAAA,YAAY,CAAC,CAAC;AACjE,QAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,MAAM,CAAC;AACzD,QAAOD,OAAAA,OAAO,WAAW,OAAO,oBAAoB,SAAS,QAAQ,CAAC;EACtE;;;ACXJ,MAAa,qBACX,OAEAE,OAAAA,OAAO,cAA6C;CAClD,IAAI;AACJ,KAAI;AACF,WAAS,IAAI;UACN,GAAG;AACV,SAAOA,OAAAA,OAAO,KAAK,EAAE;;AAEvB,KAAIA,OAAAA,OAAO,SAAS,OAAO,CAAE,QAAO;AACpC,KAAI,kBAAkB,QACpB,QAAOA,OAAAA,OAAO,WAAW;EAAE,WAAW;EAAsB,QAAQ,MAAM;EAAG,CAAC;AAEhF,QAAOA,OAAAA,OAAO,QAAQ,OAAO;EAC7B"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { t as AbortError } from "./upload-error-
|
|
1
|
+
import { t as AbortError } from "./upload-error-Dbz_9j81.mjs";
|
|
2
2
|
import { Effect } from "effect";
|
|
3
3
|
//#region src/utils/abort-interop.ts
|
|
4
4
|
const fromAbortSignal = (signal) => Effect.async((resume) => {
|
|
@@ -30,4 +30,4 @@ const normalizeCallback = (fn) => Effect.suspend(() => {
|
|
|
30
30
|
//#endregion
|
|
31
31
|
export { fromAbortSignal as n, normalizeCallback as t };
|
|
32
32
|
|
|
33
|
-
//# sourceMappingURL=normalize-callback-
|
|
33
|
+
//# sourceMappingURL=normalize-callback-tcZ_nyq5.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"normalize-callback-
|
|
1
|
+
{"version":3,"file":"normalize-callback-tcZ_nyq5.mjs","names":[],"sources":["../src/utils/abort-interop.ts","../src/utils/normalize-callback.ts"],"sourcesContent":["import { Effect } from \"effect\"\nimport { AbortError } from \"../errors/upload-error.js\"\n\nexport const fromAbortSignal = (signal?: AbortSignal): Effect.Effect<never, AbortError> =>\n Effect.async<never, AbortError>((resume) => {\n if (!signal) return\n if (signal.aborted) {\n resume(Effect.fail(new AbortError()))\n return\n }\n const handler = (): void => resume(Effect.fail(new AbortError()))\n signal.addEventListener(\"abort\", handler, { once: true })\n return Effect.sync(() => signal.removeEventListener(\"abort\", handler))\n })\n","import { Effect } from \"effect\"\n\nexport const normalizeCallback = <A, E = never>(\n fn: () => A | Promise<A> | Effect.Effect<A, E>\n): Effect.Effect<A, E | unknown> =>\n Effect.suspend((): Effect.Effect<A, E | unknown> => {\n let result: A | Promise<A> | Effect.Effect<A, E>\n try {\n result = fn()\n } catch (e) {\n return Effect.fail(e)\n }\n if (Effect.isEffect(result)) return result\n if (result instanceof Promise) {\n return Effect.tryPromise({ try: () => result as Promise<A>, catch: (e) => e })\n }\n return Effect.succeed(result)\n })\n"],"mappings":";;;AAGA,MAAa,mBAAmB,WAC9B,OAAO,OAA0B,WAAW;AAC1C,KAAI,CAAC,OAAQ;AACb,KAAI,OAAO,SAAS;AAClB,SAAO,OAAO,KAAK,IAAI,YAAY,CAAC,CAAC;AACrC;;CAEF,MAAM,gBAAsB,OAAO,OAAO,KAAK,IAAI,YAAY,CAAC,CAAC;AACjE,QAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,MAAM,CAAC;AACzD,QAAO,OAAO,WAAW,OAAO,oBAAoB,SAAS,QAAQ,CAAC;EACtE;;;ACXJ,MAAa,qBACX,OAEA,OAAO,cAA6C;CAClD,IAAI;AACJ,KAAI;AACF,WAAS,IAAI;UACN,GAAG;AACV,SAAO,OAAO,KAAK,EAAE;;AAEvB,KAAI,OAAO,SAAS,OAAO,CAAE,QAAO;AACpC,KAAI,kBAAkB,QACpB,QAAO,OAAO,WAAW;EAAE,WAAW;EAAsB,QAAQ,MAAM;EAAG,CAAC;AAEhF,QAAO,OAAO,QAAQ,OAAO;EAC7B"}
|
package/dist/oneshot.cjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
const require_compression_service = require("./compression-service-Bn86iTJe.cjs");
|
|
3
3
|
const require_logger_service = require("./logger-service-cx8vzkXs.cjs");
|
|
4
|
-
const require_upload_error = require("./upload-error-
|
|
5
|
-
const require_normalize_callback = require("./normalize-callback-
|
|
4
|
+
const require_upload_error = require("./upload-error-BG1dOOl3.cjs");
|
|
5
|
+
const require_normalize_callback = require("./normalize-callback-BdLtk9jb.cjs");
|
|
6
6
|
let effect = require("effect");
|
|
7
7
|
//#region src/oneshot/upload.ts
|
|
8
8
|
const uploadOnceEffect = (options) => {
|
package/dist/oneshot.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { u as UploadError } from "./upload-error-DTYVNlaJ.cjs";
|
|
2
2
|
import { n as LoggerService } from "./logger-service-CbN12RhO.cjs";
|
|
3
|
-
import { a as UploadEvent, i as UploadCompleted } from "./upload-event-
|
|
3
|
+
import { a as UploadEvent, i as UploadCompleted } from "./upload-event-BT_nXgM9.cjs";
|
|
4
4
|
import { t as Transform } from "./middleware-CYcctmlY.cjs";
|
|
5
5
|
import { Effect, Stream } from "effect";
|
|
6
6
|
|
package/dist/oneshot.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { u as UploadError } from "./upload-error-jBco270d.mjs";
|
|
2
2
|
import { n as LoggerService } from "./logger-service-BF2pZOHN.mjs";
|
|
3
|
-
import { a as UploadEvent, i as UploadCompleted } from "./upload-event-
|
|
3
|
+
import { a as UploadEvent, i as UploadCompleted } from "./upload-event-DOGbegxa.mjs";
|
|
4
4
|
import { t as Transform } from "./middleware-CAI0cnW2.mjs";
|
|
5
5
|
import { Effect, Stream } from "effect";
|
|
6
6
|
|
package/dist/oneshot.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { n as CompressionServiceLive } from "./compression-service-Bm1VBnhT.mjs";
|
|
2
2
|
import { n as LoggerServiceLive, t as LoggerService } from "./logger-service-1J5r_akj.mjs";
|
|
3
|
-
import { r as CompleteUploadError, t as AbortError } from "./upload-error-
|
|
4
|
-
import { n as fromAbortSignal, t as normalizeCallback } from "./normalize-callback-
|
|
3
|
+
import { r as CompleteUploadError, t as AbortError } from "./upload-error-Dbz_9j81.mjs";
|
|
4
|
+
import { n as fromAbortSignal, t as normalizeCallback } from "./normalize-callback-tcZ_nyq5.mjs";
|
|
5
5
|
import { Cause, Effect, Exit, Stream } from "effect";
|
|
6
6
|
//#region src/oneshot/upload.ts
|
|
7
7
|
const uploadOnceEffect = (options) => {
|
package/dist/progress.d.cts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as UploadEvent, i as UploadCompleted, n as PartCompleted, o as UploadInitiated, r as ProgressTick, t as CircuitOpen } from "./upload-event-
|
|
2
|
-
import { n as Progress } from "./index-
|
|
1
|
+
import { a as UploadEvent, i as UploadCompleted, n as PartCompleted, o as UploadInitiated, r as ProgressTick, t as CircuitOpen } from "./upload-event-BT_nXgM9.cjs";
|
|
2
|
+
import { n as Progress } from "./index-BaeUV_fj.cjs";
|
|
3
3
|
export { type CircuitOpen, type PartCompleted, type Progress, type ProgressTick, type UploadCompleted, type UploadEvent, type UploadInitiated };
|
package/dist/progress.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { a as UploadEvent, i as UploadCompleted, n as PartCompleted, o as UploadInitiated, r as ProgressTick, t as CircuitOpen } from "./upload-event-
|
|
2
|
-
import { n as Progress } from "./index-
|
|
1
|
+
import { a as UploadEvent, i as UploadCompleted, n as PartCompleted, o as UploadInitiated, r as ProgressTick, t as CircuitOpen } from "./upload-event-DOGbegxa.mjs";
|
|
2
|
+
import { n as Progress } from "./index-bpWq6tje.mjs";
|
|
3
3
|
export { type CircuitOpen, type PartCompleted, type Progress, type ProgressTick, type UploadCompleted, type UploadEvent, type UploadInitiated };
|
|
@@ -66,6 +66,25 @@ var CircuitOpenError = class extends Error {
|
|
|
66
66
|
this.name = "CircuitOpenError";
|
|
67
67
|
}
|
|
68
68
|
};
|
|
69
|
+
/**
|
|
70
|
+
* Raised when a resume attempt fails pre-flight validation (before any byte is
|
|
71
|
+
* uploaded). The `reason` discriminant identifies *why* the resume is unsafe.
|
|
72
|
+
*
|
|
73
|
+
* **Stylistic exception:** unlike the other variants (one class per `_tag`),
|
|
74
|
+
* `ResumeMismatchError` is a single class with a `reason` discriminant. The
|
|
75
|
+
* pre-flight validation refusal is *one* kind of error; `reason` carries its
|
|
76
|
+
* specific cause. Per-reason dispatch is via `Match.value(err.reason)` inside
|
|
77
|
+
* a `Match.tag("ResumeMismatchError", ...)` handler, or a `switch (err.reason)`.
|
|
78
|
+
*/
|
|
79
|
+
var ResumeMismatchError = class extends Error {
|
|
80
|
+
_tag = "ResumeMismatchError";
|
|
81
|
+
constructor(reason, cause) {
|
|
82
|
+
super(`Resume state mismatch: ${reason}`);
|
|
83
|
+
this.reason = reason;
|
|
84
|
+
this.cause = cause;
|
|
85
|
+
this.name = "ResumeMismatchError";
|
|
86
|
+
}
|
|
87
|
+
};
|
|
69
88
|
//#endregion
|
|
70
89
|
Object.defineProperty(exports, "AbortError", {
|
|
71
90
|
enumerable: true,
|
|
@@ -115,5 +134,11 @@ Object.defineProperty(exports, "ReconcileError", {
|
|
|
115
134
|
return ReconcileError;
|
|
116
135
|
}
|
|
117
136
|
});
|
|
137
|
+
Object.defineProperty(exports, "ResumeMismatchError", {
|
|
138
|
+
enumerable: true,
|
|
139
|
+
get: function() {
|
|
140
|
+
return ResumeMismatchError;
|
|
141
|
+
}
|
|
142
|
+
});
|
|
118
143
|
|
|
119
|
-
//# sourceMappingURL=upload-error-
|
|
144
|
+
//# sourceMappingURL=upload-error-BG1dOOl3.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload-error-BG1dOOl3.cjs","names":[],"sources":["../src/errors/upload-error.ts"],"sourcesContent":["export class PartUploadError extends Error {\n readonly _tag = \"PartUploadError\" as const\n\n constructor(\n readonly partNumber: number,\n readonly attempt: number,\n override readonly cause: unknown\n ) {\n super(`Part ${partNumber} failed on attempt ${attempt}`)\n this.name = \"PartUploadError\"\n }\n}\n\nexport class MaxRetriesExceededError extends Error {\n readonly _tag = \"MaxRetriesExceededError\" as const\n\n constructor(\n readonly partNumber: number,\n readonly totalAttempts: number,\n override readonly cause: unknown\n ) {\n super(`Part ${partNumber} failed after ${totalAttempts} attempts`)\n this.name = \"MaxRetriesExceededError\"\n }\n}\n\nexport class PresignedUrlError extends Error {\n readonly _tag = \"PresignedUrlError\" as const\n\n constructor(override readonly cause: unknown) {\n super(\"Failed to obtain pre-signed URL\")\n this.name = \"PresignedUrlError\"\n }\n}\n\nexport class InitiateUploadError extends Error {\n readonly _tag = \"InitiateUploadError\" as const\n\n constructor(override readonly cause: unknown) {\n super(\"Failed to initiate multipart upload\")\n this.name = \"InitiateUploadError\"\n }\n}\n\nexport class ReconcileError extends Error {\n readonly _tag = \"ReconcileError\" as const\n\n constructor(override readonly cause: unknown) {\n super(\"Failed to reconcile completed parts\")\n this.name = \"ReconcileError\"\n }\n}\n\nexport class CompleteUploadError extends Error {\n readonly _tag = \"CompleteUploadError\" as const\n\n constructor(override readonly cause: unknown) {\n super(\"Failed to complete multipart upload\")\n this.name = \"CompleteUploadError\"\n }\n}\n\nexport class AbortError extends Error {\n readonly _tag = \"AbortError\" as const\n\n constructor() {\n super(\"Upload aborted\")\n this.name = \"AbortError\"\n }\n}\n\nexport class CircuitOpenError extends Error {\n readonly _tag = \"CircuitOpenError\" as const\n\n constructor(readonly failedParts: number) {\n super(`Circuit breaker opened after ${failedParts} consecutive part failures`)\n this.name = \"CircuitOpenError\"\n }\n}\n\n/**\n * Raised when a resume attempt fails pre-flight validation (before any byte is\n * uploaded). The `reason` discriminant identifies *why* the resume is unsafe.\n *\n * **Stylistic exception:** unlike the other variants (one class per `_tag`),\n * `ResumeMismatchError` is a single class with a `reason` discriminant. The\n * pre-flight validation refusal is *one* kind of error; `reason` carries its\n * specific cause. Per-reason dispatch is via `Match.value(err.reason)` inside\n * a `Match.tag(\"ResumeMismatchError\", ...)` handler, or a `switch (err.reason)`.\n */\nexport class ResumeMismatchError extends Error {\n readonly _tag = \"ResumeMismatchError\" as const\n\n constructor(\n readonly reason:\n | \"version_mismatch\"\n | \"chunksize_mismatch\"\n | \"pipeline_mismatch\"\n | \"content_mismatch\",\n override readonly cause?: unknown\n ) {\n super(`Resume state mismatch: ${reason}`)\n this.name = \"ResumeMismatchError\"\n }\n}\n\nexport type UploadError =\n | PartUploadError\n | MaxRetriesExceededError\n | PresignedUrlError\n | InitiateUploadError\n | ReconcileError\n | CompleteUploadError\n | AbortError\n | CircuitOpenError\n | ResumeMismatchError\n"],"mappings":";AAAA,IAAa,kBAAb,cAAqC,MAAM;CACzC,OAAgB;CAEhB,YACE,YACA,SACA,OACA;AACA,QAAM,QAAQ,WAAW,qBAAqB,UAAU;AAJ/C,OAAA,aAAA;AACA,OAAA,UAAA;AACS,OAAA,QAAA;AAGlB,OAAK,OAAO;;;AAIhB,IAAa,0BAAb,cAA6C,MAAM;CACjD,OAAgB;CAEhB,YACE,YACA,eACA,OACA;AACA,QAAM,QAAQ,WAAW,gBAAgB,cAAc,WAAW;AAJzD,OAAA,aAAA;AACA,OAAA,gBAAA;AACS,OAAA,QAAA;AAGlB,OAAK,OAAO;;;AAIhB,IAAa,oBAAb,cAAuC,MAAM;CAC3C,OAAgB;CAEhB,YAAY,OAAkC;AAC5C,QAAM,kCAAkC;AADZ,OAAA,QAAA;AAE5B,OAAK,OAAO;;;AAIhB,IAAa,sBAAb,cAAyC,MAAM;CAC7C,OAAgB;CAEhB,YAAY,OAAkC;AAC5C,QAAM,sCAAsC;AADhB,OAAA,QAAA;AAE5B,OAAK,OAAO;;;AAIhB,IAAa,iBAAb,cAAoC,MAAM;CACxC,OAAgB;CAEhB,YAAY,OAAkC;AAC5C,QAAM,sCAAsC;AADhB,OAAA,QAAA;AAE5B,OAAK,OAAO;;;AAIhB,IAAa,sBAAb,cAAyC,MAAM;CAC7C,OAAgB;CAEhB,YAAY,OAAkC;AAC5C,QAAM,sCAAsC;AADhB,OAAA,QAAA;AAE5B,OAAK,OAAO;;;AAIhB,IAAa,aAAb,cAAgC,MAAM;CACpC,OAAgB;CAEhB,cAAc;AACZ,QAAM,iBAAiB;AACvB,OAAK,OAAO;;;AAIhB,IAAa,mBAAb,cAAsC,MAAM;CAC1C,OAAgB;CAEhB,YAAY,aAA8B;AACxC,QAAM,gCAAgC,YAAY,4BAA4B;AAD3D,OAAA,cAAA;AAEnB,OAAK,OAAO;;;;;;;;;;;;;AAchB,IAAa,sBAAb,cAAyC,MAAM;CAC7C,OAAgB;CAEhB,YACE,QAKA,OACA;AACA,QAAM,0BAA0B,SAAS;AAPhC,OAAA,SAAA;AAKS,OAAA,QAAA;AAGlB,OAAK,OAAO"}
|
|
@@ -42,7 +42,23 @@ declare class CircuitOpenError extends Error {
|
|
|
42
42
|
readonly _tag: "CircuitOpenError";
|
|
43
43
|
constructor(failedParts: number);
|
|
44
44
|
}
|
|
45
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Raised when a resume attempt fails pre-flight validation (before any byte is
|
|
47
|
+
* uploaded). The `reason` discriminant identifies *why* the resume is unsafe.
|
|
48
|
+
*
|
|
49
|
+
* **Stylistic exception:** unlike the other variants (one class per `_tag`),
|
|
50
|
+
* `ResumeMismatchError` is a single class with a `reason` discriminant. The
|
|
51
|
+
* pre-flight validation refusal is *one* kind of error; `reason` carries its
|
|
52
|
+
* specific cause. Per-reason dispatch is via `Match.value(err.reason)` inside
|
|
53
|
+
* a `Match.tag("ResumeMismatchError", ...)` handler, or a `switch (err.reason)`.
|
|
54
|
+
*/
|
|
55
|
+
declare class ResumeMismatchError extends Error {
|
|
56
|
+
readonly reason: "version_mismatch" | "chunksize_mismatch" | "pipeline_mismatch" | "content_mismatch";
|
|
57
|
+
readonly cause?: unknown | undefined;
|
|
58
|
+
readonly _tag: "ResumeMismatchError";
|
|
59
|
+
constructor(reason: "version_mismatch" | "chunksize_mismatch" | "pipeline_mismatch" | "content_mismatch", cause?: unknown | undefined);
|
|
60
|
+
}
|
|
61
|
+
type UploadError = PartUploadError | MaxRetriesExceededError | PresignedUrlError | InitiateUploadError | ReconcileError | CompleteUploadError | AbortError | CircuitOpenError | ResumeMismatchError;
|
|
46
62
|
//#endregion
|
|
47
|
-
export {
|
|
48
|
-
//# sourceMappingURL=upload-error-
|
|
63
|
+
export { MaxRetriesExceededError as a, ReconcileError as c, InitiateUploadError as i, ResumeMismatchError as l, CircuitOpenError as n, PartUploadError as o, CompleteUploadError as r, PresignedUrlError as s, AbortError as t, UploadError as u };
|
|
64
|
+
//# sourceMappingURL=upload-error-DTYVNlaJ.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upload-error-
|
|
1
|
+
{"version":3,"file":"upload-error-DTYVNlaJ.d.cts","names":[],"sources":["../src/errors/upload-error.ts"],"mappings":";cAAa,eAAA,SAAwB,KAAA;EAAA,SAIxB,UAAA;EAAA,SACA,OAAA;EAAA,SACS,KAAA;EAAA,SALX,IAAA;cAGE,UAAA,UACA,OAAA,UACS,KAAA;AAAA;AAAA,cAOT,uBAAA,SAAgC,KAAA;EAAA,SAIhC,UAAA;EAAA,SACA,aAAA;EAAA,SACS,KAAA;EAAA,SALX,IAAA;cAGE,UAAA,UACA,aAAA,UACS,KAAA;AAAA;AAAA,cAOT,iBAAA,SAA0B,KAAA;EAAA,SAGP,KAAA;EAAA,SAFrB,IAAA;cAEqB,KAAA;AAAA;AAAA,cAMnB,mBAAA,SAA4B,KAAA;EAAA,SAGT,KAAA;EAAA,SAFrB,IAAA;cAEqB,KAAA;AAAA;AAAA,cAMnB,cAAA,SAAuB,KAAA;EAAA,SAGJ,KAAA;EAAA,SAFrB,IAAA;cAEqB,KAAA;AAAA;AAAA,cAMnB,mBAAA,SAA4B,KAAA;EAAA,SAGT,KAAA;EAAA,SAFrB,IAAA;cAEqB,KAAA;AAAA;AAAA,cAMnB,UAAA,SAAmB,KAAA;EAAA,SACrB,IAAA;;;cAQE,gBAAA,SAAyB,KAAA;EAAA,SAGf,WAAA;EAAA,SAFZ,IAAA;cAEY,WAAA;AAAA;;;;;;;;;;;cAgBV,mBAAA,SAA4B,KAAA;EAAA,SAI5B,MAAA;EAAA,SAKS,KAAA;EAAA,SARX,IAAA;cAGE,MAAA,wFAKS,KAAA;AAAA;AAAA,KAOV,WAAA,GACR,eAAA,GACA,uBAAA,GACA,iBAAA,GACA,mBAAA,GACA,cAAA,GACA,mBAAA,GACA,UAAA,GACA,gBAAA,GACA,mBAAA"}
|
|
@@ -66,7 +66,26 @@ var CircuitOpenError = class extends Error {
|
|
|
66
66
|
this.name = "CircuitOpenError";
|
|
67
67
|
}
|
|
68
68
|
};
|
|
69
|
+
/**
|
|
70
|
+
* Raised when a resume attempt fails pre-flight validation (before any byte is
|
|
71
|
+
* uploaded). The `reason` discriminant identifies *why* the resume is unsafe.
|
|
72
|
+
*
|
|
73
|
+
* **Stylistic exception:** unlike the other variants (one class per `_tag`),
|
|
74
|
+
* `ResumeMismatchError` is a single class with a `reason` discriminant. The
|
|
75
|
+
* pre-flight validation refusal is *one* kind of error; `reason` carries its
|
|
76
|
+
* specific cause. Per-reason dispatch is via `Match.value(err.reason)` inside
|
|
77
|
+
* a `Match.tag("ResumeMismatchError", ...)` handler, or a `switch (err.reason)`.
|
|
78
|
+
*/
|
|
79
|
+
var ResumeMismatchError = class extends Error {
|
|
80
|
+
_tag = "ResumeMismatchError";
|
|
81
|
+
constructor(reason, cause) {
|
|
82
|
+
super(`Resume state mismatch: ${reason}`);
|
|
83
|
+
this.reason = reason;
|
|
84
|
+
this.cause = cause;
|
|
85
|
+
this.name = "ResumeMismatchError";
|
|
86
|
+
}
|
|
87
|
+
};
|
|
69
88
|
//#endregion
|
|
70
|
-
export { MaxRetriesExceededError as a, ReconcileError as c, InitiateUploadError as i, CircuitOpenError as n, PartUploadError as o, CompleteUploadError as r, PresignedUrlError as s, AbortError as t };
|
|
89
|
+
export { MaxRetriesExceededError as a, ReconcileError as c, InitiateUploadError as i, ResumeMismatchError as l, CircuitOpenError as n, PartUploadError as o, CompleteUploadError as r, PresignedUrlError as s, AbortError as t };
|
|
71
90
|
|
|
72
|
-
//# sourceMappingURL=upload-error-
|
|
91
|
+
//# sourceMappingURL=upload-error-Dbz_9j81.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload-error-Dbz_9j81.mjs","names":[],"sources":["../src/errors/upload-error.ts"],"sourcesContent":["export class PartUploadError extends Error {\n readonly _tag = \"PartUploadError\" as const\n\n constructor(\n readonly partNumber: number,\n readonly attempt: number,\n override readonly cause: unknown\n ) {\n super(`Part ${partNumber} failed on attempt ${attempt}`)\n this.name = \"PartUploadError\"\n }\n}\n\nexport class MaxRetriesExceededError extends Error {\n readonly _tag = \"MaxRetriesExceededError\" as const\n\n constructor(\n readonly partNumber: number,\n readonly totalAttempts: number,\n override readonly cause: unknown\n ) {\n super(`Part ${partNumber} failed after ${totalAttempts} attempts`)\n this.name = \"MaxRetriesExceededError\"\n }\n}\n\nexport class PresignedUrlError extends Error {\n readonly _tag = \"PresignedUrlError\" as const\n\n constructor(override readonly cause: unknown) {\n super(\"Failed to obtain pre-signed URL\")\n this.name = \"PresignedUrlError\"\n }\n}\n\nexport class InitiateUploadError extends Error {\n readonly _tag = \"InitiateUploadError\" as const\n\n constructor(override readonly cause: unknown) {\n super(\"Failed to initiate multipart upload\")\n this.name = \"InitiateUploadError\"\n }\n}\n\nexport class ReconcileError extends Error {\n readonly _tag = \"ReconcileError\" as const\n\n constructor(override readonly cause: unknown) {\n super(\"Failed to reconcile completed parts\")\n this.name = \"ReconcileError\"\n }\n}\n\nexport class CompleteUploadError extends Error {\n readonly _tag = \"CompleteUploadError\" as const\n\n constructor(override readonly cause: unknown) {\n super(\"Failed to complete multipart upload\")\n this.name = \"CompleteUploadError\"\n }\n}\n\nexport class AbortError extends Error {\n readonly _tag = \"AbortError\" as const\n\n constructor() {\n super(\"Upload aborted\")\n this.name = \"AbortError\"\n }\n}\n\nexport class CircuitOpenError extends Error {\n readonly _tag = \"CircuitOpenError\" as const\n\n constructor(readonly failedParts: number) {\n super(`Circuit breaker opened after ${failedParts} consecutive part failures`)\n this.name = \"CircuitOpenError\"\n }\n}\n\n/**\n * Raised when a resume attempt fails pre-flight validation (before any byte is\n * uploaded). The `reason` discriminant identifies *why* the resume is unsafe.\n *\n * **Stylistic exception:** unlike the other variants (one class per `_tag`),\n * `ResumeMismatchError` is a single class with a `reason` discriminant. The\n * pre-flight validation refusal is *one* kind of error; `reason` carries its\n * specific cause. Per-reason dispatch is via `Match.value(err.reason)` inside\n * a `Match.tag(\"ResumeMismatchError\", ...)` handler, or a `switch (err.reason)`.\n */\nexport class ResumeMismatchError extends Error {\n readonly _tag = \"ResumeMismatchError\" as const\n\n constructor(\n readonly reason:\n | \"version_mismatch\"\n | \"chunksize_mismatch\"\n | \"pipeline_mismatch\"\n | \"content_mismatch\",\n override readonly cause?: unknown\n ) {\n super(`Resume state mismatch: ${reason}`)\n this.name = \"ResumeMismatchError\"\n }\n}\n\nexport type UploadError =\n | PartUploadError\n | MaxRetriesExceededError\n | PresignedUrlError\n | InitiateUploadError\n | ReconcileError\n | CompleteUploadError\n | AbortError\n | CircuitOpenError\n | ResumeMismatchError\n"],"mappings":";AAAA,IAAa,kBAAb,cAAqC,MAAM;CACzC,OAAgB;CAEhB,YACE,YACA,SACA,OACA;AACA,QAAM,QAAQ,WAAW,qBAAqB,UAAU;AAJ/C,OAAA,aAAA;AACA,OAAA,UAAA;AACS,OAAA,QAAA;AAGlB,OAAK,OAAO;;;AAIhB,IAAa,0BAAb,cAA6C,MAAM;CACjD,OAAgB;CAEhB,YACE,YACA,eACA,OACA;AACA,QAAM,QAAQ,WAAW,gBAAgB,cAAc,WAAW;AAJzD,OAAA,aAAA;AACA,OAAA,gBAAA;AACS,OAAA,QAAA;AAGlB,OAAK,OAAO;;;AAIhB,IAAa,oBAAb,cAAuC,MAAM;CAC3C,OAAgB;CAEhB,YAAY,OAAkC;AAC5C,QAAM,kCAAkC;AADZ,OAAA,QAAA;AAE5B,OAAK,OAAO;;;AAIhB,IAAa,sBAAb,cAAyC,MAAM;CAC7C,OAAgB;CAEhB,YAAY,OAAkC;AAC5C,QAAM,sCAAsC;AADhB,OAAA,QAAA;AAE5B,OAAK,OAAO;;;AAIhB,IAAa,iBAAb,cAAoC,MAAM;CACxC,OAAgB;CAEhB,YAAY,OAAkC;AAC5C,QAAM,sCAAsC;AADhB,OAAA,QAAA;AAE5B,OAAK,OAAO;;;AAIhB,IAAa,sBAAb,cAAyC,MAAM;CAC7C,OAAgB;CAEhB,YAAY,OAAkC;AAC5C,QAAM,sCAAsC;AADhB,OAAA,QAAA;AAE5B,OAAK,OAAO;;;AAIhB,IAAa,aAAb,cAAgC,MAAM;CACpC,OAAgB;CAEhB,cAAc;AACZ,QAAM,iBAAiB;AACvB,OAAK,OAAO;;;AAIhB,IAAa,mBAAb,cAAsC,MAAM;CAC1C,OAAgB;CAEhB,YAAY,aAA8B;AACxC,QAAM,gCAAgC,YAAY,4BAA4B;AAD3D,OAAA,cAAA;AAEnB,OAAK,OAAO;;;;;;;;;;;;;AAchB,IAAa,sBAAb,cAAyC,MAAM;CAC7C,OAAgB;CAEhB,YACE,QAKA,OACA;AACA,QAAM,0BAA0B,SAAS;AAPhC,OAAA,SAAA;AAKS,OAAA,QAAA;AAGlB,OAAK,OAAO"}
|
|
@@ -42,7 +42,23 @@ declare class CircuitOpenError extends Error {
|
|
|
42
42
|
readonly _tag: "CircuitOpenError";
|
|
43
43
|
constructor(failedParts: number);
|
|
44
44
|
}
|
|
45
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Raised when a resume attempt fails pre-flight validation (before any byte is
|
|
47
|
+
* uploaded). The `reason` discriminant identifies *why* the resume is unsafe.
|
|
48
|
+
*
|
|
49
|
+
* **Stylistic exception:** unlike the other variants (one class per `_tag`),
|
|
50
|
+
* `ResumeMismatchError` is a single class with a `reason` discriminant. The
|
|
51
|
+
* pre-flight validation refusal is *one* kind of error; `reason` carries its
|
|
52
|
+
* specific cause. Per-reason dispatch is via `Match.value(err.reason)` inside
|
|
53
|
+
* a `Match.tag("ResumeMismatchError", ...)` handler, or a `switch (err.reason)`.
|
|
54
|
+
*/
|
|
55
|
+
declare class ResumeMismatchError extends Error {
|
|
56
|
+
readonly reason: "version_mismatch" | "chunksize_mismatch" | "pipeline_mismatch" | "content_mismatch";
|
|
57
|
+
readonly cause?: unknown | undefined;
|
|
58
|
+
readonly _tag: "ResumeMismatchError";
|
|
59
|
+
constructor(reason: "version_mismatch" | "chunksize_mismatch" | "pipeline_mismatch" | "content_mismatch", cause?: unknown | undefined);
|
|
60
|
+
}
|
|
61
|
+
type UploadError = PartUploadError | MaxRetriesExceededError | PresignedUrlError | InitiateUploadError | ReconcileError | CompleteUploadError | AbortError | CircuitOpenError | ResumeMismatchError;
|
|
46
62
|
//#endregion
|
|
47
|
-
export {
|
|
48
|
-
//# sourceMappingURL=upload-error-
|
|
63
|
+
export { MaxRetriesExceededError as a, ReconcileError as c, InitiateUploadError as i, ResumeMismatchError as l, CircuitOpenError as n, PartUploadError as o, CompleteUploadError as r, PresignedUrlError as s, AbortError as t, UploadError as u };
|
|
64
|
+
//# sourceMappingURL=upload-error-jBco270d.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upload-error-
|
|
1
|
+
{"version":3,"file":"upload-error-jBco270d.d.mts","names":[],"sources":["../src/errors/upload-error.ts"],"mappings":";cAAa,eAAA,SAAwB,KAAA;EAAA,SAIxB,UAAA;EAAA,SACA,OAAA;EAAA,SACS,KAAA;EAAA,SALX,IAAA;cAGE,UAAA,UACA,OAAA,UACS,KAAA;AAAA;AAAA,cAOT,uBAAA,SAAgC,KAAA;EAAA,SAIhC,UAAA;EAAA,SACA,aAAA;EAAA,SACS,KAAA;EAAA,SALX,IAAA;cAGE,UAAA,UACA,aAAA,UACS,KAAA;AAAA;AAAA,cAOT,iBAAA,SAA0B,KAAA;EAAA,SAGP,KAAA;EAAA,SAFrB,IAAA;cAEqB,KAAA;AAAA;AAAA,cAMnB,mBAAA,SAA4B,KAAA;EAAA,SAGT,KAAA;EAAA,SAFrB,IAAA;cAEqB,KAAA;AAAA;AAAA,cAMnB,cAAA,SAAuB,KAAA;EAAA,SAGJ,KAAA;EAAA,SAFrB,IAAA;cAEqB,KAAA;AAAA;AAAA,cAMnB,mBAAA,SAA4B,KAAA;EAAA,SAGT,KAAA;EAAA,SAFrB,IAAA;cAEqB,KAAA;AAAA;AAAA,cAMnB,UAAA,SAAmB,KAAA;EAAA,SACrB,IAAA;;;cAQE,gBAAA,SAAyB,KAAA;EAAA,SAGf,WAAA;EAAA,SAFZ,IAAA;cAEY,WAAA;AAAA;;;;;;;;;;;cAgBV,mBAAA,SAA4B,KAAA;EAAA,SAI5B,MAAA;EAAA,SAKS,KAAA;EAAA,SARX,IAAA;cAGE,MAAA,wFAKS,KAAA;AAAA;AAAA,KAOV,WAAA,GACR,eAAA,GACA,uBAAA,GACA,iBAAA,GACA,mBAAA,GACA,cAAA,GACA,mBAAA,GACA,UAAA,GACA,gBAAA,GACA,mBAAA"}
|
|
@@ -4,6 +4,12 @@ import { Option } from "effect";
|
|
|
4
4
|
interface UploadInitiated {
|
|
5
5
|
readonly _tag: "UploadInitiated";
|
|
6
6
|
readonly uploadId: string;
|
|
7
|
+
/**
|
|
8
|
+
* Populated when `getContentDigest` was provided on the fresh-init path.
|
|
9
|
+
* Carried on the event so the public wrapper can build a complete `ResumeState`
|
|
10
|
+
* without needing access to the internal digest Ref.
|
|
11
|
+
*/
|
|
12
|
+
readonly contentDigest?: string;
|
|
7
13
|
readonly timestamp: number;
|
|
8
14
|
}
|
|
9
15
|
interface UploadCompleted {
|
|
@@ -33,4 +39,4 @@ interface ProgressTick {
|
|
|
33
39
|
type UploadEvent = UploadInitiated | UploadCompleted | PartCompleted | ProgressTick | CircuitOpen;
|
|
34
40
|
//#endregion
|
|
35
41
|
export { UploadEvent as a, UploadCompleted as i, PartCompleted as n, UploadInitiated as o, ProgressTick as r, CircuitOpen as t };
|
|
36
|
-
//# sourceMappingURL=upload-event-
|
|
42
|
+
//# sourceMappingURL=upload-event-BT_nXgM9.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload-event-BT_nXgM9.d.cts","names":[],"sources":["../src/progress/upload-event.ts"],"mappings":";;;UAEiB,eAAA;EAAA,SACN,IAAA;EAAA,SACA,QAAA;EAFqB;;;;;EAAA,SAQrB,aAAA;EAAA,SACA,SAAA;AAAA;AAAA,UAGM,eAAA;EAAA,SACN,IAAA;EAAA,SACA,QAAA;EAAA,SACA,UAAA;EAAA,SACA,SAAA;AAAA;AAAA,UAGM,aAAA;EAAA,SACN,IAAA;EAAA,SACA,UAAA;EAAA,SACA,IAAA;EAAA,SACA,aAAA;EAAA,SACA,SAAA;AAAA;AAAA,UAGM,WAAA;EAAA,SACN,IAAA;EAAA,SACA,WAAA;EAAA,SACA,SAAA;AAAA;AAAA,UAGM,YAAA;EAAA,SACN,IAAA;EAAA,SACA,aAAA;EAAA,SACA,UAAA,EAAY,MAAA,CAAO,MAAA;EAAA,SACnB,SAAA;AAAA;AAAA,KAGC,WAAA,GAAc,eAAA,GAAkB,eAAA,GAAkB,aAAA,GAAgB,YAAA,GAAe,WAAA"}
|
|
@@ -4,6 +4,12 @@ import { Option } from "effect";
|
|
|
4
4
|
interface UploadInitiated {
|
|
5
5
|
readonly _tag: "UploadInitiated";
|
|
6
6
|
readonly uploadId: string;
|
|
7
|
+
/**
|
|
8
|
+
* Populated when `getContentDigest` was provided on the fresh-init path.
|
|
9
|
+
* Carried on the event so the public wrapper can build a complete `ResumeState`
|
|
10
|
+
* without needing access to the internal digest Ref.
|
|
11
|
+
*/
|
|
12
|
+
readonly contentDigest?: string;
|
|
7
13
|
readonly timestamp: number;
|
|
8
14
|
}
|
|
9
15
|
interface UploadCompleted {
|
|
@@ -33,4 +39,4 @@ interface ProgressTick {
|
|
|
33
39
|
type UploadEvent = UploadInitiated | UploadCompleted | PartCompleted | ProgressTick | CircuitOpen;
|
|
34
40
|
//#endregion
|
|
35
41
|
export { UploadEvent as a, UploadCompleted as i, PartCompleted as n, UploadInitiated as o, ProgressTick as r, CircuitOpen as t };
|
|
36
|
-
//# sourceMappingURL=upload-event-
|
|
42
|
+
//# sourceMappingURL=upload-event-DOGbegxa.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"upload-event-DOGbegxa.d.mts","names":[],"sources":["../src/progress/upload-event.ts"],"mappings":";;;UAEiB,eAAA;EAAA,SACN,IAAA;EAAA,SACA,QAAA;EAFqB;;;;;EAAA,SAQrB,aAAA;EAAA,SACA,SAAA;AAAA;AAAA,UAGM,eAAA;EAAA,SACN,IAAA;EAAA,SACA,QAAA;EAAA,SACA,UAAA;EAAA,SACA,SAAA;AAAA;AAAA,UAGM,aAAA;EAAA,SACN,IAAA;EAAA,SACA,UAAA;EAAA,SACA,IAAA;EAAA,SACA,aAAA;EAAA,SACA,SAAA;AAAA;AAAA,UAGM,WAAA;EAAA,SACN,IAAA;EAAA,SACA,WAAA;EAAA,SACA,SAAA;AAAA;AAAA,UAGM,YAAA;EAAA,SACN,IAAA;EAAA,SACA,aAAA;EAAA,SACA,UAAA,EAAY,MAAA,CAAO,MAAA;EAAA,SACnB,SAAA;AAAA;AAAA,KAGC,WAAA,GAAc,eAAA,GAAkB,eAAA,GAAkB,aAAA,GAAgB,YAAA,GAAe,WAAA"}
|
package/package.json
CHANGED
package/src/errors/index.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
CompleteUploadError,
|
|
9
9
|
AbortError,
|
|
10
10
|
CircuitOpenError,
|
|
11
|
+
ResumeMismatchError,
|
|
11
12
|
type UploadError,
|
|
12
13
|
} from './upload-error.js'
|
|
13
14
|
|
|
@@ -188,6 +189,38 @@ describe("AbortError", () => {
|
|
|
188
189
|
})
|
|
189
190
|
})
|
|
190
191
|
|
|
192
|
+
describe("ResumeMismatchError", () => {
|
|
193
|
+
it("is instanceof Error", () => {
|
|
194
|
+
const err = new ResumeMismatchError("version_mismatch")
|
|
195
|
+
expect(err instanceof Error).toBe(true)
|
|
196
|
+
})
|
|
197
|
+
it("has correct _tag", () => {
|
|
198
|
+
const err = new ResumeMismatchError("chunksize_mismatch")
|
|
199
|
+
expect(err._tag).toBe("ResumeMismatchError")
|
|
200
|
+
})
|
|
201
|
+
it("has human-readable message including reason", () => {
|
|
202
|
+
const err = new ResumeMismatchError("pipeline_mismatch")
|
|
203
|
+
expect(err.message).toBe("Resume state mismatch: pipeline_mismatch")
|
|
204
|
+
})
|
|
205
|
+
it("name equals _tag for logger compat", () => {
|
|
206
|
+
const err = new ResumeMismatchError("content_mismatch")
|
|
207
|
+
expect(err.name).toBe("ResumeMismatchError")
|
|
208
|
+
})
|
|
209
|
+
it("preserves the reason discriminant", () => {
|
|
210
|
+
const err = new ResumeMismatchError("content_mismatch")
|
|
211
|
+
expect(err.reason).toBe("content_mismatch")
|
|
212
|
+
})
|
|
213
|
+
it("preserves optional cause", () => {
|
|
214
|
+
const cause = new Error("digest function threw")
|
|
215
|
+
const err = new ResumeMismatchError("content_mismatch", cause)
|
|
216
|
+
expect(err.cause).toBe(cause)
|
|
217
|
+
})
|
|
218
|
+
it("cause is undefined when omitted", () => {
|
|
219
|
+
const err = new ResumeMismatchError("version_mismatch")
|
|
220
|
+
expect(err.cause).toBeUndefined()
|
|
221
|
+
})
|
|
222
|
+
})
|
|
223
|
+
|
|
191
224
|
it("UploadError union is exhaustive", () => {
|
|
192
225
|
const check = (err: UploadError): string => {
|
|
193
226
|
switch (err._tag) {
|
|
@@ -199,6 +232,7 @@ it("UploadError union is exhaustive", () => {
|
|
|
199
232
|
case "CompleteUploadError": return "complete"
|
|
200
233
|
case "AbortError": return "abort"
|
|
201
234
|
case "CircuitOpenError": return "circuitOpen"
|
|
235
|
+
case "ResumeMismatchError": return "resumeMismatch"
|
|
202
236
|
default: {
|
|
203
237
|
// If a new variant is added to UploadError without a matching case above,
|
|
204
238
|
// TypeScript will error here: "Type 'NewVariant' is not assignable to type 'never'"
|
|
@@ -215,4 +249,5 @@ it("UploadError union is exhaustive", () => {
|
|
|
215
249
|
expect(check(new PartUploadError(1, 1, null))).toBe("part")
|
|
216
250
|
expect(check(new MaxRetriesExceededError(1, 1, null))).toBe("maxRetries")
|
|
217
251
|
expect(check(new CircuitOpenError(3))).toBe("circuitOpen")
|
|
252
|
+
expect(check(new ResumeMismatchError("chunksize_mismatch"))).toBe("resumeMismatch")
|
|
218
253
|
})
|
|
@@ -78,6 +78,32 @@ export class CircuitOpenError extends Error {
|
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Raised when a resume attempt fails pre-flight validation (before any byte is
|
|
83
|
+
* uploaded). The `reason` discriminant identifies *why* the resume is unsafe.
|
|
84
|
+
*
|
|
85
|
+
* **Stylistic exception:** unlike the other variants (one class per `_tag`),
|
|
86
|
+
* `ResumeMismatchError` is a single class with a `reason` discriminant. The
|
|
87
|
+
* pre-flight validation refusal is *one* kind of error; `reason` carries its
|
|
88
|
+
* specific cause. Per-reason dispatch is via `Match.value(err.reason)` inside
|
|
89
|
+
* a `Match.tag("ResumeMismatchError", ...)` handler, or a `switch (err.reason)`.
|
|
90
|
+
*/
|
|
91
|
+
export class ResumeMismatchError extends Error {
|
|
92
|
+
readonly _tag = "ResumeMismatchError" as const
|
|
93
|
+
|
|
94
|
+
constructor(
|
|
95
|
+
readonly reason:
|
|
96
|
+
| "version_mismatch"
|
|
97
|
+
| "chunksize_mismatch"
|
|
98
|
+
| "pipeline_mismatch"
|
|
99
|
+
| "content_mismatch",
|
|
100
|
+
override readonly cause?: unknown
|
|
101
|
+
) {
|
|
102
|
+
super(`Resume state mismatch: ${reason}`)
|
|
103
|
+
this.name = "ResumeMismatchError"
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
81
107
|
export type UploadError =
|
|
82
108
|
| PartUploadError
|
|
83
109
|
| MaxRetriesExceededError
|
|
@@ -87,3 +113,4 @@ export type UploadError =
|
|
|
87
113
|
| CompleteUploadError
|
|
88
114
|
| AbortError
|
|
89
115
|
| CircuitOpenError
|
|
116
|
+
| ResumeMismatchError
|