@vankyle/storage-cloudflare 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/README.md +158 -0
- package/dist/d1/d1-kysely-dialect.d.ts +10 -0
- package/dist/d1/d1-kysely-dialect.d.ts.map +1 -0
- package/dist/d1/d1-kysely-dialect.js +77 -0
- package/dist/d1/d1-kysely-dialect.js.map +1 -0
- package/dist/d1/index.d.ts +2 -0
- package/dist/d1/index.d.ts.map +1 -0
- package/dist/d1/index.js +2 -0
- package/dist/d1/index.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/storage/index.d.ts +2 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +2 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/r2-binding-storage.d.ts +16 -0
- package/dist/storage/r2-binding-storage.d.ts.map +1 -0
- package/dist/storage/r2-binding-storage.js +126 -0
- package/dist/storage/r2-binding-storage.js.map +1 -0
- package/package.json +42 -0
package/README.md
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# @vankyle-hub/storage-cloudflare
|
|
2
|
+
|
|
3
|
+
Cloudflare Workers adapters for `vankyle-storage`:
|
|
4
|
+
|
|
5
|
+
- **`R2BindingStorage`** — `IStorage` using the native Cloudflare R2 Worker binding API.
|
|
6
|
+
- **`D1Dialect`** — Kysely `Dialect` for Cloudflare D1, enabling `KyselyMetadataStore` to work on D1.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pnpm add @vankyle-hub/storage-cloudflare @vankyle-hub/storage-core @vankyle-hub/storage-shared
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
For metadata on D1, also install:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pnpm add @vankyle-hub/storage-kysely kysely
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## R2 Binding Storage
|
|
21
|
+
|
|
22
|
+
Use `R2BindingStorage` when running inside a Cloudflare Worker and you have a direct `R2Bucket` binding (i.e. `env.BUCKET`).
|
|
23
|
+
|
|
24
|
+
> For Cloudflare R2 accessed via the S3-compatible HTTP API (outside of Workers), use [`@vankyle-hub/storage-s3`](../s3/README.md) instead.
|
|
25
|
+
|
|
26
|
+
### Setup
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { R2BindingStorage } from "@vankyle-hub/storage-cloudflare";
|
|
30
|
+
|
|
31
|
+
interface Env {
|
|
32
|
+
BUCKET: R2Bucket;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default {
|
|
36
|
+
async fetch(request: Request, env: Env): Promise<Response> {
|
|
37
|
+
const storage = new R2BindingStorage(env.BUCKET);
|
|
38
|
+
// ...
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### Capabilities
|
|
44
|
+
|
|
45
|
+
| Capability | Supported |
|
|
46
|
+
|---|---|
|
|
47
|
+
| `multipartUpload` | ✓ |
|
|
48
|
+
| `signedReadUrl` | ✗ |
|
|
49
|
+
| `signedPutUrl` | ✗ |
|
|
50
|
+
| `signedPartUrl` | ✗ |
|
|
51
|
+
|
|
52
|
+
Workers with R2 bindings cannot issue presigned URLs — all data transfers go through the Worker itself. For single-mode uploads, the client must POST to your Worker which calls `service.uploadPart(...)`.
|
|
53
|
+
|
|
54
|
+
### Multipart upload
|
|
55
|
+
|
|
56
|
+
`R2BindingStorage` uses the native R2 multipart API:
|
|
57
|
+
|
|
58
|
+
| `IStorage` method | R2 Binding API |
|
|
59
|
+
|---|---|
|
|
60
|
+
| `initUploadSession` | `bucket.createMultipartUpload()` |
|
|
61
|
+
| `uploadPart` | `multipart.uploadPart()` |
|
|
62
|
+
| `completeUploadSession` | `multipart.complete()` |
|
|
63
|
+
| `abortUploadSession` | `multipart.abort()` |
|
|
64
|
+
|
|
65
|
+
The `providerUploadId` returned by `initUploadSession` is the R2 `uploadId`.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## D1 Kysely Dialect
|
|
70
|
+
|
|
71
|
+
`D1Dialect` adapts Cloudflare D1 to the Kysely query builder interface. This allows `KyselyMetadataStore` from `@vankyle-hub/storage-kysely` to run on D1 without any additional code changes.
|
|
72
|
+
|
|
73
|
+
### Setup
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
import { D1Dialect } from "@vankyle-hub/storage-cloudflare";
|
|
77
|
+
import { KyselyMetadataStore } from "@vankyle-hub/storage-kysely";
|
|
78
|
+
import { Kysely } from "kysely";
|
|
79
|
+
import type { StorageDatabase } from "@vankyle-hub/storage-kysely";
|
|
80
|
+
|
|
81
|
+
interface Env {
|
|
82
|
+
DB: D1Database;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Inside your Worker handler:
|
|
86
|
+
const db = new Kysely<StorageDatabase>({
|
|
87
|
+
dialect: new D1Dialect({ database: env.DB }),
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const metadata = new KyselyMetadataStore(db);
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Limitations
|
|
94
|
+
|
|
95
|
+
D1 does not support:
|
|
96
|
+
- Traditional transactions (`BEGIN` / `COMMIT` / `ROLLBACK`). Transaction calls in `D1Dialect` are stubbed.
|
|
97
|
+
- Query streaming. Calling `streamQuery` will throw.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Complete Worker example
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
import { R2BindingStorage, D1Dialect } from "@vankyle-hub/storage-cloudflare";
|
|
105
|
+
import { KyselyMetadataStore } from "@vankyle-hub/storage-kysely";
|
|
106
|
+
import { DefaultStorageService, UploadMode } from "@vankyle-hub/storage-core";
|
|
107
|
+
import { Kysely } from "kysely";
|
|
108
|
+
import type { StorageDatabase } from "@vankyle-hub/storage-kysely";
|
|
109
|
+
|
|
110
|
+
interface Env {
|
|
111
|
+
BUCKET: R2Bucket;
|
|
112
|
+
DB: D1Database;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export default {
|
|
116
|
+
async fetch(request: Request, env: Env): Promise<Response> {
|
|
117
|
+
const storage = new R2BindingStorage(env.BUCKET);
|
|
118
|
+
const db = new Kysely<StorageDatabase>({ dialect: new D1Dialect({ database: env.DB }) });
|
|
119
|
+
const metadata = new KyselyMetadataStore(db);
|
|
120
|
+
|
|
121
|
+
const service = new DefaultStorageService({
|
|
122
|
+
storage,
|
|
123
|
+
metadata,
|
|
124
|
+
bucket: "my-bucket",
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const url = new URL(request.url);
|
|
128
|
+
|
|
129
|
+
if (request.method === "POST" && url.pathname === "/upload/start") {
|
|
130
|
+
const body = await request.json<{ fileName: string }>();
|
|
131
|
+
const { session } = await service.createUploadSession({
|
|
132
|
+
fileName: body.fileName,
|
|
133
|
+
mode: UploadMode.Single,
|
|
134
|
+
});
|
|
135
|
+
return Response.json({ sessionId: session.id });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ... additional routes
|
|
139
|
+
return new Response("Not Found", { status: 404 });
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## D1 Migrations
|
|
145
|
+
|
|
146
|
+
Run migrations from `@vankyle-hub/storage-kysely` using the Kysely `Migrator` with `D1Dialect`. The SQL is compatible with SQLite, which D1 is based on.
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
import { Migrator } from "kysely";
|
|
150
|
+
import { migrations } from "@vankyle-hub/storage-kysely";
|
|
151
|
+
|
|
152
|
+
const migrator = new Migrator({
|
|
153
|
+
db,
|
|
154
|
+
provider: { getMigrations: async () => migrations },
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
await migrator.migrateToLatest();
|
|
158
|
+
```
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type DatabaseIntrospector, type Dialect, type DialectAdapter, type Driver, type Kysely, type QueryCompiler } from "kysely";
|
|
2
|
+
export declare class D1Dialect implements Dialect {
|
|
3
|
+
private readonly db;
|
|
4
|
+
constructor(db: D1Database);
|
|
5
|
+
createDriver(): Driver;
|
|
6
|
+
createQueryCompiler(): QueryCompiler;
|
|
7
|
+
createAdapter(): DialectAdapter;
|
|
8
|
+
createIntrospector(db: Kysely<unknown>): DatabaseIntrospector;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=d1-kysely-dialect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"d1-kysely-dialect.d.ts","sourceRoot":"","sources":["../../src/d1/d1-kysely-dialect.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,oBAAoB,EACzB,KAAK,OAAO,EACZ,KAAK,cAAc,EACnB,KAAK,MAAM,EACX,KAAK,MAAM,EACX,KAAK,aAAa,EAMnB,MAAM,QAAQ,CAAC;AAEhB,qBAAa,SAAU,YAAW,OAAO;IACvC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAa;gBAEpB,EAAE,EAAE,UAAU;IAI1B,YAAY,IAAI,MAAM;IAItB,mBAAmB,IAAI,aAAa;IAIpC,aAAa,IAAI,cAAc;IAI/B,kBAAkB,CAAC,EAAE,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,oBAAoB;CAG9D"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { SqliteAdapter, SqliteIntrospector, SqliteQueryCompiler, } from "kysely";
|
|
2
|
+
export class D1Dialect {
|
|
3
|
+
db;
|
|
4
|
+
constructor(db) {
|
|
5
|
+
this.db = db;
|
|
6
|
+
}
|
|
7
|
+
createDriver() {
|
|
8
|
+
return new D1Driver(this.db);
|
|
9
|
+
}
|
|
10
|
+
createQueryCompiler() {
|
|
11
|
+
return new SqliteQueryCompiler();
|
|
12
|
+
}
|
|
13
|
+
createAdapter() {
|
|
14
|
+
return new SqliteAdapter();
|
|
15
|
+
}
|
|
16
|
+
createIntrospector(db) {
|
|
17
|
+
return new SqliteIntrospector(db);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
class D1Driver {
|
|
21
|
+
db;
|
|
22
|
+
constructor(db) {
|
|
23
|
+
this.db = db;
|
|
24
|
+
}
|
|
25
|
+
async init() {
|
|
26
|
+
// No initialization needed
|
|
27
|
+
}
|
|
28
|
+
async acquireConnection() {
|
|
29
|
+
return new D1Connection(this.db);
|
|
30
|
+
}
|
|
31
|
+
async beginTransaction(connection) {
|
|
32
|
+
// D1 doesn't support transactions in the traditional sense
|
|
33
|
+
// Batch operations are used instead
|
|
34
|
+
}
|
|
35
|
+
async commitTransaction(connection) {
|
|
36
|
+
// D1 doesn't support transactions
|
|
37
|
+
}
|
|
38
|
+
async rollbackTransaction(connection) {
|
|
39
|
+
// D1 doesn't support transactions
|
|
40
|
+
}
|
|
41
|
+
async releaseConnection(connection) {
|
|
42
|
+
// No-op
|
|
43
|
+
}
|
|
44
|
+
async destroy() {
|
|
45
|
+
// No-op
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
class D1Connection {
|
|
49
|
+
db;
|
|
50
|
+
constructor(db) {
|
|
51
|
+
this.db = db;
|
|
52
|
+
}
|
|
53
|
+
async executeQuery(compiledQuery) {
|
|
54
|
+
const { sql, parameters } = compiledQuery;
|
|
55
|
+
const stmt = this.db.prepare(sql).bind(...parameters);
|
|
56
|
+
const result = await stmt.all();
|
|
57
|
+
if (result.error) {
|
|
58
|
+
throw new Error(result.error);
|
|
59
|
+
}
|
|
60
|
+
const rows = (result.results ?? []);
|
|
61
|
+
const numAffectedRows = result.meta.changes > 0
|
|
62
|
+
? BigInt(result.meta.changes)
|
|
63
|
+
: undefined;
|
|
64
|
+
const insertId = result.meta.last_row_id === null || result.meta.last_row_id === undefined
|
|
65
|
+
? undefined
|
|
66
|
+
: BigInt(result.meta.last_row_id);
|
|
67
|
+
return {
|
|
68
|
+
rows,
|
|
69
|
+
...(numAffectedRows !== undefined ? { numAffectedRows } : {}),
|
|
70
|
+
...(insertId !== undefined ? { insertId } : {}),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
streamQuery() {
|
|
74
|
+
throw new Error("D1 does not support streaming queries");
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=d1-kysely-dialect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"d1-kysely-dialect.js","sourceRoot":"","sources":["../../src/d1/d1-kysely-dialect.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/d1/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC"}
|
package/dist/d1/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/d1/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC"}
|
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,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/storage/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type IStorage, type StorageCapabilities, type PutObjectInput, type PutObjectResult, type GetObjectInput, type GetObjectResult, type HeadObjectInput, type HeadObjectResult, type DeleteObjectInput, type InitUploadSessionInput, type InitUploadSessionResult, type UploadPartInput, type UploadPartResult, type CompleteUploadSessionInput, type CompleteUploadSessionResult, type AbortUploadSessionInput } from "@vankyle-hub/storage-core";
|
|
2
|
+
export declare class R2BindingStorage implements IStorage {
|
|
3
|
+
readonly provider: "r2-binding";
|
|
4
|
+
readonly capabilities: StorageCapabilities;
|
|
5
|
+
private readonly bucket;
|
|
6
|
+
constructor(bucket: R2Bucket);
|
|
7
|
+
putObject(input: PutObjectInput): Promise<PutObjectResult>;
|
|
8
|
+
getObject(input: GetObjectInput): Promise<GetObjectResult>;
|
|
9
|
+
headObject(input: HeadObjectInput): Promise<HeadObjectResult>;
|
|
10
|
+
deleteObject(input: DeleteObjectInput): Promise<void>;
|
|
11
|
+
initUploadSession(input: InitUploadSessionInput): Promise<InitUploadSessionResult>;
|
|
12
|
+
uploadPart(input: UploadPartInput): Promise<UploadPartResult>;
|
|
13
|
+
completeUploadSession(input: CompleteUploadSessionInput): Promise<CompleteUploadSessionResult>;
|
|
14
|
+
abortUploadSession(input: AbortUploadSessionInput): Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=r2-binding-storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"r2-binding-storage.d.ts","sourceRoot":"","sources":["../../src/storage/r2-binding-storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,QAAQ,EACb,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,uBAAuB,EAC5B,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACrB,KAAK,0BAA0B,EAC/B,KAAK,2BAA2B,EAChC,KAAK,uBAAuB,EAC7B,MAAM,2BAA2B,CAAC;AAMnC,qBAAa,gBAAiB,YAAW,QAAQ;IAC/C,QAAQ,CAAC,QAAQ,eAA6B;IAC9C,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAKxC;IAEF,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAW;gBAEtB,MAAM,EAAE,QAAQ;IAItB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;IAwB1D,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;IAgC1D,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAgB7D,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAIrD,iBAAiB,CACrB,KAAK,EAAE,sBAAsB,GAC5B,OAAO,CAAC,uBAAuB,CAAC;IAkB7B,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAoB7D,qBAAqB,CACzB,KAAK,EAAE,0BAA0B,GAChC,OAAO,CAAC,2BAA2B,CAAC;IAkBjC,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC;CAQxE"}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { StorageProvider, } from "@vankyle-hub/storage-core";
|
|
2
|
+
import { StorageObjectNotFoundError, } from "@vankyle-hub/storage-shared";
|
|
3
|
+
export class R2BindingStorage {
|
|
4
|
+
provider = StorageProvider.R2Binding;
|
|
5
|
+
capabilities = {
|
|
6
|
+
multipartUpload: true,
|
|
7
|
+
signedReadUrl: false,
|
|
8
|
+
signedPutUrl: false,
|
|
9
|
+
signedPartUrl: false,
|
|
10
|
+
};
|
|
11
|
+
bucket;
|
|
12
|
+
constructor(bucket) {
|
|
13
|
+
this.bucket = bucket;
|
|
14
|
+
}
|
|
15
|
+
async putObject(input) {
|
|
16
|
+
const body = input.body instanceof Uint8Array
|
|
17
|
+
? input.body.buffer.slice(input.body.byteOffset, input.body.byteOffset + input.body.byteLength)
|
|
18
|
+
: input.body;
|
|
19
|
+
const options = {};
|
|
20
|
+
if (input.contentType) {
|
|
21
|
+
options.httpMetadata = { contentType: input.contentType };
|
|
22
|
+
}
|
|
23
|
+
if (input.sha256) {
|
|
24
|
+
options.sha256 = hexToArrayBuffer(input.sha256);
|
|
25
|
+
}
|
|
26
|
+
if (input.metadata) {
|
|
27
|
+
options.customMetadata = input.metadata;
|
|
28
|
+
}
|
|
29
|
+
const result = await this.bucket.put(input.objectKey, body, options);
|
|
30
|
+
return {
|
|
31
|
+
etag: result?.etag,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
async getObject(input) {
|
|
35
|
+
const options = {};
|
|
36
|
+
if (input.range) {
|
|
37
|
+
if (input.range.end !== undefined) {
|
|
38
|
+
options.range = {
|
|
39
|
+
offset: input.range.start,
|
|
40
|
+
length: input.range.end - input.range.start + 1,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
options.range = {
|
|
45
|
+
offset: input.range.start,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const result = await this.bucket.get(input.objectKey, options);
|
|
50
|
+
if (!result) {
|
|
51
|
+
throw new StorageObjectNotFoundError(input.bucket, input.objectKey);
|
|
52
|
+
}
|
|
53
|
+
const r2ObjectBody = result;
|
|
54
|
+
return {
|
|
55
|
+
body: r2ObjectBody.body,
|
|
56
|
+
contentType: result.httpMetadata?.contentType,
|
|
57
|
+
contentLength: result.size,
|
|
58
|
+
etag: result.etag,
|
|
59
|
+
metadata: result.customMetadata,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
async headObject(input) {
|
|
63
|
+
const result = await this.bucket.head(input.objectKey);
|
|
64
|
+
if (!result) {
|
|
65
|
+
throw new StorageObjectNotFoundError(input.bucket, input.objectKey);
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
contentType: result.httpMetadata?.contentType,
|
|
69
|
+
contentLength: result.size,
|
|
70
|
+
etag: result.etag,
|
|
71
|
+
lastModified: result.uploaded,
|
|
72
|
+
metadata: result.customMetadata,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
async deleteObject(input) {
|
|
76
|
+
await this.bucket.delete(input.objectKey);
|
|
77
|
+
}
|
|
78
|
+
async initUploadSession(input) {
|
|
79
|
+
const multipartOptions = {};
|
|
80
|
+
if (input.contentType) {
|
|
81
|
+
multipartOptions.httpMetadata = { contentType: input.contentType };
|
|
82
|
+
}
|
|
83
|
+
if (input.metadata) {
|
|
84
|
+
multipartOptions.customMetadata = input.metadata;
|
|
85
|
+
}
|
|
86
|
+
const multipart = await this.bucket.createMultipartUpload(input.objectKey, multipartOptions);
|
|
87
|
+
return {
|
|
88
|
+
providerUploadId: multipart.uploadId,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
async uploadPart(input) {
|
|
92
|
+
const multipart = this.bucket.resumeMultipartUpload(input.objectKey, input.providerUploadId);
|
|
93
|
+
const body = input.body instanceof Uint8Array
|
|
94
|
+
? input.body.buffer.slice(input.body.byteOffset, input.body.byteOffset + input.body.byteLength)
|
|
95
|
+
: input.body;
|
|
96
|
+
const part = await multipart.uploadPart(input.partNumber, body);
|
|
97
|
+
return {
|
|
98
|
+
etag: part.etag,
|
|
99
|
+
partNumber: part.partNumber,
|
|
100
|
+
size: input.contentLength,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
async completeUploadSession(input) {
|
|
104
|
+
const multipart = this.bucket.resumeMultipartUpload(input.objectKey, input.providerUploadId);
|
|
105
|
+
const parts = input.parts.map((p) => ({
|
|
106
|
+
partNumber: p.partNumber,
|
|
107
|
+
etag: p.etag ?? "",
|
|
108
|
+
}));
|
|
109
|
+
const result = await multipart.complete(parts);
|
|
110
|
+
return {
|
|
111
|
+
etag: result.etag,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
async abortUploadSession(input) {
|
|
115
|
+
const multipart = this.bucket.resumeMultipartUpload(input.objectKey, input.providerUploadId);
|
|
116
|
+
await multipart.abort();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
function hexToArrayBuffer(hex) {
|
|
120
|
+
const bytes = new Uint8Array(hex.length / 2);
|
|
121
|
+
for (let i = 0; i < hex.length; i += 2) {
|
|
122
|
+
bytes[i / 2] = parseInt(hex.slice(i, i + 2), 16);
|
|
123
|
+
}
|
|
124
|
+
return bytes.buffer;
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=r2-binding-storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"r2-binding-storage.js","sourceRoot":"","sources":["../../src/storage/r2-binding-storage.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vankyle/storage-cloudflare",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Cloudflare R2 Binding and D1 adapters for vankyle-storage.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc -b tsconfig.json --force",
|
|
19
|
+
"clean": "rimraf dist tsconfig.tsbuildinfo",
|
|
20
|
+
"typecheck": "tsc -p tsconfig.json --noEmit"
|
|
21
|
+
},
|
|
22
|
+
"license": "MPL-2.0",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/Vankyle-Hub/storage-ts.git",
|
|
26
|
+
"directory": "packages/cloudflare"
|
|
27
|
+
},
|
|
28
|
+
"bugs": {
|
|
29
|
+
"url": "https://github.com/Vankyle-Hub/storage-ts/issues"
|
|
30
|
+
},
|
|
31
|
+
"homepage": "https://github.com/Vankyle-Hub/storage-ts",
|
|
32
|
+
"packageManager": "pnpm@10.28.2",
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"@vankyle/storage-core": "0.1.0",
|
|
35
|
+
"@vankyle/storage-shared": "0.1.0",
|
|
36
|
+
"@cloudflare/workers-types": "^4.20260310.1",
|
|
37
|
+
"kysely": "^0.28.2"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
}
|
|
42
|
+
}
|