@hot-updater/cloudflare 0.31.4 → 0.33.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,9 +1,13 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { DEFAULT_ROLLOUT_COHORT_COUNT, getAssetBaseStorageUri, getBundlePatches, getManifestFileHash, getManifestStorageUri, stripBundleArtifactMetadata } from "@hot-updater/core";
3
- import { calculatePagination, createDatabasePlugin, createDatabasePluginGetUpdateInfo, createNodeStoragePlugin, createStorageKeyBuilder, getContentType, parseStorageUri } from "@hot-updater/plugin-core";
3
+ import { calculatePagination, createDatabasePlugin, createDatabasePluginGetUpdateInfo, createStorageKeyBuilder, createUniversalStoragePlugin, getContentType, parseStorageUri } from "@hot-updater/plugin-core";
4
4
  import Cloudflare from "cloudflare";
5
5
  import fs from "node:fs/promises";
6
6
  import path from "node:path";
7
+ import { DeleteObjectCommand, GetObjectCommand, HeadObjectCommand, S3Client } from "@aws-sdk/client-s3";
8
+ import { Upload } from "@aws-sdk/lib-storage";
9
+ import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
10
+ import os, { constants } from "node:os";
7
11
  import { fileURLToPath } from "node:url";
8
12
  import { ChildProcess, execFile, spawn, spawnSync } from "node:child_process";
9
13
  import { StringDecoder } from "node:string_decoder";
@@ -11,7 +15,6 @@ import { aborted, callbackify, debuglog, inspect, promisify, stripVTControlChara
11
15
  import process$1, { execArgv, execPath, hrtime, platform } from "node:process";
12
16
  import tty from "node:tty";
13
17
  import { scheduler, setImmediate, setTimeout } from "node:timers/promises";
14
- import { constants } from "node:os";
15
18
  import { EventEmitter, addAbortListener, on, once, setMaxListeners } from "node:events";
16
19
  import { serialize } from "node:v8";
17
20
  import { appendFileSync, createReadStream, createWriteStream, readFileSync, statSync, writeFileSync } from "node:fs";
@@ -25,7 +28,7 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
25
28
  var __getOwnPropNames = Object.getOwnPropertyNames;
26
29
  var __getProtoOf = Object.getPrototypeOf;
27
30
  var __hasOwnProp = Object.prototype.hasOwnProperty;
28
- var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
31
+ var __commonJSMin = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
29
32
  var __copyProps = (to, from, except, desc) => {
30
33
  if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
31
34
  key = keys[i];
@@ -273,6 +276,11 @@ var import_lib = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((export
273
276
  parser.parsingErrorCode = error.parsingErrorCode;
274
277
  module.exports = parser;
275
278
  })))(), 1);
