@hot-updater/cloudflare 0.36.7 → 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 +988 -10
- package/dist/iac/index.mjs +991 -14
- 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
|
@@ -1,260 +0,0 @@
|
|
|
1
|
-
import type { DatabasePlugin } from "@hot-updater/plugin-core";
|
|
2
|
-
import { beforeEach, describe, expect, it } from "vitest";
|
|
3
|
-
|
|
4
|
-
import { d1Database, type RequestEnvContext } from "./worker";
|
|
5
|
-
|
|
6
|
-
type WorkerBundleRow = {
|
|
7
|
-
id: string;
|
|
8
|
-
channel: string;
|
|
9
|
-
enabled: number;
|
|
10
|
-
should_force_update: number;
|
|
11
|
-
file_hash: string;
|
|
12
|
-
git_commit_hash: string | null;
|
|
13
|
-
message: string | null;
|
|
14
|
-
platform: "ios" | "android";
|
|
15
|
-
target_app_version: string | null;
|
|
16
|
-
storage_uri: string;
|
|
17
|
-
fingerprint_hash: string | null;
|
|
18
|
-
metadata: string;
|
|
19
|
-
manifest_storage_uri: string | null;
|
|
20
|
-
manifest_file_hash: string | null;
|
|
21
|
-
asset_base_storage_uri: string | null;
|
|
22
|
-
rollout_cohort_count: number | null;
|
|
23
|
-
target_cohorts: string | null;
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
type WorkerPatchRow = {
|
|
27
|
-
id: string;
|
|
28
|
-
bundle_id: string;
|
|
29
|
-
base_bundle_id: string;
|
|
30
|
-
base_file_hash: string;
|
|
31
|
-
patch_file_hash: string;
|
|
32
|
-
patch_storage_uri: string;
|
|
33
|
-
order_index: number | null;
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
type TestEnv = {
|
|
37
|
-
DB: ReturnType<typeof createD1Binding>;
|
|
38
|
-
JWT_SECRET: string;
|
|
39
|
-
BUCKET: {
|
|
40
|
-
get: (key: string) => Promise<{ text: () => Promise<string> } | null>;
|
|
41
|
-
};
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const rows = new Map<string, WorkerBundleRow>();
|
|
45
|
-
const patchRows = new Map<string, WorkerPatchRow>();
|
|
46
|
-
|
|
47
|
-
const createBundleRow = (index: number): WorkerBundleRow => {
|
|
48
|
-
const id = `00000000-0000-0000-0000-${String(index).padStart(12, "0")}`;
|
|
49
|
-
return {
|
|
50
|
-
id,
|
|
51
|
-
channel: "production",
|
|
52
|
-
enabled: 1,
|
|
53
|
-
should_force_update: 0,
|
|
54
|
-
file_hash: `hash-${index}`,
|
|
55
|
-
git_commit_hash: null,
|
|
56
|
-
message: null,
|
|
57
|
-
platform: "ios",
|
|
58
|
-
target_app_version: `>=0.${index}.0`,
|
|
59
|
-
storage_uri: `r2://bucket/${id}.zip`,
|
|
60
|
-
fingerprint_hash: null,
|
|
61
|
-
metadata: "{}",
|
|
62
|
-
manifest_storage_uri: null,
|
|
63
|
-
manifest_file_hash: null,
|
|
64
|
-
asset_base_storage_uri: null,
|
|
65
|
-
rollout_cohort_count: 1000,
|
|
66
|
-
target_cohorts: null,
|
|
67
|
-
};
|
|
68
|
-
};
|
|
69
|
-
|
|
70
|
-
const normalizeSql = (sql: string) => sql.replace(/\s+/g, " ").trim();
|
|
71
|
-
|
|
72
|
-
const filterRows = (sql: string, params: unknown[]) => {
|
|
73
|
-
let filteredRows = Array.from(rows.values());
|
|
74
|
-
let index = 0;
|
|
75
|
-
|
|
76
|
-
if (sql.includes("channel = ?")) {
|
|
77
|
-
const channel = params[index++];
|
|
78
|
-
filteredRows = filteredRows.filter((row) => row.channel === channel);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (sql.includes("platform = ?")) {
|
|
82
|
-
const platform = params[index++];
|
|
83
|
-
filteredRows = filteredRows.filter((row) => row.platform === platform);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (sql.includes("enabled = ?")) {
|
|
87
|
-
const enabled = Number(params[index++]);
|
|
88
|
-
filteredRows = filteredRows.filter((row) => row.enabled === enabled);
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (sql.includes("id >= ?")) {
|
|
92
|
-
const id = String(params[index++]);
|
|
93
|
-
filteredRows = filteredRows.filter((row) => row.id.localeCompare(id) >= 0);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (sql.includes("target_app_version IS NOT NULL")) {
|
|
97
|
-
filteredRows = filteredRows.filter(
|
|
98
|
-
(row) => row.target_app_version !== null,
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const inMatch = sql.match(/target_app_version IN \(([^)]+)\)/);
|
|
103
|
-
if (inMatch) {
|
|
104
|
-
const body = inMatch[1] ?? "";
|
|
105
|
-
const inValues = body.includes("json_each(")
|
|
106
|
-
? (JSON.parse(String(params[index++])) as unknown[])
|
|
107
|
-
: params.slice(index, index + (body.match(/\?/g) ?? []).length);
|
|
108
|
-
const values = new Set(inValues);
|
|
109
|
-
filteredRows = filteredRows.filter((row) =>
|
|
110
|
-
values.has(row.target_app_version),
|
|
111
|
-
);
|
|
112
|
-
if (!body.includes("json_each(")) {
|
|
113
|
-
index += inValues.length;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
return { filteredRows, index };
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
function createD1Binding() {
|
|
121
|
-
return {
|
|
122
|
-
prepare(sql: string) {
|
|
123
|
-
return {
|
|
124
|
-
bind(...params: unknown[]) {
|
|
125
|
-
if (params.length > 100) {
|
|
126
|
-
throw new Error(
|
|
127
|
-
"D1_ERROR: too many SQL variables at offset 386: SQLITE_ERROR",
|
|
128
|
-
);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return {
|
|
132
|
-
async all<T>() {
|
|
133
|
-
const normalizedSql = normalizeSql(sql).toLowerCase();
|
|
134
|
-
|
|
135
|
-
if (
|
|
136
|
-
normalizedSql.startsWith(
|
|
137
|
-
"select target_app_version from bundles",
|
|
138
|
-
)
|
|
139
|
-
) {
|
|
140
|
-
const { filteredRows } = filterRows(sql, params);
|
|
141
|
-
const targetAppVersions = Array.from(
|
|
142
|
-
new Set(
|
|
143
|
-
filteredRows
|
|
144
|
-
.map((row) => row.target_app_version)
|
|
145
|
-
.filter(
|
|
146
|
-
(targetAppVersion): targetAppVersion is string =>
|
|
147
|
-
targetAppVersion !== null,
|
|
148
|
-
),
|
|
149
|
-
),
|
|
150
|
-
).map((targetAppVersion) => ({
|
|
151
|
-
target_app_version: targetAppVersion,
|
|
152
|
-
}));
|
|
153
|
-
|
|
154
|
-
return { results: targetAppVersions as T[] };
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (
|
|
158
|
-
normalizedSql.startsWith(
|
|
159
|
-
"select count(*) as total from bundles",
|
|
160
|
-
)
|
|
161
|
-
) {
|
|
162
|
-
const { filteredRows } = filterRows(sql, params);
|
|
163
|
-
return { results: [{ total: filteredRows.length }] as T[] };
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (normalizedSql.startsWith("select * from bundles")) {
|
|
167
|
-
const { filteredRows, index } = filterRows(sql, params);
|
|
168
|
-
const limit = Number(params[index] ?? filteredRows.length);
|
|
169
|
-
const offset = Number(params[index + 1] ?? 0);
|
|
170
|
-
const result = filteredRows
|
|
171
|
-
.sort((left, right) => right.id.localeCompare(left.id))
|
|
172
|
-
.slice(offset, offset + limit);
|
|
173
|
-
|
|
174
|
-
return { results: result as T[] };
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (
|
|
178
|
-
normalizedSql.startsWith(
|
|
179
|
-
"select * from bundle_patches where bundle_id in",
|
|
180
|
-
)
|
|
181
|
-
) {
|
|
182
|
-
const selectedBundleIds = new Set(
|
|
183
|
-
normalizedSql.includes("json_each")
|
|
184
|
-
? (JSON.parse(String(params[0])) as unknown[]).map(String)
|
|
185
|
-
: params.map(String),
|
|
186
|
-
);
|
|
187
|
-
const result = Array.from(patchRows.values()).filter((row) =>
|
|
188
|
-
selectedBundleIds.has(row.bundle_id),
|
|
189
|
-
);
|
|
190
|
-
|
|
191
|
-
return { results: result as T[] };
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
throw new Error(`Unsupported SQL in D1 worker mock: ${sql}`);
|
|
195
|
-
},
|
|
196
|
-
async first<T>() {
|
|
197
|
-
return (await this.all<T>()).results?.[0] ?? null;
|
|
198
|
-
},
|
|
199
|
-
async run() {
|
|
200
|
-
return {};
|
|
201
|
-
},
|
|
202
|
-
};
|
|
203
|
-
},
|
|
204
|
-
};
|
|
205
|
-
},
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
describe("cloudflare worker d1Database", () => {
|
|
210
|
-
let plugin: DatabasePlugin<RequestEnvContext<TestEnv>>;
|
|
211
|
-
let context: RequestEnvContext<TestEnv>;
|
|
212
|
-
|
|
213
|
-
beforeEach(() => {
|
|
214
|
-
rows.clear();
|
|
215
|
-
patchRows.clear();
|
|
216
|
-
plugin = d1Database<RequestEnvContext<TestEnv>>()();
|
|
217
|
-
context = {
|
|
218
|
-
env: {
|
|
219
|
-
DB: createD1Binding(),
|
|
220
|
-
JWT_SECRET: "test-secret",
|
|
221
|
-
BUCKET: {
|
|
222
|
-
get: async () => null,
|
|
223
|
-
},
|
|
224
|
-
},
|
|
225
|
-
};
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
it("queries getUpdateInfo with 200 distinct target_app_versions without exceeding D1's 100-bind cap", async () => {
|
|
229
|
-
for (let index = 0; index < 200; index++) {
|
|
230
|
-
const row = createBundleRow(index);
|
|
231
|
-
rows.set(row.id, row);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
const result = await plugin.getUpdateInfo?.(
|
|
235
|
-
{
|
|
236
|
-
appVersion: "1.0.0",
|
|
237
|
-
bundleId: "00000000-0000-0000-0000-000000000000",
|
|
238
|
-
platform: "ios",
|
|
239
|
-
channel: "production",
|
|
240
|
-
minBundleId: "00000000-0000-0000-0000-000000000000",
|
|
241
|
-
_updateStrategy: "appVersion",
|
|
242
|
-
},
|
|
243
|
-
context,
|
|
244
|
-
);
|
|
245
|
-
|
|
246
|
-
expect(result).not.toBeNull();
|
|
247
|
-
});
|
|
248
|
-
|
|
249
|
-
it("queries patches for 200 listed bundles without exceeding D1's 100-bind cap", async () => {
|
|
250
|
-
for (let index = 0; index < 200; index++) {
|
|
251
|
-
const row = createBundleRow(index);
|
|
252
|
-
rows.set(row.id, row);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const result = await plugin.getBundles({ limit: 200 }, context);
|
|
256
|
-
|
|
257
|
-
expect(result.data).toHaveLength(200);
|
|
258
|
-
expect(result.pagination.total).toBe(200);
|
|
259
|
-
});
|
|
260
|
-
});
|