@hot-updater/cloudflare 1.2.0-rc.8 → 1.2.0-rc.9

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.cjs CHANGED
@@ -24,9 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  let _hot_updater_plugin_core = require("@hot-updater/plugin-core");
25
25
  let cloudflare = require("cloudflare");
26
26
  cloudflare = __toESM(cloudflare, 1);
27
- let _aws_sdk_client_s3 = require("@aws-sdk/client-s3");
28
- let _aws_sdk_lib_storage = require("@aws-sdk/lib-storage");
29
- let _aws_sdk_s3_request_presigner = require("@aws-sdk/s3-request-presigner");
27
+ let _hot_updater_storage_s3 = require("@hot-updater/storage-s3");
30
28
  //#region src/d1Executor.ts
31
29
  var D1ExecutionError = class extends Error {
32
30
  name = "D1ExecutionError";
@@ -156,113 +154,18 @@ const d1Database = (config) => ({
156
154
  })
157
155
  });
158
156
  //#endregion
159
- //#region src/lazyCredentials.ts
160
- /**
161
- * `credentials` as the AWS SDK takes them: access keys with a lazy field
162
- * become a provider, which the SDK calls once, on its first request.
163
- */
164
- const toSdkCredentials = (credentials) => {
165
- if (credentials === void 0 || typeof credentials === "function") return credentials;
166
- const { accessKeyId, secretAccessKey, sessionToken, ...rest } = credentials;
167
- if (typeof accessKeyId !== "function" && typeof secretAccessKey !== "function" && typeof sessionToken !== "function") return credentials;
168
- return async () => ({
169
- ...rest,
170
- accessKeyId: await (0, _hot_updater_plugin_core.resolveLazy)(accessKeyId),
171
- secretAccessKey: await (0, _hot_updater_plugin_core.resolveLazy)(secretAccessKey),
172
- ...sessionToken === void 0 ? {} : { sessionToken: await (0, _hot_updater_plugin_core.resolveLazy)(sessionToken) }
173
- });
174
- };
175
- //#endregion
176
157
  //#region src/r2S3Storage.ts