279
+ const buildJsonEachInClause = (columnName, values, params) => {
280
+ if (values.length === 0) return "1 = 0";
281
+ params.push(JSON.stringify(values));
282
+ return `${columnName} IN (SELECT value FROM json_each(?))`;
283
+ };
276
284
  async function resolvePage(singlePage) {
277
285
  const results = [];
278
286
  for await (const page of singlePage.iterPages()) {
@@ -296,11 +304,7 @@ function buildWhereClause(conditions) {
296
304
  clauses.push("enabled = ?");
297
305
  params.push(conditions.enabled ? 1 : 0);
298
306
  }
299
- if (conditions.id?.in) if (conditions.id.in.length === 0) clauses.push("1 = 0");
300
- else {
301
- clauses.push(`id IN (${conditions.id.in.map(() => "?").join(", ")})`);
302
- params.push(...conditions.id.in);
303
- }
307
+ if (conditions.id?.in) clauses.push(buildJsonEachInClause("id", conditions.id.in, params));
304
308
  if (conditions.id?.eq) {
305
309
  clauses.push("id = ?");
306
310
  params.push(conditions.id.eq);
@@ -327,11 +331,7 @@ function buildWhereClause(conditions) {
327
331
  clauses.push("target_app_version = ?");
328
332
  params.push(conditions.targetAppVersion);
329
333
  }
330
- if (conditions.targetAppVersionIn) if (conditions.targetAppVersionIn.length === 0) clauses.push("1 = 0");
331
- else {
332
- clauses.push(`target_app_version IN (${conditions.targetAppVersionIn.map(() => "?").join(", ")})`);
333
- params.push(...conditions.targetAppVersionIn);
334
- }
334
+ if (conditions.targetAppVersionIn) clauses.push(buildJsonEachInClause("target_app_version", conditions.targetAppVersionIn, params));
335
335
  if (conditions.fingerprintHash !== void 0) if (conditions.fingerprintHash === null) clauses.push("fingerprint_hash IS NULL");
336
336
  else {
337
337
  clauses.push("fingerprint_hash = ?");
@@ -416,13 +416,13 @@ const d1Database = createDatabasePlugin({
416
416
  const sql = (0, import_lib.default)(`
417
417
  SELECT *
418
418
  FROM bundle_patches
419
- WHERE bundle_id IN (${bundleIds.map(() => "?").join(", ")})
419
+ WHERE bundle_id IN (SELECT value FROM json_each(?))
420
420
  ORDER BY order_index ASC, base_bundle_id ASC
421
421
  `);
422
422
  const rows = await resolvePage(await cf.d1.database.query(config.databaseId, {
423
423
  account_id: config.accountId,
424
424
  sql,
425
- params: bundleIds
425
+ params: [JSON.stringify(bundleIds)]
426
426
  }));
427
427
  for (const row of rows) {
428
428
  const current = patchMap.get(row.bundle_id) ?? [];
@@ -665,6 +665,112 @@ const d1Database = createDatabasePlugin({
665
665
  }
666
666
  });
667
667
  //#endregion
668
+ //#region src/r2S3Storage.ts
669
+ const ensureExpectedR2Bucket$1 = (bucket, bucketName) => {
670
+ if (bucket !== bucketName) throw new Error(`Bucket name mismatch: expected "${bucketName}", but found "${bucket}".`);
671
+ };
672
+ const isS3ObjectNotFoundError = (error) => {
673
+ if (error instanceof Error) return error.name === "NotFound" || error.name === "NoSuchKey";
674
+ if (typeof error === "object" && error !== null && "$metadata" in error) return error.$metadata?.httpStatusCode === 404;
675
+ return false;
676
+ };
677
+ const createS3Client = (config) => {
678
+ const { accountId, basePath: _basePath, bucketName: _bucketName, endpoint, forcePathStyle, region, ...s3Config } = config;
679
+ return new S3Client({
680
+ ...s3Config,
681
+ endpoint: endpoint ?? `https://${accountId}.r2.cloudflarestorage.com`,
682
+ forcePathStyle: forcePathStyle ?? true,
683
+ region: region ?? "auto"
684
+ });
685
+ };
686
+ const createS3StorageProfile = (config) => {
687
+ const { bucketName } = config;
688
+ const client = createS3Client(config);
689
+ const getStorageKey = createStorageKeyBuilder(config.basePath);
690
+ return {
691
+ async delete(storageUri) {
692
+ const { bucket, key } = parseStorageUri(storageUri, "r2");
693
+ ensureExpectedR2Bucket$1(bucket, bucketName);
694
+ await client.send(new DeleteObjectCommand({
695
+ Bucket: bucketName,
696
+ Key: key
697
+ }));
698
+ },
699
+ async upload(key, filePath) {
700
+ const Body = await fs.readFile(filePath);
701
+ const ContentType = getContentType(filePath);
702
+ const Key = getStorageKey(key, path.basename(filePath));
703
+ await new Upload({
704
+ client,
705
+ params: {
706
+ Body,
707
+ Bucket: bucketName,
708
+ CacheControl: "max-age=31536000",
709
+ ContentType,
710
+ Key
711
+ }
712
+ }).done();
713
+ return { storageUri: `r2://${bucketName}/${Key}` };
714
+ },
715
+ async exists(storageUri) {
716
+ const { bucket, key } = parseStorageUri(storageUri, "r2");
717
+ ensureExpectedR2Bucket$1(bucket, bucketName);
718
+ try {
719
+ await client.send(new HeadObjectCommand({
720
+ Bucket: bucketName,
721
+ Key: key
722
+ }));
723
+ return true;
724
+ } catch (error) {
725
+ if (isS3ObjectNotFoundError(error)) return false;
726
+ throw error;
727
+ }
728
+ },
729
+ async downloadFile(storageUri, filePath) {
730
+ const { bucket, key } = parseStorageUri(storageUri, "r2");
731
+ ensureExpectedR2Bucket$1(bucket, bucketName);
732
+ const response = await client.send(new GetObjectCommand({
733
+ Bucket: bucketName,
734
+ Key: key
735
+ }));
736
+ if (!response.Body) throw new Error("R2 object body is empty");
737
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
738
+ await fs.writeFile(filePath, await response.Body.transformToByteArray());
739
+ }
740
+ };
741
+ };
742
+ const createS3RuntimeStorageProfile = (config) => {
743
+ const { bucketName } = config;
744
+ const client = createS3Client(config);
745
+ return {
746
+ async readText(storageUri) {
747
+ const { bucket, key } = parseStorageUri(storageUri, "r2");
748
+ ensureExpectedR2Bucket$1(bucket, bucketName);
749
+ try {
750
+ const response = await client.send(new GetObjectCommand({
751
+ Bucket: bucketName,
752
+ Key: key
753
+ }));
754
+ if (!response.Body) return null;
755
+ return response.Body.transformToString();
756
+ } catch (error) {
757
+ if (isS3ObjectNotFoundError(error)) return null;
758
+ throw error;
759
+ }
760
+ },
761
+ async getDownloadUrl(storageUri) {
762
+ const { bucket, key } = parseStorageUri(storageUri, "r2");
763
+ ensureExpectedR2Bucket$1(bucket, bucketName);
764
+ const signedUrl = await getSignedUrl(client, new GetObjectCommand({
765
+ Bucket: bucketName,
766
+ Key: key
767
+ }), { expiresIn: 3600 });
768
+ if (!signedUrl) throw new Error("Failed to presign R2 URL");
769
+ return { fileUrl: signedUrl };
770
+ }
771
+ };
772
+ };
773
+ //#endregion
668
774
  //#region ../../node_modules/.pnpm/is-plain-obj@4.1.0/node_modules/is-plain-obj/index.js
669
775
  function isPlainObject(value) {
670
776
  if (typeof value !== "object" || value === null) return false;
@@ -6838,55 +6944,108 @@ const createWrangler = ({ stdio, accountId, cloudflareApiToken, cwd }) => {
6838
6944
  return (...command) => $("npx", ["wrangler", ...command]);
6839
6945
  };
6840
6946
  //#endregion
6947
+ //#region src/r2WranglerStorage.ts
6948
+ const ensureExpectedR2Bucket = (bucket, bucketName) => {
6949
+ if (bucket !== bucketName) throw new Error(`Bucket name mismatch: expected "${bucketName}", but found "${bucket}".`);
6950
+ };
6951
+ const isR2ObjectNotFoundError = (error) => {
6952
+ const output = [
6953
+ error.stderr,
6954
+ error.stdout,
6955
+ error.shortMessage,
6956
+ error.message
6957
+ ].filter(Boolean).join("\n").toLowerCase();
6958
+ return output.includes("not found") || output.includes("no such object") || output.includes("does not exist");
6959
+ };
6960
+ const createWranglerStorageProfile = (config) => {
6961
+ const { bucketName, cloudflareApiToken, accountId } = config;
6962
+ const wrangler = createWrangler({
6963
+ accountId,
6964
+ cloudflareApiToken,
6965
+ cwd: process.cwd()
6966
+ });
6967
+ const getStorageKey = createStorageKeyBuilder(config.basePath);
6968
+ return {
6969
+ async delete(storageUri) {
6970
+ const { bucket, key } = parseStorageUri(storageUri, "r2");
6971
+ ensureExpectedR2Bucket(bucket, bucketName);
6972
+ try {
6973
+ await wrangler("r2", "object", "delete", [bucketName, key].join("/"), "--remote");
6974
+ } catch {
6975
+ throw new Error("Can not delete bundle");
6976
+ }
6977
+ },
6978
+ async upload(key, filePath) {
6979
+ const contentType = getContentType(filePath);
6980
+ const Key = getStorageKey(key, path.basename(filePath));
6981
+ try {
6982
+ const { stderr, exitCode } = await wrangler("r2", "object", "put", [bucketName, Key].join("/"), "--file", filePath, "--content-type", contentType, "--remote");
6983
+ if (exitCode !== 0 && stderr) throw new Error(stderr);
6984
+ } catch (error) {
6985
+ if (error instanceof ExecaError) throw new Error(error.stderr || error.stdout);
6986
+ throw error;
6987
+ }
6988
+ return { storageUri: `r2://${bucketName}/${Key}` };
6989
+ },
6990
+ async exists(storageUri) {
6991
+ const { bucket, key } = parseStorageUri(storageUri, "r2");
6992
+ ensureExpectedR2Bucket(bucket, bucketName);
6993
+ const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), "hot-updater-r2-exists-"));
6994
+ const tempFilePath = path.join(tempDir, "object");
6995
+ try {
6996
+ await wrangler("r2", "object", "get", [bucketName, key].join("/"), "--file", tempFilePath, "--remote");
6997
+ return true;
6998
+ } catch (error) {
6999
+ if (error instanceof ExecaError && isR2ObjectNotFoundError(error)) return false;
7000
+ throw error;
7001
+ } finally {
7002
+ await fs.rm(tempDir, {
7003
+ force: true,
7004
+ recursive: true
7005
+ });
7006
+ }
7007
+ },
7008
+ async downloadFile(storageUri, filePath) {
7009
+ const { bucket, key } = parseStorageUri(storageUri, "r2");
7010
+ ensureExpectedR2Bucket(bucket, bucketName);
7011
+ try {
7012
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
7013
+ const { stderr, exitCode } = await wrangler("r2", "object", "get", [bucketName, key].join("/"), "--file", filePath, "--remote");
7014
+ if (exitCode !== 0 && stderr) throw new Error(stderr);
7015
+ } catch (error) {
7016
+ if (error instanceof ExecaError) throw new Error(error.stderr || error.stdout);
7017
+ throw error;
7018
+ }
7019
+ }
7020
+ };
7021
+ };
7022
+ const createWranglerRuntimeStorageProfile = () => {
7023
+ const error = /* @__PURE__ */ new Error("r2Storage runtime profile requires R2 S3 credentials. Wrangler-based R2 access is only supported by the node profile.");
7024
+ return {
7025
+ async readText() {
7026
+ throw error;
7027
+ },
7028
+ async getDownloadUrl() {
7029
+ throw error;
7030
+ }
7031
+ };
7032
+ };
7033
+ //#endregion
6841
7034
  //#region src/r2Storage.ts
6842
- /**
6843
- * Cloudflare R2 storage plugin for Hot Updater.
6844
- */
6845
- const r2Storage = createNodeStoragePlugin({
7035
+ const hasS3Credentials = (config) => {
7036
+ return Boolean(config.credentials);
7037
+ };
7038
+ const r2Storage = createUniversalStoragePlugin({
6846
7039
  name: "r2Storage",
6847
7040
  supportedProtocol: "r2",
6848
7041
  factory: (config) => {
6849
- const { bucketName, cloudflareApiToken, accountId } = config;
6850
- const wrangler = createWrangler({
6851
- accountId,
6852
- cloudflareApiToken,
6853
- cwd: process.cwd()
6854
- });
6855
- const getStorageKey = createStorageKeyBuilder(config.basePath);
7042
+ if (hasS3Credentials(config)) return {
7043
+ node: createS3StorageProfile(config),
7044
+ runtime: createS3RuntimeStorageProfile(config)
7045
+ };
6856
7046
  return {
6857
- async delete(storageUri) {
6858
- const { bucket, key } = parseStorageUri(storageUri, "r2");
6859
- if (bucket !== bucketName) throw new Error(`Bucket name mismatch: expected "${bucketName}", but found "${bucket}".`);
6860
- try {
6861
- await wrangler("r2", "object", "delete", [bucketName, key].join("/"), "--remote");
6862
- } catch {
6863
- throw new Error("Can not delete bundle");
6864
- }
6865
- },
6866
- async upload(key, filePath) {
6867
- const contentType = getContentType(filePath);
6868
- const Key = getStorageKey(key, path.basename(filePath));
6869
- try {
6870
- const { stderr, exitCode } = await wrangler("r2", "object", "put", [bucketName, Key].join("/"), "--file", filePath, "--content-type", contentType, "--remote");
6871
- if (exitCode !== 0 && stderr) throw new Error(stderr);
6872
- } catch (error) {
6873
- if (error instanceof ExecaError) throw new Error(error.stderr || error.stdout);
6874
- throw error;
6875
- }
6876
- return { storageUri: `r2://${bucketName}/${Key}` };
6877
- },
6878
- async downloadFile(storageUri, filePath) {
6879
- const { bucket, key } = parseStorageUri(storageUri, "r2");
6880
- if (bucket !== bucketName) throw new Error(`Bucket name mismatch: expected "${bucketName}", but found "${bucket}".`);
6881
- try {
6882
- await fs.mkdir(path.dirname(filePath), { recursive: true });
6883
- const { stderr, exitCode } = await wrangler("r2", "object", "get", [bucketName, key].join("/"), "--file", filePath, "--remote");
6884
- if (exitCode !== 0 && stderr) throw new Error(stderr);
6885
- } catch (error) {
6886
- if (error instanceof ExecaError) throw new Error(error.stderr || error.stdout);
6887
- throw error;
6888
- }
6889
- }
7047
+ node: createWranglerStorageProfile(config),
7048
+ runtime: createWranglerRuntimeStorageProfile()
6890
7049
  };
6891
7050
  }
6892
7051
  });
@@ -3,6 +3,11 @@ let _hot_updater_js = require("@hot-updater/js");
3
3
  let _hot_updater_core = require("@hot-updater/core");
4
4
  let _hot_updater_plugin_core = require("@hot-updater/plugin-core");
5
5
  //#region src/cloudflareWorkerDatabase.ts
6
+ const buildJsonEachInClause = (columnName, values, params) => {
7
+ if (values.length === 0) return "1 = 0";
8
+ params.push(JSON.stringify(values));
9
+ return `${columnName} IN (SELECT value FROM json_each(?))`;
10
+ };
6
11
  function buildWhereClause(conditions) {
7
12
  if (!conditions) return {
8
13
  sql: "",
@@ -22,11 +27,7 @@ function buildWhereClause(conditions) {
22
27
  clauses.push("enabled = ?");
23
28
  params.push(conditions.enabled ? 1 : 0);
24
29
  }
25
- if (conditions.id?.in) if (conditions.id.in.length === 0) clauses.push("1 = 0");
26
- else {
27
- clauses.push(`id IN (${conditions.id.in.map(() => "?").join(", ")})`);
28
- params.push(...conditions.id.in);
29
- }
30
+ if (conditions.id?.in) clauses.push(buildJsonEachInClause("id", conditions.id.in, params));
30
31
  if (conditions.id?.eq) {
31
32
  clauses.push("id = ?");
32
33
  params.push(conditions.id.eq);
@@ -53,11 +54,7 @@ function buildWhereClause(conditions) {
53
54
  clauses.push("target_app_version = ?");
54
55
  params.push(conditions.targetAppVersion);
55
56
  }
56
- if (conditions.targetAppVersionIn) if (conditions.targetAppVersionIn.length === 0) clauses.push("1 = 0");
57
- else {
58
- clauses.push(`target_app_version IN (${conditions.targetAppVersionIn.map(() => "?").join(", ")})`);
59
- params.push(...conditions.targetAppVersionIn);
60
- }
57
+ if (conditions.targetAppVersionIn) clauses.push(buildJsonEachInClause("target_app_version", conditions.targetAppVersionIn, params));
61
58
  if (conditions.fingerprintHash !== void 0) if (conditions.fingerprintHash === null) clauses.push("fingerprint_hash IS NULL");
62
59
  else {
63
60
  clauses.push("fingerprint_hash = ?");
@@ -152,9 +149,9 @@ const d1WorkerDatabase = () => (0, _hot_updater_plugin_core.createDatabasePlugin
152
149
  const rows = await queryAll(`
153
150
  SELECT *
154
151
  FROM bundle_patches
155
- WHERE bundle_id IN (${bundleIds.map(() => "?").join(", ")})
152
+ WHERE bundle_id IN (SELECT value FROM json_each(?))
156
153
  ORDER BY order_index ASC, base_bundle_id ASC
157
- `, bundleIds, context);
154
+ `, [JSON.stringify(bundleIds)], context);
158
155
  for (const row of rows) {
159
156
  const current = patchMap.get(row.bundle_id) ?? [];
160
157
  current.push(row);
@@ -1,5 +1,6 @@
1
- import { verifyJwtSignedUrl } from "@hot-updater/js";
1
+ import * as _$_hot_updater_plugin_core0 from "@hot-updater/plugin-core";
2
2
  import { HotUpdaterContext, RequestEnvContext as RequestEnvContext$1 } from "@hot-updater/plugin-core";
3
+ import { verifyJwtSignedUrl } from "@hot-updater/js";
3
4
 
4
5
  //#region src/cloudflareWorkerDatabase.d.ts
5
6
  type D1Result<T> = {
@@ -37,7 +38,7 @@ interface CloudflareWorkerStorageConfig<TContext extends RequestEnvContext$1<Clo
37
38
  //#region src/worker/index.d.ts
38
39
  interface CloudflareWorkerRuntimeEnv extends CloudflareWorkerDatabaseEnv, CloudflareWorkerStorageEnv {}
39
40
  type RequestEnvContext<TEnv = CloudflareWorkerRuntimeEnv> = RequestEnvContext$1<TEnv>;
40
- declare const d1Database: <TContext extends RequestEnvContext<CloudflareWorkerRuntimeEnv> = RequestEnvContext<CloudflareWorkerRuntimeEnv>>() => () => import("@hot-updater/plugin-core").DatabasePlugin<TContext>;
41
- declare const r2Storage: <TContext extends RequestEnvContext<CloudflareWorkerRuntimeEnv> = RequestEnvContext<CloudflareWorkerRuntimeEnv>>(config: CloudflareWorkerStorageConfig<TContext>) => () => import("@hot-updater/plugin-core").RuntimeStoragePlugin<TContext>;
41
+ declare const d1Database: <TContext extends RequestEnvContext<CloudflareWorkerRuntimeEnv> = RequestEnvContext<CloudflareWorkerRuntimeEnv>>() => () => _$_hot_updater_plugin_core0.DatabasePlugin<TContext>;
42
+ declare const r2Storage: <TContext extends RequestEnvContext<CloudflareWorkerRuntimeEnv> = RequestEnvContext<CloudflareWorkerRuntimeEnv>>(config: CloudflareWorkerStorageConfig<TContext>) => () => _$_hot_updater_plugin_core0.RuntimeStoragePlugin<TContext>;
42
43
  //#endregion
43
44
  export { type CloudflareWorkerDatabaseEnv, CloudflareWorkerRuntimeEnv, type CloudflareWorkerStorageEnv, RequestEnvContext, d1Database, r2Storage, verifyJwtSignedUrl };
@@ -1,4 +1,5 @@
1
1
  import { verifyJwtSignedUrl } from "@hot-updater/js";
2
+ import * as _$_hot_updater_plugin_core0 from "@hot-updater/plugin-core";
2
3
  import { HotUpdaterContext, RequestEnvContext as RequestEnvContext$1 } from "@hot-updater/plugin-core";
3
4
 
4
5
  //#region src/cloudflareWorkerDatabase.d.ts
@@ -37,7 +38,7 @@ interface CloudflareWorkerStorageConfig<TContext extends RequestEnvContext$1<Clo
37
38
  //#region src/worker/index.d.ts
38
39
  interface CloudflareWorkerRuntimeEnv extends CloudflareWorkerDatabaseEnv, CloudflareWorkerStorageEnv {}
39
40
  type RequestEnvContext<TEnv = CloudflareWorkerRuntimeEnv> = RequestEnvContext$1<TEnv>;
40
- declare const d1Database: <TContext extends RequestEnvContext<CloudflareWorkerRuntimeEnv> = RequestEnvContext<CloudflareWorkerRuntimeEnv>>() => () => import("@hot-updater/plugin-core").DatabasePlugin<TContext>;
41
- declare const r2Storage: <TContext extends RequestEnvContext<CloudflareWorkerRuntimeEnv> = RequestEnvContext<CloudflareWorkerRuntimeEnv>>(config: CloudflareWorkerStorageConfig<TContext>) => () => import("@hot-updater/plugin-core").RuntimeStoragePlugin<TContext>;
41
+ declare const d1Database: <TContext extends RequestEnvContext<CloudflareWorkerRuntimeEnv> = RequestEnvContext<CloudflareWorkerRuntimeEnv>>() => () => _$_hot_updater_plugin_core0.DatabasePlugin<TContext>;
42
+ declare const r2Storage: <TContext extends RequestEnvContext<CloudflareWorkerRuntimeEnv> = RequestEnvContext<CloudflareWorkerRuntimeEnv>>(config: CloudflareWorkerStorageConfig<TContext>) => () => _$_hot_updater_plugin_core0.RuntimeStoragePlugin<TContext>;
42
43
  //#endregion
43
44
  export { type CloudflareWorkerDatabaseEnv, CloudflareWorkerRuntimeEnv, type CloudflareWorkerStorageEnv, RequestEnvContext, d1Database, r2Storage, verifyJwtSignedUrl };
@@ -2,6 +2,11 @@ import { signToken, verifyJwtSignedUrl } from "@hot-updater/js";
2
2
  import { DEFAULT_ROLLOUT_COHORT_COUNT, getAssetBaseStorageUri, getBundlePatches, getManifestFileHash, getManifestStorageUri, stripBundleArtifactMetadata } from "@hot-updater/core";
3
3
  import { calculatePagination, createDatabasePlugin, createDatabasePluginGetUpdateInfo, createRuntimeStoragePlugin } from "@hot-updater/plugin-core";
4
4
  //#region src/cloudflareWorkerDatabase.ts
5
+ const buildJsonEachInClause = (columnName, values, params) => {
6
+ if (values.length === 0) return "1 = 0";
7
+ params.push(JSON.stringify(values));
8
+ return `${columnName} IN (SELECT value FROM json_each(?))`;
9
+ };
5
10
  function buildWhereClause(conditions) {
6
11
  if (!conditions) return {
7
12
  sql: "",
@@ -21,11 +26,7 @@ function buildWhereClause(conditions) {
21
26
  clauses.push("enabled = ?");
22
27
  params.push(conditions.enabled ? 1 : 0);
23
28
  }
24
- if (conditions.id?.in) if (conditions.id.in.length === 0) clauses.push("1 = 0");
25
- else {
26
- clauses.push(`id IN (${conditions.id.in.map(() => "?").join(", ")})`);
27
- params.push(...conditions.id.in);
28
- }
29
+ if (conditions.id?.in) clauses.push(buildJsonEachInClause("id", conditions.id.in, params));
29
30
  if (conditions.id?.eq) {
30
31
  clauses.push("id = ?");
31
32
  params.push(conditions.id.eq);
@@ -52,11 +53,7 @@ function buildWhereClause(conditions) {
52
53
  clauses.push("target_app_version = ?");
53
54
  params.push(conditions.targetAppVersion);
54
55
  }
55
- if (conditions.targetAppVersionIn) if (conditions.targetAppVersionIn.length === 0) clauses.push("1 = 0");
56
- else {
57
- clauses.push(`target_app_version IN (${conditions.targetAppVersionIn.map(() => "?").join(", ")})`);
58
- params.push(...conditions.targetAppVersionIn);
59
- }
56
+ if (conditions.targetAppVersionIn) clauses.push(buildJsonEachInClause("target_app_version", conditions.targetAppVersionIn, params));
60
57
  if (conditions.fingerprintHash !== void 0) if (conditions.fingerprintHash === null) clauses.push("fingerprint_hash IS NULL");
61
58
  else {
62
59
  clauses.push("fingerprint_hash = ?");
@@ -151,9 +148,9 @@ const d1WorkerDatabase = () => createDatabasePlugin({
151
148
  const rows = await queryAll(`
152
149
  SELECT *
153
150
  FROM bundle_patches
154
- WHERE bundle_id IN (${bundleIds.map(() => "?").join(", ")})
151
+ WHERE bundle_id IN (SELECT value FROM json_each(?))
155
152
  ORDER BY order_index ASC, base_bundle_id ASC
156
- `, bundleIds, context);
153
+ `, [JSON.stringify(bundleIds)], context);
157
154
  for (const row of rows) {
158
155
  const current = patchMap.get(row.bundle_id) ?? [];
159
156
  current.push(row);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hot-updater/cloudflare",
3
3
  "type": "module",
4
- "version": "0.31.4",
4
+ "version": "0.33.0",
5
5
  "description": "React Native OTA solution for self-hosted",
6
6
  "main": "dist/index.cjs",
7
7
  "module": "dist/index.mjs",
@@ -47,14 +47,17 @@
47
47
  "package.json"
48
48
  ],
49
49
  "dependencies": {
50
+ "@aws-sdk/client-s3": "3.1008.0",
51
+ "@aws-sdk/lib-storage": "3.1008.0",
52
+ "@aws-sdk/s3-request-presigner": "3.1008.0",
50
53
  "cloudflare": "4.2.0",
51
54
  "hono": "4.12.9",
52
55
  "uuidv7": "^1.0.2",
53
- "@hot-updater/cli-tools": "0.31.4",
54
- "@hot-updater/js": "0.31.4",
55
- "@hot-updater/plugin-core": "0.31.4",
56
- "@hot-updater/server": "0.31.4",
57
- "@hot-updater/core": "0.31.4"
56
+ "@hot-updater/core": "0.33.0",
57
+ "@hot-updater/js": "0.33.0",
58
+ "@hot-updater/cli-tools": "0.33.0",
59
+ "@hot-updater/plugin-core": "0.33.0",
60
+ "@hot-updater/server": "0.33.0"
58
61
  },
59
62
  "devDependencies": {
60
63
  "@cloudflare/vitest-pool-workers": "0.13.0",
@@ -71,7 +74,7 @@
71
74
  "vitest": "4.1.4",
72
75
  "wrangler": "^4.5.0",
73
76
  "xdg-app-paths": "^8.3.0",
74
- "@hot-updater/test-utils": "0.31.4"
77
+ "@hot-updater/test-utils": "0.33.0"
75
78
  },
76
79
  "scripts": {
77
80
  "build": "tsdown && pnpm build:worker",