@hot-updater/cloudflare 1.0.0-rc.2 → 1.0.0-rc.3
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 +281 -161
- package/dist/iac/index.d.cts +1 -5
- package/dist/iac/index.d.mts +1 -5
- package/dist/iac/index.mjs +277 -157
- package/dist/index.cjs +69 -14
- package/dist/index.d.cts +1 -3
- package/dist/index.d.mts +1 -3
- package/dist/index.mjs +67 -12
- package/dist/worker/index.cjs +71 -15
- package/dist/worker/index.d.cts +2 -3
- package/dist/worker/index.d.mts +2 -3
- package/dist/worker/index.mjs +71 -15
- package/package.json +6 -6
- package/sql/bundles.sql +29 -7
- package/src/d1Database.spec.ts +99 -0
- package/src/d1Database.ts +7 -1
- package/src/d1Implementation.spec.ts +90 -0
- package/src/d1Implementation.ts +42 -3
- package/src/d1Query.ts +3 -0
- package/src/d1Rows.spec.ts +32 -0
- package/src/d1Rows.ts +35 -0
- package/src/worker/d1Database.spec.ts +36 -0
- package/src/worker/d1Database.ts +9 -1
- package/worker/dist/README.md +1 -1
- package/worker/dist/index.js +997 -659
- package/worker/dist/index.js.map +4 -4
- package/worker/migrations/0001_hot-updater_1.0.0.sql +30 -13
- package/worker/src/databaseMigration.integration.spec.ts +62 -1
- package/worker/src/databasePlugins.integration.spec.ts +35 -1
- package/worker/src/insightsMovement.integration.spec.ts +73 -0
package/src/d1Rows.spec.ts
CHANGED
|
@@ -6,6 +6,20 @@ import {
|
|
|
6
6
|
} from "../../../packages/test-utils/src/databaseTestFixtures";
|
|
7
7
|
import { parseD1Row } from "./d1Rows";
|
|
8
8
|
|
|
9
|
+
const installationD1Row = {
|
|
10
|
+
id: "event-1",
|
|
11
|
+
install_id: "install-1",
|
|
12
|
+
user_id: "user-1",
|
|
13
|
+
username: "Demo User",
|
|
14
|
+
to_bundle_id: "bundle-1",
|
|
15
|
+
type: "UPDATE_APPLIED",
|
|
16
|
+
platform: "ios",
|
|
17
|
+
app_version: "1.0.0",
|
|
18
|
+
channel: "production",
|
|
19
|
+
cohort: "cohort-1",
|
|
20
|
+
received_at_ms: 100,
|
|
21
|
+
} as const;
|
|
22
|
+
|
|
9
23
|
const bundleD1Row = {
|
|
10
24
|
id: "bundle-1",
|
|
11
25
|
platform: "android",
|
|
@@ -109,3 +123,21 @@ it.each([
|
|
|
109
123
|
}),
|
|
110
124
|
).toThrow("D1 returned an invalid bundle_events row");
|
|
111
125
|
});
|
|
126
|
+
|
|
127
|
+
it("parses a current Insights installation", () => {
|
|
128
|
+
expect(parseD1Row("bundle_installations", installationD1Row)).toEqual(
|
|
129
|
+
installationD1Row,
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it.each([{ type: "UNKNOWN" }, { platform: "web" }])(
|
|
134
|
+
"rejects an invalid current Insights installation",
|
|
135
|
+
(overrides) => {
|
|
136
|
+
expect(() =>
|
|
137
|
+
parseD1Row("bundle_installations", {
|
|
138
|
+
...installationD1Row,
|
|
139
|
+
...overrides,
|
|
140
|
+
}),
|
|
141
|
+
).toThrow("D1 returned an invalid bundle_installations row");
|
|
142
|
+
},
|
|
143
|
+
);
|
package/src/d1Rows.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type {
|
|
|
3
3
|
BundleRow,
|
|
4
4
|
BundleEventRow,
|
|
5
5
|
ChannelRow,
|
|
6
|
+
InsightsInstallationRow,
|
|
6
7
|
ApiKeyRow,
|
|
7
8
|
ReleaseCatalogRow,
|
|
8
9
|
ReleaseRow,
|
|
@@ -188,6 +189,34 @@ const eventRow = (row: Record<string, unknown>): BundleEventRow => {
|
|
|
188
189
|
} as BundleEventRow;
|
|
189
190
|
};
|
|
190
191
|
|
|
192
|
+
const installationRow = (
|
|
193
|
+
row: Record<string, unknown>,
|
|
194
|
+
): InsightsInstallationRow => {
|
|
195
|
+
const type = stringValue(row, "type", "bundle_installations");
|
|
196
|
+
const platform = stringValue(row, "platform", "bundle_installations");
|
|
197
|
+
if (
|
|
198
|
+
!["UPDATE_APPLIED", "RECOVERED", "RELEASE_ADOPTED", "UNCHANGED"].includes(
|
|
199
|
+
type,
|
|
200
|
+
) ||
|
|
201
|
+
(platform !== "ios" && platform !== "android")
|
|
202
|
+
) {
|
|
203
|
+
throw new InvalidD1RowError("bundle_installations");
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
id: stringValue(row, "id", "bundle_installations"),
|
|
207
|
+
install_id: stringValue(row, "install_id", "bundle_installations"),
|
|
208
|
+
user_id: nullableString(row, "user_id", "bundle_installations"),
|
|
209
|
+
username: nullableString(row, "username", "bundle_installations"),
|
|
210
|
+
to_bundle_id: stringValue(row, "to_bundle_id", "bundle_installations"),
|
|
211
|
+
type: type as InsightsInstallationRow["type"],
|
|
212
|
+
platform,
|
|
213
|
+
app_version: stringValue(row, "app_version", "bundle_installations"),
|
|
214
|
+
channel: stringValue(row, "channel", "bundle_installations"),
|
|
215
|
+
cohort: stringValue(row, "cohort", "bundle_installations"),
|
|
216
|
+
received_at_ms: numberValue(row, "received_at_ms", "bundle_installations"),
|
|
217
|
+
};
|
|
218
|
+
};
|
|
219
|
+
|
|
191
220
|
const apiKeyRow = (row: Record<string, unknown>): ApiKeyRow => {
|
|
192
221
|
const role = stringValue(row, "role", "api_keys");
|
|
193
222
|
if (role !== "client") throw new InvalidD1RowError("api_keys");
|
|
@@ -292,6 +321,10 @@ export function parseD1Row(
|
|
|
292
321
|
model: "bundle_events",
|
|
293
322
|
value: unknown,
|
|
294
323
|
): BundleEventRow;
|
|
324
|
+
export function parseD1Row(
|
|
325
|
+
model: "bundle_installations",
|
|
326
|
+
value: unknown,
|
|
327
|
+
): InsightsInstallationRow;
|
|
295
328
|
export function parseD1Row(model: "api_keys", value: unknown): ApiKeyRow;
|
|
296
329
|
export function parseD1Row(model: "releases", value: unknown): ReleaseRow;
|
|
297
330
|
export function parseD1Row(
|
|
@@ -316,6 +349,8 @@ export function parseD1Row(
|
|
|
316
349
|
return channelRow(value);
|
|
317
350
|
case "bundle_events":
|
|
318
351
|
return eventRow(value);
|
|
352
|
+
case "bundle_installations":
|
|
353
|
+
return installationRow(value);
|
|
319
354
|
case "api_keys":
|
|
320
355
|
return apiKeyRow(value);
|
|
321
356
|
case "releases":
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { toInsightsInstallationRow } from "@hot-updater/plugin-core";
|
|
1
2
|
import { expect, it } from "vitest";
|
|
2
3
|
|
|
4
|
+
import { createBundleEventRowFixture } from "../../../../packages/test-utils/src/databaseTestFixtures";
|
|
3
5
|
import { d1Database, type D1Like } from "./d1Database";
|
|
4
6
|
|
|
5
7
|
const database: D1Like = {
|
|
@@ -31,3 +33,37 @@ it("uses the D1 binding supplied at the Worker composition boundary", async () =
|
|
|
31
33
|
channels: [{ id: "channel-production", name: "production" }],
|
|
32
34
|
});
|
|
33
35
|
});
|
|
36
|
+
|
|
37
|
+
it("does not report a failed D1 query as empty Insights history", async () => {
|
|
38
|
+
const plugin = d1Database({
|
|
39
|
+
...database,
|
|
40
|
+
prepare: () => ({
|
|
41
|
+
bind: () => ({ all: async () => ({ success: false, results: [] }) }),
|
|
42
|
+
}),
|
|
43
|
+
});
|
|
44
|
+
await expect(
|
|
45
|
+
plugin.models.insights.listEvents({
|
|
46
|
+
filter: { kind: "all" },
|
|
47
|
+
beforeReceivedAtMs: 100,
|
|
48
|
+
limit: 10,
|
|
49
|
+
}),
|
|
50
|
+
).rejects.toThrow(
|
|
51
|
+
"D1 did not successfully execute every requested statement",
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("rejects an incomplete Insights batch response", async () => {
|
|
56
|
+
const plugin = d1Database({
|
|
57
|
+
...database,
|
|
58
|
+
batch: async () => [{ success: true, results: [] }],
|
|
59
|
+
});
|
|
60
|
+
const event = createBundleEventRowFixture("1", 1);
|
|
61
|
+
await expect(
|
|
62
|
+
plugin.models.insights.record({
|
|
63
|
+
event,
|
|
64
|
+
installation: toInsightsInstallationRow(event),
|
|
65
|
+
}),
|
|
66
|
+
).rejects.toThrow(
|
|
67
|
+
"D1 did not successfully execute every requested statement",
|
|
68
|
+
);
|
|
69
|
+
});
|
package/src/worker/d1Database.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { createDatabasePlugin } from "@hot-updater/plugin-core";
|
|
2
2
|
import { createDatabasePluginAdapter } from "@hot-updater/plugin-core/internal";
|
|
3
3
|
|
|
4
|
-
import { createD1Implementation } from "../d1Implementation";
|
|
4
|
+
import { createD1Implementation, D1ExecutionError } from "../d1Implementation";
|
|
5
5
|
|
|
6
6
|
type D1Result = {
|
|
7
7
|
readonly results?: readonly unknown[];
|
|
8
|
+
readonly success?: boolean;
|
|
8
9
|
};
|
|
9
10
|
|
|
10
11
|
type D1BoundStatement = {
|
|
@@ -31,6 +32,7 @@ export const d1Database = (database: D1Like) => {
|
|
|
31
32
|
.prepare(sql)
|
|
32
33
|
.bind(...params)
|
|
33
34
|
.all();
|
|
35
|
+
if (result.success === false) throw new D1ExecutionError();
|
|
34
36
|
return result.results ?? [];
|
|
35
37
|
},
|
|
36
38
|
async batch(statements) {
|
|
@@ -39,6 +41,12 @@ export const d1Database = (database: D1Like) => {
|
|
|
39
41
|
database.prepare(sql).bind(...params),
|
|
40
42
|
),
|
|
41
43
|
);
|
|
44
|
+
if (
|
|
45
|
+
results.length !== statements.length ||
|
|
46
|
+
results.some(({ success }) => success === false)
|
|
47
|
+
) {
|
|
48
|
+
throw new D1ExecutionError();
|
|
49
|
+
}
|
|
42
50
|
return results.map(({ results }) => results ?? []);
|
|
43
51
|
},
|
|
44
52
|
});
|
package/worker/dist/README.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
This folder contains the built output assets for the worker "hot-updater" generated at 2026-
|
|
1
|
+
This folder contains the built output assets for the worker "hot-updater" generated at 2026-09-07T08:27:12.590Z.
|