@hot-updater/cloudflare 0.32.0 → 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.cjs +9 -12
- package/dist/index.mjs +9 -12
- package/dist/worker/index.cjs +9 -12
- package/dist/worker/index.mjs +9 -12
- package/package.json +7 -7
- package/src/cloudflareWorkerDatabase.spec.ts +260 -0
- package/src/cloudflareWorkerDatabase.ts +23 -19
- package/src/d1Database.spec.ts +16 -2
- package/src/d1Database.ts +23 -19
- package/worker/dist/README.md +1 -1
- package/worker/dist/index.js +165 -35
- package/worker/dist/index.js.map +4 -4
- package/worker/src/getUpdateInfo.ts +0 -194
package/dist/index.cjs
CHANGED
|
@@ -281,6 +281,11 @@ var import_lib = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((export
|
|
|
281
281
|
parser.parsingErrorCode = error.parsingErrorCode;
|
|
282
282
|
module.exports = parser;
|
|
283
283
|
})))(), 1);
|
|
284
|
+
const buildJsonEachInClause = (columnName, values, params) => {
|
|
285
|
+
if (values.length === 0) return "1 = 0";
|
|
286
|
+
params.push(JSON.stringify(values));
|
|
287
|
+
return `${columnName} IN (SELECT value FROM json_each(?))`;
|
|
288
|
+
};
|
|
284
289
|
async function resolvePage(singlePage) {
|
|
285
290
|
const results = [];
|
|
286
291
|
for await (const page of singlePage.iterPages()) {
|
|
@@ -304,11 +309,7 @@ function buildWhereClause(conditions) {
|
|
|
304
309
|
clauses.push("enabled = ?");
|
|
305
310
|
params.push(conditions.enabled ? 1 : 0);
|
|
306
311
|
}
|
|
307
|
-
if (conditions.id?.in)
|
|
308
|
-
else {
|
|
309
|
-
clauses.push(`id IN (${conditions.id.in.map(() => "?").join(", ")})`);
|
|
310
|
-
params.push(...conditions.id.in);
|
|
311
|
-
}
|
|
312
|
+
if (conditions.id?.in) clauses.push(buildJsonEachInClause("id", conditions.id.in, params));
|
|
312
313
|
if (conditions.id?.eq) {
|
|
313
314
|
clauses.push("id = ?");
|
|
314
315
|
params.push(conditions.id.eq);
|
|
@@ -335,11 +336,7 @@ function buildWhereClause(conditions) {
|
|
|
335
336
|
clauses.push("target_app_version = ?");
|
|
336
337
|
params.push(conditions.targetAppVersion);
|
|
337
338
|
}
|
|
338
|
-
if (conditions.targetAppVersionIn)
|
|
339
|
-
else {
|
|
340
|
-
clauses.push(`target_app_version IN (${conditions.targetAppVersionIn.map(() => "?").join(", ")})`);
|
|
341
|
-
params.push(...conditions.targetAppVersionIn);
|
|
342
|
-
}
|
|
339
|
+
if (conditions.targetAppVersionIn) clauses.push(buildJsonEachInClause("target_app_version", conditions.targetAppVersionIn, params));
|
|
343
340
|
if (conditions.fingerprintHash !== void 0) if (conditions.fingerprintHash === null) clauses.push("fingerprint_hash IS NULL");
|
|
344
341
|
else {
|
|
345
342
|
clauses.push("fingerprint_hash = ?");
|
|
@@ -424,13 +421,13 @@ const d1Database = (0, _hot_updater_plugin_core.createDatabasePlugin)({
|
|
|
424
421
|
const sql = (0, import_lib.default)(`
|
|
425
422
|
SELECT *
|
|
426
423
|
FROM bundle_patches
|
|
427
|
-
WHERE bundle_id IN (
|
|
424
|
+
WHERE bundle_id IN (SELECT value FROM json_each(?))
|
|
428
425
|
ORDER BY order_index ASC, base_bundle_id ASC
|
|
429
426
|
`);
|
|
430
427
|
const rows = await resolvePage(await cf.d1.database.query(config.databaseId, {
|
|
431
428
|
account_id: config.accountId,
|
|
432
429
|
sql,
|
|
433
|
-
params: bundleIds
|
|
430
|
+
params: [JSON.stringify(bundleIds)]
|
|
434
431
|
}));
|
|
435
432
|
for (const row of rows) {
|
|
436
433
|
const current = patchMap.get(row.bundle_id) ?? [];
|
package/dist/index.mjs
CHANGED
|
@@ -276,6 +276,11 @@ var import_lib = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((export
|
|
|
276
276
|
parser.parsingErrorCode = error.parsingErrorCode;
|
|
277
277
|
module.exports = parser;
|
|
278
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
|
+
};
|
|
279
284
|
async function resolvePage(singlePage) {
|
|
280
285
|
const results = [];
|
|
281
286
|
for await (const page of singlePage.iterPages()) {
|
|
@@ -299,11 +304,7 @@ function buildWhereClause(conditions) {
|
|
|
299
304
|
clauses.push("enabled = ?");
|
|
300
305
|
params.push(conditions.enabled ? 1 : 0);
|
|
301
306
|
}
|
|
302
|
-
if (conditions.id?.in)
|
|
303
|
-
else {
|
|
304
|
-
clauses.push(`id IN (${conditions.id.in.map(() => "?").join(", ")})`);
|
|
305
|
-
params.push(...conditions.id.in);
|
|
306
|
-
}
|
|
307
|
+
if (conditions.id?.in) clauses.push(buildJsonEachInClause("id", conditions.id.in, params));
|
|
307
308
|
if (conditions.id?.eq) {
|
|
308
309
|
clauses.push("id = ?");
|
|
309
310
|
params.push(conditions.id.eq);
|
|
@@ -330,11 +331,7 @@ function buildWhereClause(conditions) {
|
|
|
330
331
|
clauses.push("target_app_version = ?");
|
|
331
332
|
params.push(conditions.targetAppVersion);
|
|
332
333
|
}
|
|
333
|
-
if (conditions.targetAppVersionIn)
|
|
334
|
-
else {
|
|
335
|
-
clauses.push(`target_app_version IN (${conditions.targetAppVersionIn.map(() => "?").join(", ")})`);
|
|
336
|
-
params.push(...conditions.targetAppVersionIn);
|
|
337
|
-
}
|
|
334
|
+
if (conditions.targetAppVersionIn) clauses.push(buildJsonEachInClause("target_app_version", conditions.targetAppVersionIn, params));
|
|
338
335
|
if (conditions.fingerprintHash !== void 0) if (conditions.fingerprintHash === null) clauses.push("fingerprint_hash IS NULL");
|
|
339
336
|
else {
|
|
340
337
|
clauses.push("fingerprint_hash = ?");
|
|
@@ -419,13 +416,13 @@ const d1Database = createDatabasePlugin({
|
|
|
419
416
|
const sql = (0, import_lib.default)(`
|
|
420
417
|
SELECT *
|
|
421
418
|
FROM bundle_patches
|
|
422
|
-
WHERE bundle_id IN (
|
|
419
|
+
WHERE bundle_id IN (SELECT value FROM json_each(?))
|
|
423
420
|
ORDER BY order_index ASC, base_bundle_id ASC
|
|
424
421
|
`);
|
|
425
422
|
const rows = await resolvePage(await cf.d1.database.query(config.databaseId, {
|
|
426
423
|
account_id: config.accountId,
|
|
427
424
|
sql,
|
|
428
|
-
params: bundleIds
|
|
425
|
+
params: [JSON.stringify(bundleIds)]
|
|
429
426
|
}));
|
|
430
427
|
for (const row of rows) {
|
|
431
428
|
const current = patchMap.get(row.bundle_id) ?? [];
|
package/dist/worker/index.cjs
CHANGED
|
@@ -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)
|
|
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)
|
|
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 (
|
|
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);
|
package/dist/worker/index.mjs
CHANGED
|
@@ -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)
|
|
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)
|
|
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 (
|
|
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.
|
|
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",
|
|
@@ -53,11 +53,11 @@
|
|
|
53
53
|
"cloudflare": "4.2.0",
|
|
54
54
|
"hono": "4.12.9",
|
|
55
55
|
"uuidv7": "^1.0.2",
|
|
56
|
-
"@hot-updater/core": "0.
|
|
57
|
-
"@hot-updater/js": "0.
|
|
58
|
-
"@hot-updater/
|
|
59
|
-
"@hot-updater/
|
|
60
|
-
"@hot-updater/
|
|
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"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
63
|
"@cloudflare/vitest-pool-workers": "0.13.0",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"vitest": "4.1.4",
|
|
75
75
|
"wrangler": "^4.5.0",
|
|
76
76
|
"xdg-app-paths": "^8.3.0",
|
|
77
|
-
"@hot-updater/test-utils": "0.
|
|
77
|
+
"@hot-updater/test-utils": "0.33.0"
|
|
78
78
|
},
|
|
79
79
|
"scripts": {
|
|
80
80
|
"build": "tsdown && pnpm build:worker",
|
|
@@ -0,0 +1,260 @@
|
|
|
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
|
+
});
|
|
@@ -52,6 +52,19 @@ interface BuildQueryResult {
|
|
|
52
52
|
params: unknown[];
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
const buildJsonEachInClause = (
|
|
56
|
+
columnName: string,
|
|
57
|
+
values: string[],
|
|
58
|
+
params: unknown[],
|
|
59
|
+
) => {
|
|
60
|
+
if (values.length === 0) {
|
|
61
|
+
return "1 = 0";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
params.push(JSON.stringify(values));
|
|
65
|
+
return `${columnName} IN (SELECT value FROM json_each(?))`;
|
|
66
|
+
};
|
|
67
|
+
|
|
55
68
|
interface D1WorkerBundleRow {
|
|
56
69
|
id: string;
|
|
57
70
|
channel: string;
|
|
@@ -108,12 +121,7 @@ function buildWhereClause(
|
|
|
108
121
|
}
|
|
109
122
|
|
|
110
123
|
if (conditions.id?.in) {
|
|
111
|
-
|
|
112
|
-
clauses.push("1 = 0");
|
|
113
|
-
} else {
|
|
114
|
-
clauses.push(`id IN (${conditions.id.in.map(() => "?").join(", ")})`);
|
|
115
|
-
params.push(...conditions.id.in);
|
|
116
|
-
}
|
|
124
|
+
clauses.push(buildJsonEachInClause("id", conditions.id.in, params));
|
|
117
125
|
}
|
|
118
126
|
|
|
119
127
|
if (conditions.id?.eq) {
|
|
@@ -155,16 +163,13 @@ function buildWhereClause(
|
|
|
155
163
|
}
|
|
156
164
|
|
|
157
165
|
if (conditions.targetAppVersionIn) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
);
|
|
166
|
-
params.push(...conditions.targetAppVersionIn);
|
|
167
|
-
}
|
|
166
|
+
clauses.push(
|
|
167
|
+
buildJsonEachInClause(
|
|
168
|
+
"target_app_version",
|
|
169
|
+
conditions.targetAppVersionIn,
|
|
170
|
+
params,
|
|
171
|
+
),
|
|
172
|
+
);
|
|
168
173
|
}
|
|
169
174
|
|
|
170
175
|
if (conditions.fingerprintHash !== undefined) {
|
|
@@ -335,15 +340,14 @@ export const d1WorkerDatabase = <
|
|
|
335
340
|
return patchMap;
|
|
336
341
|
}
|
|
337
342
|
|
|
338
|
-
const placeholders = bundleIds.map(() => "?").join(", ");
|
|
339
343
|
const rows = await queryAll<D1WorkerBundlePatchRow>(
|
|
340
344
|
`
|
|
341
345
|
SELECT *
|
|
342
346
|
FROM bundle_patches
|
|
343
|
-
WHERE bundle_id IN (
|
|
347
|
+
WHERE bundle_id IN (SELECT value FROM json_each(?))
|
|
344
348
|
ORDER BY order_index ASC, base_bundle_id ASC
|
|
345
349
|
`,
|
|
346
|
-
bundleIds,
|
|
350
|
+
[JSON.stringify(bundleIds)],
|
|
347
351
|
context,
|
|
348
352
|
);
|
|
349
353
|
|
package/src/d1Database.spec.ts
CHANGED
|
@@ -64,7 +64,13 @@ const getFilteredRows = (sql: string, params: any[]) => {
|
|
|
64
64
|
return null;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
const
|
|
67
|
+
const body = match[1] ?? "";
|
|
68
|
+
if (body.includes("json_each(")) {
|
|
69
|
+
const values = JSON.parse(String(params[index++])) as unknown;
|
|
70
|
+
return Array.isArray(values) ? values : [];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const count = (body.match(/\?/g) ?? []).length;
|
|
68
74
|
const values = params.slice(index, index + count);
|
|
69
75
|
index += count;
|
|
70
76
|
return values;
|
|
@@ -169,6 +175,12 @@ vi.mock("cloudflare", () => ({
|
|
|
169
175
|
params?: any[];
|
|
170
176
|
},
|
|
171
177
|
) => {
|
|
178
|
+
if (params.length > 100) {
|
|
179
|
+
throw new Error(
|
|
180
|
+
"D1_ERROR: too many SQL variables at offset 386: SQLITE_ERROR",
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
172
184
|
const normalizedSql = sql.replace(/\s+/g, " ").trim().toLowerCase();
|
|
173
185
|
|
|
174
186
|
if (
|
|
@@ -190,7 +202,9 @@ vi.mock("cloudflare", () => ({
|
|
|
190
202
|
)
|
|
191
203
|
) {
|
|
192
204
|
const selectedBundleIds = new Set(
|
|
193
|
-
|
|
205
|
+
normalizedSql.includes("json_each")
|
|
206
|
+
? (JSON.parse(String(params[0])) as unknown[]).map(String)
|
|
207
|
+
: params.map((value) => String(value)),
|
|
194
208
|
);
|
|
195
209
|
const result = Array.from(patchRows.values())
|
|
196
210
|
.filter((row) => selectedBundleIds.has(row.bundle_id))
|