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.
Files changed (3) hide show
  1. package/README.md +144 -0
  2. package/dist/index.mjs +247 -216
  3. 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 CloudflareAPIClient {
2
- baseUrl = "https://api.cloudflare.com/client/v4";
3
- accountId;
4
- apiToken;
5
- constructor(accountId, apiToken) {
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
- async get(endpoint, headers) {
28
- return this.request("GET", endpoint, void 0, headers);
7
+ bindings = [];
8
+ bind(...values) {
9
+ this.bindings = values;
10
+ return this;
29
11
  }
30
- async post(endpoint, body, headers) {
31
- return this.request("POST", endpoint, body, headers);
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 put(endpoint, body, headers) {
34
- return this.request("PUT", endpoint, body, headers);
34
+ async all() {
35
+ return this.run();
35
36
  }
36
- async delete(endpoint, body, headers) {
37
- return this.request("DELETE", endpoint, body, headers);
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
- getAccountId() {
40
- return this.accountId;
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
- "Authorization": `Bearer ${client.apiToken}`,
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
- "Authorization": `Bearer ${client.apiToken}`
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
- "Authorization": `Bearer ${client.apiToken}`
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) break;
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 Error("Unsupported value type");
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
- "Authorization": `Bearer ${client.apiToken}`
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 + "\n",
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 ? "?" + new URLSearchParams(options.queryParams).toString() : "";
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) break;
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 ? "?" + new URLSearchParams(options.queryParams).toString() : "";
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 { offset, length, suffix } = options.range;
532
- if (suffix) {
533
- headers["Range"] = `bytes=-${suffix}`;
534
- } else if (offset !== void 0) {
535
- headers["Range"] = length ? `bytes=${offset}-${offset + length - 1}` : `bytes=${offset}-`;
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
- if (options.onlyIf.etagMatches) {
541
- headers["If-Match"] = options.onlyIf.etagMatches;
502
+ const onlyIf = options.onlyIf;
503
+ if (onlyIf.etagMatches) {
504
+ headers["If-Match"] = onlyIf.etagMatches;
542
505
  }
543
- if (options.onlyIf.etagDoesNotMatch) {
544
- headers["If-None-Match"] = options.onlyIf.etagDoesNotMatch;
506
+ if (onlyIf.etagDoesNotMatch) {
507
+ headers["If-None-Match"] = onlyIf.etagDoesNotMatch;
545
508
  }
546
- if (options.onlyIf.uploadedBefore) {
547
- headers["If-Unmodified-Since"] = options.onlyIf.uploadedBefore.toUTCString();
509
+ if (onlyIf.uploadedBefore) {
510
+ headers["If-Unmodified-Since"] = onlyIf.uploadedBefore.toUTCString();
548
511
  }
549
- if (options.onlyIf.uploadedAfter) {
550
- headers["If-Modified-Since"] = options.onlyIf.uploadedAfter.toUTCString();
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
- writeHttpMetadata: (headers2) => {
595
- if (r2Object.httpMetadata.contentType) {
596
- headers2.set("content-type", r2Object.httpMetadata.contentType);
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 (r2Object.httpMetadata.contentLanguage) {
599
- headers2.set("content-language", r2Object.httpMetadata.contentLanguage);
566
+ if (metadata.contentLanguage) {
567
+ headers2.set("content-language", metadata.contentLanguage);
600
568
  }
601
- if (r2Object.httpMetadata.contentDisposition) {
602
- headers2.set("content-disposition", r2Object.httpMetadata.contentDisposition);
569
+ if (metadata.contentDisposition) {
570
+ headers2.set("content-disposition", metadata.contentDisposition);
603
571
  }
604
- if (r2Object.httpMetadata.contentEncoding) {
605
- headers2.set("content-encoding", r2Object.httpMetadata.contentEncoding);
572
+ if (metadata.contentEncoding) {
573
+ headers2.set("content-encoding", metadata.contentEncoding);
606
574
  }
607
- if (r2Object.httpMetadata.cacheControl) {
608
- headers2.set("cache-control", r2Object.httpMetadata.cacheControl);
575
+ if (metadata.cacheControl) {
576
+ headers2.set("cache-control", metadata.cacheControl);
609
577
  }
610
- if (r2Object.httpMetadata.cacheExpiry) {
611
- headers2.set("expires", r2Object.httpMetadata.cacheExpiry.toUTCString());
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
- if (options.httpMetadata.contentType) {
621
- headers["Content-Type"] = options.httpMetadata.contentType;
588
+ const metadata = options.httpMetadata;
589
+ if (metadata.contentType) {
590
+ headers["Content-Type"] = metadata.contentType;
622
591
  }
623
- if (options.httpMetadata.contentLanguage) {
624
- headers["Content-Language"] = options.httpMetadata.contentLanguage;
592
+ if (metadata.contentLanguage) {
593
+ headers["Content-Language"] = metadata.contentLanguage;
625
594
  }
626
- if (options.httpMetadata.contentDisposition) {
627
- headers["Content-Disposition"] = options.httpMetadata.contentDisposition;
595
+ if (metadata.contentDisposition) {
596
+ headers["Content-Disposition"] = metadata.contentDisposition;
628
597
  }
629
- if (options.httpMetadata.contentEncoding) {
630
- headers["Content-Encoding"] = options.httpMetadata.contentEncoding;
598
+ if (metadata.contentEncoding) {
599
+ headers["Content-Encoding"] = metadata.contentEncoding;
631
600
  }
632
- if (options.httpMetadata.cacheControl) {
633
- headers["Cache-Control"] = options.httpMetadata.cacheControl;
601
+ if (metadata.cacheControl) {
602
+ headers["Cache-Control"] = metadata.cacheControl;
634
603
  }
635
- if (options.httpMetadata.cacheExpiry) {
636
- headers["Expires"] = options.httpMetadata.cacheExpiry.toUTCString();
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 (ArrayBuffer.isView(value)) {
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["prefix"] = options.prefix;
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["delimiter"] = options.delimiter;
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(key, options) {
768
+ createMultipartUpload(_key, _options) {
781
769
  throw new Error("R2 multipart upload not yet implemented");
782
770
  },
783
- resumeMultipartUpload(key, uploadId) {
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
- "import": "./dist/index.mjs",
8
- "types": "./dist/index.d.ts"
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
- "@bino0216/nitro-cloudflare-dev": "0.2.6"
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
  }