openwrangler 0.0.2 → 0.0.4

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/index.d.mts CHANGED
@@ -1,12 +1,19 @@
1
1
  import { R2Bucket, KVNamespace, D1Database } from '@cloudflare/workers-types/experimental';
2
2
 
3
+ interface R2Config {
4
+ accountId: string;
5
+ r2AccessKeyId: string;
6
+ r2SecretAccessKey: string;
7
+ }
8
+
3
9
  interface BindingsConfig {
4
10
  accountId: string;
5
11
  apiToken: string;
6
12
  }
7
- declare function createR2Binding(config: BindingsConfig, bucketName: string): R2Bucket;
13
+
14
+ declare function createR2Binding(config: R2Config, bucketName: string): R2Bucket;
8
15
  declare function createKVBinding(config: BindingsConfig, namespaceId: string): KVNamespace;
9
16
  declare function createD1Binding(config: BindingsConfig, databaseId: string): D1Database;
10
17
 
11
18
  export { createD1Binding, createKVBinding, createR2Binding };
12
- export type { BindingsConfig };
19
+ export type { BindingsConfig, R2Config };
package/dist/index.d.ts CHANGED
@@ -1,12 +1,19 @@
1
1
  import { R2Bucket, KVNamespace, D1Database } from '@cloudflare/workers-types/experimental';
2
2
 
3
+ interface R2Config {
4
+ accountId: string;
5
+ r2AccessKeyId: string;
6
+ r2SecretAccessKey: string;
7
+ }
8
+
3
9
  interface BindingsConfig {
4
10
  accountId: string;
5
11
  apiToken: string;
6
12
  }
7
- declare function createR2Binding(config: BindingsConfig, bucketName: string): R2Bucket;
13
+
14
+ declare function createR2Binding(config: R2Config, bucketName: string): R2Bucket;
8
15
  declare function createKVBinding(config: BindingsConfig, namespaceId: string): KVNamespace;
9
16
  declare function createD1Binding(config: BindingsConfig, databaseId: string): D1Database;
10
17
 
11
18
  export { createD1Binding, createKVBinding, createR2Binding };