177
- /** Devices download right after the update check that returns the URL. */
178
- const PRESIGNED_URL_EXPIRES_IN_SECONDS = 3600;
179
- const isObjectNotFoundError = (error) => {
180
- if (error instanceof Error && (error.name === "NotFound" || error.name === "NoSuchKey")) return true;
181
- return typeof error === "object" && error !== null && "$metadata" in error && error.$metadata?.httpStatusCode === 404;
182
- };
183
158
  const createR2S3Storage = (config) => {
184
- const { accountId, basePath, bucketName, endpoint, forcePathStyle, region, credentials, ...s3Config } = config;
185
- const client = new _aws_sdk_client_s3.S3Client({
159
+ const { accountId, endpoint, forcePathStyle, region, ...s3Config } = config;
160
+ return (0, _hot_updater_storage_s3.createS3Storage)({
161
+ name: "r2Storage",
162
+ protocol: "r2"
163
+ }, {
186
164
  ...s3Config,
187
- credentials: toSdkCredentials(credentials),
188
165
  endpoint: endpoint ?? `https://${accountId}.r2.cloudflarestorage.com`,
189
166
  forcePathStyle: forcePathStyle ?? true,
190
167
  region: region ?? "auto"
191
168
  });
192
- const getStorageKey = (0, _hot_updater_plugin_core.createStorageKeyBuilder)(basePath);
193
- const parseAndValidate = (storageUri) => {
194
- const parsed = (0, _hot_updater_plugin_core.parseStorageUri)(storageUri, "r2");
195
- if (parsed.bucket !== bucketName) throw new Error(`Bucket name mismatch: expected "${bucketName}", but found "${parsed.bucket}".`);
196
- return parsed;
197
- };
198
- return (0, _hot_updater_plugin_core.createStorageAdapter)({
199
- name: "r2Storage",
200
- protocol: "r2",
201
- async put({ key, body, contentLength, contentType }) {
202
- const storageKey = getStorageKey(key);
203
- await new _aws_sdk_lib_storage.Upload({
204
- client,
205
- params: {
206
- Body: body,
207
- Bucket: bucketName,
208
- CacheControl: "max-age=31536000",
209
- ContentLength: contentLength,
210
- ContentType: contentType,
211
- Key: storageKey
212
- }
213
- }).done();
214
- return { storageUri: (0, _hot_updater_plugin_core.createStorageUri)({
215
- protocol: "r2",
216
- bucket: bucketName,
217
- key: storageKey
218
- }) };
219
- },
220
- async get({ storageUri }) {
221
- const { key } = parseAndValidate(storageUri);
222
- try {
223
- const response = await client.send(new _aws_sdk_client_s3.GetObjectCommand({
224
- Bucket: bucketName,
225
- Key: key
226
- }));
227
- if (!response.Body) return { response: null };
228
- const headers = new Headers();
229
- if (response.ContentType) headers.set("content-type", response.ContentType);
230
- if (response.ContentLength !== void 0) headers.set("content-length", String(response.ContentLength));
231
- return { response: new Response(response.Body.transformToWebStream(), { headers }) };
232
- } catch (error) {
233
- if (isObjectNotFoundError(error)) return { response: null };
234
- throw error;
235
- }
236
- },
237
- async getDownloadUrl({ storageUri }) {
238
- const { key } = parseAndValidate(storageUri);
239
- return { url: await (0, _aws_sdk_s3_request_presigner.getSignedUrl)(client, new _aws_sdk_client_s3.GetObjectCommand({
240
- Bucket: bucketName,
241
- Key: key
242
- }), { expiresIn: PRESIGNED_URL_EXPIRES_IN_SECONDS }) };
243
- },
244
- async exists({ storageUri }) {
245
- const { key } = parseAndValidate(storageUri);
246
- try {
247
- await client.send(new _aws_sdk_client_s3.HeadObjectCommand({
248
- Bucket: bucketName,
249
- Key: key
250
- }));
251
- return { exists: true };
252
- } catch (error) {
253
- if (isObjectNotFoundError(error)) return { exists: false };
254
- throw error;
255
- }
256
- },
257
- async delete({ storageUri }) {
258
- const { key } = parseAndValidate(storageUri);
259
- await client.send(new _aws_sdk_client_s3.DeleteObjectCommand({
260
- Bucket: bucketName,
261
- Key: key
262
- }));
263
- return { deleted: true };
264
- }
265
- });
266
169
  };
267
170
  //#endregion
268
171
  //#region src/r2Storage.ts
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Lazy, StorageAdapterWith, ToolingDatabase } from "@hot-updater/plugin-core";
2
- import { S3ClientConfig } from "@aws-sdk/client-s3";
2
+ import { S3ClientConfig, S3CredentialsOption, S3StorageOperations } from "@hot-updater/storage-s3";
3
3
  //#region src/d1Rest.d.ts
4
4
  interface D1RestConfig {
5
5
  readonly databaseId: string;
@@ -30,32 +30,18 @@ type D1DatabaseConfig = D1RestConfig;
30
30
  */
31
31
  declare const d1Database: (config: D1DatabaseConfig) => ToolingDatabase;
32
32
  //#endregion
33
- //#region src/lazyCredentials.d.ts
34
- type SdkCredentials = S3ClientConfig["credentials"];
35
- /**
36
- * Access keys whose fields may each be a function that returns them, such
37
- * as `secret("<name>")` in `hot-updater.config.ts`, called on first use.
38
- */
39
- interface LazyAwsCredentials {
40
- readonly accessKeyId: Lazy<string>;
41
- readonly secretAccessKey: Lazy<string>;
42
- readonly sessionToken?: Lazy<string>;
43
- }
44
- /** The credentials the AWS SDK takes, or access keys with lazy fields. */
45
- type AwsCredentialsOption = SdkCredentials | LazyAwsCredentials;
46
- //#endregion
47
33
  //#region src/r2S3Storage.d.ts
48
34
  interface R2S3StorageConfig extends Omit<S3ClientConfig, "credentials"> {
49
35
  accountId: string;
50
36
  bucketName: string;
51
37
  /** The SDK's credentials, or an R2 access key whose fields are lazy. */
52
- credentials: NonNullable<AwsCredentialsOption>;
38
+ credentials: NonNullable<S3CredentialsOption>;
53
39
  /** Base path where bundles will be stored in the bucket. */
54
40
  basePath?: string;
55
41
  }
56
42
  //#endregion
57
43
  //#region src/r2Storage.d.ts
58
44
  type R2StorageConfig = R2S3StorageConfig;
59
- declare const r2Storage: (config: R2S3StorageConfig) => StorageAdapterWith<"put" | "get" | "getDownloadUrl" | "exists" | "delete">;
45
+ declare const r2Storage: (config: R2S3StorageConfig) => StorageAdapterWith<S3StorageOperations>;
60
46
  //#endregion
61
47
  export { D1DatabaseConfig, D1ExecutionError, type R2S3StorageConfig, R2StorageConfig, d1Database, r2Storage };
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Lazy, StorageAdapterWith, ToolingDatabase } from "@hot-updater/plugin-core";
2
- import { S3ClientConfig } from "@aws-sdk/client-s3";
2
+ import { S3ClientConfig, S3CredentialsOption, S3StorageOperations } from "@hot-updater/storage-s3";
3
3
  //#region src/d1Rest.d.ts
4
4
  interface D1RestConfig {
5
5
  readonly databaseId: string;
@@ -30,32 +30,18 @@ type D1DatabaseConfig = D1RestConfig;
30
30
  */
31
31
  declare const d1Database: (config: D1DatabaseConfig) => ToolingDatabase;
32
32
  //#endregion
33
- //#region src/lazyCredentials.d.ts
34
- type SdkCredentials = S3ClientConfig["credentials"];
35
- /**
36
- * Access keys whose fields may each be a function that returns them, such
37
- * as `secret("<name>")` in `hot-updater.config.ts`, called on first use.
38
- */
39
- interface LazyAwsCredentials {
40
- readonly accessKeyId: Lazy<string>;
41
- readonly secretAccessKey: Lazy<string>;
42
- readonly sessionToken?: Lazy<string>;
43
- }
44
- /** The credentials the AWS SDK takes, or access keys with lazy fields. */
45
- type AwsCredentialsOption = SdkCredentials | LazyAwsCredentials;
46
- //#endregion
47
33
  //#region src/r2S3Storage.d.ts
48
34
  interface R2S3StorageConfig extends Omit<S3ClientConfig, "credentials"> {
49
35
  accountId: string;
50
36
  bucketName: string;
51
37
  /** The SDK's credentials, or an R2 access key whose fields are lazy. */
52
- credentials: NonNullable<AwsCredentialsOption>;
38
+ credentials: NonNullable<S3CredentialsOption>;
53
39
  /** Base path where bundles will be stored in the bucket. */
54
40
  basePath?: string;
55
41
  }
56
42
  //#endregion
57
43
  //#region src/r2Storage.d.ts
58
44
  type R2StorageConfig = R2S3StorageConfig;
59
- declare const r2Storage: (config: R2S3StorageConfig) => StorageAdapterWith<"put" | "get" | "getDownloadUrl" | "exists" | "delete">;
45
+ declare const r2Storage: (config: R2S3StorageConfig) => StorageAdapterWith<S3StorageOperations>;
60
46
  //#endregion
61
47
  export { D1DatabaseConfig, D1ExecutionError, type R2S3StorageConfig, R2StorageConfig, d1Database, r2Storage };
package/dist/index.mjs CHANGED
@@ -1,8 +1,6 @@
1
- import { WRITE_GUARD_TABLE, coreSettings, coreTarget, createEngineDatabase, createSqlAdapter, createStorageAdapter, createStorageKeyBuilder, createStorageUri, createTableStatements, generateEngineSql, parseStorageUri, resolveLazy } from "@hot-updater/plugin-core";
1
+ import { WRITE_GUARD_TABLE, coreSettings, coreTarget, createEngineDatabase, createSqlAdapter, createTableStatements, generateEngineSql } from "@hot-updater/plugin-core";
2
2
  import Cloudflare from "cloudflare";
3
- import { DeleteObjectCommand, GetObjectCommand, HeadObjectCommand, S3Client } from "@aws-sdk/client-s3";
4
- import { Upload } from "@aws-sdk/lib-storage";
5
- import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
3
+ import { createS3Storage } from "@hot-updater/storage-s3";
6
4
  //#region src/d1Executor.ts
7
5
  var D1ExecutionError = class extends Error {
8
6
  name = "D1ExecutionError";
@@ -132,113 +130,18 @@ const d1Database = (config) => ({
132
130
  })
133
131
  });
134
132
  //#endregion
135
- //#region src/lazyCredentials.ts
136
- /**
137
- * `credentials` as the AWS SDK takes them: access keys with a lazy field
138
- * become a provider, which the SDK calls once, on its first request.
139
- */
140
- const toSdkCredentials = (credentials) => {
141
- if (credentials === void 0 || typeof credentials === "function") return credentials;
142
- const { accessKeyId, secretAccessKey, sessionToken, ...rest } = credentials;
143
- if (typeof accessKeyId !== "function" && typeof secretAccessKey !== "function" && typeof sessionToken !== "function") return credentials;
144
- return async () => ({
145
- ...rest,
146
- accessKeyId: await resolveLazy(accessKeyId),
147
- secretAccessKey: await resolveLazy(secretAccessKey),
148
- ...sessionToken === void 0 ? {} : { sessionToken: await resolveLazy(sessionToken) }
149
- });
150
- };
151
- //#endregion
152
133
  //#region src/r2S3Storage.ts
153
- /** Devices download right after the update check that returns the URL. */
154
- const PRESIGNED_URL_EXPIRES_IN_SECONDS = 3600;
155
- const isObjectNotFoundError = (error) => {
156
- if (error instanceof Error && (error.name === "NotFound" || error.name === "NoSuchKey")) return true;
157
- return typeof error === "object" && error !== null && "$metadata" in error && error.$metadata?.httpStatusCode === 404;
158
- };
159
134
  const createR2S3Storage = (config) => {
160
- const { accountId, basePath, bucketName, endpoint, forcePathStyle, region, credentials, ...s3Config } = config;
161
- const client = new S3Client({
135
+ const { accountId, endpoint, forcePathStyle, region, ...s3Config } = config;
136
+ return createS3Storage({
137
+ name: "r2Storage",
138
+ protocol: "r2"
139
+ }, {
162
140
  ...s3Config,
163
- credentials: toSdkCredentials(credentials),
164
141
  endpoint: endpoint ?? `https://${accountId}.r2.cloudflarestorage.com`,
165
142
  forcePathStyle: forcePathStyle ?? true,
166
143
  region: region ?? "auto"
167
144
  });
168
- const getStorageKey = createStorageKeyBuilder(basePath);
169
- const parseAndValidate = (storageUri) => {
170
- const parsed = parseStorageUri(storageUri, "r2");
171
- if (parsed.bucket !== bucketName) throw new Error(`Bucket name mismatch: expected "${bucketName}", but found "${parsed.bucket}".`);
172
- return parsed;
173
- };
174
- return createStorageAdapter({
175
- name: "r2Storage",
176
- protocol: "r2",
177
- async put({ key, body, contentLength, contentType }) {
178
- const storageKey = getStorageKey(key);
179
- await new Upload({
180
- client,
181
- params: {
182
- Body: body,
183
- Bucket: bucketName,
184
- CacheControl: "max-age=31536000",
185
- ContentLength: contentLength,
186
- ContentType: contentType,
187
- Key: storageKey
188
- }
189
- }).done();
190
- return { storageUri: createStorageUri({
191
- protocol: "r2",
192
- bucket: bucketName,
193
- key: storageKey
194
- }) };
195
- },
196
- async get({ storageUri }) {
197
- const { key } = parseAndValidate(storageUri);
198
- try {
199
- const response = await client.send(new GetObjectCommand({
200
- Bucket: bucketName,
201
- Key: key
202
- }));
203
- if (!response.Body) return { response: null };
204
- const headers = new Headers();
205
- if (response.ContentType) headers.set("content-type", response.ContentType);
206
- if (response.ContentLength !== void 0) headers.set("content-length", String(response.ContentLength));
207
- return { response: new Response(response.Body.transformToWebStream(), { headers }) };
208
- } catch (error) {
209
- if (isObjectNotFoundError(error)) return { response: null };
210
- throw error;
211
- }
212
- },
213
- async getDownloadUrl({ storageUri }) {
214
- const { key } = parseAndValidate(storageUri);
215
- return { url: await getSignedUrl(client, new GetObjectCommand({
216
- Bucket: bucketName,
217
- Key: key
218
- }), { expiresIn: PRESIGNED_URL_EXPIRES_IN_SECONDS }) };
219
- },
220
- async exists({ storageUri }) {
221
- const { key } = parseAndValidate(storageUri);
222
- try {
223
- await client.send(new HeadObjectCommand({
224
- Bucket: bucketName,
225
- Key: key
226
- }));
227
- return { exists: true };
228
- } catch (error) {
229
- if (isObjectNotFoundError(error)) return { exists: false };
230
- throw error;
231
- }
232
- },
233
- async delete({ storageUri }) {
234
- const { key } = parseAndValidate(storageUri);
235
- await client.send(new DeleteObjectCommand({
236
- Bucket: bucketName,
237
- Key: key
238
- }));
239
- return { deleted: true };
240
- }
241
- });
242
145
  };
243
146
  //#endregion
244
147
  //#region src/r2Storage.ts
@@ -121,8 +121,11 @@ const presignR2GetUrl = async ({ accountId, bucketName, credentials, expiresInSe
121
121
  //#region src/r2WorkerStorage.ts
122
122
  /** Devices download right after the update check that returns the URL. */
123
123
  const PRESIGNED_URL_EXPIRES_IN_SECONDS = 3600;
124
+ /** Keys the binding deletes in one call. */
125
+ const DELETE_BATCH_SIZE = 1e3;
124
126
  function r2WorkerStorage(config) {
125
127
  const getStorageKey = (0, _hot_updater_plugin_core.createStorageKeyBuilder)(config.basePath);
128
+ const basePrefix = getStorageKey() ? `${getStorageKey()}/` : "";
126
129
  const parseAndValidate = (storageUri) => {
127
130
  const parsed = (0, _hot_updater_plugin_core.parseStorageUri)(storageUri, "r2");
128
131
  if (parsed.bucket !== config.bucketName) throw new Error(`Bucket name mismatch: expected "${config.bucketName}", but found "${parsed.bucket}".`);
@@ -178,6 +181,42 @@ function r2WorkerStorage(config) {
178
181
  const { key } = parseAndValidate(storageUri);
179
182
  await config.bucket.delete(key);
180
183
  return { deleted: true };
184
+ },
185
+ async listObjects(prefix) {
186
+ const folder = getStorageKey(prefix ?? "");
187
+ const objects = [];
188
+ let cursor;
189
+ do {
190
+ const listed = await config.bucket.list({
191
+ prefix: folder ? `${folder}/` : "",
192
+ ...cursor === void 0 ? {} : { cursor }
193
+ });
194
+ for (const object of listed.objects) {
195
+ if (!object.key.startsWith(basePrefix)) continue;
196
+ let storageUri;
197
+ try {
198
+ storageUri = (0, _hot_updater_plugin_core.createStorageUri)({
199
+ protocol: "r2",
200
+ bucket: config.bucketName,
201
+ key: object.key
202
+ });
203
+ } catch {
204
+ continue;
205
+ }
206
+ objects.push({
207
+ key: object.key.slice(basePrefix.length),
208
+ lastModifiedAt: object.uploaded,
209
+ size: object.size,
210
+ storageUri
211
+ });
212
+ }
213
+ cursor = listed.truncated ? listed.cursor : void 0;
214
+ } while (cursor !== void 0);
215
+ return objects;
216
+ },
217
+ async deleteObjects(relativeKeys) {
218
+ const keys = relativeKeys.map((key) => getStorageKey(key));
219
+ for (let offset = 0; offset < keys.length; offset += DELETE_BATCH_SIZE) await config.bucket.delete(keys.slice(offset, offset + DELETE_BATCH_SIZE));
181
220
  }
182
221
  });
183
222
  }
@@ -48,6 +48,7 @@ type CloudflareWorkerStorageConfig = {
48
48
  readonly accountId?: undefined;
49
49
  readonly credentials?: undefined;
50
50
  });
51
+ type R2WorkerOperations = "put" | "get" | "exists" | "delete" | "listObjects" | "deleteObjects";
51
52
  /**
52
53
  * Storage on the Worker's R2 binding. With R2's S3-compatible credentials,
53
54
  * its download URLs are presigned and devices download from the bucket;
@@ -56,7 +57,7 @@ type CloudflareWorkerStorageConfig = {
56
57
  */
57
58
  declare function r2WorkerStorage(config: CloudflareWorkerStorageConfig & {
58
59
  readonly credentials: R2Credentials;
59
- }): StorageAdapterWith<"put" | "get" | "getDownloadUrl" | "exists" | "delete">;
60
- declare function r2WorkerStorage(config: CloudflareWorkerStorageConfig): StorageAdapterWith<"put" | "get" | "exists" | "delete">;
60
+ }): StorageAdapterWith<R2WorkerOperations | "getDownloadUrl">;
61
+ declare function r2WorkerStorage(config: CloudflareWorkerStorageConfig): StorageAdapterWith<R2WorkerOperations>;
61
62
  //#endregion
62
63
  export { type CloudflareWorkerStorageConfig, type D1Like, d1Database, r2WorkerStorage as r2Storage };
@@ -48,6 +48,7 @@ type CloudflareWorkerStorageConfig = {
48
48
  readonly accountId?: undefined;
49
49
  readonly credentials?: undefined;
50
50
  });
