@panorama-industries/effect-vercel-blob 0.0.0 → 0.3.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/LICENSE +21 -0
- package/README.md +33 -1
- package/dist/client.d.ts +8 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +8 -0
- package/dist/client.js.map +1 -0
- package/dist/contracts/blob-client.d.ts +67 -0
- package/dist/contracts/blob-client.d.ts.map +1 -0
- package/dist/contracts/blob-client.js +11 -0
- package/dist/contracts/blob-client.js.map +1 -0
- package/dist/contracts/blob-config.d.ts +114 -0
- package/dist/contracts/blob-config.d.ts.map +1 -0
- package/dist/contracts/blob-config.js +7 -0
- package/dist/contracts/blob-config.js.map +1 -0
- package/dist/contracts/blob-domain.d.ts +336 -0
- package/dist/contracts/blob-domain.d.ts.map +1 -0
- package/dist/contracts/blob-domain.js +152 -0
- package/dist/contracts/blob-domain.js.map +1 -0
- package/dist/contracts/blob-errors.d.ts +125 -0
- package/dist/contracts/blob-errors.d.ts.map +1 -0
- package/dist/contracts/blob-errors.js +114 -0
- package/dist/contracts/blob-errors.js.map +1 -0
- package/dist/contracts/blob-signed-urls.d.ts +83 -0
- package/dist/contracts/blob-signed-urls.d.ts.map +1 -0
- package/dist/contracts/blob-signed-urls.js +10 -0
- package/dist/contracts/blob-signed-urls.js.map +1 -0
- package/dist/contracts/blob-store.d.ts +153 -0
- package/dist/contracts/blob-store.d.ts.map +1 -0
- package/dist/contracts/blob-store.js +15 -0
- package/dist/contracts/blob-store.js.map +1 -0
- package/dist/contracts/blob-uploads.d.ts +101 -0
- package/dist/contracts/blob-uploads.d.ts.map +1 -0
- package/dist/contracts/blob-uploads.js +17 -0
- package/dist/contracts/blob-uploads.js.map +1 -0
- package/dist/implementation/blob-client-live.d.ts +37 -0
- package/dist/implementation/blob-client-live.d.ts.map +1 -0
- package/dist/implementation/blob-client-live.js +213 -0
- package/dist/implementation/blob-client-live.js.map +1 -0
- package/dist/implementation/blob-config-live.d.ts +49 -0
- package/dist/implementation/blob-config-live.d.ts.map +1 -0
- package/dist/implementation/blob-config-live.js +82 -0
- package/dist/implementation/blob-config-live.js.map +1 -0
- package/dist/implementation/blob-live.d.ts +26 -0
- package/dist/implementation/blob-live.d.ts.map +1 -0
- package/dist/implementation/blob-live.js +26 -0
- package/dist/implementation/blob-live.js.map +1 -0
- package/dist/implementation/blob-signed-urls-live.d.ts +12 -0
- package/dist/implementation/blob-signed-urls-live.d.ts.map +1 -0
- package/dist/implementation/blob-signed-urls-live.js +92 -0
- package/dist/implementation/blob-signed-urls-live.js.map +1 -0
- package/dist/implementation/blob-store-live.d.ts +18 -0
- package/dist/implementation/blob-store-live.d.ts.map +1 -0
- package/dist/implementation/blob-store-live.js +364 -0
- package/dist/implementation/blob-store-live.js.map +1 -0
- package/dist/implementation/blob-uploads-live.d.ts +11 -0
- package/dist/implementation/blob-uploads-live.d.ts.map +1 -0
- package/dist/implementation/blob-uploads-live.js +218 -0
- package/dist/implementation/blob-uploads-live.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/blob-errors.d.ts +79 -0
- package/dist/internal/blob-errors.d.ts.map +1 -0
- package/dist/internal/blob-errors.js +196 -0
- package/dist/internal/blob-errors.js.map +1 -0
- package/dist/internal/blob-request.d.ts +255 -0
- package/dist/internal/blob-request.d.ts.map +1 -0
- package/dist/internal/blob-request.js +291 -0
- package/dist/internal/blob-request.js.map +1 -0
- package/dist/internal/callback.d.ts +70 -0
- package/dist/internal/callback.d.ts.map +1 -0
- package/dist/internal/callback.js +141 -0
- package/dist/internal/callback.js.map +1 -0
- package/dist/internal/client-token.d.ts +65 -0
- package/dist/internal/client-token.d.ts.map +1 -0
- package/dist/internal/client-token.js +80 -0
- package/dist/internal/client-token.js.map +1 -0
- package/dist/internal/env.d.ts +34 -0
- package/dist/internal/env.d.ts.map +1 -0
- package/dist/internal/env.js +61 -0
- package/dist/internal/env.js.map +1 -0
- package/dist/internal/multipart.d.ts +110 -0
- package/dist/internal/multipart.d.ts.map +1 -0
- package/dist/internal/multipart.js +290 -0
- package/dist/internal/multipart.js.map +1 -0
- package/dist/internal/presign.d.ts +123 -0
- package/dist/internal/presign.d.ts.map +1 -0
- package/dist/internal/presign.js +345 -0
- package/dist/internal/presign.js.map +1 -0
- package/dist/internal/store-id.d.ts +38 -0
- package/dist/internal/store-id.d.ts.map +1 -0
- package/dist/internal/store-id.js +54 -0
- package/dist/internal/store-id.js.map +1 -0
- package/dist/internal/urls.d.ts +26 -0
- package/dist/internal/urls.d.ts.map +1 -0
- package/dist/internal/urls.js +33 -0
- package/dist/internal/urls.js.map +1 -0
- package/dist/internal/wire.d.ts +55 -0
- package/dist/internal/wire.d.ts.map +1 -0
- package/dist/internal/wire.js +64 -0
- package/dist/internal/wire.js.map +1 -0
- package/dist/internal/write-headers.d.ts +56 -0
- package/dist/internal/write-headers.d.ts.map +1 -0
- package/dist/internal/write-headers.js +82 -0
- package/dist/internal/write-headers.js.map +1 -0
- package/dist/testing/index.d.ts +2 -0
- package/dist/testing/index.d.ts.map +1 -0
- package/dist/testing/index.js +2 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/test-blob-store.d.ts +134 -0
- package/dist/testing/test-blob-store.d.ts.map +1 -0
- package/dist/testing/test-blob-store.js +718 -0
- package/dist/testing/test-blob-store.js.map +1 -0
- package/package.json +58 -4
- package/src/client.ts +11 -0
- package/src/contracts/blob-client.ts +121 -0
- package/src/contracts/blob-config.ts +64 -0
- package/src/contracts/blob-domain.ts +304 -0
- package/src/contracts/blob-errors.ts +161 -0
- package/src/contracts/blob-signed-urls.ts +112 -0
- package/src/contracts/blob-store.ts +258 -0
- package/src/contracts/blob-uploads.ts +151 -0
- package/src/implementation/blob-client-live.ts +387 -0
- package/src/implementation/blob-config-live.ts +145 -0
- package/src/implementation/blob-live.ts +55 -0
- package/src/implementation/blob-signed-urls-live.ts +121 -0
- package/src/implementation/blob-store-live.ts +528 -0
- package/src/implementation/blob-uploads-live.ts +326 -0
- package/src/index.ts +18 -0
- package/src/internal/blob-errors.ts +325 -0
- package/src/internal/blob-request.ts +617 -0
- package/src/internal/callback.ts +200 -0
- package/src/internal/client-token.ts +132 -0
- package/src/internal/env.ts +128 -0
- package/src/internal/multipart.ts +526 -0
- package/src/internal/presign.ts +477 -0
- package/src/internal/store-id.ts +69 -0
- package/src/internal/urls.ts +42 -0
- package/src/internal/wire.ts +80 -0
- package/src/internal/write-headers.ts +118 -0
- package/src/testing/index.ts +1 -0
- package/src/testing/test-blob-store.ts +1136 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Effect, Option, Result, Schema } from "effect";
|
|
2
|
+
import { Base64, Hex } from "effect/encoding";
|
|
3
|
+
import { ClientToken } from "../contracts/blob-domain.js";
|
|
4
|
+
/*
|
|
5
|
+
* Client tokens, as the SDK's `generateClientTokenFromReadWriteToken` builds them:
|
|
6
|
+
* `vercel_blob_client_<storeId>_<base64("<hex HMAC-SHA256(payload)>.<payload>")>`, where `payload` is
|
|
7
|
+
* the base64 of the token's JSON and the HMAC is keyed by the store's read-write token. The JSON is
|
|
8
|
+
* serialized exactly as the SDK does (key order and all), so tokens are byte-identical.
|
|
9
|
+
*/
|
|
10
|
+
/** Prefix of every client token. */
|
|
11
|
+
export const clientTokenPrefix = "vercel_blob_client_";
|
|
12
|
+
/** How long a client token generated without `validUntil` stays valid, as the SDK: 30 seconds. */
|
|
13
|
+
export const defaultClientTokenLifetimeMillis = 30_000;
|
|
14
|
+
/**
|
|
15
|
+
* The JSON a client token encodes, as the SDK serializes it: the fields in their order, with
|
|
16
|
+
* `allowOverwrite: true` appended when `ifMatch` is set and `allowOverwrite` is not, and
|
|
17
|
+
* `validUntil` defaulted to `defaultValidUntil` (kept in place when the fields have it).
|
|
18
|
+
*
|
|
19
|
+
* Callers reject `ifMatch` with `allowOverwrite: false` first; see {@link contradictsIfMatch}.
|
|
20
|
+
*/
|
|
21
|
+
export const clientTokenPayloadJson = (fields, defaultValidUntil) => {
|
|
22
|
+
const args = { ...fields };
|
|
23
|
+
if (Boolean(args["ifMatch"]) && args["allowOverwrite"] === undefined) {
|
|
24
|
+
args["allowOverwrite"] = true;
|
|
25
|
+
}
|
|
26
|
+
return JSON.stringify({ ...args, validUntil: args["validUntil"] ?? defaultValidUntil });
|
|
27
|
+
};
|
|
28
|
+
/** The SDK's message for an `ifMatch` with `allowOverwrite: false`, which it refuses to sign. */
|
|
29
|
+
export const ifMatchContradictionMessage = "ifMatch and allowOverwrite: false are contradictory. ifMatch is used for conditional overwrites, which requires allowOverwrite to be true.";
|
|
30
|
+
/** Whether the fields set `ifMatch` together with `allowOverwrite: false`, which the SDK rejects. */
|
|
31
|
+
export const contradictsIfMatch = (fields) => Boolean(fields["ifMatch"]) && fields["allowOverwrite"] === false;
|
|
32
|
+
const textEncoder = new TextEncoder();
|
|
33
|
+
/** HMAC-SHA256 of `data` keyed by `secret` (both UTF-8), as lowercase hex. */
|
|
34
|
+
export const hmacSha256Hex = Effect.fnUntraced(function* (secret, data) {
|
|
35
|
+
const key = yield* Effect.promise(() => globalThis.crypto.subtle.importKey("raw", textEncoder.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]));
|
|
36
|
+
const signature = yield* Effect.promise(() => globalThis.crypto.subtle.sign("HMAC", key, textEncoder.encode(data)));
|
|
37
|
+
return Hex.encode(new Uint8Array(signature));
|
|
38
|
+
});
|
|
39
|
+
/**
|
|
40
|
+
* Sign a client token's JSON with the store's read-write token.
|
|
41
|
+
*
|
|
42
|
+
* @param payloadJson - The token's JSON, from {@link clientTokenPayloadJson}.
|
|
43
|
+
* @returns `vercel_blob_client_<storeId>_<base64("<hex HMAC>.<base64 payloadJson>")>`.
|
|
44
|
+
*/
|
|
45
|
+
export const signClientToken = Effect.fnUntraced(function* (readWriteToken, storeId, payloadJson) {
|
|
46
|
+
const payload = Base64.encode(payloadJson);
|
|
47
|
+
const signature = yield* hmacSha256Hex(readWriteToken, payload);
|
|
48
|
+
return ClientToken.make(`${clientTokenPrefix}${storeId}_${Base64.encode(`${signature}.${payload}`)}`);
|
|
49
|
+
});
|
|
50
|
+
/** `onUploadCompleted` as a client token carries it. */
|
|
51
|
+
export const ClientTokenCallback = Schema.Struct({
|
|
52
|
+
callbackUrl: Schema.String,
|
|
53
|
+
tokenPayload: Schema.optionalKey(Schema.NullOr(Schema.String)),
|
|
54
|
+
});
|
|
55
|
+
/** The JSON a client token encodes, as the SDK's `DecodedClientTokenPayload`. */
|
|
56
|
+
export const ClientTokenPayload = Schema.Struct({
|
|
57
|
+
pathname: Schema.String,
|
|
58
|
+
/** Expiry in epoch milliseconds. */
|
|
59
|
+
validUntil: Schema.Number,
|
|
60
|
+
onUploadCompleted: Schema.optionalKey(ClientTokenCallback),
|
|
61
|
+
maximumSizeInBytes: Schema.optionalKey(Schema.Number),
|
|
62
|
+
allowedContentTypes: Schema.optionalKey(Schema.Array(Schema.String)),
|
|
63
|
+
addRandomSuffix: Schema.optionalKey(Schema.Boolean),
|
|
64
|
+
allowOverwrite: Schema.optionalKey(Schema.Boolean),
|
|
65
|
+
cacheControlMaxAge: Schema.optionalKey(Schema.Number),
|
|
66
|
+
ifMatch: Schema.optionalKey(Schema.String),
|
|
67
|
+
/** Set by upload routes, which copy the route's `tokenPayload` into the token as the SDK does. */
|
|
68
|
+
tokenPayload: Schema.optionalKey(Schema.NullOr(Schema.String)),
|
|
69
|
+
});
|
|
70
|
+
const decodePayload = Schema.decodeUnknownOption(Schema.fromJsonString(ClientTokenPayload));
|
|
71
|
+
const decodeBase64String = (input) => Result.getSuccess(Base64.decodeString(input));
|
|
72
|
+
/**
|
|
73
|
+
* Read the JSON a client token encodes without verifying its signature, as the SDK's
|
|
74
|
+
* `getPayloadFromClientToken`: the fifth `_`-separated segment is base64 of `<hmac>.<base64 JSON>`.
|
|
75
|
+
* Works in browsers.
|
|
76
|
+
*
|
|
77
|
+
* @returns None when the token does not have that shape or its JSON is not a client token payload.
|
|
78
|
+
*/
|
|
79
|
+
export const getPayloadFromClientToken = (clientToken) => decodeBase64String(clientToken.split("_")[4] ?? "").pipe(Option.flatMap((secured) => Option.fromUndefinedOr(secured.split(".")[1])), Option.flatMap(decodeBase64String), Option.flatMap(decodePayload));
|
|
80
|
+
//# sourceMappingURL=client-token.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-token.js","sourceRoot":"","sources":["../../src/internal/client-token.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Config, Effect, Option } from "effect";
|
|
2
|
+
/** A `ConfigError` with a message explaining what to fix. */
|
|
3
|
+
export declare const configError: (message: string) => Config.ConfigError;
|
|
4
|
+
/**
|
|
5
|
+
* A variable as the SDK's `readEnv` reads it: trimmed, and absent when blank. In browsers these
|
|
6
|
+
* are usually `NEXT_PUBLIC_*` values inlined by the bundler, or absent.
|
|
7
|
+
*/
|
|
8
|
+
export declare const env: (name: string) => Config.Config<Option.Option<string>>;
|
|
9
|
+
/** The first of several variables that is set, such as a name and its `NEXT_PUBLIC_` twin. */
|
|
10
|
+
export declare const firstEnv: (...names: ReadonlyArray<string>) => Effect.Effect<Option.Option<string>, Config.ConfigError, never>;
|
|
11
|
+
/** Values that replace what {@link requestEnv} reads from the environment. */
|
|
12
|
+
export interface RequestEnvOptions {
|
|
13
|
+
readonly apiUrl?: URL | string | undefined;
|
|
14
|
+
readonly headers?: Readonly<Record<string, string>> | undefined;
|
|
15
|
+
readonly retries?: number | undefined;
|
|
16
|
+
}
|
|
17
|
+
/** How every Blob API request is addressed and retried, shared by the server and browser clients. */
|
|
18
|
+
export interface RequestEnv {
|
|
19
|
+
readonly apiUrl: URL;
|
|
20
|
+
readonly apiVersion: string;
|
|
21
|
+
readonly retries: number;
|
|
22
|
+
readonly sendContentLength: boolean;
|
|
23
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Reads the request settings the SDK's `requestApi` reads on every call: `VERCEL_BLOB_API_URL`,
|
|
27
|
+
* `VERCEL_BLOB_API_VERSION_OVERRIDE`, `VERCEL_BLOB_PROXY_THROUGH_ALTERNATIVE_API` (each also with a
|
|
28
|
+
* `NEXT_PUBLIC_` prefix), `VERCEL_BLOB_RETRIES` and `VERCEL_BLOB_USE_X_CONTENT_LENGTH`.
|
|
29
|
+
*
|
|
30
|
+
* Fails with a `ConfigError` when the API URL is not absolute or the retry count is not a
|
|
31
|
+
* non-negative integer.
|
|
32
|
+
*/
|
|
33
|
+
export declare const requestEnv: (options: RequestEnvOptions) => Effect.Effect<RequestEnv, Config.ConfigError, never>;
|
|
34
|
+
//# sourceMappingURL=env.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.d.ts","sourceRoot":"","sources":["../../src/internal/env.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAkB,MAAM,EAAE,MAAM,EAAU,MAAM,QAAQ,CAAC;AAQxE,6DAA6D;AAC7D,eAAO,MAAM,WAAW,YAAa,MAAM,uBAC0B,CAAC;AAEtE;;;GAGG;AACH,eAAO,MAAM,GAAG,SAAU,MAAM,yCAQ7B,CAAC;AAEJ,8FAA8F;AAC9F,eAAO,MAAM,QAAQ,aAAc,aAAa,CAAC,MAAM,CAAC,oEAOpD,CAAC;AAQL,8EAA8E;AAC9E,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,CAAC,EAAE,GAAG,GAAG,MAAM,GAAG,SAAS,CAAC;IAC3C,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GAAG,SAAS,CAAC;IAChE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CACvC;AAED,qGAAqG;AACrG,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,GAAG,CAAC;IACrB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACpD;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,sFA6DrB,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Config, ConfigProvider, Effect, Option, Schema } from "effect";
|
|
2
|
+
import { defaultApiUrl } from "./urls.js";
|
|
3
|
+
const defaultApiVersion = "13";
|
|
4
|
+
const defaultRetries = 10;
|
|
5
|
+
/** A `ConfigError` with a message explaining what to fix. */
|
|
6
|
+
export const configError = (message) => new Config.ConfigError(new ConfigProvider.SourceError({ message }));
|
|
7
|
+
/**
|
|
8
|
+
* A variable as the SDK's `readEnv` reads it: trimmed, and absent when blank. In browsers these
|
|
9
|
+
* are usually `NEXT_PUBLIC_*` values inlined by the bundler, or absent.
|
|
10
|
+
*/
|
|
11
|
+
export const env = (name) => Config.option(Config.String(name)).pipe(Config.map((value) => Option.filter(Option.map(value, (value) => value.trim()), (value) => value !== "")));
|
|
12
|
+
/** The first of several variables that is set, such as a name and its `NEXT_PUBLIC_` twin. */
|
|
13
|
+
export const firstEnv = (...names) => Effect.gen(function* () {
|
|
14
|
+
for (const name of names) {
|
|
15
|
+
const value = yield* env(name);
|
|
16
|
+
if (Option.isSome(value))
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
return Option.none();
|
|
20
|
+
});
|
|
21
|
+
const decodeUrl = Schema.decodeUnknownOption(Schema.URLFromString);
|
|
22
|
+
const decodeRetries = Schema.decodeUnknownOption(Schema.NumberFromString.check(Schema.isInt(), Schema.isGreaterThanOrEqualTo(0)));
|
|
23
|
+
/**
|
|
24
|
+
* Reads the request settings the SDK's `requestApi` reads on every call: `VERCEL_BLOB_API_URL`,
|
|
25
|
+
* `VERCEL_BLOB_API_VERSION_OVERRIDE`, `VERCEL_BLOB_PROXY_THROUGH_ALTERNATIVE_API` (each also with a
|
|
26
|
+
* `NEXT_PUBLIC_` prefix), `VERCEL_BLOB_RETRIES` and `VERCEL_BLOB_USE_X_CONTENT_LENGTH`.
|
|
27
|
+
*
|
|
28
|
+
* Fails with a `ConfigError` when the API URL is not absolute or the retry count is not a
|
|
29
|
+
* non-negative integer.
|
|
30
|
+
*/
|
|
31
|
+
export const requestEnv = Effect.fnUntraced(function* (options) {
|
|
32
|
+
const apiUrlInput = options.apiUrl ??
|
|
33
|
+
Option.getOrElse(yield* firstEnv("VERCEL_BLOB_API_URL", "NEXT_PUBLIC_VERCEL_BLOB_API_URL"), () => defaultApiUrl);
|
|
34
|
+
const apiUrl = yield* Option.match(decodeUrl(String(apiUrlInput)), {
|
|
35
|
+
onNone: () => Effect.fail(configError(`Invalid Blob API URL "${String(apiUrlInput)}": expected an absolute URL.`)),
|
|
36
|
+
onSome: Effect.succeed,
|
|
37
|
+
});
|
|
38
|
+
const apiVersion = Option.getOrElse(yield* firstEnv("VERCEL_BLOB_API_VERSION_OVERRIDE", "NEXT_PUBLIC_VERCEL_BLOB_API_VERSION_OVERRIDE"), () => defaultApiVersion);
|
|
39
|
+
const retries = options.retries ??
|
|
40
|
+
(yield* Option.match(yield* env("VERCEL_BLOB_RETRIES"), {
|
|
41
|
+
onNone: () => Effect.succeed(defaultRetries),
|
|
42
|
+
onSome: (value) => Option.match(decodeRetries(value), {
|
|
43
|
+
onNone: () => Effect.fail(configError(`Invalid VERCEL_BLOB_RETRIES "${value}": expected a non-negative integer.`)),
|
|
44
|
+
onSome: Effect.succeed,
|
|
45
|
+
}),
|
|
46
|
+
}));
|
|
47
|
+
if (!Number.isInteger(retries) || retries < 0) {
|
|
48
|
+
return yield* Effect.fail(configError(`Invalid retries ${retries}: expected a non-negative integer.`));
|
|
49
|
+
}
|
|
50
|
+
const sendContentLength = Option.contains(yield* env("VERCEL_BLOB_USE_X_CONTENT_LENGTH"), "1");
|
|
51
|
+
const proxyThroughAlternativeApi = yield* firstEnv("VERCEL_BLOB_PROXY_THROUGH_ALTERNATIVE_API", "NEXT_PUBLIC_VERCEL_BLOB_PROXY_THROUGH_ALTERNATIVE_API");
|
|
52
|
+
const headers = {
|
|
53
|
+
...Option.match(proxyThroughAlternativeApi, {
|
|
54
|
+
onNone: () => ({}),
|
|
55
|
+
onSome: (value) => ({ "x-proxy-through-alternative-api": value }),
|
|
56
|
+
}),
|
|
57
|
+
...options.headers,
|
|
58
|
+
};
|
|
59
|
+
return { apiUrl, apiVersion, retries, sendContentLength, headers };
|
|
60
|
+
});
|
|
61
|
+
//# sourceMappingURL=env.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/internal/env.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { Duration, Effect } from "effect";
|
|
2
|
+
import type { BlobBody, OnUploadProgress, PutBlobResult } from "../contracts/blob-domain.ts";
|
|
3
|
+
import type { BlobApiError, BlobDomainErrorTag } from "./blob-errors.ts";
|
|
4
|
+
import type { BlobRequestAuth, BlobRequester } from "./blob-request.ts";
|
|
5
|
+
import type { WriteHeaderOptions, WriteOption } from "./write-headers.ts";
|
|
6
|
+
import { MultipartUpload, UploadedPart } from "../contracts/blob-domain.ts";
|
|
7
|
+
import { BlobUploadRejected } from "../contracts/blob-errors.ts";
|
|
8
|
+
/** Size of the parts an automatic multipart upload sends, as the SDK's `partSizeInBytes`. */
|
|
9
|
+
export declare const multipartPartSize: number;
|
|
10
|
+
/** Minimum time between progress events of an automatic multipart upload, as in the SDK. */
|
|
11
|
+
export declare const multipartProgressInterval: Duration.Duration;
|
|
12
|
+
/**
|
|
13
|
+
* How many parts an automatic multipart upload sends at once, as the SDK's `maxConcurrentUploads`:
|
|
14
|
+
* 6 in browsers (where `window` is defined), which allow about six connections per host, and 8
|
|
15
|
+
* elsewhere. Read on every call.
|
|
16
|
+
*/
|
|
17
|
+
export declare const maxConcurrentPartUploads: () => number;
|
|
18
|
+
/**
|
|
19
|
+
* Which write options become headers at each step. Creation (and every request of an automatic
|
|
20
|
+
* upload, which the SDK sends with the `put` headers throughout) uses `create`; `uploadPart` and
|
|
21
|
+
* `completeMultipartUpload` rebuild their headers from the {@link MultipartUpload} handle with
|
|
22
|
+
* `part`.
|
|
23
|
+
*/
|
|
24
|
+
export interface MultipartWriteOptions {
|
|
25
|
+
readonly create: ReadonlyArray<WriteOption>;
|
|
26
|
+
readonly part: ReadonlyArray<WriteOption>;
|
|
27
|
+
}
|
|
28
|
+
/** The SDK's server lists: every option at creation, all but `ifMatch` for parts and completion. */
|
|
29
|
+
export declare const serverMultipartWriteOptions: MultipartWriteOptions;
|
|
30
|
+
/** The SDK's client-token lists: only `contentType` (and `access`); the token fixes the rest. */
|
|
31
|
+
export declare const clientMultipartWriteOptions: MultipartWriteOptions;
|
|
32
|
+
/**
|
|
33
|
+
* Where and how multipart requests are sent. Build it once per layer for the server; the browser
|
|
34
|
+
* client builds one per call because its auth (client token or presigned payload) is per upload.
|
|
35
|
+
*
|
|
36
|
+
* @template EA - Failure of `auth`, such as `OidcTokenUnavailable`.
|
|
37
|
+
* @template RA - Requirements of `auth`.
|
|
38
|
+
* @template K - Domain errors the calling service's contract declares for writes.
|
|
39
|
+
*/
|
|
40
|
+
export interface MultipartTarget<EA, RA, K extends BlobDomainErrorTag> {
|
|
41
|
+
readonly requester: BlobRequester;
|
|
42
|
+
/** Evaluated once per request, so refreshed OIDC tokens are picked up between parts. */
|
|
43
|
+
readonly auth: Effect.Effect<BlobRequestAuth, EA, RA>;
|
|
44
|
+
/**
|
|
45
|
+
* Prefix of the operation names in errors and spans: `BlobStore` or `BlobClient`. Requests are
|
|
46
|
+
* named `<service>.createMultipartUpload`, `<service>.uploadPart` and
|
|
47
|
+
* `<service>.completeMultipartUpload`.
|
|
48
|
+
*/
|
|
49
|
+
readonly service: string;
|
|
50
|
+
readonly domainErrors: ReadonlyArray<K>;
|
|
51
|
+
readonly writeOptions: MultipartWriteOptions;
|
|
52
|
+
}
|
|
53
|
+
/** A part as the complete request sends it; `UploadedPart` and plain records both fit. */
|
|
54
|
+
export interface CompletedPart {
|
|
55
|
+
readonly etag: string;
|
|
56
|
+
readonly partNumber: number;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Start a multipart upload: `POST /mpu?pathname=…` with `x-mpu-action: create` and the
|
|
60
|
+
* `writeOptions.create` headers.
|
|
61
|
+
*
|
|
62
|
+
* @returns The handle: pathname, the store's key and upload id, `access` and every write option
|
|
63
|
+
* creation sent (cache lifetime in seconds), so parts and completion resend the same headers.
|
|
64
|
+
*/
|
|
65
|
+
export declare const createMultipartUpload: <EA, RA, K extends BlobDomainErrorTag>(target: MultipartTarget<EA, RA, K>, pathname: string & import("effect/Brand").Brand<"BlobPathname">, options: WriteHeaderOptions) => Effect.Effect<MultipartUpload, EA | BlobApiError<K>, RA>;
|
|
66
|
+
/**
|
|
67
|
+
* Upload one part: `POST /mpu?pathname=…` with `x-mpu-action: upload`, `x-mpu-key`
|
|
68
|
+
* (URI-encoded), `x-mpu-upload-id`, `x-mpu-part-number` and the `writeOptions.part` headers
|
|
69
|
+
* rebuilt from the handle. The body is sent as given; a `Stream` body is not retried.
|
|
70
|
+
*/
|
|
71
|
+
export declare const uploadPart: <EA, RA, K extends BlobDomainErrorTag, E = never, R = never>(target: MultipartTarget<EA, RA, K>, upload: MultipartUpload, partNumber: number & import("effect/Brand").Brand<"PartNumber">, body: BlobBody<E, R>) => Effect.Effect<UploadedPart, E | EA | BlobApiError<K>, R | RA>;
|
|
72
|
+
/**
|
|
73
|
+
* Assemble the parts into the blob: `POST /mpu?pathname=…` with `x-mpu-action: complete`, the
|
|
74
|
+
* `writeOptions.part` headers rebuilt from the handle, and the JSON array
|
|
75
|
+
* `[{"etag","partNumber"}, …]` in the order given.
|
|
76
|
+
*/
|
|
77
|
+
export declare const completeMultipartUpload: <EA, RA, K extends BlobDomainErrorTag>(target: MultipartTarget<EA, RA, K>, upload: MultipartUpload, parts: ReadonlyArray<CompletedPart>) => Effect.Effect<PutBlobResult, EA | BlobApiError<K>, RA>;
|
|
78
|
+
/** Options of an automatic multipart upload. */
|
|
79
|
+
export type MultipartPutOptions = WriteHeaderOptions & {
|
|
80
|
+
/**
|
|
81
|
+
* Fail with `BlobUploadRejected` / `FileTooLarge` before any request when the body's known size
|
|
82
|
+
* is larger. Streams are not checked.
|
|
83
|
+
*/
|
|
84
|
+
readonly maximumSizeInBytes?: number | undefined;
|
|
85
|
+
readonly onUploadProgress?: OnUploadProgress | undefined;
|
|
86
|
+
/** Parts sent at once; {@link maxConcurrentPartUploads} by default. */
|
|
87
|
+
readonly concurrency?: number | undefined;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Upload a body with a multipart upload, as the SDK's `uncontrolledMultipartUpload`: create it,
|
|
91
|
+
* send the body in {@link multipartPartSize} parts (the last one smaller) with bounded concurrency,
|
|
92
|
+
* then complete it with the parts in part-number order. Every request carries the
|
|
93
|
+
* `writeOptions.create` headers, as the SDK resends the `put` headers.
|
|
94
|
+
*
|
|
95
|
+
* - `maximumSizeInBytes` is checked against the known size before any request.
|
|
96
|
+
* - Memory stays bounded: a part's bytes are produced only when an upload slot is free, so at most
|
|
97
|
+
* `concurrency` parts (plus the parts cut from the stream chunk being read) are held at once.
|
|
98
|
+
* - The first failure, of a part or of the body stream, interrupts the parts in flight; the upload
|
|
99
|
+
* is then not completed.
|
|
100
|
+
* - An empty body uploads no part and completes with an empty part list, as the SDK does.
|
|
101
|
+
* - Progress: 0 % once the upload is created, then the bytes sent by all parts summed and
|
|
102
|
+
* throttled to one event per {@link multipartProgressInterval} (the SDK's numbers, rounded to two
|
|
103
|
+
* decimals). The event in which every byte has been acknowledged is held back, and
|
|
104
|
+
* `{ loaded, total: loaded, percentage: 100 }` is reported once the upload is completed instead.
|
|
105
|
+
*
|
|
106
|
+
* @returns The stored blob; fails with `BlobUploadRejected` for an oversized body, the requests'
|
|
107
|
+
* errors, or the body stream's error.
|
|
108
|
+
*/
|
|
109
|
+
export declare const multipartPut: <EA, RA, K extends BlobDomainErrorTag, E = never, R = never>(target: MultipartTarget<EA, RA, K>, pathname: string & import("effect/Brand").Brand<"BlobPathname">, body: BlobBody<E, R>, options: MultipartPutOptions) => Effect.Effect<PutBlobResult, E | EA | BlobUploadRejected | BlobApiError<K>, R | RA>;
|
|
110
|
+
//# sourceMappingURL=multipart.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multipart.d.ts","sourceRoot":"","sources":["../../src/internal/multipart.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { Duration, Effect, Stream, SynchronizedRef } from "effect";
|
|
2
|
+
import { MultipartUpload, UploadedPart } from "../contracts/blob-domain.js";
|
|
3
|
+
import { BlobUploadRejected, FileTooLarge } from "../contracts/blob-errors.js";
|
|
4
|
+
import { bodyLength, uploadProgressThrottle } from "./blob-request.js";
|
|
5
|
+
import { CreateMultipartUploadResponse, PutBlobResponse, UploadPartResponse } from "./wire.js";
|
|
6
|
+
import { allowedWriteOptions, cacheControlMaxAgeSeconds, writeHeaders } from "./write-headers.js";
|
|
7
|
+
/*
|
|
8
|
+
* Multipart uploads, shared by the server `BlobStore` and the browser `BlobClient`: the manual
|
|
9
|
+
* primitives (create, upload a part, complete) and the automatic upload behind `put({ multipart })`,
|
|
10
|
+
* ported from the SDK's `multipart/*.ts` and `uncontrolledMultipartUpload`.
|
|
11
|
+
*/
|
|
12
|
+
/** Size of the parts an automatic multipart upload sends, as the SDK's `partSizeInBytes`. */
|
|
13
|
+
export const multipartPartSize = 8 * 1024 * 1024;
|
|
14
|
+
/** Minimum time between progress events of an automatic multipart upload, as in the SDK. */
|
|
15
|
+
export const multipartProgressInterval = Duration.millis(150);
|
|
16
|
+
/**
|
|
17
|
+
* How many parts an automatic multipart upload sends at once, as the SDK's `maxConcurrentUploads`:
|
|
18
|
+
* 6 in browsers (where `window` is defined), which allow about six connections per host, and 8
|
|
19
|
+
* elsewhere. Read on every call.
|
|
20
|
+
*/
|
|
21
|
+
export const maxConcurrentPartUploads = () => globalThis.window === undefined ? 8 : 6;
|
|
22
|
+
/** The SDK's server lists: every option at creation, all but `ifMatch` for parts and completion. */
|
|
23
|
+
export const serverMultipartWriteOptions = {
|
|
24
|
+
create: allowedWriteOptions.put,
|
|
25
|
+
part: allowedWriteOptions.multipartPart,
|
|
26
|
+
};
|
|
27
|
+
/** The SDK's client-token lists: only `contentType` (and `access`); the token fixes the rest. */
|
|
28
|
+
export const clientMultipartWriteOptions = {
|
|
29
|
+
create: allowedWriteOptions.client,
|
|
30
|
+
part: allowedWriteOptions.client,
|
|
31
|
+
};
|
|
32
|
+
const mpuPath = (pathname) => `/mpu?${new URLSearchParams({ pathname }).toString()}`;
|
|
33
|
+
/** The handle's write options, filtered to what creation sent, with the cache lifetime in seconds. */
|
|
34
|
+
const handleWriteOptions = (allowed, options) => ({
|
|
35
|
+
access: options.access,
|
|
36
|
+
...(allowed.includes("addRandomSuffix") && options.addRandomSuffix !== undefined
|
|
37
|
+
? { addRandomSuffix: options.addRandomSuffix }
|
|
38
|
+
: {}),
|
|
39
|
+
...(allowed.includes("allowOverwrite") && options.allowOverwrite !== undefined
|
|
40
|
+
? { allowOverwrite: options.allowOverwrite }
|
|
41
|
+
: {}),
|
|
42
|
+
...(allowed.includes("contentType") && options.contentType
|
|
43
|
+
? { contentType: options.contentType }
|
|
44
|
+
: {}),
|
|
45
|
+
...(allowed.includes("cacheControlMaxAge") && options.cacheControlMaxAge !== undefined
|
|
46
|
+
? { cacheControlMaxAge: cacheControlMaxAgeSeconds(options.cacheControlMaxAge) }
|
|
47
|
+
: {}),
|
|
48
|
+
});
|
|
49
|
+
/** The write options a handle carries, back in the form `writeHeaders` reads. */
|
|
50
|
+
const uploadWriteOptions = (upload) => ({
|
|
51
|
+
access: upload.access,
|
|
52
|
+
addRandomSuffix: upload.addRandomSuffix,
|
|
53
|
+
allowOverwrite: upload.allowOverwrite,
|
|
54
|
+
contentType: upload.contentType,
|
|
55
|
+
cacheControlMaxAge: upload.cacheControlMaxAge === undefined
|
|
56
|
+
? undefined
|
|
57
|
+
: Duration.seconds(upload.cacheControlMaxAge),
|
|
58
|
+
});
|
|
59
|
+
const createRequest = ({ target, pathname, headers, }) => target.requester.call({
|
|
60
|
+
operation: `${target.service}.createMultipartUpload`,
|
|
61
|
+
method: "POST",
|
|
62
|
+
path: mpuPath(pathname),
|
|
63
|
+
auth: target.auth,
|
|
64
|
+
response: CreateMultipartUploadResponse,
|
|
65
|
+
headers: { ...headers, "x-mpu-action": "create" },
|
|
66
|
+
locator: pathname,
|
|
67
|
+
domainErrors: target.domainErrors,
|
|
68
|
+
});
|
|
69
|
+
const uploadPartRequest = ({ target, pathname, headers }, part) => target.requester.call({
|
|
70
|
+
operation: `${target.service}.uploadPart`,
|
|
71
|
+
method: "POST",
|
|
72
|
+
path: mpuPath(pathname),
|
|
73
|
+
auth: target.auth,
|
|
74
|
+
response: UploadPartResponse,
|
|
75
|
+
headers: {
|
|
76
|
+
...headers,
|
|
77
|
+
"x-mpu-action": "upload",
|
|
78
|
+
// The key can hold any UTF-8 character, and header values must be US-ASCII.
|
|
79
|
+
"x-mpu-key": encodeURIComponent(part.key),
|
|
80
|
+
"x-mpu-upload-id": part.uploadId,
|
|
81
|
+
"x-mpu-part-number": String(part.partNumber),
|
|
82
|
+
...(part.contentType === undefined ? {} : { "content-type": part.contentType }),
|
|
83
|
+
},
|
|
84
|
+
body: part.body,
|
|
85
|
+
locator: pathname,
|
|
86
|
+
domainErrors: target.domainErrors,
|
|
87
|
+
onUploadProgress: part.onUploadProgress,
|
|
88
|
+
});
|
|
89
|
+
const completeRequest = ({ target, pathname, headers }, upload) => target.requester.call({
|
|
90
|
+
operation: `${target.service}.completeMultipartUpload`,
|
|
91
|
+
method: "POST",
|
|
92
|
+
path: mpuPath(pathname),
|
|
93
|
+
auth: target.auth,
|
|
94
|
+
response: PutBlobResponse,
|
|
95
|
+
headers: {
|
|
96
|
+
...headers,
|
|
97
|
+
"x-mpu-action": "complete",
|
|
98
|
+
"x-mpu-upload-id": upload.uploadId,
|
|
99
|
+
"x-mpu-key": encodeURIComponent(upload.key),
|
|
100
|
+
},
|
|
101
|
+
// `content-type: application/json` comes with `json`; fields in the SDK's order.
|
|
102
|
+
json: upload.parts.map(({ etag, partNumber }) => upload.partNumberFirst === true ? { partNumber, etag } : { etag, partNumber }),
|
|
103
|
+
locator: pathname,
|
|
104
|
+
domainErrors: target.domainErrors,
|
|
105
|
+
});
|
|
106
|
+
/**
|
|
107
|
+
* Start a multipart upload: `POST /mpu?pathname=…` with `x-mpu-action: create` and the
|
|
108
|
+
* `writeOptions.create` headers.
|
|
109
|
+
*
|
|
110
|
+
* @returns The handle: pathname, the store's key and upload id, `access` and every write option
|
|
111
|
+
* creation sent (cache lifetime in seconds), so parts and completion resend the same headers.
|
|
112
|
+
*/
|
|
113
|
+
export const createMultipartUpload = Effect.fnUntraced(function* (target, pathname, options) {
|
|
114
|
+
const allowed = target.writeOptions.create;
|
|
115
|
+
const { key, uploadId } = yield* createRequest({
|
|
116
|
+
target,
|
|
117
|
+
pathname,
|
|
118
|
+
headers: writeHeaders(allowed, options),
|
|
119
|
+
});
|
|
120
|
+
return new MultipartUpload({
|
|
121
|
+
pathname,
|
|
122
|
+
key,
|
|
123
|
+
uploadId,
|
|
124
|
+
...handleWriteOptions(allowed, options),
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
/**
|
|
128
|
+
* Upload one part: `POST /mpu?pathname=…` with `x-mpu-action: upload`, `x-mpu-key`
|
|
129
|
+
* (URI-encoded), `x-mpu-upload-id`, `x-mpu-part-number` and the `writeOptions.part` headers
|
|
130
|
+
* rebuilt from the handle. The body is sent as given; a `Stream` body is not retried.
|
|
131
|
+
*/
|
|
132
|
+
export const uploadPart = Effect.fnUntraced(function* (target, upload, partNumber, body) {
|
|
133
|
+
const { etag } = yield* uploadPartRequest({
|
|
134
|
+
target,
|
|
135
|
+
pathname: upload.pathname,
|
|
136
|
+
headers: writeHeaders(target.writeOptions.part, uploadWriteOptions(upload)),
|
|
137
|
+
}, { key: upload.key, uploadId: upload.uploadId, partNumber, body });
|
|
138
|
+
return new UploadedPart({ etag, partNumber });
|
|
139
|
+
});
|
|
140
|
+
/**
|
|
141
|
+
* Assemble the parts into the blob: `POST /mpu?pathname=…` with `x-mpu-action: complete`, the
|
|
142
|
+
* `writeOptions.part` headers rebuilt from the handle, and the JSON array
|
|
143
|
+
* `[{"etag","partNumber"}, …]` in the order given.
|
|
144
|
+
*/
|
|
145
|
+
export const completeMultipartUpload = (target, upload, parts) => completeRequest({
|
|
146
|
+
target,
|
|
147
|
+
pathname: upload.pathname,
|
|
148
|
+
headers: writeHeaders(target.writeOptions.part, uploadWriteOptions(upload)),
|
|
149
|
+
}, { key: upload.key, uploadId: upload.uploadId, parts });
|
|
150
|
+
/**
|
|
151
|
+
* Regroup a byte stream into parts of exactly `size` bytes, the last one smaller, copying each
|
|
152
|
+
* byte once into its part (the SDK cuts parts the same way, because R2 refuses uneven parts).
|
|
153
|
+
*/
|
|
154
|
+
const regroupParts = (size) => (stream) => Stream.mapAccum(stream, () => ({
|
|
155
|
+
part: undefined,
|
|
156
|
+
filled: 0,
|
|
157
|
+
}), (state, chunk) => {
|
|
158
|
+
let { part, filled } = state;
|
|
159
|
+
const full = [];
|
|
160
|
+
let position = 0;
|
|
161
|
+
while (position < chunk.length) {
|
|
162
|
+
part ??= new Uint8Array(size);
|
|
163
|
+
const count = Math.min(size - filled, chunk.length - position);
|
|
164
|
+
part.set(chunk.subarray(position, position + count), filled);
|
|
165
|
+
filled += count;
|
|
166
|
+
position += count;
|
|
167
|
+
if (filled === size) {
|
|
168
|
+
full.push(part);
|
|
169
|
+
part = undefined;
|
|
170
|
+
filled = 0;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return [{ part, filled }, full];
|
|
174
|
+
}, {
|
|
175
|
+
onHalt: ({ part, filled }) => part === undefined || filled === 0 ? [] : [part.slice(0, filled)],
|
|
176
|
+
});
|
|
177
|
+
const partRanges = (length, size) => Array.from({ length: Math.ceil(length / size) }, (_, index) => [
|
|
178
|
+
index * size,
|
|
179
|
+
Math.min(length, (index + 1) * size),
|
|
180
|
+
]);
|
|
181
|
+
const textEncoder = new TextEncoder();
|
|
182
|
+
const isBlob = (body) => typeof globalThis.Blob !== "undefined" && body instanceof globalThis.Blob;
|
|
183
|
+
/**
|
|
184
|
+
* The body cut into parts, each one plain bytes so it can be retried and sent by browsers that
|
|
185
|
+
* cannot stream request bodies. In-memory bodies are cut into views without copying; a `Blob` is
|
|
186
|
+
* sliced and each slice read only when its upload starts; a stream is regrouped as it is read.
|
|
187
|
+
*/
|
|
188
|
+
const bodyParts = (body, size) => {
|
|
189
|
+
if (typeof body === "string" || body instanceof Uint8Array) {
|
|
190
|
+
const bytes = typeof body === "string" ? textEncoder.encode(body) : body;
|
|
191
|
+
return Stream.fromIterable(partRanges(bytes.length, size).map(([start, end]) => Effect.succeed(bytes.subarray(start, end))));
|
|
192
|
+
}
|
|
193
|
+
if (isBlob(body)) {
|
|
194
|
+
// A Blob that cannot be read is a defect, as in `bodyStream`.
|
|
195
|
+
return Stream.fromIterable(partRanges(body.size, size).map(([start, end]) => Effect.map(Effect.promise(() => body.slice(start, end).arrayBuffer()), (buffer) => new Uint8Array(buffer))));
|
|
196
|
+
}
|
|
197
|
+
return body.pipe(regroupParts(size), Stream.map(Effect.succeed));
|
|
198
|
+
};
|
|
199
|
+
const sumLoaded = (loadedByPart) => {
|
|
200
|
+
let loaded = 0;
|
|
201
|
+
for (const partLoaded of loadedByPart.values())
|
|
202
|
+
loaded += partLoaded;
|
|
203
|
+
return loaded;
|
|
204
|
+
};
|
|
205
|
+
/** The SDK's aggregate event: bytes sent by every part, against the body's known size. */
|
|
206
|
+
const aggregateProgress = (loadedByPart, totalToLoad) => {
|
|
207
|
+
const loaded = sumLoaded(loadedByPart);
|
|
208
|
+
return {
|
|
209
|
+
loaded,
|
|
210
|
+
total: totalToLoad || loaded,
|
|
211
|
+
percentage: totalToLoad > 0 ? Number(((loaded / totalToLoad) * 100).toFixed(2)) : 0,
|
|
212
|
+
};
|
|
213
|
+
};
|
|
214
|
+
/**
|
|
215
|
+
* Upload a body with a multipart upload, as the SDK's `uncontrolledMultipartUpload`: create it,
|
|
216
|
+
* send the body in {@link multipartPartSize} parts (the last one smaller) with bounded concurrency,
|
|
217
|
+
* then complete it with the parts in part-number order. Every request carries the
|
|
218
|
+
* `writeOptions.create` headers, as the SDK resends the `put` headers.
|
|
219
|
+
*
|
|
220
|
+
* - `maximumSizeInBytes` is checked against the known size before any request.
|
|
221
|
+
* - Memory stays bounded: a part's bytes are produced only when an upload slot is free, so at most
|
|
222
|
+
* `concurrency` parts (plus the parts cut from the stream chunk being read) are held at once.
|
|
223
|
+
* - The first failure, of a part or of the body stream, interrupts the parts in flight; the upload
|
|
224
|
+
* is then not completed.
|
|
225
|
+
* - An empty body uploads no part and completes with an empty part list, as the SDK does.
|
|
226
|
+
* - Progress: 0 % once the upload is created, then the bytes sent by all parts summed and
|
|
227
|
+
* throttled to one event per {@link multipartProgressInterval} (the SDK's numbers, rounded to two
|
|
228
|
+
* decimals). The event in which every byte has been acknowledged is held back, and
|
|
229
|
+
* `{ loaded, total: loaded, percentage: 100 }` is reported once the upload is completed instead.
|
|
230
|
+
*
|
|
231
|
+
* @returns The stored blob; fails with `BlobUploadRejected` for an oversized body, the requests'
|
|
232
|
+
* errors, or the body stream's error.
|
|
233
|
+
*/
|
|
234
|
+
export const multipartPut = Effect.fnUntraced(function* (target, pathname, body, options) {
|
|
235
|
+
const totalToLoad = bodyLength(body);
|
|
236
|
+
const maximum = options.maximumSizeInBytes;
|
|
237
|
+
if (maximum !== undefined && !Stream.isStream(body) && totalToLoad > maximum) {
|
|
238
|
+
return yield* new BlobUploadRejected({
|
|
239
|
+
reason: new FileTooLarge({
|
|
240
|
+
message: `Body size of ${totalToLoad} bytes exceeds the maximum allowed size of ${maximum} bytes`,
|
|
241
|
+
}),
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
const step = {
|
|
245
|
+
target,
|
|
246
|
+
pathname,
|
|
247
|
+
headers: writeHeaders(target.writeOptions.create, options),
|
|
248
|
+
};
|
|
249
|
+
const service = target.service;
|
|
250
|
+
const { key, uploadId } = yield* createRequest(step).pipe(Effect.withSpan(`${service}.createMultipartUpload`));
|
|
251
|
+
const onUploadProgress = options.onUploadProgress;
|
|
252
|
+
const loadedByPart = yield* SynchronizedRef.make(new Map());
|
|
253
|
+
const uploadParts = (report) => bodyParts(body, multipartPartSize).pipe(Stream.mapEffect((source, index) => Effect.gen(function* () {
|
|
254
|
+
const partNumber = index + 1;
|
|
255
|
+
const bytes = yield* source;
|
|
256
|
+
const { etag } = yield* uploadPartRequest(step, {
|
|
257
|
+
key,
|
|
258
|
+
uploadId,
|
|
259
|
+
partNumber,
|
|
260
|
+
body: bytes,
|
|
261
|
+
onUploadProgress: report?.(partNumber),
|
|
262
|
+
contentType: "application/octet-stream",
|
|
263
|
+
});
|
|
264
|
+
return { etag, partNumber };
|
|
265
|
+
}).pipe(Effect.withSpan(`${service}.uploadPart`, { attributes: { partNumber: index + 1 } })), { concurrency: options.concurrency ?? maxConcurrentPartUploads(), unordered: true }), Stream.runCollect);
|
|
266
|
+
const complete = (parts) => completeRequest(step, {
|
|
267
|
+
key,
|
|
268
|
+
uploadId,
|
|
269
|
+
parts: [...parts].sort((a, b) => a.partNumber - b.partNumber),
|
|
270
|
+
partNumberFirst: true,
|
|
271
|
+
}).pipe(Effect.withSpan(`${service}.completeMultipartUpload`));
|
|
272
|
+
if (onUploadProgress === undefined) {
|
|
273
|
+
return yield* Effect.flatMap(uploadParts(undefined), complete);
|
|
274
|
+
}
|
|
275
|
+
yield* onUploadProgress({ loaded: 0, total: totalToLoad, percentage: 0 });
|
|
276
|
+
const reporter = yield* uploadProgressThrottle(onUploadProgress, multipartProgressInterval);
|
|
277
|
+
// Updating the sums and offering the event under one lock keeps events in sending order. The SDK
|
|
278
|
+
// also reports the aggregate in which every byte is acknowledged; it is left for the final event.
|
|
279
|
+
const partProgress = (partNumber) => (progress) => SynchronizedRef.updateEffect(loadedByPart, (current) => {
|
|
280
|
+
const next = new Map(current).set(partNumber, progress.loaded);
|
|
281
|
+
const event = aggregateProgress(next, totalToLoad);
|
|
282
|
+
const acknowledged = totalToLoad > 0 && event.loaded >= totalToLoad;
|
|
283
|
+
return Effect.as(acknowledged ? Effect.void : reporter.report(event), next);
|
|
284
|
+
});
|
|
285
|
+
const blob = yield* uploadParts(partProgress).pipe(Effect.flatMap(complete), Effect.ensuring(reporter.stop));
|
|
286
|
+
const loaded = sumLoaded(yield* SynchronizedRef.get(loadedByPart));
|
|
287
|
+
yield* onUploadProgress({ loaded, total: loaded, percentage: 100 });
|
|
288
|
+
return blob;
|
|
289
|
+
});
|
|
290
|
+
//# sourceMappingURL=multipart.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"multipart.js","sourceRoot":"","sources":["../../src/internal/multipart.ts"],"names":[],"mappings":"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"}
|