12
- export type { BindingsConfig };
19
+ export type { BindingsConfig, R2Config };
package/dist/index.mjs CHANGED
@@ -1,11 +1,802 @@
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;
26
+ }
27
+ async get(endpoint, headers) {
28
+ return this.request("GET", endpoint, void 0, headers);
29
+ }
30
+ async post(endpoint, body, headers) {
31
+ return this.request("POST", endpoint, body, headers);
32
+ }
33
+ async put(endpoint, body, headers) {
34
+ return this.request("PUT", endpoint, body, headers);
35
+ }
36
+ async delete(endpoint, body, headers) {
37
+ return this.request("DELETE", endpoint, body, headers);
38
+ }
39
+ getAccountId() {
40
+ return this.accountId;
41
+ }
42
+ }
43
+
44
+ function createKVBinding$1(client, namespaceId) {
45
+ const baseEndpoint = `/accounts/${client.getAccountId()}/storage/kv/namespaces/${namespaceId}`;
46
+ async function getValue(key, type, cacheTtl) {
47
+ try {
48
+ const url = `${baseEndpoint}/values/${encodeURIComponent(key)}`;
49
+ const headers = {};
50
+ if (cacheTtl !== void 0) {
51
+ headers["Cache-Control"] = `max-age=${cacheTtl}`;
52
+ }
53
+ const response = await fetch(`https://api.cloudflare.com/client/v4${url}`, {
54
+ method: "GET",
55
+ headers: {
56
+ "Authorization": `Bearer ${client.apiToken}`,
57
+ ...headers
58
+ }
59
+ });
60
+ if (response.status === 404) {
61
+ return null;
62
+ }
63
+ if (!response.ok) {
64
+ throw new Error(`KV GET failed: ${response.status}`);
65
+ }
66
+ switch (type) {
67
+ case "json":
68
+ return await response.json();
69
+ case "arrayBuffer":
70
+ return await response.arrayBuffer();
71
+ case "stream":
72
+ return response.body;
73
+ case "text":
74
+ default:
75
+ return await response.text();
76
+ }
77
+ } catch (error) {
78
+ if (error?.message?.includes("404")) {
79
+ return null;
80
+ }
81
+ throw error;
82
+ }
83
+ }
84
+ async function getValueWithMetadata(key, type) {
85
+ try {
86
+ const url = `${baseEndpoint}/values/${encodeURIComponent(key)}`;
87
+ const response = await fetch(`https://api.cloudflare.com/client/v4${url}`, {
88
+ method: "GET",
89
+ headers: {
90
+ "Authorization": `Bearer ${client.apiToken}`
91
+ }
92
+ });
93
+ if (response.status === 404) {
94
+ return { value: null, metadata: null, cacheStatus: null };
95
+ }
96
+ if (!response.ok) {
97
+ throw new Error(`KV GET failed: ${response.status}`);
98
+ }
99
+ const metadata = response.headers.get("cf-kv-metadata");
100
+ const parsedMetadata = metadata ? JSON.parse(metadata) : null;
101
+ let value;
102
+ switch (type) {
103
+ case "json":
104
+ value = await response.json();
105
+ break;
106
+ case "arrayBuffer":
107
+ value = await response.arrayBuffer();
108
+ break;
109
+ case "stream":
110
+ value = response.body;
111
+ break;
112
+ case "text":
113
+ default:
114
+ value = await response.text();
115
+ }
116
+ return {
117
+ value,
118
+ metadata: parsedMetadata,
119
+ cacheStatus: response.headers.get("cf-cache-status")
120
+ };
121
+ } catch (error) {
122
+ if (error?.message?.includes("404")) {
123
+ return { value: null, metadata: null, cacheStatus: null };
124
+ }
125
+ throw error;
126
+ }
127
+ }
128
+ const kvNamespace = {
129
+ async get(key, options) {
130
+ if (Array.isArray(key)) {
131
+ const type2 = typeof options === "string" ? options : options?.type || "text";
132
+ const results = /* @__PURE__ */ new Map();
133
+ for (const k of key) {
134
+ const value = await getValue(k, type2, options?.cacheTtl);
135
+ results.set(k, value);
136
+ }
137
+ return results;
138
+ }
139
+ const type = typeof options === "string" ? options : options?.type;
140
+ return getValue(key, type, options?.cacheTtl);
141
+ },
142
+ async put(key, value, options) {
143
+ const url = `${baseEndpoint}/values/${encodeURIComponent(key)}`;
144
+ const headers = {
145
+ "Authorization": `Bearer ${client.apiToken}`
146
+ };
147
+ let body;
148
+ if (typeof value === "string") {
149
+ body = value;
150
+ } else if (value instanceof ArrayBuffer) {
151
+ body = value;
152
+ } else if (ArrayBuffer.isView(value)) {
153
+ body = value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength);
154
+ } else if (value instanceof ReadableStream) {
155
+ const reader = value.getReader();
156
+ const chunks = [];
157
+ while (true) {
158
+ const { done, value: chunk } = await reader.read();
159
+ if (done) break;
160
+ chunks.push(chunk);
161
+ }
162
+ const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
163
+ const result = new Uint8Array(totalLength);
164
+ let offset = 0;
165
+ for (const chunk of chunks) {
166
+ result.set(chunk, offset);
167
+ offset += chunk.length;
168
+ }
169
+ body = result.buffer;
170
+ } else {
171
+ throw new Error("Unsupported value type");
172
+ }
173
+ const queryParams = new URLSearchParams();
174
+ if (options?.expiration) {
175
+ queryParams.set("expiration", options.expiration.toString());
176
+ }
177
+ if (options?.expirationTtl) {
178
+ queryParams.set("expiration_ttl", options.expirationTtl.toString());
179
+ }
180
+ const queryString = queryParams.toString();
181
+ const fullUrl = queryString ? `${url}?${queryString}` : url;
182
+ const response = await fetch(`https://api.cloudflare.com/client/v4${fullUrl}`, {
183
+ method: "PUT",
184
+ headers,
185
+ body
186
+ });
187
+ if (!response.ok) {
188
+ throw new Error(`KV PUT failed: ${response.status}`);
189
+ }
190
+ },
191
+ async delete(key) {
192
+ const url = `${baseEndpoint}/values/${encodeURIComponent(key)}`;
193
+ const response = await fetch(`https://api.cloudflare.com/client/v4${url}`, {
194
+ method: "DELETE",
195
+ headers: {
196
+ "Authorization": `Bearer ${client.apiToken}`
197
+ }
198
+ });
199
+ if (!response.ok && response.status !== 404) {
200
+ throw new Error(`KV DELETE failed: ${response.status}`);
201
+ }
202
+ },
203
+ async deleteBulk(keys) {
204
+ const keyArray = Array.isArray(keys) ? keys : [keys];
205
+ const url = `${baseEndpoint}/bulk`;
206
+ const response = await fetch(`https://api.cloudflare.com/client/v4${url}`, {
207
+ method: "DELETE",
208
+ headers: {
209
+ "Authorization": `Bearer ${client.apiToken}`,
210
+ "Content-Type": "application/json"
211
+ },
212
+ body: JSON.stringify(keyArray)
213
+ });
214
+ if (!response.ok) {
215
+ throw new Error(`KV DELETE BULK failed: ${response.status}`);
216
+ }
217
+ },
218
+ async list(options) {
219
+ const queryParams = new URLSearchParams();
220
+ if (options?.limit) {
221
+ queryParams.set("limit", options.limit.toString());
222
+ }
223
+ if (options?.prefix) {
224
+ queryParams.set("prefix", options.prefix);
225
+ }
226
+ if (options?.cursor) {
227
+ queryParams.set("cursor", options.cursor);
228
+ }
229
+ const queryString = queryParams.toString();
230
+ const url = queryString ? `${baseEndpoint}/keys?${queryString}` : `${baseEndpoint}/keys`;
231
+ const data = await client.get(url);
232
+ if (data.list_complete) {
233
+ return {
234
+ list_complete: true,
235
+ keys: data.keys.map((k) => ({
236
+ name: k.name,
237
+ expiration: k.expiration,
238
+ metadata: k.metadata
239
+ })),
240
+ cacheStatus: null
241
+ };
242
+ } else {
243
+ return {
244
+ list_complete: false,
245
+ keys: data.keys.map((k) => ({
246
+ name: k.name,
247
+ expiration: k.expiration,
248
+ metadata: k.metadata
249
+ })),
250
+ cursor: data.cursor,
251
+ cacheStatus: null
252
+ };
253
+ }
254
+ },
255
+ async getWithMetadata(key, options) {
256
+ if (Array.isArray(key)) {
257
+ const type2 = typeof options === "string" ? options : options?.type || "text";
258
+ const results = /* @__PURE__ */ new Map();
259
+ for (const k of key) {
260
+ const result = await getValueWithMetadata(k, type2);
261
+ results.set(k, result);
262
+ }
263
+ return results;
264
+ }
265
+ const type = typeof options === "string" ? options : options?.type;
266
+ return getValueWithMetadata(key, type);
267
+ }
268
+ };
269
+ return kvNamespace;
270
+ }
271
+
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
+ async function sha256(data) {
387
+ const encoder = new TextEncoder();
388
+ const dataBuffer = typeof data === "string" ? encoder.encode(data) : data;
389
+ return await crypto.subtle.digest("SHA-256", dataBuffer);
390
+ }
391
+ function hex(buffer) {
392
+ return Array.from(new Uint8Array(buffer)).map((b) => b.toString(16).padStart(2, "0")).join("");
393
+ }
394
+ async function hmac(key, data) {
395
+ const encoder = new TextEncoder();
396
+ const cryptoKey = await crypto.subtle.importKey(
397
+ "raw",
398
+ key,
399
+ { name: "HMAC", hash: "SHA-256" },
400
+ false,
401
+ ["sign"]
402
+ );
403
+ return await crypto.subtle.sign("HMAC", cryptoKey, encoder.encode(data));
404
+ }
405
+ async function getSignatureKey(secretAccessKey, dateStamp, region, service) {
406
+ const encoder = new TextEncoder();
407
+ const kDate = await hmac(encoder.encode(`AWS4${secretAccessKey}`), dateStamp);
408
+ const kRegion = await hmac(kDate, region);
409
+ const kService = await hmac(kRegion, service);
410
+ const kSigning = await hmac(kService, "aws4_request");
411
+ return kSigning;
412
+ }
413
+ async function signRequest(params) {
414
+ const {
415
+ method,
416
+ url,
417
+ headers,
418
+ body,
419
+ accessKeyId,
420
+ secretAccessKey,
421
+ region = "auto",
422
+ service = "s3"
423
+ } = params;
424
+ const urlObj = new URL(url);
425
+ const host = urlObj.host;
426
+ const path = urlObj.pathname || "/";
427
+ const queryString = urlObj.search.slice(1);
428
+ const now = /* @__PURE__ */ new Date();
429
+ const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
430
+ const dateStamp = amzDate.slice(0, 8);
431
+ const canonicalHeaders = {
432
+ host,
433
+ "x-amz-date": amzDate,
434
+ ...headers
435
+ };
436
+ const signedHeaders = Object.keys(canonicalHeaders).sort().join(";");
437
+ const canonicalHeadersString = Object.keys(canonicalHeaders).sort().map((key) => `${key}:${canonicalHeaders[key]}`).join("\n");
438
+ const payloadHash = body ? hex(await sha256(body)) : hex(await sha256(""));
439
+ const canonicalRequest = [
440
+ method,
441
+ path,
442
+ queryString,
443
+ canonicalHeadersString + "\n",
444
+ signedHeaders,
445
+ payloadHash
446
+ ].join("\n");
447
+ const algorithm = "AWS4-HMAC-SHA256";
448
+ const credentialScope = `${dateStamp}/${region}/${service}/aws4_request`;
449
+ const stringToSign = [
450
+ algorithm,
451
+ amzDate,
452
+ credentialScope,
453
+ hex(await sha256(canonicalRequest))
454
+ ].join("\n");
455
+ const signingKey = await getSignatureKey(secretAccessKey, dateStamp, region, service);
456
+ const signature = hex(await hmac(signingKey, stringToSign));
457
+ const authorization = `${algorithm} Credential=${accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
458
+ return {
459
+ "Authorization": authorization,
460
+ "x-amz-date": amzDate,
461
+ "x-amz-content-sha256": payloadHash
462
+ };
463
+ }
464
+
465
+ function createR2Binding$1(config, bucketName) {
466
+ const { accountId, r2AccessKeyId, r2SecretAccessKey } = config;
467
+ const baseUrl = `https://${accountId}.r2.cloudflarestorage.com/${bucketName}`;
468
+ async function signedFetch(method, key, options) {
469
+ const queryString = options?.queryParams ? "?" + new URLSearchParams(options.queryParams).toString() : "";
470
+ const url = `${baseUrl}/${encodeURIComponent(key)}${queryString}`;
471
+ let body;
472
+ if (options?.body instanceof ReadableStream) {
473
+ const reader = options.body.getReader();
474
+ const chunks = [];
475
+ while (true) {
476
+ const { done, value } = await reader.read();
477
+ if (done) break;
478
+ chunks.push(value);
479
+ }
480
+ const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
481
+ const result = new Uint8Array(totalLength);
482
+ let offset = 0;
483
+ for (const chunk of chunks) {
484
+ result.set(chunk, offset);
485
+ offset += chunk.length;
486
+ }
487
+ body = result.buffer;
488
+ } else {
489
+ body = options?.body;
490
+ }
491
+ const signatureHeaders = await signRequest({
492
+ method,
493
+ url,
494
+ headers: options?.headers || {},
495
+ body,
496
+ accessKeyId: r2AccessKeyId,
497
+ secretAccessKey: r2SecretAccessKey
498
+ });
499
+ return fetch(url, {
500
+ method,
501
+ headers: {
502
+ ...options?.headers,
503
+ ...signatureHeaders
504
+ },
505
+ body
506
+ });
507
+ }
508
+ async function signedFetchBucket(method, options) {
509
+ const queryString = options?.queryParams ? "?" + new URLSearchParams(options.queryParams).toString() : "";
510
+ const url = `${baseUrl}${queryString}`;
511
+ const signatureHeaders = await signRequest({
512
+ method,
513
+ url,
514
+ headers: options?.headers || {},
515
+ accessKeyId: r2AccessKeyId,
516
+ secretAccessKey: r2SecretAccessKey
517
+ });
518
+ return fetch(url, {
519
+ method,
520
+ headers: {
521
+ ...options?.headers,
522
+ ...signatureHeaders
523
+ }
524
+ });
525
+ }
526
+ const r2Bucket = {
527
+ async get(key, options) {
528
+ const headers = {};
529
+ if (options?.range) {
530
+ 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}-`;
536
+ }
537
+ }
538
+ }
539
+ if (options?.onlyIf) {
540
+ if (options.onlyIf.etagMatches) {
541
+ headers["If-Match"] = options.onlyIf.etagMatches;
542
+ }
543
+ if (options.onlyIf.etagDoesNotMatch) {
544
+ headers["If-None-Match"] = options.onlyIf.etagDoesNotMatch;
545
+ }
546
+ if (options.onlyIf.uploadedBefore) {
547
+ headers["If-Unmodified-Since"] = options.onlyIf.uploadedBefore.toUTCString();
548
+ }
549
+ if (options.onlyIf.uploadedAfter) {
550
+ headers["If-Modified-Since"] = options.onlyIf.uploadedAfter.toUTCString();
551
+ }
552
+ }
553
+ const response = await signedFetch("GET", key, { headers });
554
+ if (response.status === 404) {
555
+ return null;
556
+ }
557
+ if (!response.ok) {
558
+ throw new Error(`R2 GET failed: ${response.status}`);
559
+ }
560
+ const customMetadata = {};
561
+ response.headers.forEach((value, key2) => {
562
+ if (key2.toLowerCase().startsWith("x-amz-meta-")) {
563
+ const metaKey = key2.slice("x-amz-meta-".length);
564
+ customMetadata[metaKey] = value;
565
+ }
566
+ });
567
+ const r2Object = {
568
+ key,
569
+ version: response.headers.get("x-amz-version-id") || "",
570
+ size: parseInt(response.headers.get("content-length") || "0", 10),
571
+ etag: response.headers.get("etag") || "",
572
+ httpEtag: response.headers.get("etag") || "",
573
+ checksums: {},
574
+ uploaded: new Date(response.headers.get("last-modified") || Date.now()),
575
+ httpMetadata: {
576
+ contentType: response.headers.get("content-type") || void 0,
577
+ contentLanguage: response.headers.get("content-language") || void 0,
578
+ contentDisposition: response.headers.get("content-disposition") || void 0,
579
+ contentEncoding: response.headers.get("content-encoding") || void 0,
580
+ cacheControl: response.headers.get("cache-control") || void 0,
581
+ cacheExpiry: response.headers.get("expires") ? new Date(response.headers.get("expires")) : void 0
582
+ },
583
+ customMetadata,
584
+ range: options?.range ? {
585
+ offset: 0,
586
+ length: parseInt(response.headers.get("content-length") || "0", 10)
587
+ } : void 0,
588
+ body: response.body,
589
+ bodyUsed: false,
590
+ arrayBuffer: () => response.arrayBuffer(),
591
+ text: () => response.text(),
592
+ json: () => response.json(),
593
+ blob: () => response.blob(),
594
+ writeHttpMetadata: (headers2) => {
595
+ if (r2Object.httpMetadata.contentType) {
596
+ headers2.set("content-type", r2Object.httpMetadata.contentType);
597
+ }
598
+ if (r2Object.httpMetadata.contentLanguage) {
599
+ headers2.set("content-language", r2Object.httpMetadata.contentLanguage);
600
+ }
601
+ if (r2Object.httpMetadata.contentDisposition) {
602
+ headers2.set("content-disposition", r2Object.httpMetadata.contentDisposition);
603
+ }
604
+ if (r2Object.httpMetadata.contentEncoding) {
605
+ headers2.set("content-encoding", r2Object.httpMetadata.contentEncoding);
606
+ }
607
+ if (r2Object.httpMetadata.cacheControl) {
608
+ headers2.set("cache-control", r2Object.httpMetadata.cacheControl);
609
+ }
610
+ if (r2Object.httpMetadata.cacheExpiry) {
611
+ headers2.set("expires", r2Object.httpMetadata.cacheExpiry.toUTCString());
612
+ }
613
+ }
614
+ };
615
+ return r2Object;
616
+ },
617
+ async put(key, value, options) {
618
+ const headers = {};
619
+ if (options?.httpMetadata) {
620
+ if (options.httpMetadata.contentType) {
621
+ headers["Content-Type"] = options.httpMetadata.contentType;
622
+ }
623
+ if (options.httpMetadata.contentLanguage) {
624
+ headers["Content-Language"] = options.httpMetadata.contentLanguage;
625
+ }
626
+ if (options.httpMetadata.contentDisposition) {
627
+ headers["Content-Disposition"] = options.httpMetadata.contentDisposition;
628
+ }
629
+ if (options.httpMetadata.contentEncoding) {
630
+ headers["Content-Encoding"] = options.httpMetadata.contentEncoding;
631
+ }
632
+ if (options.httpMetadata.cacheControl) {
633
+ headers["Cache-Control"] = options.httpMetadata.cacheControl;
634
+ }
635
+ if (options.httpMetadata.cacheExpiry) {
636
+ headers["Expires"] = options.httpMetadata.cacheExpiry.toUTCString();
637
+ }
638
+ }
639
+ if (options?.customMetadata) {
640
+ for (const [key2, value2] of Object.entries(options.customMetadata)) {
641
+ headers[`x-amz-meta-${key2}`] = value2;
642
+ }
643
+ }
644
+ let body;
645
+ if (ArrayBuffer.isView(value)) {
646
+ body = value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength);
647
+ } else {
648
+ body = value;
649
+ }
650
+ const response = await signedFetch("PUT", key, { headers, body });
651
+ if (!response.ok) {
652
+ throw new Error(`R2 PUT failed: ${response.status}`);
653
+ }
654
+ return {
655
+ key,
656
+ version: response.headers.get("x-amz-version-id") || "",
657
+ size: typeof value === "string" ? value.length : 0,
658
+ etag: response.headers.get("etag") || "",
659
+ httpEtag: response.headers.get("etag") || "",
660
+ checksums: {},
661
+ uploaded: /* @__PURE__ */ new Date(),
662
+ httpMetadata: options?.httpMetadata || {},
663
+ customMetadata: options?.customMetadata || {}
664
+ };
665
+ },
666
+ async delete(keys) {
667
+ const keyArray = Array.isArray(keys) ? keys : [keys];
668
+ if (keyArray.length === 1) {
669
+ const response = await signedFetch("DELETE", keyArray[0]);
670
+ if (!response.ok && response.status !== 404) {
671
+ throw new Error(`R2 DELETE failed: ${response.status}`);
672
+ }
673
+ } else {
674
+ `<?xml version="1.0" encoding="UTF-8"?>
675
+ <Delete>
676
+ ${keyArray.map((key) => `<Object><Key>${key}</Key></Object>`).join("")}
677
+ </Delete>`;
678
+ const response = await signedFetchBucket("POST", {
679
+ queryParams: { delete: "" },
680
+ headers: { "Content-Type": "application/xml" }
681
+ });
682
+ if (!response.ok) {
683
+ throw new Error(`R2 DELETE BULK failed: ${response.status}`);
684
+ }
685
+ }
686
+ },
687
+ async head(key) {
688
+ const response = await signedFetch("HEAD", key);
689
+ if (response.status === 404) {
690
+ return null;
691
+ }
692
+ if (!response.ok) {
693
+ throw new Error(`R2 HEAD failed: ${response.status}`);
694
+ }
695
+ const customMetadata = {};
696
+ response.headers.forEach((value, key2) => {
697
+ if (key2.toLowerCase().startsWith("x-amz-meta-")) {
698
+ const metaKey = key2.slice("x-amz-meta-".length);
699
+ customMetadata[metaKey] = value;
700
+ }
701
+ });
702
+ return {
703
+ key,
704
+ version: response.headers.get("x-amz-version-id") || "",
705
+ size: parseInt(response.headers.get("content-length") || "0", 10),
706
+ etag: response.headers.get("etag") || "",
707
+ httpEtag: response.headers.get("etag") || "",
708
+ checksums: {},
709
+ uploaded: new Date(response.headers.get("last-modified") || Date.now()),
710
+ httpMetadata: {
711
+ contentType: response.headers.get("content-type") || void 0,
712
+ contentLanguage: response.headers.get("content-language") || void 0,
713
+ contentDisposition: response.headers.get("content-disposition") || void 0,
714
+ contentEncoding: response.headers.get("content-encoding") || void 0,
715
+ cacheControl: response.headers.get("cache-control") || void 0,
716
+ cacheExpiry: response.headers.get("expires") ? new Date(response.headers.get("expires")) : void 0
717
+ },
718
+ customMetadata
719
+ };
720
+ },
721
+ async list(options) {
722
+ const queryParams = {
723
+ "list-type": "2"
724
+ };
725
+ if (options?.limit) {
726
+ queryParams["max-keys"] = options.limit.toString();
727
+ }
728
+ if (options?.prefix) {
729
+ queryParams["prefix"] = options.prefix;
730
+ }
731
+ if (options?.cursor) {
732
+ queryParams["continuation-token"] = options.cursor;
733
+ }
734
+ if (options?.delimiter) {
735
+ queryParams["delimiter"] = options.delimiter;
736
+ }
737
+ if (options?.startAfter) {
738
+ queryParams["start-after"] = options.startAfter;
739
+ }
740
+ const response = await signedFetchBucket("GET", { queryParams });
741
+ if (!response.ok) {
742
+ throw new Error(`R2 LIST failed: ${response.status}`);
743
+ }
744
+ const xml = await response.text();
745
+ const objects = [];
746
+ const delimitedPrefixes = [];
747
+ const contentsRegex = /<Contents>(.*?)<\/Contents>/gs;
748
+ let match;
749
+ while ((match = contentsRegex.exec(xml)) !== null) {
750
+ const content = match[1];
751
+ const key = content.match(/<Key>(.*?)<\/Key>/)?.[1] || "";
752
+ const size = parseInt(content.match(/<Size>(.*?)<\/Size>/)?.[1] || "0", 10);
753
+ const etag = content.match(/<ETag>(.*?)<\/ETag>/)?.[1] || "";
754
+ const lastModified = content.match(/<LastModified>(.*?)<\/LastModified>/)?.[1] || "";
755
+ objects.push({
756
+ key,
757
+ version: "",
758
+ size,
759
+ etag,
760
+ httpEtag: etag,
761
+ checksums: {},
762
+ uploaded: new Date(lastModified),
763
+ httpMetadata: {},
764
+ customMetadata: {}
765
+ });
766
+ }
767
+ const prefixRegex = /<CommonPrefixes>.*?<Prefix>(.*?)<\/Prefix>.*?<\/CommonPrefixes>/gs;
768
+ while ((match = prefixRegex.exec(xml)) !== null) {
769
+ delimitedPrefixes.push(match[1]);
770
+ }
771
+ const isTruncated = xml.includes("<IsTruncated>true</IsTruncated>");
772
+ const nextContinuationToken = xml.match(/<NextContinuationToken>(.*?)<\/NextContinuationToken>/)?.[1];
773
+ return {
774
+ objects,
775
+ truncated: isTruncated,
776
+ cursor: nextContinuationToken,
777
+ delimitedPrefixes
778
+ };
779
+ },
780
+ createMultipartUpload(key, options) {
781
+ throw new Error("R2 multipart upload not yet implemented");
782
+ },
783
+ resumeMultipartUpload(key, uploadId) {
784
+ throw new Error("R2 multipart upload not yet implemented");
785
+ }
786
+ };
787
+ return r2Bucket;
788
+ }
789
+
1
790
  function createR2Binding(config, bucketName) {
2
- throw new Error("Not implemented");
791
+ return createR2Binding$1(config, bucketName);
3
792
  }
4
793
  function createKVBinding(config, namespaceId) {
5
- throw new Error("Not implemented");
794
+ const client = new CloudflareAPIClient(config.accountId, config.apiToken);
795
+ return createKVBinding$1(client, namespaceId);
6
796
  }
7
797
  function createD1Binding(config, databaseId) {
8
- throw new Error("Not implemented");
798
+ const client = new CloudflareAPIClient(config.accountId, config.apiToken);
799
+ return createD1Binding$1(client, databaseId);
9
800
  }
10
801
 
11
802
  export { createD1Binding, createKVBinding, createR2Binding };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openwrangler",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -17,12 +17,12 @@
17
17
  "@cloudflare/workers-types": "^4.20251231.0",
18
18
  "nuxt": "^3.15.0",
19
19
  "typescript": "^5.7.2",
20
+ "wrangler": "^4.54.0",
20
21
  "unbuild": "^3.3.1",
21
- "wrangler": "^3.99.0",
22
- "@binochoi/nitro-cloudflare-dev": "npm:@bino0216/nitro-cloudflare-dev@0.2.4"
22
+ "@bino0216/nitro-cloudflare-dev": "0.2.6"
23
23
  },
24
24
  "scripts": {
25
- "dev": "nuxi dev playground",
25
+ "dev": "cd ./playground && pnpm dev",
26
26
  "build": "unbuild"
27
27
  }
28
28
  }