@hot-updater/cloudflare 1.0.0-rc.2 → 1.0.0-rc.21
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 +95 -870
- package/dist/index.d.cts +724 -6
- package/dist/index.d.mts +724 -6
- package/dist/index.mjs +93 -870
- package/dist/init/index.cjs +11768 -0
- package/dist/init/index.d.cts +5 -1
- package/dist/init/index.d.mts +5 -1
- package/dist/init/index.mjs +11761 -1
- package/dist/worker/index.cjs +59 -859
- package/dist/worker/index.d.cts +725 -11
- package/dist/worker/index.d.mts +725 -11
- package/dist/worker/index.mjs +62 -863
- package/package.json +9 -13
- package/sql/bundles.sql +109 -190
- package/src/d1Database.ts +61 -40
- package/src/d1Executor.ts +84 -0
- package/src/d1Schema.ts +26 -0
- package/src/index.ts +1 -0
- package/src/plugins.ts +8 -0
- package/src/r2S3Storage.ts +4 -4
- package/src/r2Storage.ts +2 -2
- package/src/r2WorkerStorage.ts +4 -4
- package/src/worker/d1Database.ts +21 -27
- package/src/worker/index.ts +1 -0
- package/worker/dist/README.md +1 -1
- package/worker/dist/index.js +9682 -6206
- package/worker/dist/index.js.map +4 -4
- package/worker/migrations/0001_hot-updater_1.0.0.sql +109 -205
- package/worker/src/index.ts +2 -2
- package/dist/iac/index.cjs +0 -12566
- package/dist/iac/index.d.cts +0 -9
- package/dist/iac/index.d.mts +0 -9
- package/dist/iac/index.mjs +0 -12558
- package/src/d1Database.spec.ts +0 -178
- package/src/d1Implementation.spec.ts +0 -252
- package/src/d1Implementation.ts +0 -742
- package/src/d1Query.spec.ts +0 -46
- package/src/d1Query.ts +0 -89
- package/src/d1Rows.spec.ts +0 -111
- package/src/d1Rows.ts +0 -326
- package/src/d1Sql.spec.ts +0 -30
- package/src/d1Sql.ts +0 -175
- package/src/r2Storage.spec.ts +0 -346
- package/src/r2WorkerStorage.spec.ts +0 -184
- package/src/utils/createWrangler.spec.ts +0 -75
- package/src/worker/d1Database.spec.ts +0 -33
- package/worker/src/databaseMigration.integration.spec.ts +0 -130
- package/worker/src/databasePlugins.integration.spec.ts +0 -292
- package/worker/src/index.integration.spec.ts +0 -190
- package/worker/src/storagePlugin.integration.spec.ts +0 -59
- package/worker/src/testWorker.ts +0 -5
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type Bundle,
|
|
3
|
-
createReleaseCatalogScopeKey,
|
|
4
|
-
encodeChannelKey,
|
|
5
|
-
} from "@hot-updater/core";
|
|
6
|
-
import {
|
|
7
|
-
commitReleaseCatalogMutations,
|
|
8
|
-
createUUIDv7,
|
|
9
|
-
} from "@hot-updater/plugin-core";
|
|
10
|
-
import { createHotUpdater, registerApiKey } from "@hot-updater/server";
|
|
11
|
-
import { env } from "cloudflare:test";
|
|
12
|
-
import { beforeAll, beforeEach, describe, expect, inject, it } from "vitest";
|
|
13
|
-
|
|
14
|
-
import { d1Database } from "../../src/worker";
|
|
15
|
-
import worker, { HOT_UPDATER_BASE_PATH } from "./index";
|
|
16
|
-
|
|
17
|
-
declare module "vitest" {
|
|
18
|
-
export interface ProvidedContext {
|
|
19
|
-
prepareSql: string;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
declare module "cloudflare:test" {
|
|
24
|
-
interface ProvidedEnv {
|
|
25
|
-
DB: D1Database;
|
|
26
|
-
BUCKET: R2Bucket;
|
|
27
|
-
BUCKET_NAME: string;
|
|
28
|
-
STORAGE_DOWNLOAD_URL_SIGNING_KEY: string;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const PUBLIC_BASE_URL = "https://updates.example.com";
|
|
33
|
-
const API_KEY = "AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQE";
|
|
34
|
-
|
|
35
|
-
const toRuntimeBundle = (bundle: Bundle): Bundle => {
|
|
36
|
-
return {
|
|
37
|
-
...bundle,
|
|
38
|
-
storageUri: `r2://${env.BUCKET_NAME}/${bundle.id}/bundle.zip`,
|
|
39
|
-
};
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
const seedBundles = async (bundles: Bundle[]) => {
|
|
43
|
-
const database = d1Database(env.DB);
|
|
44
|
-
const seedHotUpdater = createHotUpdater({
|
|
45
|
-
database,
|
|
46
|
-
clientAccess: { type: "public" },
|
|
47
|
-
});
|
|
48
|
-
for (const bundle of bundles.map(toRuntimeBundle)) {
|
|
49
|
-
const existing = await seedHotUpdater.getBundleById(bundle.id);
|
|
50
|
-
if (existing === null) {
|
|
51
|
-
await seedHotUpdater.insertBundle(bundle);
|
|
52
|
-
} else {
|
|
53
|
-
await seedHotUpdater.updateBundleById(bundle.id, bundle);
|
|
54
|
-
}
|
|
55
|
-
const channelName = "production";
|
|
56
|
-
const channelKey = encodeChannelKey(channelName);
|
|
57
|
-
const channel = (
|
|
58
|
-
await database.models.channels.insert({
|
|
59
|
-
row: { id: `channel:${channelKey}`, name: channelName },
|
|
60
|
-
onConflict: "returnExisting",
|
|
61
|
-
})
|
|
62
|
-
).row;
|
|
63
|
-
const scopeKey = createReleaseCatalogScopeKey({
|
|
64
|
-
channelKey,
|
|
65
|
-
platform: bundle.platform,
|
|
66
|
-
strategy: "APP_VERSION",
|
|
67
|
-
});
|
|
68
|
-
const now = Date.now();
|
|
69
|
-
const releaseId = createUUIDv7();
|
|
70
|
-
await commitReleaseCatalogMutations({
|
|
71
|
-
database,
|
|
72
|
-
mutations: [
|
|
73
|
-
{
|
|
74
|
-
mutation: {
|
|
75
|
-
operation: "insert",
|
|
76
|
-
row: {
|
|
77
|
-
bundle_id: bundle.id,
|
|
78
|
-
channel_id: channel.id,
|
|
79
|
-
created_at_ms: now,
|
|
80
|
-
enabled: true,
|
|
81
|
-
fingerprint_hash: null,
|
|
82
|
-
id: releaseId,
|
|
83
|
-
kind: "BUNDLE",
|
|
84
|
-
message: "hello",
|
|
85
|
-
operation: "DEPLOY",
|
|
86
|
-
platform: bundle.platform,
|
|
87
|
-
revision: 1,
|
|
88
|
-
rollout_cohort_count: 1_000,
|
|
89
|
-
scope_key: scopeKey,
|
|
90
|
-
should_force_update: false,
|
|
91
|
-
source_release_id: null,
|
|
92
|
-
strategy: "APP_VERSION",
|
|
93
|
-
target_app_version: "1.0",
|
|
94
|
-
target_cohorts: [],
|
|
95
|
-
updated_at_ms: now,
|
|
96
|
-
},
|
|
97
|
-
},
|
|
98
|
-
scope: {
|
|
99
|
-
channelId: channel.id,
|
|
100
|
-
channelName,
|
|
101
|
-
fingerprintHash: null,
|
|
102
|
-
platform: bundle.platform,
|
|
103
|
-
scopeKey,
|
|
104
|
-
strategy: "APP_VERSION",
|
|
105
|
-
},
|
|
106
|
-
updatedAtMs: now,
|
|
107
|
-
},
|
|
108
|
-
],
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
describe.sequential("cloudflare worker runtime acceptance", () => {
|
|
114
|
-
beforeAll(async () => {
|
|
115
|
-
await env.DB.prepare(inject("prepareSql")).run();
|
|
116
|
-
const database = d1Database(env.DB);
|
|
117
|
-
await registerApiKey({
|
|
118
|
-
apiKey: API_KEY,
|
|
119
|
-
apiKeys: database.models.apiKeys,
|
|
120
|
-
name: "Runtime acceptance",
|
|
121
|
-
});
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
beforeEach(async () => {
|
|
125
|
-
await env.DB.prepare("DELETE FROM bundle_patches").run();
|
|
126
|
-
await env.DB.prepare("DELETE FROM release_catalogs").run();
|
|
127
|
-
await env.DB.prepare("DELETE FROM releases").run();
|
|
128
|
-
await env.DB.prepare("DELETE FROM bundles").run();
|
|
129
|
-
await env.DB.prepare("DELETE FROM channels").run();
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
it("serves unversioned Release Catalog routes from the worker entrypoint", async () => {
|
|
133
|
-
expect(HOT_UPDATER_BASE_PATH).toBe("/");
|
|
134
|
-
await seedBundles([
|
|
135
|
-
{
|
|
136
|
-
id: "00000000-0000-0000-0000-000000000001",
|
|
137
|
-
platform: "ios",
|
|
138
|
-
fileHash: "hash",
|
|
139
|
-
gitCommitHash: null,
|
|
140
|
-
storageUri: "storage://unused",
|
|
141
|
-
archiveByteSize: 3_000_000_001,
|
|
142
|
-
},
|
|
143
|
-
]);
|
|
144
|
-
|
|
145
|
-
const unauthorized = await worker.fetch(
|
|
146
|
-
new Request(
|
|
147
|
-
`${PUBLIC_BASE_URL}/release-catalogs/app-version/ios/cHJvZHVjdGlvbg/1.0.0`,
|
|
148
|
-
),
|
|
149
|
-
env,
|
|
150
|
-
);
|
|
151
|
-
expect(unauthorized.status).toBe(401);
|
|
152
|
-
|
|
153
|
-
const response = await worker.fetch(
|
|
154
|
-
new Request(
|
|
155
|
-
`${PUBLIC_BASE_URL}/release-catalogs/app-version/ios/cHJvZHVjdGlvbg/1.0.0`,
|
|
156
|
-
{ headers: { "x-api-key": API_KEY } },
|
|
157
|
-
),
|
|
158
|
-
env,
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
await expect(response.json()).resolves.toMatchObject({
|
|
162
|
-
catalogId: expect.stringMatching(/^[0-9a-f-]{36}$/),
|
|
163
|
-
releases: [{ bundleId: "00000000-0000-0000-0000-000000000001" }],
|
|
164
|
-
});
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
it("does not support the legacy exact path", async () => {
|
|
168
|
-
const response = await worker.fetch(
|
|
169
|
-
new Request(`${PUBLIC_BASE_URL}/api/check-update`),
|
|
170
|
-
env,
|
|
171
|
-
);
|
|
172
|
-
|
|
173
|
-
expect(response.status).toBe(404);
|
|
174
|
-
await expect(response.json()).resolves.toEqual({
|
|
175
|
-
error: "Not found",
|
|
176
|
-
});
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
it("does not expose management routes from the worker entrypoint", async () => {
|
|
180
|
-
const response = await worker.fetch(
|
|
181
|
-
new Request(`${PUBLIC_BASE_URL}/admin/bundles`),
|
|
182
|
-
env,
|
|
183
|
-
);
|
|
184
|
-
|
|
185
|
-
expect(response.status).toBe(404);
|
|
186
|
-
await expect(response.json()).resolves.toEqual({
|
|
187
|
-
error: "Not found",
|
|
188
|
-
});
|
|
189
|
-
});
|
|
190
|
-
});
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { env } from "cloudflare:test";
|
|
2
|
-
import { afterEach, describe, expect, it } from "vitest";
|
|
3
|
-
|
|
4
|
-
import { r2Storage } from "../../src/worker";
|
|
5
|
-
|
|
6
|
-
const UNKNOWN_LENGTH_KEY = "storage-tests/unknown-length.txt";
|
|
7
|
-
const KNOWN_LENGTH_KEY = "storage-tests/known-length.txt";
|
|
8
|
-
|
|
9
|
-
const createChunkedBody = (...chunks: string[]) =>
|
|
10
|
-
new ReadableStream<Uint8Array>({
|
|
11
|
-
start(controller) {
|
|
12
|
-
for (const chunk of chunks) {
|
|
13
|
-
controller.enqueue(new TextEncoder().encode(chunk));
|
|
14
|
-
}
|
|
15
|
-
controller.close();
|
|
16
|
-
},
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
const createStorage = () =>
|
|
20
|
-
r2Storage({
|
|
21
|
-
bucket: env.BUCKET,
|
|
22
|
-
bucketName: env.BUCKET_NAME,
|
|
23
|
-
downloadUrlSigningKey: env.STORAGE_DOWNLOAD_URL_SIGNING_KEY,
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
afterEach(async () => {
|
|
27
|
-
await env.BUCKET.delete([UNKNOWN_LENGTH_KEY, KNOWN_LENGTH_KEY]);
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
describe("Cloudflare Worker R2 storage uploads", () => {
|
|
31
|
-
it("writes exact bytes from an unknown-length stream", async () => {
|
|
32
|
-
const storage = createStorage();
|
|
33
|
-
|
|
34
|
-
await storage.put({
|
|
35
|
-
key: UNKNOWN_LENGTH_KEY,
|
|
36
|
-
body: createChunkedBody("unknown", "-length"),
|
|
37
|
-
contentType: "text/plain",
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
await expect(
|
|
41
|
-
env.BUCKET.get(UNKNOWN_LENGTH_KEY).then((item) => item?.text()),
|
|
42
|
-
).resolves.toBe("unknown-length");
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it("streams known-length bytes through FixedLengthStream", async () => {
|
|
46
|
-
const storage = createStorage();
|
|
47
|
-
|
|
48
|
-
await storage.put({
|
|
49
|
-
key: KNOWN_LENGTH_KEY,
|
|
50
|
-
body: createChunkedBody("known", "-length"),
|
|
51
|
-
contentLength: 12,
|
|
52
|
-
contentType: "text/plain",
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
await expect(
|
|
56
|
-
env.BUCKET.get(KNOWN_LENGTH_KEY).then((item) => item?.text()),
|
|
57
|
-
).resolves.toBe("known-length");
|
|
58
|
-
});
|
|
59
|
-
});
|