@transferx/adapter-s3 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/dist/R2Adapter.d.ts +75 -0
- package/dist/R2Adapter.d.ts.map +1 -0
- package/dist/R2Adapter.js +63 -0
- package/dist/R2Adapter.js.map +1 -0
- package/dist/S3Adapter.d.ts +132 -0
- package/dist/S3Adapter.d.ts.map +1 -0
- package/dist/S3Adapter.js +271 -0
- package/dist/S3Adapter.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/package.json +18 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module R2Adapter
|
|
3
|
+
*
|
|
4
|
+
* Cloudflare R2 multipart upload adapter for TransferX.
|
|
5
|
+
*
|
|
6
|
+
* R2 is S3-compatible but requires three non-standard settings:
|
|
7
|
+
*
|
|
8
|
+
* 1. Custom endpoint: `https://<accountId>.r2.cloudflarestorage.com`
|
|
9
|
+
* 2. forcePathStyle: `true` — avoids virtual-hosted DNS issues with R2
|
|
10
|
+
* 3. Region: `"auto"` — R2 ignores region; AWS SDK requires a value
|
|
11
|
+
*
|
|
12
|
+
* All multipart upload semantics (5 MiB min part size, ETag tracking,
|
|
13
|
+
* ListParts-based resume, sorted CompleteMultipartUpload) are inherited
|
|
14
|
+
* from S3Adapter.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* import { R2Adapter } from '@transferx/adapter-s3';
|
|
19
|
+
*
|
|
20
|
+
* const adapter = new R2Adapter({
|
|
21
|
+
* accountId: process.env.CF_ACCOUNT_ID!,
|
|
22
|
+
* bucket: 'my-r2-bucket',
|
|
23
|
+
* credentials: {
|
|
24
|
+
* accessKeyId: process.env.R2_ACCESS_KEY_ID!,
|
|
25
|
+
* secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
|
|
26
|
+
* },
|
|
27
|
+
* });
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
import type { S3Client } from "@aws-sdk/client-s3";
|
|
31
|
+
import type { LogLevel } from "@transferx/core";
|
|
32
|
+
import { S3Adapter } from "./S3Adapter.js";
|
|
33
|
+
export interface R2AdapterOptions {
|
|
34
|
+
/**
|
|
35
|
+
* Cloudflare account ID.
|
|
36
|
+
* Found in the Cloudflare dashboard URL: `dash.cloudflare.com/<accountId>`.
|
|
37
|
+
*/
|
|
38
|
+
accountId: string;
|
|
39
|
+
/** R2 bucket name. */
|
|
40
|
+
bucket: string;
|
|
41
|
+
/**
|
|
42
|
+
* R2 API credentials. Generate from Cloudflare Dashboard → R2 → Manage API tokens.
|
|
43
|
+
* These are **never** logged.
|
|
44
|
+
*/
|
|
45
|
+
credentials: {
|
|
46
|
+
/** R2 Access Key ID. */
|
|
47
|
+
accessKeyId: string;
|
|
48
|
+
/** R2 Secret Access Key. */
|
|
49
|
+
secretAccessKey: string;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Per-request HTTP timeout in milliseconds.
|
|
53
|
+
* Default: `120_000` (2 minutes).
|
|
54
|
+
*/
|
|
55
|
+
timeoutMs?: number;
|
|
56
|
+
/**
|
|
57
|
+
* Optional structured-log callback. Wire to the engine's EventBus for
|
|
58
|
+
* end-to-end observability.
|
|
59
|
+
*/
|
|
60
|
+
onLog?: (level: LogLevel, message: string, context?: Record<string, unknown>) => void;
|
|
61
|
+
/**
|
|
62
|
+
* Inject a pre-built `S3Client` instance.
|
|
63
|
+
* **For testing only.**
|
|
64
|
+
*/
|
|
65
|
+
client?: S3Client;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* R2Adapter delegates all work to S3Adapter, injecting the R2-specific
|
|
69
|
+
* connection settings in the constructor. No R2-specific overrides are needed
|
|
70
|
+
* beyond those three config values.
|
|
71
|
+
*/
|
|
72
|
+
export declare class R2Adapter extends S3Adapter {
|
|
73
|
+
constructor(opts: R2AdapterOptions);
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=R2Adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"R2Adapter.d.ts","sourceRoot":"","sources":["../src/R2Adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAyB,MAAM,gBAAgB,CAAC;AAIlE,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,sBAAsB;IACtB,MAAM,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,WAAW,EAAE;QACX,wBAAwB;QACxB,WAAW,EAAE,MAAM,CAAC;QACpB,4BAA4B;QAC5B,eAAe,EAAE,MAAM,CAAC;KACzB,CAAC;IACF;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;OAGG;IACH,KAAK,CAAC,EAAE,CACN,KAAK,EAAE,QAAQ,EACf,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC9B,IAAI,CAAC;IACV;;;OAGG;IACH,MAAM,CAAC,EAAE,QAAQ,CAAC;CACnB;AAID;;;;GAIG;AACH,qBAAa,SAAU,SAAQ,SAAS;gBAC1B,IAAI,EAAE,gBAAgB;CAoBnC"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module R2Adapter
|
|
4
|
+
*
|
|
5
|
+
* Cloudflare R2 multipart upload adapter for TransferX.
|
|
6
|
+
*
|
|
7
|
+
* R2 is S3-compatible but requires three non-standard settings:
|
|
8
|
+
*
|
|
9
|
+
* 1. Custom endpoint: `https://<accountId>.r2.cloudflarestorage.com`
|
|
10
|
+
* 2. forcePathStyle: `true` — avoids virtual-hosted DNS issues with R2
|
|
11
|
+
* 3. Region: `"auto"` — R2 ignores region; AWS SDK requires a value
|
|
12
|
+
*
|
|
13
|
+
* All multipart upload semantics (5 MiB min part size, ETag tracking,
|
|
14
|
+
* ListParts-based resume, sorted CompleteMultipartUpload) are inherited
|
|
15
|
+
* from S3Adapter.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* import { R2Adapter } from '@transferx/adapter-s3';
|
|
20
|
+
*
|
|
21
|
+
* const adapter = new R2Adapter({
|
|
22
|
+
* accountId: process.env.CF_ACCOUNT_ID!,
|
|
23
|
+
* bucket: 'my-r2-bucket',
|
|
24
|
+
* credentials: {
|
|
25
|
+
* accessKeyId: process.env.R2_ACCESS_KEY_ID!,
|
|
26
|
+
* secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
|
|
27
|
+
* },
|
|
28
|
+
* });
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
32
|
+
exports.R2Adapter = void 0;
|
|
33
|
+
const S3Adapter_js_1 = require("./S3Adapter.js");
|
|
34
|
+
// ── Adapter ───────────────────────────────────────────────────────────────────
|
|
35
|
+
/**
|
|
36
|
+
* R2Adapter delegates all work to S3Adapter, injecting the R2-specific
|
|
37
|
+
* connection settings in the constructor. No R2-specific overrides are needed
|
|
38
|
+
* beyond those three config values.
|
|
39
|
+
*/
|
|
40
|
+
class R2Adapter extends S3Adapter_js_1.S3Adapter {
|
|
41
|
+
constructor(opts) {
|
|
42
|
+
const s3Opts = {
|
|
43
|
+
bucket: opts.bucket,
|
|
44
|
+
// R2 does not enforce region routing but the AWS SDK requires a value.
|
|
45
|
+
region: "auto",
|
|
46
|
+
// Construct the R2 account-specific endpoint.
|
|
47
|
+
endpoint: `https://${opts.accountId}.r2.cloudflarestorage.com`,
|
|
48
|
+
// Path-style addressing is required to avoid virtual-hosted DNS issues.
|
|
49
|
+
forcePathStyle: true,
|
|
50
|
+
credentials: {
|
|
51
|
+
accessKeyId: opts.credentials.accessKeyId,
|
|
52
|
+
secretAccessKey: opts.credentials.secretAccessKey,
|
|
53
|
+
},
|
|
54
|
+
// Forward optional fields only when present (exactOptionalPropertyTypes).
|
|
55
|
+
...(opts.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {}),
|
|
56
|
+
...(opts.onLog !== undefined ? { onLog: opts.onLog } : {}),
|
|
57
|
+
...(opts.client !== undefined ? { client: opts.client } : {}),
|
|
58
|
+
};
|
|
59
|
+
super(s3Opts);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
exports.R2Adapter = R2Adapter;
|
|
63
|
+
//# sourceMappingURL=R2Adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"R2Adapter.js","sourceRoot":"","sources":["../src/R2Adapter.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;;;AAIH,iDAAkE;AA2ClE,iFAAiF;AAEjF;;;;GAIG;AACH,MAAa,SAAU,SAAQ,wBAAS;IACtC,YAAY,IAAsB;QAChC,MAAM,MAAM,GAAqB;YAC/B,MAAM,EAAE,IAAI,CAAC,MAAM;YACnB,uEAAuE;YACvE,MAAM,EAAE,MAAM;YACd,8CAA8C;YAC9C,QAAQ,EAAE,WAAW,IAAI,CAAC,SAAS,2BAA2B;YAC9D,wEAAwE;YACxE,cAAc,EAAE,IAAI;YACpB,WAAW,EAAE;gBACX,WAAW,EAAE,IAAI,CAAC,WAAW,CAAC,WAAW;gBACzC,eAAe,EAAE,IAAI,CAAC,WAAW,CAAC,eAAe;aAClD;YACD,0EAA0E;YAC1E,GAAG,CAAC,IAAI,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACtE,GAAG,CAAC,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC1D,GAAG,CAAC,IAAI,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC9D,CAAC;QACF,KAAK,CAAC,MAAM,CAAC,CAAC;IAChB,CAAC;CACF;AArBD,8BAqBC"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module S3Adapter
|
|
3
|
+
*
|
|
4
|
+
* AWS S3 (and S3-compatible) multipart upload adapter for TransferX.
|
|
5
|
+
*
|
|
6
|
+
* Multipart upload lifecycle:
|
|
7
|
+
*
|
|
8
|
+
* 1. CreateMultipartUpload → UploadId (stored as session.providerSessionId)
|
|
9
|
+
* 2. UploadPart × N → ETag (stored as chunk.providerToken, verbatim
|
|
10
|
+
* with surrounding quotes, e.g. `"abc123"`)
|
|
11
|
+
* 3. CompleteMultipartUpload → finalises the object
|
|
12
|
+
* (abort path: AbortMultipartUpload)
|
|
13
|
+
*
|
|
14
|
+
* Resume support:
|
|
15
|
+
* getRemoteState() calls ListParts (with pagination) to discover already-
|
|
16
|
+
* uploaded parts, so the engine can skip re-uploading them on resume.
|
|
17
|
+
*
|
|
18
|
+
* Cloudflare R2 / S3-compatible stores:
|
|
19
|
+
* Pass `endpoint` + `forcePathStyle: true` to target any S3-compatible API.
|
|
20
|
+
* See R2Adapter for a purpose-built Cloudflare R2 wrapper.
|
|
21
|
+
*
|
|
22
|
+
* Security notes:
|
|
23
|
+
* - Credentials are passed to the AWS SDK and NEVER logged or stored in
|
|
24
|
+
* instance fields accessible outside this module.
|
|
25
|
+
* - The AWS SDK's built-in retry mechanism is disabled (maxAttempts: 1) so
|
|
26
|
+
* that TransferX's retry engine is the single source of truth.
|
|
27
|
+
*
|
|
28
|
+
* ETag contract:
|
|
29
|
+
* S3 returns ETags surrounded by double-quotes (e.g. `"d8e8fca2dc0f896f"`).
|
|
30
|
+
* These are stored verbatim as chunk.providerToken and passed back verbatim
|
|
31
|
+
* in CompleteMultipartUpload — the AWS SDK serialises them correctly.
|
|
32
|
+
*
|
|
33
|
+
* Minimum part size:
|
|
34
|
+
* AWS S3 requires each non-final part to be ≥ 5 MiB. TransferX's default
|
|
35
|
+
* chunkSize (10 MiB) satisfies this. The engine enforces chunkSize ≥ 5 MiB
|
|
36
|
+
* through config validation; this adapter does not re-validate.
|
|
37
|
+
*/
|
|
38
|
+
import { S3Client } from "@aws-sdk/client-s3";
|
|
39
|
+
import type { ITransferAdapter, ChunkUploadResult, RemoteUploadState, LogLevel } from "@transferx/core";
|
|
40
|
+
import type { TransferSession, ChunkMeta } from "@transferx/core";
|
|
41
|
+
export interface S3AdapterOptions {
|
|
42
|
+
/** Target S3 bucket name. */
|
|
43
|
+
bucket: string;
|
|
44
|
+
/**
|
|
45
|
+
* AWS region (e.g. `"us-east-1"`).
|
|
46
|
+
* Ignored when `endpoint` is set and the provider does not require a region
|
|
47
|
+
* (e.g. Cloudflare R2 — use `"auto"` or omit).
|
|
48
|
+
* Defaults to `"us-east-1"`.
|
|
49
|
+
*/
|
|
50
|
+
region?: string;
|
|
51
|
+
/**
|
|
52
|
+
* AWS credentials. These are passed directly to the AWS SDK and never logged.
|
|
53
|
+
* For Cloudflare R2: use the Access Key ID and Secret from an R2 API token.
|
|
54
|
+
*/
|
|
55
|
+
credentials: {
|
|
56
|
+
/** AWS Access Key ID (or R2 Access Key). */
|
|
57
|
+
accessKeyId: string;
|
|
58
|
+
/** AWS Secret Access Key (or R2 Secret). */
|
|
59
|
+
secretAccessKey: string;
|
|
60
|
+
/** AWS STS session token for temporary credentials. Omit for R2. */
|
|
61
|
+
sessionToken?: string;
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Custom endpoint URL for S3-compatible storage providers.
|
|
65
|
+
* Required for Cloudflare R2:
|
|
66
|
+
* `https://<account-id>.r2.cloudflarestorage.com`
|
|
67
|
+
*/
|
|
68
|
+
endpoint?: string;
|
|
69
|
+
/**
|
|
70
|
+
* Use path-style URLs (`/bucket/key`) instead of virtual-hosted style
|
|
71
|
+
* (`bucket.s3.amazonaws.com/key`).
|
|
72
|
+
* Required for Cloudflare R2 and most non-AWS S3-compatible stores.
|
|
73
|
+
* Default: `false`.
|
|
74
|
+
*/
|
|
75
|
+
forcePathStyle?: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Per-request HTTP timeout in milliseconds. Any request that stalls beyond
|
|
78
|
+
* this threshold is aborted and throws a retryable `network` error.
|
|
79
|
+
* Default: `120_000` (2 minutes).
|
|
80
|
+
*/
|
|
81
|
+
timeoutMs?: number;
|
|
82
|
+
/**
|
|
83
|
+
* Optional structured-log callback for observability.
|
|
84
|
+
* `createS3Engine()` (if using the SDK façade) wires this automatically to
|
|
85
|
+
* the shared EventBus; for manual use, pass `bus.emit` directly.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* const adapter = new S3Adapter({
|
|
89
|
+
* ...,
|
|
90
|
+
* onLog: (level, msg, ctx) => bus.emit({ type: 'log', level, message: msg, ...ctx && { context: ctx } }),
|
|
91
|
+
* });
|
|
92
|
+
*/
|
|
93
|
+
onLog?: (level: LogLevel, message: string, context?: Record<string, unknown>) => void;
|
|
94
|
+
/**
|
|
95
|
+
* Inject a pre-built `S3Client` instance.
|
|
96
|
+
* **For testing only** — pass a mock client to avoid real AWS calls.
|
|
97
|
+
* When provided, all other connection options (`region`, `endpoint`, etc.)
|
|
98
|
+
* are ignored.
|
|
99
|
+
*/
|
|
100
|
+
client?: S3Client;
|
|
101
|
+
}
|
|
102
|
+
export declare class S3Adapter implements ITransferAdapter {
|
|
103
|
+
/** @internal exposed as protected so R2Adapter can subclass. */
|
|
104
|
+
protected readonly _client: S3Client;
|
|
105
|
+
private readonly _bucket;
|
|
106
|
+
private readonly _timeoutMs;
|
|
107
|
+
private readonly _onLog;
|
|
108
|
+
constructor(opts: S3AdapterOptions);
|
|
109
|
+
initTransfer(session: TransferSession): Promise<string>;
|
|
110
|
+
uploadChunk(session: TransferSession, chunk: ChunkMeta, data: Uint8Array, _sha256Hex: string): Promise<ChunkUploadResult>;
|
|
111
|
+
completeTransfer(session: TransferSession, chunks: ChunkMeta[]): Promise<void>;
|
|
112
|
+
abortTransfer(session: TransferSession): Promise<void>;
|
|
113
|
+
getRemoteState(session: TransferSession): Promise<RemoteUploadState>;
|
|
114
|
+
/**
|
|
115
|
+
* Send an S3 command with a per-request AbortController timeout and unified
|
|
116
|
+
* error mapping. The return type is cast to `T` — callers know the shape of
|
|
117
|
+
* each command's output.
|
|
118
|
+
*
|
|
119
|
+
* The `command` parameter is typed as `unknown` here because AWS SDK v3
|
|
120
|
+
* Command classes are generic and contravariant — assigning a specific
|
|
121
|
+
* `CreateMultipartUploadCommand` to `Command<ServiceInputTypes, ...>` fails
|
|
122
|
+
* TypeScript's structural check due to middlewareStack overloads. Each call
|
|
123
|
+
* site already supplies the correctly-typed Command, so the cast is safe.
|
|
124
|
+
*/
|
|
125
|
+
private _send;
|
|
126
|
+
/**
|
|
127
|
+
* Normalise any error thrown by the AWS SDK into a typed TransferError so the
|
|
128
|
+
* engine can make correct retry/abort decisions.
|
|
129
|
+
*/
|
|
130
|
+
private _mapError;
|
|
131
|
+
}
|
|
132
|
+
//# sourceMappingURL=S3Adapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"S3Adapter.d.ts","sourceRoot":"","sources":["../src/S3Adapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAEH,OAAO,EACL,QAAQ,EAMT,MAAM,oBAAoB,CAAC;AAS5B,OAAO,KAAK,EACV,gBAAgB,EAChB,iBAAiB,EACjB,iBAAiB,EACjB,QAAQ,EACT,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAIlE,MAAM,WAAW,gBAAgB;IAC/B,6BAA6B;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,WAAW,EAAE;QACX,4CAA4C;QAC5C,WAAW,EAAE,MAAM,CAAC;QACpB,4CAA4C;QAC5C,eAAe,EAAE,MAAM,CAAC;QACxB,oEAAoE;QACpE,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,CAAC;IACF;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;;;;;;OAUG;IACH,KAAK,CAAC,EAAE,CACN,KAAK,EAAE,QAAQ,EACf,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC9B,IAAI,CAAC;IACV;;;;;OAKG;IACH,MAAM,CAAC,EAAE,QAAQ,CAAC;CACnB;AAYD,qBAAa,SAAU,YAAW,gBAAgB;IAChD,gEAAgE;IAChE,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAA4B;gBAEvC,IAAI,EAAE,gBAAgB;IAuC5B,YAAY,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC;IAyBvD,WAAW,CACf,OAAO,EAAE,eAAe,EACxB,KAAK,EAAE,SAAS,EAChB,IAAI,EAAE,UAAU,EAKhB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,iBAAiB,CAAC;IAuCvB,gBAAgB,CACpB,OAAO,EAAE,eAAe,EACxB,MAAM,EAAE,SAAS,EAAE,GAClB,OAAO,CAAC,IAAI,CAAC;IAgCV,aAAa,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;IActD,cAAc,CAAC,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,iBAAiB,CAAC;IAiD1E;;;;;;;;;;OAUG;YACW,KAAK;IAqBnB;;;OAGG;IACH,OAAO,CAAC,SAAS;CAkDlB"}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @module S3Adapter
|
|
4
|
+
*
|
|
5
|
+
* AWS S3 (and S3-compatible) multipart upload adapter for TransferX.
|
|
6
|
+
*
|
|
7
|
+
* Multipart upload lifecycle:
|
|
8
|
+
*
|
|
9
|
+
* 1. CreateMultipartUpload → UploadId (stored as session.providerSessionId)
|
|
10
|
+
* 2. UploadPart × N → ETag (stored as chunk.providerToken, verbatim
|
|
11
|
+
* with surrounding quotes, e.g. `"abc123"`)
|
|
12
|
+
* 3. CompleteMultipartUpload → finalises the object
|
|
13
|
+
* (abort path: AbortMultipartUpload)
|
|
14
|
+
*
|
|
15
|
+
* Resume support:
|
|
16
|
+
* getRemoteState() calls ListParts (with pagination) to discover already-
|
|
17
|
+
* uploaded parts, so the engine can skip re-uploading them on resume.
|
|
18
|
+
*
|
|
19
|
+
* Cloudflare R2 / S3-compatible stores:
|
|
20
|
+
* Pass `endpoint` + `forcePathStyle: true` to target any S3-compatible API.
|
|
21
|
+
* See R2Adapter for a purpose-built Cloudflare R2 wrapper.
|
|
22
|
+
*
|
|
23
|
+
* Security notes:
|
|
24
|
+
* - Credentials are passed to the AWS SDK and NEVER logged or stored in
|
|
25
|
+
* instance fields accessible outside this module.
|
|
26
|
+
* - The AWS SDK's built-in retry mechanism is disabled (maxAttempts: 1) so
|
|
27
|
+
* that TransferX's retry engine is the single source of truth.
|
|
28
|
+
*
|
|
29
|
+
* ETag contract:
|
|
30
|
+
* S3 returns ETags surrounded by double-quotes (e.g. `"d8e8fca2dc0f896f"`).
|
|
31
|
+
* These are stored verbatim as chunk.providerToken and passed back verbatim
|
|
32
|
+
* in CompleteMultipartUpload — the AWS SDK serialises them correctly.
|
|
33
|
+
*
|
|
34
|
+
* Minimum part size:
|
|
35
|
+
* AWS S3 requires each non-final part to be ≥ 5 MiB. TransferX's default
|
|
36
|
+
* chunkSize (10 MiB) satisfies this. The engine enforces chunkSize ≥ 5 MiB
|
|
37
|
+
* through config validation; this adapter does not re-validate.
|
|
38
|
+
*/
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.S3Adapter = void 0;
|
|
41
|
+
const client_s3_1 = require("@aws-sdk/client-s3");
|
|
42
|
+
const core_1 = require("@transferx/core");
|
|
43
|
+
// ── Adapter ───────────────────────────────────────────────────────────────────
|
|
44
|
+
class S3Adapter {
|
|
45
|
+
/** @internal exposed as protected so R2Adapter can subclass. */
|
|
46
|
+
_client;
|
|
47
|
+
_bucket;
|
|
48
|
+
_timeoutMs;
|
|
49
|
+
_onLog;
|
|
50
|
+
constructor(opts) {
|
|
51
|
+
this._bucket = opts.bucket;
|
|
52
|
+
this._timeoutMs = opts.timeoutMs ?? 120_000;
|
|
53
|
+
this._onLog = opts.onLog;
|
|
54
|
+
if (opts.client) {
|
|
55
|
+
// Injected client — used in tests; skip constructing a real one.
|
|
56
|
+
this._client = opts.client;
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
// Disable SDK-level retries — TransferX owns the retry loop.
|
|
60
|
+
const clientConfig = {
|
|
61
|
+
maxAttempts: 1,
|
|
62
|
+
region: opts.region ?? "us-east-1",
|
|
63
|
+
credentials: {
|
|
64
|
+
accessKeyId: opts.credentials.accessKeyId,
|
|
65
|
+
secretAccessKey: opts.credentials.secretAccessKey,
|
|
66
|
+
// Conditionally include sessionToken — exactOptionalPropertyTypes safe.
|
|
67
|
+
...(opts.credentials.sessionToken !== undefined
|
|
68
|
+
? { sessionToken: opts.credentials.sessionToken }
|
|
69
|
+
: {}),
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
// Only set endpoint / forcePathStyle when explicitly provided. Setting
|
|
73
|
+
// either to undefined would trigger AWS SDK default-resolution paths
|
|
74
|
+
// that conflict with some S3-compatible providers.
|
|
75
|
+
if (opts.endpoint !== undefined) {
|
|
76
|
+
clientConfig.endpoint = opts.endpoint;
|
|
77
|
+
}
|
|
78
|
+
if (opts.forcePathStyle === true) {
|
|
79
|
+
clientConfig.forcePathStyle = true;
|
|
80
|
+
}
|
|
81
|
+
this._client = new client_s3_1.S3Client(clientConfig);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// ── ITransferAdapter ───────────────────────────────────────────────────────
|
|
85
|
+
async initTransfer(session) {
|
|
86
|
+
const cmd = new client_s3_1.CreateMultipartUploadCommand({
|
|
87
|
+
Bucket: this._bucket,
|
|
88
|
+
Key: session.targetKey,
|
|
89
|
+
ContentType: session.file.mimeType || "application/octet-stream",
|
|
90
|
+
});
|
|
91
|
+
const output = await this._send(cmd, "initTransfer");
|
|
92
|
+
const uploadId = output.UploadId;
|
|
93
|
+
if (!uploadId) {
|
|
94
|
+
throw new core_1.TransferError("S3 CreateMultipartUpload did not return an UploadId", "serverError");
|
|
95
|
+
}
|
|
96
|
+
this._onLog?.("info", "[S3] Multipart upload initiated", {
|
|
97
|
+
sessionId: session.id,
|
|
98
|
+
uploadId,
|
|
99
|
+
});
|
|
100
|
+
return uploadId;
|
|
101
|
+
}
|
|
102
|
+
async uploadChunk(session, chunk, data,
|
|
103
|
+
// sha256Hex intentionally unused: the engine handles local integrity checks.
|
|
104
|
+
// S3 computes its own ETag (MD5) for server-side integrity verification.
|
|
105
|
+
// Attaching ChecksumSHA256 would require coordinating ChecksumAlgorithm
|
|
106
|
+
// on both CreateMultipartUpload and UploadPart — a future enhancement.
|
|
107
|
+
_sha256Hex) {
|
|
108
|
+
if (!session.providerSessionId) {
|
|
109
|
+
throw new core_1.TransferError("providerSessionId is missing — initTransfer must be called first", "fatal");
|
|
110
|
+
}
|
|
111
|
+
// S3 part numbers are 1-based (range: 1–10,000).
|
|
112
|
+
const partNumber = chunk.index + 1;
|
|
113
|
+
const cmd = new client_s3_1.UploadPartCommand({
|
|
114
|
+
Bucket: this._bucket,
|
|
115
|
+
Key: session.targetKey,
|
|
116
|
+
UploadId: session.providerSessionId,
|
|
117
|
+
PartNumber: partNumber,
|
|
118
|
+
Body: data,
|
|
119
|
+
});
|
|
120
|
+
const output = await this._send(cmd, "uploadChunk", chunk.index);
|
|
121
|
+
const etag = output.ETag;
|
|
122
|
+
if (!etag) {
|
|
123
|
+
throw new core_1.TransferError(`S3 UploadPart for part ${partNumber} returned no ETag`, "serverError");
|
|
124
|
+
}
|
|
125
|
+
// Store ETag verbatim — AWS returns it with surrounding double-quotes
|
|
126
|
+
// (e.g. `"d8e8fca2dc0f896fd7cb4cb0031ba249"`). CompleteMultipartUpload
|
|
127
|
+
// requires ETags in the same format, so we must NOT strip the quotes here.
|
|
128
|
+
return { providerToken: etag };
|
|
129
|
+
}
|
|
130
|
+
async completeTransfer(session, chunks) {
|
|
131
|
+
if (!session.providerSessionId) {
|
|
132
|
+
throw new core_1.TransferError("providerSessionId missing in completeTransfer", "fatal");
|
|
133
|
+
}
|
|
134
|
+
// S3 requires Parts sorted ascending by PartNumber; do so defensively even
|
|
135
|
+
// if the engine already delivers them in order.
|
|
136
|
+
const sortedParts = [...chunks]
|
|
137
|
+
.sort((a, b) => a.index - b.index)
|
|
138
|
+
.map((c) => ({
|
|
139
|
+
PartNumber: c.index + 1,
|
|
140
|
+
ETag: c.providerToken ?? "",
|
|
141
|
+
}));
|
|
142
|
+
const cmd = new client_s3_1.CompleteMultipartUploadCommand({
|
|
143
|
+
Bucket: this._bucket,
|
|
144
|
+
Key: session.targetKey,
|
|
145
|
+
UploadId: session.providerSessionId,
|
|
146
|
+
MultipartUpload: { Parts: sortedParts },
|
|
147
|
+
});
|
|
148
|
+
await this._send(cmd, "completeTransfer");
|
|
149
|
+
this._onLog?.("info", "[S3] Multipart upload completed", {
|
|
150
|
+
sessionId: session.id,
|
|
151
|
+
partCount: sortedParts.length,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
async abortTransfer(session) {
|
|
155
|
+
if (!session.providerSessionId)
|
|
156
|
+
return;
|
|
157
|
+
try {
|
|
158
|
+
const cmd = new client_s3_1.AbortMultipartUploadCommand({
|
|
159
|
+
Bucket: this._bucket,
|
|
160
|
+
Key: session.targetKey,
|
|
161
|
+
UploadId: session.providerSessionId,
|
|
162
|
+
});
|
|
163
|
+
await this._send(cmd, "abortTransfer");
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// Best-effort — swallow errors so cancel/fail never blocks the engine.
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
async getRemoteState(session) {
|
|
170
|
+
if (!session.providerSessionId) {
|
|
171
|
+
return { uploadedParts: [] };
|
|
172
|
+
}
|
|
173
|
+
const uploadedParts = [];
|
|
174
|
+
// S3 paginates ListParts via PartNumberMarker / NextPartNumberMarker.
|
|
175
|
+
// IsTruncated === true means there are more pages.
|
|
176
|
+
let partNumberMarker;
|
|
177
|
+
do {
|
|
178
|
+
const cmdInput = {
|
|
179
|
+
Bucket: this._bucket,
|
|
180
|
+
Key: session.targetKey,
|
|
181
|
+
UploadId: session.providerSessionId,
|
|
182
|
+
MaxParts: 1000,
|
|
183
|
+
};
|
|
184
|
+
if (partNumberMarker !== undefined) {
|
|
185
|
+
// ListPartsCommandInput.PartNumberMarker is typed as string in the
|
|
186
|
+
// AWS SDK even though S3's API uses an integer marker value.
|
|
187
|
+
cmdInput.PartNumberMarker = String(partNumberMarker);
|
|
188
|
+
}
|
|
189
|
+
const output = await this._send(new client_s3_1.ListPartsCommand(cmdInput), "getRemoteState");
|
|
190
|
+
for (const part of output.Parts ?? []) {
|
|
191
|
+
if (part.PartNumber != null && part.ETag) {
|
|
192
|
+
uploadedParts.push({
|
|
193
|
+
partNumber: part.PartNumber,
|
|
194
|
+
providerToken: part.ETag,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
partNumberMarker =
|
|
199
|
+
output.IsTruncated === true && output.NextPartNumberMarker != null
|
|
200
|
+
? output.NextPartNumberMarker
|
|
201
|
+
: undefined;
|
|
202
|
+
} while (partNumberMarker !== undefined);
|
|
203
|
+
return { uploadedParts };
|
|
204
|
+
}
|
|
205
|
+
// ── Private helpers ────────────────────────────────────────────────────────
|
|
206
|
+
/**
|
|
207
|
+
* Send an S3 command with a per-request AbortController timeout and unified
|
|
208
|
+
* error mapping. The return type is cast to `T` — callers know the shape of
|
|
209
|
+
* each command's output.
|
|
210
|
+
*
|
|
211
|
+
* The `command` parameter is typed as `unknown` here because AWS SDK v3
|
|
212
|
+
* Command classes are generic and contravariant — assigning a specific
|
|
213
|
+
* `CreateMultipartUploadCommand` to `Command<ServiceInputTypes, ...>` fails
|
|
214
|
+
* TypeScript's structural check due to middlewareStack overloads. Each call
|
|
215
|
+
* site already supplies the correctly-typed Command, so the cast is safe.
|
|
216
|
+
*/
|
|
217
|
+
async _send(command, op, chunkIndex) {
|
|
218
|
+
const controller = new AbortController();
|
|
219
|
+
const timer = setTimeout(() => controller.abort(), this._timeoutMs);
|
|
220
|
+
try {
|
|
221
|
+
return (await this._client.send(command, { abortSignal: controller.signal }));
|
|
222
|
+
}
|
|
223
|
+
catch (err) {
|
|
224
|
+
throw this._mapError(err, op, chunkIndex);
|
|
225
|
+
}
|
|
226
|
+
finally {
|
|
227
|
+
clearTimeout(timer);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Normalise any error thrown by the AWS SDK into a typed TransferError so the
|
|
232
|
+
* engine can make correct retry/abort decisions.
|
|
233
|
+
*/
|
|
234
|
+
_mapError(err, op, chunkIndex) {
|
|
235
|
+
// AbortController fired → request timed out.
|
|
236
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
237
|
+
return (0, core_1.networkError)(`[S3] ${op} timed out after ${this._timeoutMs}ms`, err, chunkIndex);
|
|
238
|
+
}
|
|
239
|
+
// AWS SDK v3 service exceptions carry $metadata.httpStatusCode.
|
|
240
|
+
// Duck-type to avoid importing the (internal) base exception class.
|
|
241
|
+
const sdkErr = err;
|
|
242
|
+
const status = sdkErr.$metadata?.httpStatusCode ?? 0;
|
|
243
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
244
|
+
const retryAfterHeader = sdkErr.$response?.headers?.["retry-after"];
|
|
245
|
+
if (status === 401 || status === 403) {
|
|
246
|
+
this._onLog?.("warn", `[S3] ${op} authorisation error (HTTP ${status})`, {
|
|
247
|
+
chunkIndex,
|
|
248
|
+
});
|
|
249
|
+
return (0, core_1.authError)(`S3 ${status}: ${msg}`);
|
|
250
|
+
}
|
|
251
|
+
if (status === 429) {
|
|
252
|
+
const retryAfterMs = retryAfterHeader
|
|
253
|
+
? parseInt(retryAfterHeader, 10) * 1000
|
|
254
|
+
: undefined;
|
|
255
|
+
return (0, core_1.rateLimitError)(retryAfterMs);
|
|
256
|
+
}
|
|
257
|
+
if (status >= 500) {
|
|
258
|
+
return (0, core_1.serverError)(status, msg);
|
|
259
|
+
}
|
|
260
|
+
if (status >= 400) {
|
|
261
|
+
return (0, core_1.clientError)(status, msg);
|
|
262
|
+
}
|
|
263
|
+
// No HTTP status — treat as a transient network failure.
|
|
264
|
+
if (err instanceof Error) {
|
|
265
|
+
return (0, core_1.networkError)(`[S3] ${op} request failed: ${err.message}`, err, chunkIndex);
|
|
266
|
+
}
|
|
267
|
+
return new core_1.TransferError(String(err), "unknown", err, chunkIndex);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
exports.S3Adapter = S3Adapter;
|
|
271
|
+
//# sourceMappingURL=S3Adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"S3Adapter.js","sourceRoot":"","sources":["../src/S3Adapter.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;;;AAEH,kDAO4B;AAC5B,0CAOyB;AAqFzB,iFAAiF;AAEjF,MAAa,SAAS;IACpB,gEAAgE;IAC7C,OAAO,CAAW;IACpB,OAAO,CAAS;IAChB,UAAU,CAAS;IACnB,MAAM,CAA4B;IAEnD,YAAY,IAAsB;QAChC,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC;QAC3B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,SAAS,IAAI,OAAO,CAAC;QAC5C,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC;QAEzB,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChB,iEAAiE;YACjE,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC;QAC7B,CAAC;aAAM,CAAC;YACN,6DAA6D;YAC7D,MAAM,YAAY,GAA8C;gBAC9D,WAAW,EAAE,CAAC;gBACd,MAAM,EAAE,IAAI,CAAC,MAAM,IAAI,WAAW;gBAClC,WAAW,EAAE;oBACX,WAAW,EAAE,IAAI,CAAC,WAAW,CAAC,WAAW;oBACzC,eAAe,EAAE,IAAI,CAAC,WAAW,CAAC,eAAe;oBACjD,wEAAwE;oBACxE,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,YAAY,KAAK,SAAS;wBAC7C,CAAC,CAAC,EAAE,YAAY,EAAE,IAAI,CAAC,WAAW,CAAC,YAAY,EAAE;wBACjD,CAAC,CAAC,EAAE,CAAC;iBACR;aACF,CAAC;YAEF,uEAAuE;YACvE,qEAAqE;YACrE,mDAAmD;YACnD,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;gBAChC,YAAY,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;YACxC,CAAC;YACD,IAAI,IAAI,CAAC,cAAc,KAAK,IAAI,EAAE,CAAC;gBACjC,YAAY,CAAC,cAAc,GAAG,IAAI,CAAC;YACrC,CAAC;YAED,IAAI,CAAC,OAAO,GAAG,IAAI,oBAAQ,CAAC,YAAY,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC;IAED,8EAA8E;IAE9E,KAAK,CAAC,YAAY,CAAC,OAAwB;QACzC,MAAM,GAAG,GAAG,IAAI,wCAA4B,CAAC;YAC3C,MAAM,EAAE,IAAI,CAAC,OAAO;YACpB,GAAG,EAAE,OAAO,CAAC,SAAS;YACtB,WAAW,EAAE,OAAO,CAAC,IAAI,CAAC,QAAQ,IAAI,0BAA0B;SACjE,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAwB,GAAG,EAAE,cAAc,CAAC,CAAC;QAE5E,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAAC;QACjC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,oBAAa,CACrB,qDAAqD,EACrD,aAAa,CACd,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,iCAAiC,EAAE;YACvD,SAAS,EAAE,OAAO,CAAC,EAAE;YACrB,QAAQ;SACT,CAAC,CAAC;QAEH,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,KAAK,CAAC,WAAW,CACf,OAAwB,EACxB,KAAgB,EAChB,IAAgB;IAChB,6EAA6E;IAC7E,yEAAyE;IACzE,wEAAwE;IACxE,uEAAuE;IACvE,UAAkB;QAElB,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,CAAC;YAC/B,MAAM,IAAI,oBAAa,CACrB,kEAAkE,EAClE,OAAO,CACR,CAAC;QACJ,CAAC;QAED,iDAAiD;QACjD,MAAM,UAAU,GAAG,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC;QAEnC,MAAM,GAAG,GAAG,IAAI,6BAAiB,CAAC;YAChC,MAAM,EAAE,IAAI,CAAC,OAAO;YACpB,GAAG,EAAE,OAAO,CAAC,SAAS;YACtB,QAAQ,EAAE,OAAO,CAAC,iBAAiB;YACnC,UAAU,EAAE,UAAU;YACtB,IAAI,EAAE,IAAI;SACX,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAC7B,GAAG,EACH,aAAa,EACb,KAAK,CAAC,KAAK,CACZ,CAAC;QAEF,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;QACzB,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,oBAAa,CACrB,0BAA0B,UAAU,mBAAmB,EACvD,aAAa,CACd,CAAC;QACJ,CAAC;QAED,sEAAsE;QACtE,wEAAwE;QACxE,2EAA2E;QAC3E,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC;IACjC,CAAC;IAED,KAAK,CAAC,gBAAgB,CACpB,OAAwB,EACxB,MAAmB;QAEnB,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,CAAC;YAC/B,MAAM,IAAI,oBAAa,CACrB,+CAA+C,EAC/C,OAAO,CACR,CAAC;QACJ,CAAC;QAED,2EAA2E;QAC3E,gDAAgD;QAChD,MAAM,WAAW,GAAG,CAAC,GAAG,MAAM,CAAC;aAC5B,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;aACjC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACX,UAAU,EAAE,CAAC,CAAC,KAAK,GAAG,CAAC;YACvB,IAAI,EAAE,CAAC,CAAC,aAAa,IAAI,EAAE;SAC5B,CAAC,CAAC,CAAC;QAEN,MAAM,GAAG,GAAG,IAAI,0CAA8B,CAAC;YAC7C,MAAM,EAAE,IAAI,CAAC,OAAO;YACpB,GAAG,EAAE,OAAO,CAAC,SAAS;YACtB,QAAQ,EAAE,OAAO,CAAC,iBAAiB;YACnC,eAAe,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE;SACxC,CAAC,CAAC;QAEH,MAAM,IAAI,CAAC,KAAK,CAAS,GAAG,EAAE,kBAAkB,CAAC,CAAC;QAElD,IAAI,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,iCAAiC,EAAE;YACvD,SAAS,EAAE,OAAO,CAAC,EAAE;YACrB,SAAS,EAAE,WAAW,CAAC,MAAM;SAC9B,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,aAAa,CAAC,OAAwB;QAC1C,IAAI,CAAC,OAAO,CAAC,iBAAiB;YAAE,OAAO;QACvC,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,IAAI,uCAA2B,CAAC;gBAC1C,MAAM,EAAE,IAAI,CAAC,OAAO;gBACpB,GAAG,EAAE,OAAO,CAAC,SAAS;gBACtB,QAAQ,EAAE,OAAO,CAAC,iBAAiB;aACpC,CAAC,CAAC;YACH,MAAM,IAAI,CAAC,KAAK,CAAS,GAAG,EAAE,eAAe,CAAC,CAAC;QACjD,CAAC;QAAC,MAAM,CAAC;YACP,uEAAuE;QACzE,CAAC;IACH,CAAC;IAED,KAAK,CAAC,cAAc,CAAC,OAAwB;QAC3C,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE,CAAC;YAC/B,OAAO,EAAE,aAAa,EAAE,EAAE,EAAE,CAAC;QAC/B,CAAC;QAED,MAAM,aAAa,GAAuC,EAAE,CAAC;QAE7D,sEAAsE;QACtE,mDAAmD;QACnD,IAAI,gBAA6C,CAAC;QAClD,GAAG,CAAC;YACF,MAAM,QAAQ,GAAsD;gBAClE,MAAM,EAAE,IAAI,CAAC,OAAO;gBACpB,GAAG,EAAE,OAAO,CAAC,SAAS;gBACtB,QAAQ,EAAE,OAAO,CAAC,iBAAiB;gBACnC,QAAQ,EAAE,IAAI;aACf,CAAC;YACF,IAAI,gBAAgB,KAAK,SAAS,EAAE,CAAC;gBACnC,mEAAmE;gBACnE,6DAA6D;gBAC7D,QAAQ,CAAC,gBAAgB,GAAG,MAAM,CAAC,gBAAgB,CAAC,CAAC;YACvD,CAAC;YAED,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAI5B,IAAI,4BAAgB,CAAC,QAAQ,CAAC,EAAE,gBAAgB,CAAC,CAAC;YAErD,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,KAAK,IAAI,EAAE,EAAE,CAAC;gBACtC,IAAI,IAAI,CAAC,UAAU,IAAI,IAAI,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;oBACzC,aAAa,CAAC,IAAI,CAAC;wBACjB,UAAU,EAAE,IAAI,CAAC,UAAU;wBAC3B,aAAa,EAAE,IAAI,CAAC,IAAI;qBACzB,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;YAED,gBAAgB;gBACd,MAAM,CAAC,WAAW,KAAK,IAAI,IAAI,MAAM,CAAC,oBAAoB,IAAI,IAAI;oBAChE,CAAC,CAAC,MAAM,CAAC,oBAAoB;oBAC7B,CAAC,CAAC,SAAS,CAAC;QAClB,CAAC,QAAQ,gBAAgB,KAAK,SAAS,EAAE;QAEzC,OAAO,EAAE,aAAa,EAAE,CAAC;IAC3B,CAAC;IAED,8EAA8E;IAE9E;;;;;;;;;;OAUG;IACK,KAAK,CAAC,KAAK,CACjB,OAAgB,EAChB,EAAU,EACV,UAAmB;QAEnB,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;QACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QACpE,IAAI,CAAC;YACH,OAAO,CAAC,MACN,IAAI,CAAC,OAAO,CAAC,IAId,CAAC,OAAO,EAAE,EAAE,WAAW,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC,CAAM,CAAC;QACvD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,EAAE,UAAU,CAAC,CAAC;QAC5C,CAAC;gBAAS,CAAC;YACT,YAAY,CAAC,KAAK,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;IAED;;;OAGG;IACK,SAAS,CACf,GAAY,EACZ,EAAU,EACV,UAAmB;QAEnB,6CAA6C;QAC7C,IAAI,GAAG,YAAY,KAAK,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;YACtD,OAAO,IAAA,mBAAY,EACjB,QAAQ,EAAE,oBAAoB,IAAI,CAAC,UAAU,IAAI,EACjD,GAAG,EACH,UAAU,CACX,CAAC;QACJ,CAAC;QAED,gEAAgE;QAChE,oEAAoE;QACpE,MAAM,MAAM,GAAG,GAAkB,CAAC;QAClC,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,EAAE,cAAc,IAAI,CAAC,CAAC;QACrD,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC7D,MAAM,gBAAgB,GAAG,MAAM,CAAC,SAAS,EAAE,OAAO,EAAE,CAAC,aAAa,CAAC,CAAC;QAEpE,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;YACrC,IAAI,CAAC,MAAM,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,8BAA8B,MAAM,GAAG,EAAE;gBACvE,UAAU;aACX,CAAC,CAAC;YACH,OAAO,IAAA,gBAAS,EAAC,MAAM,MAAM,KAAK,GAAG,EAAE,CAAC,CAAC;QAC3C,CAAC;QACD,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;YACnB,MAAM,YAAY,GAAG,gBAAgB;gBACnC,CAAC,CAAC,QAAQ,CAAC,gBAAgB,EAAE,EAAE,CAAC,GAAG,IAAI;gBACvC,CAAC,CAAC,SAAS,CAAC;YACd,OAAO,IAAA,qBAAc,EAAC,YAAY,CAAC,CAAC;QACtC,CAAC;QACD,IAAI,MAAM,IAAI,GAAG,EAAE,CAAC;YAClB,OAAO,IAAA,kBAAW,EAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAClC,CAAC;QACD,IAAI,MAAM,IAAI,GAAG,EAAE,CAAC;YAClB,OAAO,IAAA,kBAAW,EAAC,MAAM,EAAE,GAAG,CAAC,CAAC;QAClC,CAAC;QAED,yDAAyD;QACzD,IAAI,GAAG,YAAY,KAAK,EAAE,CAAC;YACzB,OAAO,IAAA,mBAAY,EACjB,QAAQ,EAAE,oBAAoB,GAAG,CAAC,OAAO,EAAE,EAC3C,GAAG,EACH,UAAU,CACX,CAAC;QACJ,CAAC;QACD,OAAO,IAAI,oBAAa,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,UAAU,CAAC,CAAC;IACpE,CAAC;CACF;AA/SD,8BA+SC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.R2Adapter = exports.S3Adapter = void 0;
|
|
4
|
+
var S3Adapter_js_1 = require("./S3Adapter.js");
|
|
5
|
+
Object.defineProperty(exports, "S3Adapter", { enumerable: true, get: function () { return S3Adapter_js_1.S3Adapter; } });
|
|
6
|
+
var R2Adapter_js_1 = require("./R2Adapter.js");
|
|
7
|
+
Object.defineProperty(exports, "R2Adapter", { enumerable: true, get: function () { return R2Adapter_js_1.R2Adapter; } });
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,+CAA2C;AAAlC,yGAAA,SAAS,OAAA;AAGlB,+CAA2C;AAAlC,yGAAA,SAAS,OAAA"}
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@transferx/adapter-s3",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TransferX adapters for AWS S3 and Cloudflare R2 multipart uploads",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist"
|
|
9
|
+
],
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc -p tsconfig.json",
|
|
12
|
+
"typecheck": "tsc --noEmit"
|
|
13
|
+
},
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@aws-sdk/client-s3": "^3.0.0",
|
|
16
|
+
"@transferx/core": "0.1.0"
|
|
17
|
+
}
|
|
18
|
+
}
|