51
+ type R2WorkerOperations = "put" | "get" | "exists" | "delete" | "listObjects" | "deleteObjects";
51
52
  /**
52
53
  * Storage on the Worker's R2 binding. With R2's S3-compatible credentials,
53
54
  * its download URLs are presigned and devices download from the bucket;
@@ -56,7 +57,7 @@ type CloudflareWorkerStorageConfig = {
56
57
  */
57
58
  declare function r2WorkerStorage(config: CloudflareWorkerStorageConfig & {
58
59
  readonly credentials: R2Credentials;
59
- }): StorageAdapterWith<"put" | "get" | "getDownloadUrl" | "exists" | "delete">;
60
- declare function r2WorkerStorage(config: CloudflareWorkerStorageConfig): StorageAdapterWith<"put" | "get" | "exists" | "delete">;
60
+ }): StorageAdapterWith<R2WorkerOperations | "getDownloadUrl">;
61
+ declare function r2WorkerStorage(config: CloudflareWorkerStorageConfig): StorageAdapterWith<R2WorkerOperations>;
61
62
  //#endregion
62
63
  export { type CloudflareWorkerStorageConfig, type D1Like, d1Database, r2WorkerStorage as r2Storage };
@@ -120,8 +120,11 @@ const presignR2GetUrl = async ({ accountId, bucketName, credentials, expiresInSe
120
120
  //#region src/r2WorkerStorage.ts
121
121
  /** Devices download right after the update check that returns the URL. */
122
122
  const PRESIGNED_URL_EXPIRES_IN_SECONDS = 3600;
123
+ /** Keys the binding deletes in one call. */
124
+ const DELETE_BATCH_SIZE = 1e3;
123
125
  function r2WorkerStorage(config) {
124
126
  const getStorageKey = createStorageKeyBuilder(config.basePath);
127
+ const basePrefix = getStorageKey() ? `${getStorageKey()}/` : "";
125
128
  const parseAndValidate = (storageUri) => {
126
129
  const parsed = parseStorageUri(storageUri, "r2");
127
130
  if (parsed.bucket !== config.bucketName) throw new Error(`Bucket name mismatch: expected "${config.bucketName}", but found "${parsed.bucket}".`);
@@ -177,6 +180,42 @@ function r2WorkerStorage(config) {
177
180
  const { key } = parseAndValidate(storageUri);
178
181
  await config.bucket.delete(key);
179
182
  return { deleted: true };
183
+ },
184
+ async listObjects(prefix) {
185
+ const folder = getStorageKey(prefix ?? "");
186
+ const objects = [];
187
+ let cursor;
188
+ do {
189
+ const listed = await config.bucket.list({
190
+ prefix: folder ? `${folder}/` : "",
191
+ ...cursor === void 0 ? {} : { cursor }
192
+ });
193
+ for (const object of listed.objects) {
194
+ if (!object.key.startsWith(basePrefix)) continue;
195
+ let storageUri;
196
+ try {
197
+ storageUri = createStorageUri({
198
+ protocol: "r2",
199
+ bucket: config.bucketName,
200
+ key: object.key
201
+ });
202
+ } catch {
203
+ continue;
204
+ }
205
+ objects.push({
206
+ key: object.key.slice(basePrefix.length),
207
+ lastModifiedAt: object.uploaded,
208
+ size: object.size,
209
+ storageUri
210
+ });
211
+ }
212
+ cursor = listed.truncated ? listed.cursor : void 0;
213
+ } while (cursor !== void 0);
214
+ return objects;
215
+ },
216
+ async deleteObjects(relativeKeys) {
217
+ const keys = relativeKeys.map((key) => getStorageKey(key));
218
+ for (let offset = 0; offset < keys.length; offset += DELETE_BATCH_SIZE) await config.bucket.delete(keys.slice(offset, offset + DELETE_BATCH_SIZE));
180
219
  }
181
220
  });
182
221
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hot-updater/cloudflare",
3
3
  "type": "module",
4
- "version": "1.2.0-rc.8",
4
+ "version": "1.2.0-rc.9",
5
5
  "description": "React Native OTA solution for self-hosted",
6
6
  "main": "dist/index.cjs",
7
7
  "module": "dist/index.mjs",
@@ -49,18 +49,19 @@
49
49
  "package.json"
50
50
  ],
51
51
  "dependencies": {
52
- "@aws-sdk/client-s3": "3.1066.0",
53
- "@aws-sdk/lib-storage": "3.1008.0",
54
- "@aws-sdk/s3-request-presigner": "3.1066.0",
55
52
  "cloudflare": "4.2.0",
56
53
  "hono": "4.12.34",
57
54
  "uuidv7": "^1.0.2",
58
- "@hot-updater/cli-tools": "1.2.0-rc.8",
59
- "@hot-updater/protocol": "1.2.0-rc.8",
60
- "@hot-updater/server": "1.2.0-rc.8",
61
- "@hot-updater/plugin-core": "1.2.0-rc.8"
55
+ "@hot-updater/cli-tools": "1.2.0-rc.9",
56
+ "@hot-updater/protocol": "1.2.0-rc.9",
57
+ "@hot-updater/plugin-core": "1.2.0-rc.9",
58
+ "@hot-updater/server": "1.2.0-rc.9",
59
+ "@hot-updater/storage-s3": "1.2.0-rc.9"
62
60
  },
63
61
  "devDependencies": {
62
+ "@aws-sdk/client-s3": "3.1066.0",
63
+ "@aws-sdk/lib-storage": "3.1066.0",
64
+ "@aws-sdk/s3-request-presigner": "3.1066.0",
64
65
  "@cloudflare/vitest-pool-workers": "0.13.0",
65
66
  "@cloudflare/workers-types": "^4.20260312.1",
66
67
  "@types/node": "^20",
@@ -74,7 +75,7 @@
74
75
  "vitest": "4.1.4",
75
76
  "wrangler": "^4.69.0",
76
77
  "xdg-app-paths": "^8.3.0",
77
- "@hot-updater/test-utils": "1.2.0-rc.8"
78
+ "@hot-updater/test-utils": "1.2.0-rc.9"
78
79
  },
79
80
  "scripts": {
80
81
  "build": "tsdown && pnpm build:worker",