openwrangler 0.0.4 → 0.0.5
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 +144 -0
- package/dist/index.mjs +247 -216
- package/package.json +10 -6
package/README.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# openwrangler
|
|
2
|
+
|
|
3
|
+
A library that implements wrangler's remote functionality, allowing you to use Cloudflare bindings via direct REST API calls instead of local simulation.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
`openwrangler` provides the same binding interface as wrangler's `getPlatformProxy`, but instead of using local miniflare/workerd simulation, it directly calls the Cloudflare REST API. This enables you to work with remote Cloudflare resources during development.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install openwrangler
|
|
13
|
+
# or
|
|
14
|
+
pnpm add openwrangler
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Direct Usage
|
|
18
|
+
|
|
19
|
+
You can use openwrangler directly in your Node.js applications:
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { getBindings } from 'openwrangler'
|
|
23
|
+
|
|
24
|
+
const bindings = getBindings({
|
|
25
|
+
accountId: 'your-account-id',
|
|
26
|
+
apiToken: 'your-api-token',
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
// Use bindings just like in Cloudflare Workers
|
|
30
|
+
await bindings.r2.put('image.png', buffer)
|
|
31
|
+
await bindings.kv.get('key')
|
|
32
|
+
await bindings.d1.exec('SELECT * FROM users')
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Nitro Integration
|
|
36
|
+
|
|
37
|
+
`openwrangler` can be seamlessly integrated with Nitro applications using `@bino0216/nitro-cloudflare-dev`.
|
|
38
|
+
|
|
39
|
+
### Setup
|
|
40
|
+
|
|
41
|
+
1. Install the Nitro integration package:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npm install npm:@bino0216/nitro-cloudflare-dev
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
2. Configure remote credentials in your Nitro config:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
// nitro.config.ts
|
|
51
|
+
export default defineNitroConfig({
|
|
52
|
+
cloudflareDev: {
|
|
53
|
+
remoteCredentials: {
|
|
54
|
+
accountId: 'your-account-id',
|
|
55
|
+
apiToken: 'your-api-token',
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
3. Mark bindings as remote in your `wrangler.toml` or `wrangler.json`:
|
|
62
|
+
|
|
63
|
+
```toml
|
|
64
|
+
# wrangler.toml
|
|
65
|
+
[[r2_buckets]]
|
|
66
|
+
binding = "MY_BUCKET"
|
|
67
|
+
bucket_name = "my-bucket"
|
|
68
|
+
remote = true
|
|
69
|
+
|
|
70
|
+
[[kv_namespaces]]
|
|
71
|
+
binding = "MY_KV"
|
|
72
|
+
id = "your-kv-id"
|
|
73
|
+
remote = true
|
|
74
|
+
|
|
75
|
+
[[d1_databases]]
|
|
76
|
+
binding = "MY_DB"
|
|
77
|
+
database_name = "my-database"
|
|
78
|
+
database_id = "your-db-id"
|
|
79
|
+
remote = true
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Or in JSON format:
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"r2_buckets": [
|
|
87
|
+
{
|
|
88
|
+
"binding": "MY_BUCKET",
|
|
89
|
+
"bucket_name": "my-bucket",
|
|
90
|
+
"remote": true
|
|
91
|
+
}
|
|
92
|
+
],
|
|
93
|
+
"kv_namespaces": [
|
|
94
|
+
{
|
|
95
|
+
"binding": "MY_KV",
|
|
96
|
+
"id": "your-kv-id",
|
|
97
|
+
"remote": true
|
|
98
|
+
}
|
|
99
|
+
],
|
|
100
|
+
"d1_databases": [
|
|
101
|
+
{
|
|
102
|
+
"binding": "MY_DB",
|
|
103
|
+
"database_name": "my-database",
|
|
104
|
+
"database_id": "your-db-id",
|
|
105
|
+
"remote": true
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
When bindings have `remote = true`, Nitro will use openwrangler's implementation to connect to your actual Cloudflare resources instead of local simulation.
|
|
112
|
+
|
|
113
|
+
## Supported Bindings
|
|
114
|
+
|
|
115
|
+
- **R2** (`R2Bucket`) - Object storage
|
|
116
|
+
- **KV** (`KVNamespace`) - Key-value storage
|
|
117
|
+
- **D1** (`D1Database`) - SQL database
|
|
118
|
+
|
|
119
|
+
All types are imported from `@cloudflare/workers-types` to ensure compatibility with Cloudflare Workers.
|
|
120
|
+
|
|
121
|
+
## Development
|
|
122
|
+
|
|
123
|
+
```bash
|
|
124
|
+
pnpm install
|
|
125
|
+
pnpm dev # Run playground
|
|
126
|
+
pnpm build # Build package
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Project Structure
|
|
130
|
+
|
|
131
|
+
- `src/index.ts` - Package entry point, `getBindings()` function
|
|
132
|
+
- `playground/` - Nuxt/Nitro v3 based test environment
|
|
133
|
+
- `build.config.ts` - unbuild configuration
|
|
134
|
+
|
|
135
|
+
## Use Cases
|
|
136
|
+
|
|
137
|
+
- **Development against production data**: Work with real Cloudflare resources during development
|
|
138
|
+
- **Testing**: Test against actual Cloudflare services without deployment
|
|
139
|
+
- **CI/CD**: Run integration tests against remote Cloudflare resources
|
|
140
|
+
- **Hybrid environments**: Mix local and remote bindings based on your needs
|
|
141
|
+
|
|
142
|
+
## License
|
|
143
|
+
|
|
144
|
+
MIT
|
package/dist/index.mjs
CHANGED
|
@@ -1,45 +1,116 @@
|
|
|
1
|
-
class
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
this.accountId = accountId;
|
|
7
|
-
this.apiToken = apiToken;
|
|
8
|
-
}
|
|
9
|
-
async request(method, endpoint, body, headers) {
|
|
10
|
-
const url = `${this.baseUrl}${endpoint}`;
|
|
11
|
-
const response = await fetch(url, {
|
|
12
|
-
method,
|
|
13
|
-
headers: {
|
|
14
|
-
"Authorization": `Bearer ${this.apiToken}`,
|
|
15
|
-
"Content-Type": "application/json",
|
|
16
|
-
...headers
|
|
17
|
-
},
|
|
18
|
-
body: body ? JSON.stringify(body) : void 0
|
|
19
|
-
});
|
|
20
|
-
const data = await response.json();
|
|
21
|
-
if (!response.ok || !data.success) {
|
|
22
|
-
const errorMessage = data.errors?.[0]?.message || `HTTP ${response.status}`;
|
|
23
|
-
throw new Error(`Cloudflare API Error: ${errorMessage}`);
|
|
24
|
-
}
|
|
25
|
-
return data.result;
|
|
1
|
+
class D1PreparedStatementImpl {
|
|
2
|
+
constructor(query, client, databaseId) {
|
|
3
|
+
this.query = query;
|
|
4
|
+
this.client = client;
|
|
5
|
+
this.databaseId = databaseId;
|
|
26
6
|
}
|
|
27
|
-
|
|
28
|
-
|
|
7
|
+
bindings = [];
|
|
8
|
+
bind(...values) {
|
|
9
|
+
this.bindings = values;
|
|
10
|
+
return this;
|
|
29
11
|
}
|
|
30
|
-
async
|
|
31
|
-
|
|
12
|
+
async run() {
|
|
13
|
+
const endpoint = `/accounts/${this.client.getAccountId()}/d1/database/${this.databaseId}/query`;
|
|
14
|
+
const data = await this.client.post(endpoint, {
|
|
15
|
+
sql: this.query,
|
|
16
|
+
params: this.bindings
|
|
17
|
+
});
|
|
18
|
+
const result = data[0];
|
|
19
|
+
return {
|
|
20
|
+
success: result.success,
|
|
21
|
+
results: result.results,
|
|
22
|
+
meta: {
|
|
23
|
+
served_by: result.meta.served_by || "",
|
|
24
|
+
duration: result.meta.duration || 0,
|
|
25
|
+
changes: result.meta.changes || 0,
|
|
26
|
+
last_row_id: result.meta.last_row_id || 0,
|
|
27
|
+
changed_db: result.meta.changed_db || false,
|
|
28
|
+
size_after: result.meta.size_after || 0,
|
|
29
|
+
rows_read: result.meta.rows_read || 0,
|
|
30
|
+
rows_written: result.meta.rows_written || 0
|
|
31
|
+
}
|
|
32
|
+
};
|
|
32
33
|
}
|
|
33
|
-
async
|
|
34
|
-
return this.
|
|
34
|
+
async all() {
|
|
35
|
+
return this.run();
|
|
35
36
|
}
|
|
36
|
-
async
|
|
37
|
-
|
|
37
|
+
async first(colName) {
|
|
38
|
+
const result = await this.run();
|
|
39
|
+
if (!result.results || result.results.length === 0) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const firstRow = result.results[0];
|
|
43
|
+
if (colName && typeof firstRow === "object" && firstRow !== null) {
|
|
44
|
+
return firstRow[colName] ?? null;
|
|
45
|
+
}
|
|
46
|
+
return firstRow;
|
|
38
47
|
}
|
|
39
|
-
|
|
40
|
-
|
|
48
|
+
async raw(options) {
|
|
49
|
+
const result = await this.run();
|
|
50
|
+
if (!result.results || result.results.length === 0) {
|
|
51
|
+
return options?.columnNames ? [[], ...[]] : [];
|
|
52
|
+
}
|
|
53
|
+
const raw = result.results.map((row) => {
|
|
54
|
+
if (typeof row === "object" && row !== null) {
|
|
55
|
+
return Object.values(row);
|
|
56
|
+
}
|
|
57
|
+
return row;
|
|
58
|
+
});
|
|
59
|
+
if (options?.columnNames && result.results.length > 0) {
|
|
60
|
+
const firstRow = result.results[0];
|
|
61
|
+
if (typeof firstRow === "object" && firstRow !== null) {
|
|
62
|
+
const columnNames = Object.keys(firstRow);
|
|
63
|
+
return [columnNames, ...raw];
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return raw;
|
|
41
67
|
}
|
|
42
68
|
}
|
|
69
|
+
function createD1Binding$1(client, databaseId) {
|
|
70
|
+
return {
|
|
71
|
+
prepare(query) {
|
|
72
|
+
return new D1PreparedStatementImpl(query, client, databaseId);
|
|
73
|
+
},
|
|
74
|
+
async batch(statements) {
|
|
75
|
+
const endpoint = `/accounts/${client.getAccountId()}/d1/database/${databaseId}/query`;
|
|
76
|
+
const queries = statements.map((stmt) => {
|
|
77
|
+
const impl = stmt;
|
|
78
|
+
return {
|
|
79
|
+
sql: impl.query,
|
|
80
|
+
params: impl.bindings
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
const data = await client.post(endpoint, queries);
|
|
84
|
+
return data.map((result) => ({
|
|
85
|
+
success: true,
|
|
86
|
+
results: result.results,
|
|
87
|
+
meta: {
|
|
88
|
+
served_by: result.meta.served_by || "",
|
|
89
|
+
duration: result.meta.duration || 0,
|
|
90
|
+
changes: result.meta.changes || 0,
|
|
91
|
+
last_row_id: result.meta.last_row_id || 0,
|
|
92
|
+
changed_db: result.meta.changed_db || false,
|
|
93
|
+
size_after: result.meta.size_after || 0,
|
|
94
|
+
rows_read: result.meta.rows_read || 0,
|
|
95
|
+
rows_written: result.meta.rows_written || 0
|
|
96
|
+
}
|
|
97
|
+
}));
|
|
98
|
+
},
|
|
99
|
+
async exec(query) {
|
|
100
|
+
const endpoint = `/accounts/${client.getAccountId()}/d1/database/${databaseId}/query`;
|
|
101
|
+
const data = await client.post(endpoint, {
|
|
102
|
+
sql: query
|
|
103
|
+
});
|
|
104
|
+
return {
|
|
105
|
+
count: data.length,
|
|
106
|
+
duration: data.reduce((acc, r) => acc + (r.meta.duration || 0), 0)
|
|
107
|
+
};
|
|
108
|
+
},
|
|
109
|
+
dump() {
|
|
110
|
+
throw new Error("D1 dump() is deprecated and not supported");
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
43
114
|
|
|
44
115
|
function createKVBinding$1(client, namespaceId) {
|
|
45
116
|
const baseEndpoint = `/accounts/${client.getAccountId()}/storage/kv/namespaces/${namespaceId}`;
|
|
@@ -53,7 +124,7 @@ function createKVBinding$1(client, namespaceId) {
|
|
|
53
124
|
const response = await fetch(`https://api.cloudflare.com/client/v4${url}`, {
|
|
54
125
|
method: "GET",
|
|
55
126
|
headers: {
|
|
56
|
-
|
|
127
|
+
Authorization: `Bearer ${client.apiToken}`,
|
|
57
128
|
...headers
|
|
58
129
|
}
|
|
59
130
|
});
|
|
@@ -87,7 +158,7 @@ function createKVBinding$1(client, namespaceId) {
|
|
|
87
158
|
const response = await fetch(`https://api.cloudflare.com/client/v4${url}`, {
|
|
88
159
|
method: "GET",
|
|
89
160
|
headers: {
|
|
90
|
-
|
|
161
|
+
Authorization: `Bearer ${client.apiToken}`
|
|
91
162
|
}
|
|
92
163
|
});
|
|
93
164
|
if (response.status === 404) {
|
|
@@ -142,7 +213,7 @@ function createKVBinding$1(client, namespaceId) {
|
|
|
142
213
|
async put(key, value, options) {
|
|
143
214
|
const url = `${baseEndpoint}/values/${encodeURIComponent(key)}`;
|
|
144
215
|
const headers = {
|
|
145
|
-
|
|
216
|
+
Authorization: `Bearer ${client.apiToken}`
|
|
146
217
|
};
|
|
147
218
|
let body;
|
|
148
219
|
if (typeof value === "string") {
|
|
@@ -156,7 +227,8 @@ function createKVBinding$1(client, namespaceId) {
|
|
|
156
227
|
const chunks = [];
|
|
157
228
|
while (true) {
|
|
158
229
|
const { done, value: chunk } = await reader.read();
|
|
159
|
-
if (done)
|
|
230
|
+
if (done)
|
|
231
|
+
break;
|
|
160
232
|
chunks.push(chunk);
|
|
161
233
|
}
|
|
162
234
|
const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
|
|
@@ -168,7 +240,7 @@ function createKVBinding$1(client, namespaceId) {
|
|
|
168
240
|
}
|
|
169
241
|
body = result.buffer;
|
|
170
242
|
} else {
|
|
171
|
-
throw new
|
|
243
|
+
throw new TypeError("Unsupported value type");
|
|
172
244
|
}
|
|
173
245
|
const queryParams = new URLSearchParams();
|
|
174
246
|
if (options?.expiration) {
|
|
@@ -193,7 +265,7 @@ function createKVBinding$1(client, namespaceId) {
|
|
|
193
265
|
const response = await fetch(`https://api.cloudflare.com/client/v4${url}`, {
|
|
194
266
|
method: "DELETE",
|
|
195
267
|
headers: {
|
|
196
|
-
|
|
268
|
+
Authorization: `Bearer ${client.apiToken}`
|
|
197
269
|
}
|
|
198
270
|
});
|
|
199
271
|
if (!response.ok && response.status !== 404) {
|
|
@@ -269,120 +341,6 @@ function createKVBinding$1(client, namespaceId) {
|
|
|
269
341
|
return kvNamespace;
|
|
270
342
|
}
|
|
271
343
|
|
|
272
|
-
class D1PreparedStatementImpl {
|
|
273
|
-
constructor(query, client, databaseId) {
|
|
274
|
-
this.query = query;
|
|
275
|
-
this.client = client;
|
|
276
|
-
this.databaseId = databaseId;
|
|
277
|
-
}
|
|
278
|
-
bindings = [];
|
|
279
|
-
bind(...values) {
|
|
280
|
-
this.bindings = values;
|
|
281
|
-
return this;
|
|
282
|
-
}
|
|
283
|
-
async run() {
|
|
284
|
-
const endpoint = `/accounts/${this.client.getAccountId()}/d1/database/${this.databaseId}/query`;
|
|
285
|
-
const data = await this.client.post(endpoint, {
|
|
286
|
-
sql: this.query,
|
|
287
|
-
params: this.bindings
|
|
288
|
-
});
|
|
289
|
-
const result = data[0];
|
|
290
|
-
return {
|
|
291
|
-
success: result.success,
|
|
292
|
-
results: result.results,
|
|
293
|
-
meta: {
|
|
294
|
-
served_by: result.meta.served_by || "",
|
|
295
|
-
duration: result.meta.duration || 0,
|
|
296
|
-
changes: result.meta.changes || 0,
|
|
297
|
-
last_row_id: result.meta.last_row_id || 0,
|
|
298
|
-
changed_db: result.meta.changed_db || false,
|
|
299
|
-
size_after: result.meta.size_after || 0,
|
|
300
|
-
rows_read: result.meta.rows_read || 0,
|
|
301
|
-
rows_written: result.meta.rows_written || 0
|
|
302
|
-
}
|
|
303
|
-
};
|
|
304
|
-
}
|
|
305
|
-
async all() {
|
|
306
|
-
return this.run();
|
|
307
|
-
}
|
|
308
|
-
async first(colName) {
|
|
309
|
-
const result = await this.run();
|
|
310
|
-
if (!result.results || result.results.length === 0) {
|
|
311
|
-
return null;
|
|
312
|
-
}
|
|
313
|
-
const firstRow = result.results[0];
|
|
314
|
-
if (colName && typeof firstRow === "object" && firstRow !== null) {
|
|
315
|
-
return firstRow[colName] ?? null;
|
|
316
|
-
}
|
|
317
|
-
return firstRow;
|
|
318
|
-
}
|
|
319
|
-
async raw(options) {
|
|
320
|
-
const result = await this.run();
|
|
321
|
-
if (!result.results || result.results.length === 0) {
|
|
322
|
-
return [];
|
|
323
|
-
}
|
|
324
|
-
const raw = result.results.map((row) => {
|
|
325
|
-
if (typeof row === "object" && row !== null) {
|
|
326
|
-
return Object.values(row);
|
|
327
|
-
}
|
|
328
|
-
return row;
|
|
329
|
-
});
|
|
330
|
-
if (options?.columnNames && result.results.length > 0) {
|
|
331
|
-
const firstRow = result.results[0];
|
|
332
|
-
if (typeof firstRow === "object" && firstRow !== null) {
|
|
333
|
-
const columnNames = Object.keys(firstRow);
|
|
334
|
-
return [columnNames, ...raw];
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
return raw;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
function createD1Binding$1(client, databaseId) {
|
|
341
|
-
return {
|
|
342
|
-
prepare(query) {
|
|
343
|
-
return new D1PreparedStatementImpl(query, client, databaseId);
|
|
344
|
-
},
|
|
345
|
-
async batch(statements) {
|
|
346
|
-
const endpoint = `/accounts/${client.getAccountId()}/d1/database/${databaseId}/query`;
|
|
347
|
-
const queries = statements.map((stmt) => {
|
|
348
|
-
const impl = stmt;
|
|
349
|
-
return {
|
|
350
|
-
sql: impl.query,
|
|
351
|
-
params: impl.bindings
|
|
352
|
-
};
|
|
353
|
-
});
|
|
354
|
-
const data = await client.post(endpoint, queries);
|
|
355
|
-
return data.map((result) => ({
|
|
356
|
-
success: result.success,
|
|
357
|
-
results: result.results,
|
|
358
|
-
meta: {
|
|
359
|
-
served_by: result.meta.served_by || "",
|
|
360
|
-
duration: result.meta.duration || 0,
|
|
361
|
-
changes: result.meta.changes || 0,
|
|
362
|
-
last_row_id: result.meta.last_row_id || 0,
|
|
363
|
-
changed_db: result.meta.changed_db || false,
|
|
364
|
-
size_after: result.meta.size_after || 0,
|
|
365
|
-
rows_read: result.meta.rows_read || 0,
|
|
366
|
-
rows_written: result.meta.rows_written || 0
|
|
367
|
-
}
|
|
368
|
-
}));
|
|
369
|
-
},
|
|
370
|
-
async exec(query) {
|
|
371
|
-
const endpoint = `/accounts/${client.getAccountId()}/d1/database/${databaseId}/query`;
|
|
372
|
-
const data = await client.post(endpoint, {
|
|
373
|
-
sql: query
|
|
374
|
-
});
|
|
375
|
-
return {
|
|
376
|
-
count: data.length,
|
|
377
|
-
duration: data.reduce((acc, r) => acc + (r.meta.duration || 0), 0)
|
|
378
|
-
};
|
|
379
|
-
},
|
|
380
|
-
dump() {
|
|
381
|
-
throw new Error("D1 dump() is deprecated and not supported");
|
|
382
|
-
}
|
|
383
|
-
};
|
|
384
|
-
}
|
|
385
|
-
|
|
386
344
|
async function sha256(data) {
|
|
387
345
|
const encoder = new TextEncoder();
|
|
388
346
|
const dataBuffer = typeof data === "string" ? encoder.encode(data) : data;
|
|
@@ -430,9 +388,11 @@ async function signRequest(params) {
|
|
|
430
388
|
const dateStamp = amzDate.slice(0, 8);
|
|
431
389
|
const canonicalHeaders = {
|
|
432
390
|
host,
|
|
433
|
-
"x-amz-date": amzDate
|
|
434
|
-
...headers
|
|
391
|
+
"x-amz-date": amzDate
|
|
435
392
|
};
|
|
393
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
394
|
+
canonicalHeaders[key.toLowerCase()] = value;
|
|
395
|
+
}
|
|
436
396
|
const signedHeaders = Object.keys(canonicalHeaders).sort().join(";");
|
|
437
397
|
const canonicalHeadersString = Object.keys(canonicalHeaders).sort().map((key) => `${key}:${canonicalHeaders[key]}`).join("\n");
|
|
438
398
|
const payloadHash = body ? hex(await sha256(body)) : hex(await sha256(""));
|
|
@@ -440,7 +400,8 @@ async function signRequest(params) {
|
|
|
440
400
|
method,
|
|
441
401
|
path,
|
|
442
402
|
queryString,
|
|
443
|
-
canonicalHeadersString
|
|
403
|
+
`${canonicalHeadersString}
|
|
404
|
+
`,
|
|
444
405
|
signedHeaders,
|
|
445
406
|
payloadHash
|
|
446
407
|
].join("\n");
|
|
@@ -466,7 +427,7 @@ function createR2Binding$1(config, bucketName) {
|
|
|
466
427
|
const { accountId, r2AccessKeyId, r2SecretAccessKey } = config;
|
|
467
428
|
const baseUrl = `https://${accountId}.r2.cloudflarestorage.com/${bucketName}`;
|
|
468
429
|
async function signedFetch(method, key, options) {
|
|
469
|
-
const queryString = options?.queryParams ?
|
|
430
|
+
const queryString = options?.queryParams ? `?${new URLSearchParams(options.queryParams).toString()}` : "";
|
|
470
431
|
const url = `${baseUrl}/${encodeURIComponent(key)}${queryString}`;
|
|
471
432
|
let body;
|
|
472
433
|
if (options?.body instanceof ReadableStream) {
|
|
@@ -474,7 +435,8 @@ function createR2Binding$1(config, bucketName) {
|
|
|
474
435
|
const chunks = [];
|
|
475
436
|
while (true) {
|
|
476
437
|
const { done, value } = await reader.read();
|
|
477
|
-
if (done)
|
|
438
|
+
if (done)
|
|
439
|
+
break;
|
|
478
440
|
chunks.push(value);
|
|
479
441
|
}
|
|
480
442
|
const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
|
|
@@ -506,7 +468,7 @@ function createR2Binding$1(config, bucketName) {
|
|
|
506
468
|
});
|
|
507
469
|
}
|
|
508
470
|
async function signedFetchBucket(method, options) {
|
|
509
|
-
const queryString = options?.queryParams ?
|
|
471
|
+
const queryString = options?.queryParams ? `?${new URLSearchParams(options.queryParams).toString()}` : "";
|
|
510
472
|
const url = `${baseUrl}${queryString}`;
|
|
511
473
|
const signatureHeaders = await signRequest({
|
|
512
474
|
method,
|
|
@@ -528,26 +490,27 @@ function createR2Binding$1(config, bucketName) {
|
|
|
528
490
|
const headers = {};
|
|
529
491
|
if (options?.range) {
|
|
530
492
|
if (typeof options.range === "object") {
|
|
531
|
-
const
|
|
532
|
-
if (suffix) {
|
|
533
|
-
headers
|
|
534
|
-
} else if (offset !== void 0) {
|
|
535
|
-
headers
|
|
493
|
+
const range = options.range;
|
|
494
|
+
if ("suffix" in range && range.suffix) {
|
|
495
|
+
headers.Range = `bytes=-${range.suffix}`;
|
|
496
|
+
} else if ("offset" in range && range.offset !== void 0) {
|
|
497
|
+
headers.Range = "length" in range && range.length ? `bytes=${range.offset}-${range.offset + range.length - 1}` : `bytes=${range.offset}-`;
|
|
536
498
|
}
|
|
537
499
|
}
|
|
538
500
|
}
|
|
539
501
|
if (options?.onlyIf) {
|
|
540
|
-
|
|
541
|
-
|
|
502
|
+
const onlyIf = options.onlyIf;
|
|
503
|
+
if (onlyIf.etagMatches) {
|
|
504
|
+
headers["If-Match"] = onlyIf.etagMatches;
|
|
542
505
|
}
|
|
543
|
-
if (
|
|
544
|
-
headers["If-None-Match"] =
|
|
506
|
+
if (onlyIf.etagDoesNotMatch) {
|
|
507
|
+
headers["If-None-Match"] = onlyIf.etagDoesNotMatch;
|
|
545
508
|
}
|
|
546
|
-
if (
|
|
547
|
-
headers["If-Unmodified-Since"] =
|
|
509
|
+
if (onlyIf.uploadedBefore) {
|
|
510
|
+
headers["If-Unmodified-Since"] = onlyIf.uploadedBefore.toUTCString();
|
|
548
511
|
}
|
|
549
|
-
if (
|
|
550
|
-
headers["If-Modified-Since"] =
|
|
512
|
+
if (onlyIf.uploadedAfter) {
|
|
513
|
+
headers["If-Modified-Since"] = onlyIf.uploadedAfter.toUTCString();
|
|
551
514
|
}
|
|
552
515
|
}
|
|
553
516
|
const response = await signedFetch("GET", key, { headers });
|
|
@@ -567,10 +530,12 @@ function createR2Binding$1(config, bucketName) {
|
|
|
567
530
|
const r2Object = {
|
|
568
531
|
key,
|
|
569
532
|
version: response.headers.get("x-amz-version-id") || "",
|
|
570
|
-
size: parseInt(response.headers.get("content-length") || "0", 10),
|
|
533
|
+
size: Number.parseInt(response.headers.get("content-length") || "0", 10),
|
|
571
534
|
etag: response.headers.get("etag") || "",
|
|
572
535
|
httpEtag: response.headers.get("etag") || "",
|
|
573
|
-
checksums: {
|
|
536
|
+
checksums: {
|
|
537
|
+
toJSON: () => ({})
|
|
538
|
+
},
|
|
574
539
|
uploaded: new Date(response.headers.get("last-modified") || Date.now()),
|
|
575
540
|
httpMetadata: {
|
|
576
541
|
contentType: response.headers.get("content-type") || void 0,
|
|
@@ -581,9 +546,10 @@ function createR2Binding$1(config, bucketName) {
|
|
|
581
546
|
cacheExpiry: response.headers.get("expires") ? new Date(response.headers.get("expires")) : void 0
|
|
582
547
|
},
|
|
583
548
|
customMetadata,
|
|
549
|
+
storageClass: "Standard",
|
|
584
550
|
range: options?.range ? {
|
|
585
551
|
offset: 0,
|
|
586
|
-
length: parseInt(response.headers.get("content-length") || "0", 10)
|
|
552
|
+
length: Number.parseInt(response.headers.get("content-length") || "0", 10)
|
|
587
553
|
} : void 0,
|
|
588
554
|
body: response.body,
|
|
589
555
|
bodyUsed: false,
|
|
@@ -591,49 +557,52 @@ function createR2Binding$1(config, bucketName) {
|
|
|
591
557
|
text: () => response.text(),
|
|
592
558
|
json: () => response.json(),
|
|
593
559
|
blob: () => response.blob(),
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
560
|
+
bytes: async () => new Uint8Array(await response.arrayBuffer()),
|
|
561
|
+
writeHttpMetadata: ((headers2) => {
|
|
562
|
+
const metadata = r2Object.httpMetadata;
|
|
563
|
+
if (metadata.contentType) {
|
|
564
|
+
headers2.set("content-type", metadata.contentType);
|
|
597
565
|
}
|
|
598
|
-
if (
|
|
599
|
-
headers2.set("content-language",
|
|
566
|
+
if (metadata.contentLanguage) {
|
|
567
|
+
headers2.set("content-language", metadata.contentLanguage);
|
|
600
568
|
}
|
|
601
|
-
if (
|
|
602
|
-
headers2.set("content-disposition",
|
|
569
|
+
if (metadata.contentDisposition) {
|
|
570
|
+
headers2.set("content-disposition", metadata.contentDisposition);
|
|
603
571
|
}
|
|
604
|
-
if (
|
|
605
|
-
headers2.set("content-encoding",
|
|
572
|
+
if (metadata.contentEncoding) {
|
|
573
|
+
headers2.set("content-encoding", metadata.contentEncoding);
|
|
606
574
|
}
|
|
607
|
-
if (
|
|
608
|
-
headers2.set("cache-control",
|
|
575
|
+
if (metadata.cacheControl) {
|
|
576
|
+
headers2.set("cache-control", metadata.cacheControl);
|
|
609
577
|
}
|
|
610
|
-
if (
|
|
611
|
-
headers2.set("expires",
|
|
578
|
+
if (metadata.cacheExpiry) {
|
|
579
|
+
headers2.set("expires", metadata.cacheExpiry.toUTCString());
|
|
612
580
|
}
|
|
613
|
-
}
|
|
581
|
+
})
|
|
614
582
|
};
|
|
615
583
|
return r2Object;
|
|
616
584
|
},
|
|
617
585
|
async put(key, value, options) {
|
|
618
586
|
const headers = {};
|
|
619
587
|
if (options?.httpMetadata) {
|
|
620
|
-
|
|
621
|
-
|
|
588
|
+
const metadata = options.httpMetadata;
|
|
589
|
+
if (metadata.contentType) {
|
|
590
|
+
headers["Content-Type"] = metadata.contentType;
|
|
622
591
|
}
|
|
623
|
-
if (
|
|
624
|
-
headers["Content-Language"] =
|
|
592
|
+
if (metadata.contentLanguage) {
|
|
593
|
+
headers["Content-Language"] = metadata.contentLanguage;
|
|
625
594
|
}
|
|
626
|
-
if (
|
|
627
|
-
headers["Content-Disposition"] =
|
|
595
|
+
if (metadata.contentDisposition) {
|
|
596
|
+
headers["Content-Disposition"] = metadata.contentDisposition;
|
|
628
597
|
}
|
|
629
|
-
if (
|
|
630
|
-
headers["Content-Encoding"] =
|
|
598
|
+
if (metadata.contentEncoding) {
|
|
599
|
+
headers["Content-Encoding"] = metadata.contentEncoding;
|
|
631
600
|
}
|
|
632
|
-
if (
|
|
633
|
-
headers["Cache-Control"] =
|
|
601
|
+
if (metadata.cacheControl) {
|
|
602
|
+
headers["Cache-Control"] = metadata.cacheControl;
|
|
634
603
|
}
|
|
635
|
-
if (
|
|
636
|
-
headers
|
|
604
|
+
if (metadata.cacheExpiry) {
|
|
605
|
+
headers.Expires = metadata.cacheExpiry.toUTCString();
|
|
637
606
|
}
|
|
638
607
|
}
|
|
639
608
|
if (options?.customMetadata) {
|
|
@@ -642,7 +611,11 @@ function createR2Binding$1(config, bucketName) {
|
|
|
642
611
|
}
|
|
643
612
|
}
|
|
644
613
|
let body;
|
|
645
|
-
if (
|
|
614
|
+
if (value === null) {
|
|
615
|
+
body = "";
|
|
616
|
+
} else if (value instanceof Blob) {
|
|
617
|
+
body = await value.arrayBuffer();
|
|
618
|
+
} else if (ArrayBuffer.isView(value)) {
|
|
646
619
|
body = value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength);
|
|
647
620
|
} else {
|
|
648
621
|
body = value;
|
|
@@ -657,10 +630,15 @@ function createR2Binding$1(config, bucketName) {
|
|
|
657
630
|
size: typeof value === "string" ? value.length : 0,
|
|
658
631
|
etag: response.headers.get("etag") || "",
|
|
659
632
|
httpEtag: response.headers.get("etag") || "",
|
|
660
|
-
checksums: {
|
|
633
|
+
checksums: {
|
|
634
|
+
toJSON: () => ({})
|
|
635
|
+
},
|
|
661
636
|
uploaded: /* @__PURE__ */ new Date(),
|
|
662
637
|
httpMetadata: options?.httpMetadata || {},
|
|
663
|
-
customMetadata: options?.customMetadata || {}
|
|
638
|
+
customMetadata: options?.customMetadata || {},
|
|
639
|
+
storageClass: "Standard",
|
|
640
|
+
writeHttpMetadata: () => {
|
|
641
|
+
}
|
|
664
642
|
};
|
|
665
643
|
},
|
|
666
644
|
async delete(keys) {
|
|
@@ -702,10 +680,12 @@ function createR2Binding$1(config, bucketName) {
|
|
|
702
680
|
return {
|
|
703
681
|
key,
|
|
704
682
|
version: response.headers.get("x-amz-version-id") || "",
|
|
705
|
-
size: parseInt(response.headers.get("content-length") || "0", 10),
|
|
683
|
+
size: Number.parseInt(response.headers.get("content-length") || "0", 10),
|
|
706
684
|
etag: response.headers.get("etag") || "",
|
|
707
685
|
httpEtag: response.headers.get("etag") || "",
|
|
708
|
-
checksums: {
|
|
686
|
+
checksums: {
|
|
687
|
+
toJSON: () => ({})
|
|
688
|
+
},
|
|
709
689
|
uploaded: new Date(response.headers.get("last-modified") || Date.now()),
|
|
710
690
|
httpMetadata: {
|
|
711
691
|
contentType: response.headers.get("content-type") || void 0,
|
|
@@ -715,7 +695,10 @@ function createR2Binding$1(config, bucketName) {
|
|
|
715
695
|
cacheControl: response.headers.get("cache-control") || void 0,
|
|
716
696
|
cacheExpiry: response.headers.get("expires") ? new Date(response.headers.get("expires")) : void 0
|
|
717
697
|
},
|
|
718
|
-
customMetadata
|
|
698
|
+
customMetadata,
|
|
699
|
+
storageClass: "Standard",
|
|
700
|
+
writeHttpMetadata: () => {
|
|
701
|
+
}
|
|
719
702
|
};
|
|
720
703
|
},
|
|
721
704
|
async list(options) {
|
|
@@ -726,13 +709,13 @@ function createR2Binding$1(config, bucketName) {
|
|
|
726
709
|
queryParams["max-keys"] = options.limit.toString();
|
|
727
710
|
}
|
|
728
711
|
if (options?.prefix) {
|
|
729
|
-
queryParams
|
|
712
|
+
queryParams.prefix = options.prefix;
|
|
730
713
|
}
|
|
731
714
|
if (options?.cursor) {
|
|
732
715
|
queryParams["continuation-token"] = options.cursor;
|
|
733
716
|
}
|
|
734
717
|
if (options?.delimiter) {
|
|
735
|
-
queryParams
|
|
718
|
+
queryParams.delimiter = options.delimiter;
|
|
736
719
|
}
|
|
737
720
|
if (options?.startAfter) {
|
|
738
721
|
queryParams["start-after"] = options.startAfter;
|
|
@@ -749,7 +732,7 @@ function createR2Binding$1(config, bucketName) {
|
|
|
749
732
|
while ((match = contentsRegex.exec(xml)) !== null) {
|
|
750
733
|
const content = match[1];
|
|
751
734
|
const key = content.match(/<Key>(.*?)<\/Key>/)?.[1] || "";
|
|
752
|
-
const size = parseInt(content.match(/<Size>(.*?)<\/Size>/)?.[1] || "0", 10);
|
|
735
|
+
const size = Number.parseInt(content.match(/<Size>(.*?)<\/Size>/)?.[1] || "0", 10);
|
|
753
736
|
const etag = content.match(/<ETag>(.*?)<\/ETag>/)?.[1] || "";
|
|
754
737
|
const lastModified = content.match(/<LastModified>(.*?)<\/LastModified>/)?.[1] || "";
|
|
755
738
|
objects.push({
|
|
@@ -758,10 +741,15 @@ function createR2Binding$1(config, bucketName) {
|
|
|
758
741
|
size,
|
|
759
742
|
etag,
|
|
760
743
|
httpEtag: etag,
|
|
761
|
-
checksums: {
|
|
744
|
+
checksums: {
|
|
745
|
+
toJSON: () => ({})
|
|
746
|
+
},
|
|
762
747
|
uploaded: new Date(lastModified),
|
|
763
748
|
httpMetadata: {},
|
|
764
|
-
customMetadata: {}
|
|
749
|
+
customMetadata: {},
|
|
750
|
+
storageClass: "Standard",
|
|
751
|
+
writeHttpMetadata: () => {
|
|
752
|
+
}
|
|
765
753
|
});
|
|
766
754
|
}
|
|
767
755
|
const prefixRegex = /<CommonPrefixes>.*?<Prefix>(.*?)<\/Prefix>.*?<\/CommonPrefixes>/gs;
|
|
@@ -777,16 +765,59 @@ function createR2Binding$1(config, bucketName) {
|
|
|
777
765
|
delimitedPrefixes
|
|
778
766
|
};
|
|
779
767
|
},
|
|
780
|
-
createMultipartUpload(
|
|
768
|
+
createMultipartUpload(_key, _options) {
|
|
781
769
|
throw new Error("R2 multipart upload not yet implemented");
|
|
782
770
|
},
|
|
783
|
-
resumeMultipartUpload(
|
|
771
|
+
resumeMultipartUpload(_key, _uploadId) {
|
|
784
772
|
throw new Error("R2 multipart upload not yet implemented");
|
|
785
773
|
}
|
|
786
774
|
};
|
|
787
775
|
return r2Bucket;
|
|
788
776
|
}
|
|
789
777
|
|
|
778
|
+
class CloudflareAPIClient {
|
|
779
|
+
baseUrl = "https://api.cloudflare.com/client/v4";
|
|
780
|
+
accountId;
|
|
781
|
+
apiToken;
|
|
782
|
+
constructor(accountId, apiToken) {
|
|
783
|
+
this.accountId = accountId;
|
|
784
|
+
this.apiToken = apiToken;
|
|
785
|
+
}
|
|
786
|
+
async request(method, endpoint, body, headers) {
|
|
787
|
+
const url = `${this.baseUrl}${endpoint}`;
|
|
788
|
+
const response = await fetch(url, {
|
|
789
|
+
method,
|
|
790
|
+
headers: {
|
|
791
|
+
"Authorization": `Bearer ${this.apiToken}`,
|
|
792
|
+
"Content-Type": "application/json",
|
|
793
|
+
...headers
|
|
794
|
+
},
|
|
795
|
+
body: body ? JSON.stringify(body) : void 0
|
|
796
|
+
});
|
|
797
|
+
const data = await response.json();
|
|
798
|
+
if (!response.ok || !data.success) {
|
|
799
|
+
const errorMessage = data.errors?.[0]?.message || `HTTP ${response.status}`;
|
|
800
|
+
throw new Error(`Cloudflare API Error: ${errorMessage}`);
|
|
801
|
+
}
|
|
802
|
+
return data.result;
|
|
803
|
+
}
|
|
804
|
+
async get(endpoint, headers) {
|
|
805
|
+
return this.request("GET", endpoint, void 0, headers);
|
|
806
|
+
}
|
|
807
|
+
async post(endpoint, body, headers) {
|
|
808
|
+
return this.request("POST", endpoint, body, headers);
|
|
809
|
+
}
|
|
810
|
+
async put(endpoint, body, headers) {
|
|
811
|
+
return this.request("PUT", endpoint, body, headers);
|
|
812
|
+
}
|
|
813
|
+
async delete(endpoint, body, headers) {
|
|
814
|
+
return this.request("DELETE", endpoint, body, headers);
|
|
815
|
+
}
|
|
816
|
+
getAccountId() {
|
|
817
|
+
return this.accountId;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
790
821
|
function createR2Binding(config, bucketName) {
|
|
791
822
|
return createR2Binding$1(config, bucketName);
|
|
792
823
|
}
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openwrangler",
|
|
3
|
-
"version": "0.0.4",
|
|
4
3
|
"type": "module",
|
|
4
|
+
"version": "0.0.5",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
7
|
-
"
|
|
8
|
-
"
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"import": "./dist/index.mjs"
|
|
9
9
|
}
|
|
10
10
|
},
|
|
11
11
|
"main": "./dist/index.mjs",
|
|
@@ -14,15 +14,19 @@
|
|
|
14
14
|
"dist"
|
|
15
15
|
],
|
|
16
16
|
"devDependencies": {
|
|
17
|
+
"@antfu/eslint-config": "^6.7.3",
|
|
17
18
|
"@cloudflare/workers-types": "^4.20251231.0",
|
|
19
|
+
"eslint": "^9.39.2",
|
|
18
20
|
"nuxt": "^3.15.0",
|
|
19
21
|
"typescript": "^5.7.2",
|
|
20
|
-
"wrangler": "^4.54.0",
|
|
21
22
|
"unbuild": "^3.3.1",
|
|
22
|
-
"
|
|
23
|
+
"wrangler": "^4.54.0",
|
|
24
|
+
"@bino0216/nitro-cloudflare-dev": "0.2.7"
|
|
23
25
|
},
|
|
24
26
|
"scripts": {
|
|
25
27
|
"dev": "cd ./playground && pnpm dev",
|
|
26
|
-
"build": "unbuild"
|
|
28
|
+
"build": "unbuild",
|
|
29
|
+
"lint": "eslint .",
|
|
30
|
+
"lint:fix": "eslint . --fix"
|
|
27
31
|
}
|
|
28
32
|
}
|