@hot-updater/cloudflare 0.36.6 → 1.0.0-rc.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/iac/index.cjs +989 -11
- package/dist/iac/index.mjs +992 -15
- package/dist/index.cjs +919 -6989
- package/dist/index.d.cts +11 -47
- package/dist/index.d.mts +11 -47
- package/dist/index.mjs +917 -7006
- package/dist/worker/index.cjs +898 -336
- package/dist/worker/index.d.cts +18 -33
- package/dist/worker/index.d.mts +18 -33
- package/dist/worker/index.mjs +899 -331
- package/package.json +7 -10
- package/sql/bundles.sql +183 -35
- package/src/d1Database.spec.ts +143 -392
- package/src/d1Database.ts +55 -650
- package/src/d1Implementation.spec.ts +252 -0
- package/src/d1Implementation.ts +742 -0
- package/src/d1Query.spec.ts +46 -0
- package/src/d1Query.ts +89 -0
- package/src/d1Rows.spec.ts +111 -0
- package/src/d1Rows.ts +326 -0
- package/src/d1Sql.spec.ts +30 -0
- package/src/d1Sql.ts +175 -0
- package/src/r2S3Storage.ts +93 -137
- package/src/r2Storage.spec.ts +120 -218
- package/src/r2Storage.ts +13 -61
- package/src/r2WorkerStorage.spec.ts +161 -50
- package/src/r2WorkerStorage.ts +85 -118
- package/src/worker/d1Database.spec.ts +33 -0
- package/src/worker/d1Database.ts +52 -0
- package/src/worker/index.ts +3 -31
- package/worker/dist/README.md +1 -1
- package/worker/dist/index.js +5968 -4701
- package/worker/dist/index.js.map +4 -4
- package/worker/migrations/0001_hot-updater_1.0.0.sql +197 -0
- package/worker/migrations/0002_hot-updater_artifact_byte_sizes.sql +11 -0
- package/worker/src/databaseMigration.integration.spec.ts +114 -0
- package/worker/src/databasePlugins.integration.spec.ts +292 -0
- package/worker/src/index.integration.spec.ts +102 -294
- package/worker/src/index.ts +14 -66
- package/worker/src/storagePlugin.integration.spec.ts +59 -0
- package/worker/wrangler.json +5 -2
- package/src/cloudflareWorkerDatabase.spec.ts +0 -260
- package/src/cloudflareWorkerDatabase.ts +0 -651
- package/src/r2WranglerStorage.ts +0 -193
- package/worker/migrations/0001_hot-updater_init.sql +0 -16
- package/worker/migrations/0002_hot-updater_0.13.0.sql +0 -8
- package/worker/migrations/0003_hot-updater_0.18.0.sql +0 -38
- package/worker/migrations/0004_hot-updater_0.29.0.sql +0 -11
- package/worker/migrations/0005_hot-updater_0.31.0.sql +0 -24
package/src/r2WranglerStorage.ts
DELETED
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
createStorageKeyBuilder,
|
|
7
|
-
getContentType,
|
|
8
|
-
type NodeStorageProfile,
|
|
9
|
-
parseStorageUri,
|
|
10
|
-
type RuntimeStorageProfile,
|
|
11
|
-
} from "@hot-updater/plugin-core";
|
|
12
|
-
import { ExecaError } from "execa";
|
|
13
|
-
|
|
14
|
-
import { createWrangler } from "./utils/createWrangler";
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* @deprecated `cloudflareApiToken` uses the Wrangler CLI for R2 operations,
|
|
18
|
-
* which is slower than direct S3-compatible API access. Create R2
|
|
19
|
-
* S3-compatible credentials in the Cloudflare dashboard and pass them with
|
|
20
|
-
* `r2Storage({ credentials })` instead.
|
|
21
|
-
*/
|
|
22
|
-
export interface R2WranglerStorageConfig {
|
|
23
|
-
accountId: string;
|
|
24
|
-
bucketName: string;
|
|
25
|
-
/**
|
|
26
|
-
* @deprecated This token keeps R2 access on the slower Wrangler CLI path.
|
|
27
|
-
* Create R2 S3-compatible credentials in the Cloudflare dashboard and use
|
|
28
|
-
* `credentials` instead.
|
|
29
|
-
*/
|
|
30
|
-
cloudflareApiToken: string;
|
|
31
|
-
/**
|
|
32
|
-
* Base path where bundles will be stored in the bucket
|
|
33
|
-
*/
|
|
34
|
-
basePath?: string;
|
|
35
|
-
credentials?: never;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
const ensureExpectedR2Bucket = (bucket: string, bucketName: string) => {
|
|
39
|
-
if (bucket !== bucketName) {
|
|
40
|
-
throw new Error(
|
|
41
|
-
`Bucket name mismatch: expected "${bucketName}", but found "${bucket}".`,
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const isR2ObjectNotFoundError = (error: ExecaError) => {
|
|
47
|
-
const output = [error.stderr, error.stdout, error.shortMessage, error.message]
|
|
48
|
-
.filter(Boolean)
|
|
49
|
-
.join("\n")
|
|
50
|
-
.toLowerCase();
|
|
51
|
-
|
|
52
|
-
return (
|
|
53
|
-
output.includes("not found") ||
|
|
54
|
-
output.includes("no such object") ||
|
|
55
|
-
output.includes("does not exist")
|
|
56
|
-
);
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
export const createWranglerStorageProfile = (
|
|
60
|
-
config: R2WranglerStorageConfig,
|
|
61
|
-
): NodeStorageProfile => {
|
|
62
|
-
const { bucketName, cloudflareApiToken, accountId } = config;
|
|
63
|
-
const wrangler = createWrangler({
|
|
64
|
-
accountId,
|
|
65
|
-
cloudflareApiToken,
|
|
66
|
-
cwd: process.cwd(),
|
|
67
|
-
});
|
|
68
|
-
const getStorageKey = createStorageKeyBuilder(config.basePath);
|
|
69
|
-
|
|
70
|
-
return {
|
|
71
|
-
async delete(storageUri) {
|
|
72
|
-
const { bucket, key } = parseStorageUri(storageUri, "r2");
|
|
73
|
-
ensureExpectedR2Bucket(bucket, bucketName);
|
|
74
|
-
|
|
75
|
-
try {
|
|
76
|
-
await wrangler(
|
|
77
|
-
"r2",
|
|
78
|
-
"object",
|
|
79
|
-
"delete",
|
|
80
|
-
[bucketName, key].join("/"),
|
|
81
|
-
"--remote",
|
|
82
|
-
);
|
|
83
|
-
} catch {
|
|
84
|
-
throw new Error("Can not delete bundle");
|
|
85
|
-
}
|
|
86
|
-
},
|
|
87
|
-
async upload(key, filePath) {
|
|
88
|
-
const contentType = getContentType(filePath);
|
|
89
|
-
|
|
90
|
-
const filename = path.basename(filePath);
|
|
91
|
-
|
|
92
|
-
const Key = getStorageKey(key, filename);
|
|
93
|
-
try {
|
|
94
|
-
const { stderr, exitCode } = await wrangler(
|
|
95
|
-
"r2",
|
|
96
|
-
"object",
|
|
97
|
-
"put",
|
|
98
|
-
[bucketName, Key].join("/"),
|
|
99
|
-
"--file",
|
|
100
|
-
filePath,
|
|
101
|
-
"--content-type",
|
|
102
|
-
contentType,
|
|
103
|
-
"--remote",
|
|
104
|
-
);
|
|
105
|
-
if (exitCode !== 0 && stderr) {
|
|
106
|
-
throw new Error(stderr);
|
|
107
|
-
}
|
|
108
|
-
} catch (error) {
|
|
109
|
-
if (error instanceof ExecaError) {
|
|
110
|
-
throw new Error(error.stderr || error.stdout);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
throw error;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return {
|
|
117
|
-
storageUri: `r2://${bucketName}/${Key}`,
|
|
118
|
-
};
|
|
119
|
-
},
|
|
120
|
-
async exists(storageUri: string) {
|
|
121
|
-
const { bucket, key } = parseStorageUri(storageUri, "r2");
|
|
122
|
-
ensureExpectedR2Bucket(bucket, bucketName);
|
|
123
|
-
|
|
124
|
-
const tempDir = await fs.mkdtemp(
|
|
125
|
-
path.join(os.tmpdir(), "hot-updater-r2-exists-"),
|
|
126
|
-
);
|
|
127
|
-
const tempFilePath = path.join(tempDir, "object");
|
|
128
|
-
|
|
129
|
-
try {
|
|
130
|
-
await wrangler(
|
|
131
|
-
"r2",
|
|
132
|
-
"object",
|
|
133
|
-
"get",
|
|
134
|
-
[bucketName, key].join("/"),
|
|
135
|
-
"--file",
|
|
136
|
-
tempFilePath,
|
|
137
|
-
"--remote",
|
|
138
|
-
);
|
|
139
|
-
return true;
|
|
140
|
-
} catch (error) {
|
|
141
|
-
if (error instanceof ExecaError && isR2ObjectNotFoundError(error)) {
|
|
142
|
-
return false;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
throw error;
|
|
146
|
-
} finally {
|
|
147
|
-
await fs.rm(tempDir, { force: true, recursive: true });
|
|
148
|
-
}
|
|
149
|
-
},
|
|
150
|
-
async downloadFile(storageUri, filePath) {
|
|
151
|
-
const { bucket, key } = parseStorageUri(storageUri, "r2");
|
|
152
|
-
ensureExpectedR2Bucket(bucket, bucketName);
|
|
153
|
-
|
|
154
|
-
try {
|
|
155
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
156
|
-
const { stderr, exitCode } = await wrangler(
|
|
157
|
-
"r2",
|
|
158
|
-
"object",
|
|
159
|
-
"get",
|
|
160
|
-
[bucketName, key].join("/"),
|
|
161
|
-
"--file",
|
|
162
|
-
filePath,
|
|
163
|
-
"--remote",
|
|
164
|
-
);
|
|
165
|
-
if (exitCode !== 0 && stderr) {
|
|
166
|
-
throw new Error(stderr);
|
|
167
|
-
}
|
|
168
|
-
} catch (error) {
|
|
169
|
-
if (error instanceof ExecaError) {
|
|
170
|
-
throw new Error(error.stderr || error.stdout);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
throw error;
|
|
174
|
-
}
|
|
175
|
-
},
|
|
176
|
-
};
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
export const createWranglerRuntimeStorageProfile =
|
|
180
|
-
(): RuntimeStorageProfile => {
|
|
181
|
-
const error = new Error(
|
|
182
|
-
"r2Storage runtime profile requires R2 S3 credentials. Wrangler-based R2 access is only supported by the node profile.",
|
|
183
|
-
);
|
|
184
|
-
|
|
185
|
-
return {
|
|
186
|
-
async readText() {
|
|
187
|
-
throw error;
|
|
188
|
-
},
|
|
189
|
-
async getDownloadUrl() {
|
|
190
|
-
throw error;
|
|
191
|
-
},
|
|
192
|
-
};
|
|
193
|
-
};
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
-- Migration number: 0001 2025-01-27T08:37:50.374Z
|
|
2
|
-
-- HotUpdater.bundles
|
|
3
|
-
|
|
4
|
-
CREATE TABLE bundles (
|
|
5
|
-
id TEXT PRIMARY KEY,
|
|
6
|
-
platform TEXT NOT NULL CHECK (platform IN ('ios', 'android')),
|
|
7
|
-
target_app_version TEXT NOT NULL,
|
|
8
|
-
should_force_update INTEGER NOT NULL CHECK (should_force_update IN (0, 1)),
|
|
9
|
-
enabled INTEGER NOT NULL CHECK (enabled IN (0, 1)),
|
|
10
|
-
file_url TEXT NOT NULL,
|
|
11
|
-
file_hash TEXT NOT NULL,
|
|
12
|
-
git_commit_hash TEXT,
|
|
13
|
-
message TEXT
|
|
14
|
-
);
|
|
15
|
-
|
|
16
|
-
CREATE INDEX bundles_target_app_version_idx ON bundles(target_app_version);
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
-- Migration number: 0003 2025-05-18T16:25:12.486Z
|
|
2
|
-
-- HotUpdater.bundles
|
|
3
|
-
|
|
4
|
-
ALTER TABLE bundles ADD COLUMN fingerprint_hash TEXT;
|
|
5
|
-
ALTER TABLE bundles ADD COLUMN storage_uri TEXT;
|
|
6
|
-
ALTER TABLE bundles ADD COLUMN metadata JSONB DEFAULT '{}';
|
|
7
|
-
|
|
8
|
-
UPDATE bundles
|
|
9
|
-
SET storage_uri = 'r2://%%BUCKET_NAME%%/' || id || '/bundle.zip'
|
|
10
|
-
WHERE storage_uri IS NULL;
|
|
11
|
-
|
|
12
|
-
CREATE TABLE bundles_temp (
|
|
13
|
-
id TEXT PRIMARY KEY,
|
|
14
|
-
platform TEXT NOT NULL,
|
|
15
|
-
should_force_update INTEGER NOT NULL,
|
|
16
|
-
enabled INTEGER NOT NULL,
|
|
17
|
-
file_hash TEXT NOT NULL,
|
|
18
|
-
git_commit_hash TEXT,
|
|
19
|
-
message TEXT,
|
|
20
|
-
channel TEXT NOT NULL DEFAULT 'production',
|
|
21
|
-
storage_uri TEXT NOT NULL,
|
|
22
|
-
target_app_version TEXT,
|
|
23
|
-
fingerprint_hash TEXT,
|
|
24
|
-
metadata JSONB DEFAULT '{}',
|
|
25
|
-
CHECK ((target_app_version IS NOT NULL) OR (fingerprint_hash IS NOT NULL))
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
INSERT INTO bundles_temp
|
|
29
|
-
SELECT id, platform, should_force_update, enabled, file_hash, git_commit_hash, message, channel, storage_uri, target_app_version, fingerprint_hash, metadata
|
|
30
|
-
FROM bundles;
|
|
31
|
-
|
|
32
|
-
DROP TABLE bundles;
|
|
33
|
-
|
|
34
|
-
ALTER TABLE bundles_temp RENAME TO bundles;
|
|
35
|
-
|
|
36
|
-
CREATE INDEX bundles_target_app_version_idx ON bundles(target_app_version);
|
|
37
|
-
CREATE INDEX bundles_fingerprint_hash_idx ON bundles(fingerprint_hash);
|
|
38
|
-
CREATE INDEX bundles_channel_idx ON bundles(channel);
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
-- Migration number: 0004 2025-12-21T00:00:00.000Z
|
|
2
|
-
|
|
3
|
-
-- HotUpdater.bundles
|
|
4
|
-
|
|
5
|
-
ALTER TABLE bundles ADD COLUMN rollout_cohort_count INTEGER DEFAULT 1000
|
|
6
|
-
CHECK (rollout_cohort_count >= 0 AND rollout_cohort_count <= 1000);
|
|
7
|
-
|
|
8
|
-
CREATE INDEX IF NOT EXISTS bundles_rollout_idx ON bundles(rollout_cohort_count);
|
|
9
|
-
|
|
10
|
-
-- SQLite doesn't have array type, store as JSON text
|
|
11
|
-
ALTER TABLE bundles ADD COLUMN target_cohorts TEXT;
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
-- Migration number: 0005 2026-04-22T00:00:00.000Z
|
|
2
|
-
|
|
3
|
-
-- HotUpdater.bundle_artifact_columns
|
|
4
|
-
|
|
5
|
-
ALTER TABLE bundles ADD COLUMN manifest_storage_uri TEXT;
|
|
6
|
-
ALTER TABLE bundles ADD COLUMN manifest_file_hash TEXT;
|
|
7
|
-
ALTER TABLE bundles ADD COLUMN asset_base_storage_uri TEXT;
|
|
8
|
-
|
|
9
|
-
CREATE TABLE bundle_patches (
|
|
10
|
-
id TEXT PRIMARY KEY,
|
|
11
|
-
bundle_id TEXT NOT NULL,
|
|
12
|
-
base_bundle_id TEXT NOT NULL,
|
|
13
|
-
base_file_hash TEXT NOT NULL,
|
|
14
|
-
patch_file_hash TEXT NOT NULL,
|
|
15
|
-
patch_storage_uri TEXT NOT NULL,
|
|
16
|
-
order_index INTEGER NOT NULL DEFAULT 0,
|
|
17
|
-
FOREIGN KEY (bundle_id) REFERENCES bundles(id) ON DELETE CASCADE,
|
|
18
|
-
FOREIGN KEY (base_bundle_id) REFERENCES bundles(id) ON DELETE CASCADE
|
|
19
|
-
);
|
|
20
|
-
|
|
21
|
-
CREATE INDEX IF NOT EXISTS bundle_patches_bundle_id_idx
|
|
22
|
-
ON bundle_patches(bundle_id);
|
|
23
|
-
CREATE INDEX IF NOT EXISTS bundle_patches_base_bundle_id_idx
|
|
24
|
-
ON bundle_patches(base_bundle_id);
|