@tranquilload/core 0.1.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/.turbo/turbo-build.log +88 -0
- package/dist/compression-service-Bm1VBnhT.mjs +18 -0
- package/dist/compression-service-Bm1VBnhT.mjs.map +1 -0
- package/dist/compression-service-Bn86iTJe.cjs +35 -0
- package/dist/compression-service-Bn86iTJe.cjs.map +1 -0
- package/dist/compression-service-CiF7Px08.d.cts +15 -0
- package/dist/compression-service-CiF7Px08.d.cts.map +1 -0
- package/dist/compression-service-DI7ZXVxH.d.mts +15 -0
- package/dist/compression-service-DI7ZXVxH.d.mts.map +1 -0
- package/dist/errors.cjs +9 -0
- package/dist/errors.d.cts +2 -0
- package/dist/errors.d.mts +2 -0
- package/dist/errors.mjs +2 -0
- package/dist/index-Ch8xM6Xt.d.cts +60 -0
- package/dist/index-Ch8xM6Xt.d.cts.map +1 -0
- package/dist/index-DBGtgXEd.d.mts +60 -0
- package/dist/index-DBGtgXEd.d.mts.map +1 -0
- package/dist/logger-service-1J5r_akj.mjs +8 -0
- package/dist/logger-service-1J5r_akj.mjs.map +1 -0
- package/dist/logger-service-BF2pZOHN.d.mts +12 -0
- package/dist/logger-service-BF2pZOHN.d.mts.map +1 -0
- package/dist/logger-service-CbN12RhO.d.cts +12 -0
- package/dist/logger-service-CbN12RhO.d.cts.map +1 -0
- package/dist/logger-service-cx8vzkXs.cjs +19 -0
- package/dist/logger-service-cx8vzkXs.cjs.map +1 -0
- package/dist/middleware-CAI0cnW2.d.mts +10 -0
- package/dist/middleware-CAI0cnW2.d.mts.map +1 -0
- package/dist/middleware-CYcctmlY.d.cts +10 -0
- package/dist/middleware-CYcctmlY.d.cts.map +1 -0
- package/dist/multipart.cjs +244 -0
- package/dist/multipart.cjs.map +1 -0
- package/dist/multipart.d.cts +2 -0
- package/dist/multipart.d.mts +2 -0
- package/dist/multipart.mjs +243 -0
- package/dist/multipart.mjs.map +1 -0
- package/dist/normalize-callback-BNBZZ1jT.cjs +44 -0
- package/dist/normalize-callback-BNBZZ1jT.cjs.map +1 -0
- package/dist/normalize-callback-DQ6C4gaV.mjs +33 -0
- package/dist/normalize-callback-DQ6C4gaV.mjs.map +1 -0
- package/dist/oneshot.cjs +64 -0
- package/dist/oneshot.cjs.map +1 -0
- package/dist/oneshot.d.cts +28 -0
- package/dist/oneshot.d.cts.map +1 -0
- package/dist/oneshot.d.mts +28 -0
- package/dist/oneshot.d.mts.map +1 -0
- package/dist/oneshot.mjs +63 -0
- package/dist/oneshot.mjs.map +1 -0
- package/dist/pipeline.cjs +16 -0
- package/dist/pipeline.cjs.map +1 -0
- package/dist/pipeline.d.cts +9 -0
- package/dist/pipeline.d.cts.map +1 -0
- package/dist/pipeline.d.mts +9 -0
- package/dist/pipeline.d.mts.map +1 -0
- package/dist/pipeline.mjs +14 -0
- package/dist/pipeline.mjs.map +1 -0
- package/dist/progress.cjs +0 -0
- package/dist/progress.d.cts +3 -0
- package/dist/progress.d.mts +3 -0
- package/dist/progress.mjs +1 -0
- package/dist/services.cjs +8 -0
- package/dist/services.d.cts +3 -0
- package/dist/services.d.mts +3 -0
- package/dist/services.mjs +3 -0
- package/dist/upload-error-B2ISUc_k.d.cts +48 -0
- package/dist/upload-error-B2ISUc_k.d.cts.map +1 -0
- package/dist/upload-error-BUexBh08.cjs +119 -0
- package/dist/upload-error-BUexBh08.cjs.map +1 -0
- package/dist/upload-error-jol-eoDW.d.mts +48 -0
- package/dist/upload-error-jol-eoDW.d.mts.map +1 -0
- package/dist/upload-error-zDvpxT9X.mjs +72 -0
- package/dist/upload-error-zDvpxT9X.mjs.map +1 -0
- package/dist/upload-event-C9TOVp5l.d.mts +36 -0
- package/dist/upload-event-C9TOVp5l.d.mts.map +1 -0
- package/dist/upload-event-D77olieX.d.cts +36 -0
- package/dist/upload-event-D77olieX.d.cts.map +1 -0
- package/package.json +70 -0
- package/src/errors/index.ts +10 -0
- package/src/errors/upload-error.test.ts +218 -0
- package/src/errors/upload-error.ts +89 -0
- package/src/multipart/chunk-stream.test.ts +79 -0
- package/src/multipart/chunk-stream.ts +37 -0
- package/src/multipart/circuit-breaker.test.ts +95 -0
- package/src/multipart/circuit-breaker.ts +68 -0
- package/src/multipart/index.test.ts +283 -0
- package/src/multipart/index.ts +119 -0
- package/src/multipart/upload-stream.test.ts +336 -0
- package/src/multipart/upload-stream.ts +246 -0
- package/src/oneshot/index.test.ts +153 -0
- package/src/oneshot/index.ts +76 -0
- package/src/oneshot/upload.test.ts +130 -0
- package/src/oneshot/upload.ts +52 -0
- package/src/pipeline/compress.test.ts +69 -0
- package/src/pipeline/compress.ts +8 -0
- package/src/pipeline/index.ts +3 -0
- package/src/pipeline/middleware.test.ts +102 -0
- package/src/pipeline/middleware.ts +30 -0
- package/src/progress/getprogress.test.ts +102 -0
- package/src/progress/index.ts +10 -0
- package/src/progress/upload-event.test.ts +102 -0
- package/src/progress/upload-event.ts +37 -0
- package/src/scaffold.test.ts +5 -0
- package/src/services/compression-service.test.ts +68 -0
- package/src/services/compression-service.ts +31 -0
- package/src/services/index.ts +11 -0
- package/src/services/logger-service-integration.test.ts +98 -0
- package/src/services/logger-service.test.ts +40 -0
- package/src/services/logger-service.ts +17 -0
- package/src/utils/abort-interop.test.ts +65 -0
- package/src/utils/abort-interop.ts +14 -0
- package/src/utils/normalize-callback.test.ts +46 -0
- package/src/utils/normalize-callback.ts +18 -0
- package/tsconfig.json +8 -0
- package/tsdown.config.ts +16 -0
- package/vitest.config.ts +7 -0
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_compression_service = require("./compression-service-Bn86iTJe.cjs");
|
|
3
|
+
const require_logger_service = require("./logger-service-cx8vzkXs.cjs");
|
|
4
|
+
const require_upload_error = require("./upload-error-BUexBh08.cjs");
|
|
5
|
+
const require_normalize_callback = require("./normalize-callback-BNBZZ1jT.cjs");
|
|
6
|
+
let effect = require("effect");
|
|
7
|
+
//#region src/multipart/circuit-breaker.ts
|
|
8
|
+
const makeCircuitBreaker = (config) => effect.Effect.gen(function* () {
|
|
9
|
+
const refState = yield* effect.Ref.make({
|
|
10
|
+
_tag: "Closed",
|
|
11
|
+
consecutiveFailures: 0
|
|
12
|
+
});
|
|
13
|
+
return {
|
|
14
|
+
guard: effect.Effect.gen(function* () {
|
|
15
|
+
if (yield* effect.Ref.modify(refState, (state) => {
|
|
16
|
+
if (state._tag !== "Open") return [false, state];
|
|
17
|
+
if (Date.now() - state.openedAt < config.cooldown) return [true, state];
|
|
18
|
+
return [false, { _tag: "HalfOpen" }];
|
|
19
|
+
})) return yield* effect.Effect.fail(new require_upload_error.CircuitOpenError(config.threshold));
|
|
20
|
+
}),
|
|
21
|
+
onSuccess: effect.Ref.update(refState, (state) => state._tag === "HalfOpen" || state._tag === "Closed" ? {
|
|
22
|
+
_tag: "Closed",
|
|
23
|
+
consecutiveFailures: 0
|
|
24
|
+
} : state),
|
|
25
|
+
onFailure: effect.Ref.modify(refState, (state) => {
|
|
26
|
+
if (state._tag === "Closed") {
|
|
27
|
+
const newFailures = state.consecutiveFailures + 1;
|
|
28
|
+
if (newFailures >= config.threshold) return [{
|
|
29
|
+
_tag: "CircuitOpen",
|
|
30
|
+
failedParts: newFailures,
|
|
31
|
+
timestamp: Date.now()
|
|
32
|
+
}, {
|
|
33
|
+
_tag: "Open",
|
|
34
|
+
openedAt: Date.now()
|
|
35
|
+
}];
|
|
36
|
+
return [null, {
|
|
37
|
+
_tag: "Closed",
|
|
38
|
+
consecutiveFailures: newFailures
|
|
39
|
+
}];
|
|
40
|
+
}
|
|
41
|
+
if (state._tag === "HalfOpen") return [{
|
|
42
|
+
_tag: "CircuitOpen",
|
|
43
|
+
failedParts: config.threshold,
|
|
44
|
+
timestamp: Date.now()
|
|
45
|
+
}, {
|
|
46
|
+
_tag: "Open",
|
|
47
|
+
openedAt: Date.now()
|
|
48
|
+
}];
|
|
49
|
+
return [null, state];
|
|
50
|
+
})
|
|
51
|
+
};
|
|
52
|
+
});
|
|
53
|
+
//#endregion
|
|
54
|
+
//#region src/multipart/chunk-stream.ts
|
|
55
|
+
const chunkStream = (stream, chunkSize) => {
|
|
56
|
+
let buffer = new Uint8Array(0);
|
|
57
|
+
const transform = new TransformStream({
|
|
58
|
+
transform(chunk, controller) {
|
|
59
|
+
const merged = new Uint8Array(buffer.length + chunk.length);
|
|
60
|
+
merged.set(buffer);
|
|
61
|
+
merged.set(chunk, buffer.length);
|
|
62
|
+
buffer = merged;
|
|
63
|
+
while (buffer.length >= chunkSize) {
|
|
64
|
+
controller.enqueue(buffer.slice(0, chunkSize));
|
|
65
|
+
buffer = buffer.slice(chunkSize);
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
flush(controller) {
|
|
69
|
+
if (buffer.length > 0) controller.enqueue(buffer);
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
const chunked = stream.pipeThrough(transform);
|
|
73
|
+
return effect.Stream.fromReadableStream(() => chunked, (e) => e);
|
|
74
|
+
};
|
|
75
|
+
//#endregion
|
|
76
|
+
//#region src/multipart/upload-stream.ts
|
|
77
|
+
const DEFAULT_MAX_CONCURRENCY = 4;
|
|
78
|
+
const DEFAULT_RETRY_SCHEDULE = effect.Schedule.exponential("100 millis").pipe(effect.Schedule.compose(effect.Schedule.recurs(2)));
|
|
79
|
+
const uploadMultipartEffect = (options) => {
|
|
80
|
+
const { stream, chunkSize, uploadPart, completeUpload, initiate, reconcileCompletedParts, maxConcurrency = DEFAULT_MAX_CONCURRENCY, signal, retrySchedule = DEFAULT_RETRY_SCHEDULE } = options;
|
|
81
|
+
return effect.Stream.unwrap(effect.Effect.gen(function* () {
|
|
82
|
+
const logger = yield* require_logger_service.LoggerService;
|
|
83
|
+
const semaphore = yield* effect.Effect.makeSemaphore(maxConcurrency);
|
|
84
|
+
const refParts = yield* effect.Ref.make([]);
|
|
85
|
+
const refBytesUploaded = yield* effect.Ref.make(0);
|
|
86
|
+
const refUploadId = yield* effect.Ref.make("");
|
|
87
|
+
const breaker = options.circuitBreaker ? yield* makeCircuitBreaker(options.circuitBreaker) : null;
|
|
88
|
+
const reconciledMap = reconcileCompletedParts ? new Map((yield* require_normalize_callback.normalizeCallback(reconcileCompletedParts).pipe(effect.Effect.mapError((cause) => new require_upload_error.ReconcileError(cause)))).map((p) => [p.partNumber, p.etag])) : /* @__PURE__ */ new Map();
|
|
89
|
+
const initiateStream = initiate ? effect.Stream.fromEffect(require_normalize_callback.normalizeCallback(initiate).pipe(effect.Effect.mapError((cause) => new require_upload_error.InitiateUploadError(cause)), effect.Effect.flatMap(({ uploadId }) => effect.Ref.set(refUploadId, uploadId).pipe(effect.Effect.as({
|
|
90
|
+
_tag: "UploadInitiated",
|
|
91
|
+
uploadId,
|
|
92
|
+
timestamp: Date.now()
|
|
93
|
+
}))))) : effect.Stream.empty;
|
|
94
|
+
const makeUploadOne = (partNumber, chunk) => effect.Effect.gen(function* () {
|
|
95
|
+
const reconciledEtag = reconciledMap.get(partNumber);
|
|
96
|
+
if (reconciledEtag !== void 0) {
|
|
97
|
+
const event = {
|
|
98
|
+
_tag: "PartCompleted",
|
|
99
|
+
partNumber,
|
|
100
|
+
etag: reconciledEtag,
|
|
101
|
+
bytesUploaded: chunk.length,
|
|
102
|
+
timestamp: Date.now()
|
|
103
|
+
};
|
|
104
|
+
yield* effect.Ref.update(refParts, (parts) => [...parts, {
|
|
105
|
+
partNumber,
|
|
106
|
+
etag: reconciledEtag
|
|
107
|
+
}]);
|
|
108
|
+
yield* effect.Effect.sync(() => logger.log("info", `Part ${partNumber} skipped (reconciled)`));
|
|
109
|
+
return event;
|
|
110
|
+
}
|
|
111
|
+
const refAttempts = yield* effect.Ref.make(0);
|
|
112
|
+
const single = effect.Effect.gen(function* () {
|
|
113
|
+
yield* effect.Ref.update(refAttempts, (n) => n + 1);
|
|
114
|
+
const attempt = yield* effect.Ref.get(refAttempts);
|
|
115
|
+
return yield* require_normalize_callback.normalizeCallback(() => uploadPart(partNumber, chunk)).pipe(effect.Effect.mapError((cause) => new require_upload_error.PartUploadError(partNumber, attempt, cause)));
|
|
116
|
+
});
|
|
117
|
+
const etag = yield* effect.Effect.retry(single, retrySchedule).pipe(effect.Effect.catchAll((err) => effect.Effect.gen(function* () {
|
|
118
|
+
const totalAttempts = yield* effect.Ref.get(refAttempts);
|
|
119
|
+
if (totalAttempts <= 1) return yield* effect.Effect.fail(err);
|
|
120
|
+
return yield* effect.Effect.fail(new require_upload_error.MaxRetriesExceededError(partNumber, totalAttempts, err.cause));
|
|
121
|
+
})));
|
|
122
|
+
const event = {
|
|
123
|
+
_tag: "PartCompleted",
|
|
124
|
+
partNumber,
|
|
125
|
+
etag,
|
|
126
|
+
bytesUploaded: chunk.length,
|
|
127
|
+
timestamp: Date.now()
|
|
128
|
+
};
|
|
129
|
+
yield* effect.Ref.update(refParts, (parts) => [...parts, {
|
|
130
|
+
partNumber,
|
|
131
|
+
etag
|
|
132
|
+
}]);
|
|
133
|
+
yield* effect.Effect.sync(() => logger.log("info", `Part ${partNumber} completed`));
|
|
134
|
+
return event;
|
|
135
|
+
});
|
|
136
|
+
const partsStream = chunkStream(stream, chunkSize).pipe(effect.Stream.mapError((cause) => new require_upload_error.PartUploadError(0, 0, cause)), effect.Stream.zipWithIndex, effect.Stream.mapEffect(([chunk, idx]) => {
|
|
137
|
+
const partNumber = Number(idx) + 1;
|
|
138
|
+
if (!breaker) {
|
|
139
|
+
const partEffect = semaphore.withPermits(1)(makeUploadOne(partNumber, chunk));
|
|
140
|
+
return signal ? effect.Effect.raceFirst(partEffect, require_normalize_callback.fromAbortSignal(signal)) : partEffect;
|
|
141
|
+
}
|
|
142
|
+
const partEffect = effect.Effect.gen(function* () {
|
|
143
|
+
yield* breaker.guard;
|
|
144
|
+
return yield* semaphore.withPermits(1)(effect.Effect.gen(function* () {
|
|
145
|
+
const exit = yield* effect.Effect.exit(makeUploadOne(partNumber, chunk));
|
|
146
|
+
if (effect.Exit.isSuccess(exit)) {
|
|
147
|
+
yield* breaker.onSuccess;
|
|
148
|
+
return exit.value;
|
|
149
|
+
}
|
|
150
|
+
const circuitEvent = yield* breaker.onFailure;
|
|
151
|
+
if (circuitEvent !== null) return yield* effect.Effect.fail(new require_upload_error.CircuitOpenError(circuitEvent.failedParts));
|
|
152
|
+
return yield* effect.Effect.fail(effect.Cause.squash(exit.cause));
|
|
153
|
+
}));
|
|
154
|
+
});
|
|
155
|
+
return signal ? effect.Effect.raceFirst(partEffect, require_normalize_callback.fromAbortSignal(signal)) : partEffect;
|
|
156
|
+
}, { concurrency: "unbounded" }), effect.Stream.flatMap((event) => {
|
|
157
|
+
const tickEffect = effect.Ref.updateAndGet(refBytesUploaded, (n) => n + event.bytesUploaded).pipe(effect.Effect.map((total) => ({
|
|
158
|
+
_tag: "ProgressTick",
|
|
159
|
+
bytesUploaded: total,
|
|
160
|
+
totalBytes: effect.Option.none(),
|
|
161
|
+
timestamp: Date.now()
|
|
162
|
+
})));
|
|
163
|
+
return effect.Stream.concat(effect.Stream.make(event), effect.Stream.fromEffect(tickEffect));
|
|
164
|
+
}), effect.Stream.catchAll((err) => {
|
|
165
|
+
if (breaker && err._tag === "CircuitOpenError") {
|
|
166
|
+
const event = {
|
|
167
|
+
_tag: "CircuitOpen",
|
|
168
|
+
failedParts: err.failedParts,
|
|
169
|
+
timestamp: Date.now()
|
|
170
|
+
};
|
|
171
|
+
return effect.Stream.concat(effect.Stream.succeed(event), effect.Stream.fail(err));
|
|
172
|
+
}
|
|
173
|
+
return effect.Stream.fail(err);
|
|
174
|
+
}));
|
|
175
|
+
const finalEffect = effect.Effect.gen(function* () {
|
|
176
|
+
const uploadId = yield* effect.Ref.get(refUploadId);
|
|
177
|
+
const parts = yield* effect.Ref.get(refParts);
|
|
178
|
+
yield* require_normalize_callback.normalizeCallback(() => completeUpload(uploadId, parts)).pipe(effect.Effect.mapError((cause) => new require_upload_error.CompleteUploadError(cause)));
|
|
179
|
+
yield* effect.Effect.sync(() => logger.log("info", "Multipart upload completed"));
|
|
180
|
+
return {
|
|
181
|
+
_tag: "UploadCompleted",
|
|
182
|
+
uploadId,
|
|
183
|
+
totalParts: parts.length,
|
|
184
|
+
timestamp: Date.now()
|
|
185
|
+
};
|
|
186
|
+
});
|
|
187
|
+
return effect.Stream.concat(initiateStream, partsStream.pipe(effect.Stream.concat(effect.Stream.fromEffect(finalEffect))));
|
|
188
|
+
}));
|
|
189
|
+
};
|
|
190
|
+
//#endregion
|
|
191
|
+
//#region src/multipart/index.ts
|
|
192
|
+
const uploadMultipart = (options) => {
|
|
193
|
+
const refProgress = effect.Effect.runSync(effect.Ref.make({
|
|
194
|
+
bytesUploaded: 0,
|
|
195
|
+
totalBytes: options.totalBytes !== void 0 ? effect.Option.some(options.totalBytes) : effect.Option.none()
|
|
196
|
+
}));
|
|
197
|
+
let resolveUploadId;
|
|
198
|
+
const uploadIdPromise = new Promise((resolve) => {
|
|
199
|
+
resolveUploadId = resolve;
|
|
200
|
+
});
|
|
201
|
+
const collected = (async () => {
|
|
202
|
+
let processedStream = options.stream;
|
|
203
|
+
if (options.pipeline !== void 0) if (typeof options.pipeline === "function") processedStream = options.pipeline(options.stream);
|
|
204
|
+
else processedStream = (await effect.Effect.runPromise(effect.Effect.provide(options.pipeline, require_compression_service.CompressionServiceLive)))(options.stream);
|
|
205
|
+
const program = uploadMultipartEffect({
|
|
206
|
+
...options,
|
|
207
|
+
stream: processedStream
|
|
208
|
+
}).pipe(effect.Stream.tap((event) => {
|
|
209
|
+
if (event._tag === "UploadInitiated") return effect.Effect.sync(() => resolveUploadId(event.uploadId));
|
|
210
|
+
if (event._tag === "PartCompleted") return effect.Ref.update(refProgress, (p) => ({
|
|
211
|
+
...p,
|
|
212
|
+
bytesUploaded: p.bytesUploaded + event.bytesUploaded
|
|
213
|
+
}));
|
|
214
|
+
return effect.Effect.void;
|
|
215
|
+
}), effect.Stream.provideLayer(require_logger_service.LoggerServiceLive));
|
|
216
|
+
const exit = await effect.Stream.runCollect(program).pipe(effect.Effect.map((chunk) => Array.from(chunk)), effect.Effect.runPromiseExit);
|
|
217
|
+
if (effect.Exit.isSuccess(exit)) return exit.value;
|
|
218
|
+
return Promise.reject(effect.Cause.squash(exit.cause));
|
|
219
|
+
})();
|
|
220
|
+
collected.finally(() => resolveUploadId("")).catch(() => {});
|
|
221
|
+
return {
|
|
222
|
+
events: new ReadableStream({ async start(controller) {
|
|
223
|
+
try {
|
|
224
|
+
const evts = await collected;
|
|
225
|
+
for (const event of evts) controller.enqueue(event);
|
|
226
|
+
controller.close();
|
|
227
|
+
} catch (_) {
|
|
228
|
+
controller.close();
|
|
229
|
+
}
|
|
230
|
+
} }),
|
|
231
|
+
result: collected.then((evts) => {
|
|
232
|
+
const last = evts[evts.length - 1];
|
|
233
|
+
if (last === void 0) return Promise.reject(/* @__PURE__ */ new Error("uploadMultipart: stream ended without emitting an event"));
|
|
234
|
+
return last;
|
|
235
|
+
}),
|
|
236
|
+
getProgress: Object.assign(() => effect.Effect.runPromise(effect.Ref.get(refProgress)), { effect: effect.Ref.get(refProgress) }),
|
|
237
|
+
uploadId: uploadIdPromise
|
|
238
|
+
};
|
|
239
|
+
};
|
|
240
|
+
uploadMultipart.effect = uploadMultipartEffect;
|
|
241
|
+
//#endregion
|
|
242
|
+
exports.uploadMultipart = uploadMultipart;
|
|
243
|
+
|
|
244
|
+
//# sourceMappingURL=multipart.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multipart.cjs","names":["Effect","Ref","CircuitOpenError","Stream","Schedule","Stream","Effect","LoggerService","Ref","normalizeCallback","ReconcileError","InitiateUploadError","PartUploadError","MaxRetriesExceededError","fromAbortSignal","Exit","CircuitOpenError","Cause","Option","CompleteUploadError","Effect","Ref","Option","CompressionServiceLive","Stream","LoggerServiceLive","Exit","Cause"],"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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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as CompletedPart, i as uploadMultipart, n as Progress, o as UploadMultipartOptions, r as UploadResult, t as MultipartPublicOptions } from "./index-Ch8xM6Xt.cjs";
|
|
2
|
+
export { CompletedPart, MultipartPublicOptions, Progress, UploadMultipartOptions, UploadResult, uploadMultipart };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as CompletedPart, i as uploadMultipart, n as Progress, o as UploadMultipartOptions, r as UploadResult, t as MultipartPublicOptions } from "./index-DBGtgXEd.mjs";
|
|
2
|
+
export { CompletedPart, MultipartPublicOptions, Progress, UploadMultipartOptions, UploadResult, uploadMultipart };
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { n as CompressionServiceLive } from "./compression-service-Bm1VBnhT.mjs";
|
|
2
|
+
import { n as LoggerServiceLive, t as LoggerService } from "./logger-service-1J5r_akj.mjs";
|
|
3
|
+
import { a as MaxRetriesExceededError, c as ReconcileError, i as InitiateUploadError, n as CircuitOpenError, o as PartUploadError, r as CompleteUploadError } from "./upload-error-zDvpxT9X.mjs";
|
|
4
|
+
import { n as fromAbortSignal, t as normalizeCallback } from "./normalize-callback-DQ6C4gaV.mjs";
|
|
5
|
+
import { Cause, Effect, Exit, Option, Ref, Schedule, Stream } from "effect";
|
|
6
|
+
//#region src/multipart/circuit-breaker.ts
|
|
7
|
+
const makeCircuitBreaker = (config) => Effect.gen(function* () {
|
|
8
|
+
const refState = yield* Ref.make({
|
|
9
|
+
_tag: "Closed",
|
|
10
|
+
consecutiveFailures: 0
|
|
11
|
+
});
|
|
12
|
+
return {
|
|
13
|
+
guard: Effect.gen(function* () {
|
|
14
|
+
if (yield* Ref.modify(refState, (state) => {
|
|
15
|
+
if (state._tag !== "Open") return [false, state];
|
|
16
|
+
if (Date.now() - state.openedAt < config.cooldown) return [true, state];
|
|
17
|
+
return [false, { _tag: "HalfOpen" }];
|
|
18
|
+
})) return yield* Effect.fail(new CircuitOpenError(config.threshold));
|
|
19
|
+
}),
|
|
20
|
+
onSuccess: Ref.update(refState, (state) => state._tag === "HalfOpen" || state._tag === "Closed" ? {
|
|
21
|
+
_tag: "Closed",
|
|
22
|
+
consecutiveFailures: 0
|
|
23
|
+
} : state),
|
|
24
|
+
onFailure: Ref.modify(refState, (state) => {
|
|
25
|
+
if (state._tag === "Closed") {
|
|
26
|
+
const newFailures = state.consecutiveFailures + 1;
|
|
27
|
+
if (newFailures >= config.threshold) return [{
|
|
28
|
+
_tag: "CircuitOpen",
|
|
29
|
+
failedParts: newFailures,
|
|
30
|
+
timestamp: Date.now()
|
|
31
|
+
}, {
|
|
32
|
+
_tag: "Open",
|
|
33
|
+
openedAt: Date.now()
|
|
34
|
+
}];
|
|
35
|
+
return [null, {
|
|
36
|
+
_tag: "Closed",
|
|
37
|
+
consecutiveFailures: newFailures
|
|
38
|
+
}];
|
|
39
|
+
}
|
|
40
|
+
if (state._tag === "HalfOpen") return [{
|
|
41
|
+
_tag: "CircuitOpen",
|
|
42
|
+
failedParts: config.threshold,
|
|
43
|
+
timestamp: Date.now()
|
|
44
|
+
}, {
|
|
45
|
+
_tag: "Open",
|
|
46
|
+
openedAt: Date.now()
|
|
47
|
+
}];
|
|
48
|
+
return [null, state];
|
|
49
|
+
})
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/multipart/chunk-stream.ts
|
|
54
|
+
const chunkStream = (stream, chunkSize) => {
|
|
55
|
+
let buffer = new Uint8Array(0);
|
|
56
|
+
const transform = new TransformStream({
|
|
57
|
+
transform(chunk, controller) {
|
|
58
|
+
const merged = new Uint8Array(buffer.length + chunk.length);
|
|
59
|
+
merged.set(buffer);
|
|
60
|
+
merged.set(chunk, buffer.length);
|
|
61
|
+
buffer = merged;
|
|
62
|
+
while (buffer.length >= chunkSize) {
|
|
63
|
+
controller.enqueue(buffer.slice(0, chunkSize));
|
|
64
|
+
buffer = buffer.slice(chunkSize);
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
flush(controller) {
|
|
68
|
+
if (buffer.length > 0) controller.enqueue(buffer);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
const chunked = stream.pipeThrough(transform);
|
|
72
|
+
return Stream.fromReadableStream(() => chunked, (e) => e);
|
|
73
|
+
};
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region src/multipart/upload-stream.ts
|
|
76
|
+
const DEFAULT_MAX_CONCURRENCY = 4;
|
|
77
|
+
const DEFAULT_RETRY_SCHEDULE = Schedule.exponential("100 millis").pipe(Schedule.compose(Schedule.recurs(2)));
|
|
78
|
+
const uploadMultipartEffect = (options) => {
|
|
79
|
+
const { stream, chunkSize, uploadPart, completeUpload, initiate, reconcileCompletedParts, maxConcurrency = DEFAULT_MAX_CONCURRENCY, signal, retrySchedule = DEFAULT_RETRY_SCHEDULE } = options;
|
|
80
|
+
return Stream.unwrap(Effect.gen(function* () {
|
|
81
|
+
const logger = yield* LoggerService;
|
|
82
|
+
const semaphore = yield* Effect.makeSemaphore(maxConcurrency);
|
|
83
|
+
const refParts = yield* Ref.make([]);
|
|
84
|
+
const refBytesUploaded = yield* Ref.make(0);
|
|
85
|
+
const refUploadId = yield* Ref.make("");
|
|
86
|
+
const breaker = options.circuitBreaker ? yield* makeCircuitBreaker(options.circuitBreaker) : null;
|
|
87
|
+
const reconciledMap = reconcileCompletedParts ? new Map((yield* normalizeCallback(reconcileCompletedParts).pipe(Effect.mapError((cause) => new ReconcileError(cause)))).map((p) => [p.partNumber, p.etag])) : /* @__PURE__ */ new Map();
|
|
88
|
+
const initiateStream = initiate ? Stream.fromEffect(normalizeCallback(initiate).pipe(Effect.mapError((cause) => new InitiateUploadError(cause)), Effect.flatMap(({ uploadId }) => Ref.set(refUploadId, uploadId).pipe(Effect.as({
|
|
89
|
+
_tag: "UploadInitiated",
|
|
90
|
+
uploadId,
|
|
91
|
+
timestamp: Date.now()
|
|
92
|
+
}))))) : Stream.empty;
|
|
93
|
+
const makeUploadOne = (partNumber, chunk) => Effect.gen(function* () {
|
|
94
|
+
const reconciledEtag = reconciledMap.get(partNumber);
|
|
95
|
+
if (reconciledEtag !== void 0) {
|
|
96
|
+
const event = {
|
|
97
|
+
_tag: "PartCompleted",
|
|
98
|
+
partNumber,
|
|
99
|
+
etag: reconciledEtag,
|
|
100
|
+
bytesUploaded: chunk.length,
|
|
101
|
+
timestamp: Date.now()
|
|
102
|
+
};
|
|
103
|
+
yield* Ref.update(refParts, (parts) => [...parts, {
|
|
104
|
+
partNumber,
|
|
105
|
+
etag: reconciledEtag
|
|
106
|
+
}]);
|
|
107
|
+
yield* Effect.sync(() => logger.log("info", `Part ${partNumber} skipped (reconciled)`));
|
|
108
|
+
return event;
|
|
109
|
+
}
|
|
110
|
+
const refAttempts = yield* Ref.make(0);
|
|
111
|
+
const single = Effect.gen(function* () {
|
|
112
|
+
yield* Ref.update(refAttempts, (n) => n + 1);
|
|
113
|
+
const attempt = yield* Ref.get(refAttempts);
|
|
114
|
+
return yield* normalizeCallback(() => uploadPart(partNumber, chunk)).pipe(Effect.mapError((cause) => new PartUploadError(partNumber, attempt, cause)));
|
|
115
|
+
});
|
|
116
|
+
const etag = yield* Effect.retry(single, retrySchedule).pipe(Effect.catchAll((err) => Effect.gen(function* () {
|
|
117
|
+
const totalAttempts = yield* Ref.get(refAttempts);
|
|
118
|
+
if (totalAttempts <= 1) return yield* Effect.fail(err);
|
|
119
|
+
return yield* Effect.fail(new MaxRetriesExceededError(partNumber, totalAttempts, err.cause));
|
|
120
|
+
})));
|
|
121
|
+
const event = {
|
|
122
|
+
_tag: "PartCompleted",
|
|
123
|
+
partNumber,
|
|
124
|
+
etag,
|
|
125
|
+
bytesUploaded: chunk.length,
|
|
126
|
+
timestamp: Date.now()
|
|
127
|
+
};
|
|
128
|
+
yield* Ref.update(refParts, (parts) => [...parts, {
|
|
129
|
+
partNumber,
|
|
130
|
+
etag
|
|
131
|
+
}]);
|
|
132
|
+
yield* Effect.sync(() => logger.log("info", `Part ${partNumber} completed`));
|
|
133
|
+
return event;
|
|
134
|
+
});
|
|
135
|
+
const partsStream = chunkStream(stream, chunkSize).pipe(Stream.mapError((cause) => new PartUploadError(0, 0, cause)), Stream.zipWithIndex, Stream.mapEffect(([chunk, idx]) => {
|
|
136
|
+
const partNumber = Number(idx) + 1;
|
|
137
|
+
if (!breaker) {
|
|
138
|
+
const partEffect = semaphore.withPermits(1)(makeUploadOne(partNumber, chunk));
|
|
139
|
+
return signal ? Effect.raceFirst(partEffect, fromAbortSignal(signal)) : partEffect;
|
|
140
|
+
}
|
|
141
|
+
const partEffect = Effect.gen(function* () {
|
|
142
|
+
yield* breaker.guard;
|
|
143
|
+
return yield* semaphore.withPermits(1)(Effect.gen(function* () {
|
|
144
|
+
const exit = yield* Effect.exit(makeUploadOne(partNumber, chunk));
|
|
145
|
+
if (Exit.isSuccess(exit)) {
|
|
146
|
+
yield* breaker.onSuccess;
|
|
147
|
+
return exit.value;
|
|
148
|
+
}
|
|
149
|
+
const circuitEvent = yield* breaker.onFailure;
|
|
150
|
+
if (circuitEvent !== null) return yield* Effect.fail(new CircuitOpenError(circuitEvent.failedParts));
|
|
151
|
+
return yield* Effect.fail(Cause.squash(exit.cause));
|
|
152
|
+
}));
|
|
153
|
+
});
|
|
154
|
+
return signal ? Effect.raceFirst(partEffect, fromAbortSignal(signal)) : partEffect;
|
|
155
|
+
}, { concurrency: "unbounded" }), Stream.flatMap((event) => {
|
|
156
|
+
const tickEffect = Ref.updateAndGet(refBytesUploaded, (n) => n + event.bytesUploaded).pipe(Effect.map((total) => ({
|
|
157
|
+
_tag: "ProgressTick",
|
|
158
|
+
bytesUploaded: total,
|
|
159
|
+
totalBytes: Option.none(),
|
|
160
|
+
timestamp: Date.now()
|
|
161
|
+
})));
|
|
162
|
+
return Stream.concat(Stream.make(event), Stream.fromEffect(tickEffect));
|
|
163
|
+
}), Stream.catchAll((err) => {
|
|
164
|
+
if (breaker && err._tag === "CircuitOpenError") {
|
|
165
|
+
const event = {
|
|
166
|
+
_tag: "CircuitOpen",
|
|
167
|
+
failedParts: err.failedParts,
|
|
168
|
+
timestamp: Date.now()
|
|
169
|
+
};
|
|
170
|
+
return Stream.concat(Stream.succeed(event), Stream.fail(err));
|
|
171
|
+
}
|
|
172
|
+
return Stream.fail(err);
|
|
173
|
+
}));
|
|
174
|
+
const finalEffect = Effect.gen(function* () {
|
|
175
|
+
const uploadId = yield* Ref.get(refUploadId);
|
|
176
|
+
const parts = yield* Ref.get(refParts);
|
|
177
|
+
yield* normalizeCallback(() => completeUpload(uploadId, parts)).pipe(Effect.mapError((cause) => new CompleteUploadError(cause)));
|
|
178
|
+
yield* Effect.sync(() => logger.log("info", "Multipart upload completed"));
|
|
179
|
+
return {
|
|
180
|
+
_tag: "UploadCompleted",
|
|
181
|
+
uploadId,
|
|
182
|
+
totalParts: parts.length,
|
|
183
|
+
timestamp: Date.now()
|
|
184
|
+
};
|
|
185
|
+
});
|
|
186
|
+
return Stream.concat(initiateStream, partsStream.pipe(Stream.concat(Stream.fromEffect(finalEffect))));
|
|
187
|
+
}));
|
|
188
|
+
};
|
|
189
|
+
//#endregion
|
|
190
|
+
//#region src/multipart/index.ts
|
|
191
|
+
const uploadMultipart = (options) => {
|
|
192
|
+
const refProgress = Effect.runSync(Ref.make({
|
|
193
|
+
bytesUploaded: 0,
|
|
194
|
+
totalBytes: options.totalBytes !== void 0 ? Option.some(options.totalBytes) : Option.none()
|
|
195
|
+
}));
|
|
196
|
+
let resolveUploadId;
|
|
197
|
+
const uploadIdPromise = new Promise((resolve) => {
|
|
198
|
+
resolveUploadId = resolve;
|
|
199
|
+
});
|
|
200
|
+
const collected = (async () => {
|
|
201
|
+
let processedStream = options.stream;
|
|
202
|
+
if (options.pipeline !== void 0) if (typeof options.pipeline === "function") processedStream = options.pipeline(options.stream);
|
|
203
|
+
else processedStream = (await Effect.runPromise(Effect.provide(options.pipeline, CompressionServiceLive)))(options.stream);
|
|
204
|
+
const program = uploadMultipartEffect({
|
|
205
|
+
...options,
|
|
206
|
+
stream: processedStream
|
|
207
|
+
}).pipe(Stream.tap((event) => {
|
|
208
|
+
if (event._tag === "UploadInitiated") return Effect.sync(() => resolveUploadId(event.uploadId));
|
|
209
|
+
if (event._tag === "PartCompleted") return Ref.update(refProgress, (p) => ({
|
|
210
|
+
...p,
|
|
211
|
+
bytesUploaded: p.bytesUploaded + event.bytesUploaded
|
|
212
|
+
}));
|
|
213
|
+
return Effect.void;
|
|
214
|
+
}), Stream.provideLayer(LoggerServiceLive));
|
|
215
|
+
const exit = await Stream.runCollect(program).pipe(Effect.map((chunk) => Array.from(chunk)), Effect.runPromiseExit);
|
|
216
|
+
if (Exit.isSuccess(exit)) return exit.value;
|
|
217
|
+
return Promise.reject(Cause.squash(exit.cause));
|
|
218
|
+
})();
|
|
219
|
+
collected.finally(() => resolveUploadId("")).catch(() => {});
|
|
220
|
+
return {
|
|
221
|
+
events: new ReadableStream({ async start(controller) {
|
|
222
|
+
try {
|
|
223
|
+
const evts = await collected;
|
|
224
|
+
for (const event of evts) controller.enqueue(event);
|
|
225
|
+
controller.close();
|
|
226
|
+
} catch (_) {
|
|
227
|
+
controller.close();
|
|
228
|
+
}
|
|
229
|
+
} }),
|
|
230
|
+
result: collected.then((evts) => {
|
|
231
|
+
const last = evts[evts.length - 1];
|
|
232
|
+
if (last === void 0) return Promise.reject(/* @__PURE__ */ new Error("uploadMultipart: stream ended without emitting an event"));
|
|
233
|
+
return last;
|
|
234
|
+
}),
|
|
235
|
+
getProgress: Object.assign(() => Effect.runPromise(Ref.get(refProgress)), { effect: Ref.get(refProgress) }),
|
|
236
|
+
uploadId: uploadIdPromise
|
|
237
|
+
};
|
|
238
|
+
};
|
|
239
|
+
uploadMultipart.effect = uploadMultipartEffect;
|
|
240
|
+
//#endregion
|
|
241
|
+
export { uploadMultipart };
|
|
242
|
+
|
|
243
|
+
//# sourceMappingURL=multipart.mjs.map
|
|
@@ -0,0 +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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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const require_upload_error = require("./upload-error-BUexBh08.cjs");
|
|
2
|
+
let effect = require("effect");
|
|
3
|
+
//#region src/utils/abort-interop.ts
|
|
4
|
+
const fromAbortSignal = (signal) => effect.Effect.async((resume) => {
|
|
5
|
+
if (!signal) return;
|
|
6
|
+
if (signal.aborted) {
|
|
7
|
+
resume(effect.Effect.fail(new require_upload_error.AbortError()));
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const handler = () => resume(effect.Effect.fail(new require_upload_error.AbortError()));
|
|
11
|
+
signal.addEventListener("abort", handler, { once: true });
|
|
12
|
+
return effect.Effect.sync(() => signal.removeEventListener("abort", handler));
|
|
13
|
+
});
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/utils/normalize-callback.ts
|
|
16
|
+
const normalizeCallback = (fn) => effect.Effect.suspend(() => {
|
|
17
|
+
let result;
|
|
18
|
+
try {
|
|
19
|
+
result = fn();
|
|
20
|
+
} catch (e) {
|
|
21
|
+
return effect.Effect.fail(e);
|
|
22
|
+
}
|
|
23
|
+
if (effect.Effect.isEffect(result)) return result;
|
|
24
|
+
if (result instanceof Promise) return effect.Effect.tryPromise({
|
|
25
|
+
try: () => result,
|
|
26
|
+
catch: (e) => e
|
|
27
|
+
});
|
|
28
|
+
return effect.Effect.succeed(result);
|
|
29
|
+
});
|
|
30
|
+
//#endregion
|
|
31
|
+
Object.defineProperty(exports, "fromAbortSignal", {
|
|
32
|
+
enumerable: true,
|
|
33
|
+
get: function() {
|
|
34
|
+
return fromAbortSignal;
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
Object.defineProperty(exports, "normalizeCallback", {
|
|
38
|
+
enumerable: true,
|
|
39
|
+
get: function() {
|
|
40
|
+
return normalizeCallback;
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
//# sourceMappingURL=normalize-callback-BNBZZ1jT.cjs.map
|