@hot-updater/server 0.36.7 → 1.0.0-rc.1

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.
Files changed (291) hide show
  1. package/dist/adapters/databaseConstraintErrors.cjs +23 -0
  2. package/dist/adapters/databaseConstraintErrors.mjs +21 -0
  3. package/dist/adapters/databasePluginUtils.cjs +117 -0
  4. package/dist/adapters/databasePluginUtils.mjs +106 -0
  5. package/dist/adapters/drizzle.cjs +53 -176
  6. package/dist/adapters/drizzle.d.cts +6 -4
  7. package/dist/adapters/drizzle.d.mts +6 -4
  8. package/dist/adapters/drizzle.mjs +54 -177
  9. package/dist/adapters/drizzleCrud.cjs +250 -0
  10. package/dist/adapters/drizzleCrud.mjs +249 -0
  11. package/dist/adapters/drizzleLazyDB.cjs +84 -36
  12. package/dist/adapters/drizzleLazyDB.mjs +84 -36
  13. package/dist/adapters/drizzleQuery.cjs +68 -0
  14. package/dist/adapters/drizzleQuery.mjs +67 -0
  15. package/dist/adapters/kysely.cjs +18 -138
  16. package/dist/adapters/kysely.d.cts +3 -12
  17. package/dist/adapters/kysely.d.mts +3 -12
  18. package/dist/adapters/kysely.mjs +19 -139
  19. package/dist/adapters/kyselyCrud.cjs +254 -0
  20. package/dist/adapters/kyselyCrud.mjs +253 -0
  21. package/dist/adapters/kyselyQuery.cjs +56 -0
  22. package/dist/adapters/kyselyQuery.mjs +55 -0
  23. package/dist/adapters/mongodb.cjs +21 -174
  24. package/dist/adapters/mongodb.d.cts +3 -2
  25. package/dist/adapters/mongodb.d.mts +3 -2
  26. package/dist/adapters/mongodb.mjs +21 -174
  27. package/dist/adapters/mongodbCollections.cjs +36 -0
  28. package/dist/adapters/mongodbCollections.mjs +30 -0
  29. package/dist/adapters/mongodbQuery.cjs +77 -0
  30. package/dist/adapters/mongodbQuery.mjs +70 -0
  31. package/dist/adapters/mongodbReads.cjs +148 -0
  32. package/dist/adapters/mongodbReads.mjs +147 -0
  33. package/dist/adapters/mongodbWrites.cjs +134 -0
  34. package/dist/adapters/mongodbWrites.mjs +133 -0
  35. package/dist/adapters/prisma.cjs +205 -180
  36. package/dist/adapters/prisma.d.cts +3 -3
  37. package/dist/adapters/prisma.d.mts +3 -3
  38. package/dist/adapters/prisma.mjs +206 -181
  39. package/dist/adapters/prismaQuery.cjs +58 -0
  40. package/dist/adapters/prismaQuery.mjs +56 -0
  41. package/dist/adapters/prismaRows.cjs +198 -0
  42. package/dist/adapters/prismaRows.mjs +188 -0
  43. package/dist/apiKeys.cjs +139 -0
  44. package/dist/apiKeys.d.cts +50 -0
  45. package/dist/apiKeys.d.mts +50 -0
  46. package/dist/apiKeys.mjs +133 -0
  47. package/dist/createHotUpdaterCore.cjs +48 -26
  48. package/dist/createHotUpdaterCore.d.cts +49 -17
  49. package/dist/createHotUpdaterCore.d.mts +49 -17
  50. package/dist/createHotUpdaterCore.mjs +51 -29
  51. package/dist/db/createBundleDiff.cjs +45 -68
  52. package/dist/db/createBundleDiff.d.cts +3 -3
  53. package/dist/db/createBundleDiff.d.mts +3 -3
  54. package/dist/db/createBundleDiff.mjs +47 -68
  55. package/dist/db/databasePluginCore.cjs +138 -0
  56. package/dist/db/databasePluginCore.mjs +137 -0
  57. package/dist/db/fixedMigrator.cjs +36 -93
  58. package/dist/db/fixedMigrator.mjs +36 -93
  59. package/dist/db/fixedMigratorMongo.cjs +319 -0
  60. package/dist/db/fixedMigratorMongo.mjs +319 -0
  61. package/dist/db/fixedMigratorShared.cjs +23 -0
  62. package/dist/db/fixedMigratorShared.mjs +18 -0
  63. package/dist/db/mongoMigrationExecution.cjs +11 -0
  64. package/dist/db/mongoMigrationExecution.mjs +11 -0
  65. package/dist/db/releaseCatalog.cjs +66 -0
  66. package/dist/db/releaseCatalog.d.cts +17 -0
  67. package/dist/db/releaseCatalog.d.mts +17 -0
  68. package/dist/db/releaseCatalog.mjs +64 -0
  69. package/dist/db/schema/mongodb.cjs +14 -6
  70. package/dist/db/schema/mongodb.mjs +14 -6
  71. package/dist/db/schema/registry.cjs +0 -2
  72. package/dist/db/schema/registry.mjs +1 -2
  73. package/dist/db/schema/sql.cjs +31 -14
  74. package/dist/db/schema/sql.mjs +32 -13
  75. package/dist/db/schema/sqlOperations.cjs +4 -4
  76. package/dist/db/schema/sqlOperations.mjs +5 -5
  77. package/dist/db/schemaGenerators.cjs +52 -18
  78. package/dist/db/schemaGenerators.mjs +52 -18
  79. package/dist/db/schemaReadiness.cjs +4 -3
  80. package/dist/db/schemaReadiness.mjs +4 -3
  81. package/dist/db/sqlMigrationExecution.cjs +67 -0
  82. package/dist/db/sqlMigrationExecution.mjs +66 -0
  83. package/dist/db/types.cjs +2 -6
  84. package/dist/db/types.d.cts +60 -16
  85. package/dist/db/types.d.mts +60 -16
  86. package/dist/db/types.mjs +3 -6
  87. package/dist/db/updateArtifacts.cjs +114 -60
  88. package/dist/db/updateArtifacts.mjs +117 -62
  89. package/dist/handler.cjs +108 -282
  90. package/dist/handler.d.cts +3 -46
  91. package/dist/handler.d.mts +3 -46
  92. package/dist/handler.mjs +107 -281
  93. package/dist/handlerBundleRoutes.cjs +155 -0
  94. package/dist/handlerBundleRoutes.mjs +155 -0
  95. package/dist/handlerErrors.cjs +9 -0
  96. package/dist/handlerErrors.mjs +9 -0
  97. package/dist/handlerParameters.cjs +39 -0
  98. package/dist/handlerParameters.mjs +34 -0
  99. package/dist/handlerReleaseCatalogRoutes.cjs +111 -0
  100. package/dist/handlerReleaseCatalogRoutes.mjs +110 -0
  101. package/dist/handlerReleaseManagementRoutes.cjs +125 -0
  102. package/dist/handlerReleaseManagementRoutes.mjs +124 -0
  103. package/dist/handlerTypes.d.cts +63 -0
  104. package/dist/handlerTypes.d.mts +63 -0
  105. package/dist/handlerVersionRoutes.cjs +10 -0
  106. package/dist/handlerVersionRoutes.d.cts +4 -0
  107. package/dist/handlerVersionRoutes.d.mts +4 -0
  108. package/dist/handlerVersionRoutes.mjs +9 -0
  109. package/dist/index.cjs +10 -1
  110. package/dist/index.d.cts +9 -3
  111. package/dist/index.d.mts +9 -3
  112. package/dist/index.mjs +5 -2
  113. package/dist/insights/bounded/activeOverview.cjs +87 -0
  114. package/dist/insights/bounded/activeOverview.mjs +85 -0
  115. package/dist/insights/bounded/installationSearch.cjs +42 -0
  116. package/dist/insights/bounded/installationSearch.mjs +42 -0
  117. package/dist/insights/bounded/persistence.cjs +39 -0
  118. package/dist/insights/bounded/persistence.mjs +38 -0
  119. package/dist/insights/bounded/provider.cjs +170 -0
  120. package/dist/insights/bounded/provider.d.cts +7 -0
  121. package/dist/insights/bounded/provider.d.mts +7 -0
  122. package/dist/insights/bounded/provider.mjs +170 -0
  123. package/dist/insights/bounded/scan.cjs +144 -0
  124. package/dist/insights/bounded/scan.mjs +138 -0
  125. package/dist/insights/domain.d.cts +121 -0
  126. package/dist/insights/domain.d.mts +121 -0
  127. package/dist/insights/errors.cjs +22 -0
  128. package/dist/insights/errors.mjs +20 -0
  129. package/dist/insights/eventInput.cjs +111 -0
  130. package/dist/insights/eventInput.mjs +111 -0
  131. package/dist/insights/persistence.d.cts +6 -0
  132. package/dist/insights/persistence.d.mts +6 -0
  133. package/dist/insights/queryInput.cjs +58 -0
  134. package/dist/insights/queryInput.mjs +55 -0
  135. package/dist/insights/routes.cjs +63 -0
  136. package/dist/insights/routes.mjs +61 -0
  137. package/dist/insights/types.d.cts +17 -0
  138. package/dist/insights/types.d.mts +17 -0
  139. package/dist/node.cjs +19 -12
  140. package/dist/node.d.cts +15 -10
  141. package/dist/node.d.mts +15 -10
  142. package/dist/node.mjs +19 -13
  143. package/dist/package.cjs +1 -1
  144. package/dist/package.mjs +1 -1
  145. package/dist/schema/dsl.cjs +19 -2
  146. package/dist/schema/dsl.mjs +17 -3
  147. package/dist/schema/index.cjs +1 -8
  148. package/dist/schema/index.mjs +2 -8
  149. package/dist/schema/types.cjs +3 -1
  150. package/dist/schema/types.mjs +3 -2
  151. package/dist/schema/v1_0_0.cjs +364 -0
  152. package/dist/schema/v1_0_0.mjs +357 -0
  153. package/dist/storageAccess.cjs +69 -25
  154. package/dist/storageAccess.mjs +68 -25
  155. package/dist/types/index.d.cts +4 -3
  156. package/dist/types/index.d.mts +5 -4
  157. package/package.json +8 -7
  158. package/src/adapters/databaseConstraintErrors.ts +25 -0
  159. package/src/adapters/databasePluginTestDatabase.ts +106 -0
  160. package/src/adapters/databasePluginUtils.spec.ts +29 -0
  161. package/src/adapters/databasePluginUtils.ts +214 -0
  162. package/src/adapters/drizzle.spec.ts +208 -47
  163. package/src/adapters/drizzle.ts +106 -293
  164. package/src/adapters/drizzleCrud.ts +419 -0
  165. package/src/adapters/drizzleLazyDB.ts +206 -100
  166. package/src/adapters/drizzleQuery.ts +164 -0
  167. package/src/adapters/kysely.spec.ts +126 -88
  168. package/src/adapters/kysely.ts +76 -334
  169. package/src/adapters/kyselyCrud.ts +588 -0
  170. package/src/adapters/kyselyQuery.ts +128 -0
  171. package/src/adapters/mongodb.spec.ts +141 -0
  172. package/src/adapters/mongodb.ts +60 -288
  173. package/src/adapters/mongodbCollections.ts +62 -0
  174. package/src/adapters/mongodbQuery.ts +226 -0
  175. package/src/adapters/mongodbReads.ts +311 -0
  176. package/src/adapters/mongodbRows.spec.ts +64 -0
  177. package/src/adapters/mongodbRows.ts +118 -0
  178. package/src/adapters/mongodbTestClient.ts +342 -0
  179. package/src/adapters/mongodbTestFilter.ts +166 -0
  180. package/src/adapters/mongodbWrites.ts +260 -0
  181. package/src/adapters/prisma.spec.ts +196 -0
  182. package/src/adapters/prisma.ts +417 -292
  183. package/src/adapters/prismaQuery.ts +129 -0
  184. package/src/adapters/prismaRows.spec.ts +98 -0
  185. package/src/adapters/prismaRows.ts +301 -0
  186. package/src/adapters/prismaTestClient.ts +396 -0
  187. package/src/apiKeys.integration.spec.ts +192 -0
  188. package/src/apiKeys.spec.ts +162 -0
  189. package/src/apiKeys.ts +220 -0
  190. package/src/createHotUpdaterCore.ts +164 -100
  191. package/src/db/bundleRows.spec.ts +164 -0
  192. package/src/db/bundleRows.ts +10 -140
  193. package/src/db/createBundleDiff.spec.ts +172 -127
  194. package/src/db/createBundleDiff.ts +91 -110
  195. package/src/db/databasePluginCore.spec.ts +62 -0
  196. package/src/db/databasePluginCore.testFixtures.ts +76 -0
  197. package/src/db/databasePluginCore.ts +185 -0
  198. package/src/db/databasePluginUpdateCheck.bench.ts +107 -0
  199. package/src/db/fixedMigrator.spec.ts +184 -42
  200. package/src/db/fixedMigrator.ts +64 -205
  201. package/src/db/fixedMigratorMongo.spec.ts +192 -0
  202. package/src/db/fixedMigratorMongo.ts +425 -0
  203. package/src/db/fixedMigratorShared.ts +36 -0
  204. package/src/db/hotUpdaterSchema.ts +0 -1
  205. package/src/db/index.spec.ts +333 -1025
  206. package/src/db/mongoMigrationExecution.ts +23 -0
  207. package/src/db/node-sqlite.d.ts +25 -0
  208. package/src/db/releaseCatalog.ts +148 -0
  209. package/src/db/schema/mongodb.ts +20 -6
  210. package/src/db/schema/sql.ts +52 -11
  211. package/src/db/schema/sqlOperations.ts +5 -4
  212. package/src/db/schemaGenerators.ts +107 -23
  213. package/src/db/schemaReadiness.ts +2 -1
  214. package/src/db/sqlMigrationExecution.spec.ts +65 -0
  215. package/src/db/sqlMigrationExecution.ts +108 -0
  216. package/src/db/types.spec.ts +55 -0
  217. package/src/db/types.ts +129 -65
  218. package/src/db/updateArtifacts.spec.ts +359 -0
  219. package/src/db/updateArtifacts.ts +290 -126
  220. package/src/handler-standalone.integration.spec.ts +401 -364
  221. package/src/handler.spec.ts +137 -582
  222. package/src/handler.testFixtures.ts +30 -0
  223. package/src/handler.ts +218 -616
  224. package/src/handlerAuth.integration.spec.ts +33 -0
  225. package/src/handlerBundleRoutes.ts +259 -0
  226. package/src/handlerErrors.ts +7 -0
  227. package/src/handlerManagement.spec.ts +262 -0
  228. package/src/handlerParameters.ts +105 -0
  229. package/src/handlerReleaseCatalogRoutes.ts +154 -0
  230. package/src/handlerReleaseManagementRoutes.ts +213 -0
  231. package/src/handlerTypes.ts +96 -0
  232. package/src/handlerVersionRoutes.ts +12 -0
  233. package/src/index.ts +22 -2
  234. package/src/insights/bounded/activeOverview.spec.ts +189 -0
  235. package/src/insights/bounded/activeOverview.ts +148 -0
  236. package/src/insights/bounded/installationSearch.spec.ts +130 -0
  237. package/src/insights/bounded/installationSearch.ts +69 -0
  238. package/src/insights/bounded/persistence.ts +43 -0
  239. package/src/insights/bounded/provider.spec.ts +219 -0
  240. package/src/insights/bounded/provider.ts +229 -0
  241. package/src/insights/bounded/scan.ts +235 -0
  242. package/src/insights/domain.ts +139 -0
  243. package/src/insights/errors.ts +18 -0
  244. package/src/insights/eventInput.ts +168 -0
  245. package/src/insights/insights.integration.spec.ts +144 -0
  246. package/src/insights/persistence.ts +10 -0
  247. package/src/insights/queryInput.ts +102 -0
  248. package/src/insights/routes.ts +123 -0
  249. package/src/insights/types.ts +44 -0
  250. package/src/node.spec.ts +79 -10
  251. package/src/node.ts +20 -17
  252. package/src/releaseCatalogRoutes.integration.spec.ts +238 -0
  253. package/src/runtime.spec.ts +126 -959
  254. package/src/runtime.testFixtures.ts +71 -0
  255. package/src/schema/dsl-all-versions.spec.ts +26 -17
  256. package/src/schema/dsl.ts +41 -2
  257. package/src/schema/index.ts +3 -9
  258. package/src/schema/types.ts +9 -3
  259. package/src/schema/v1_0_0.spec.ts +146 -0
  260. package/src/schema/v1_0_0.ts +465 -0
  261. package/src/storageAccess.spec.ts +127 -32
  262. package/src/storageAccess.ts +106 -55
  263. package/src/types/index.ts +2 -1
  264. package/dist/db/bundleRows.cjs +0 -82
  265. package/dist/db/bundleRows.d.cts +0 -33
  266. package/dist/db/bundleRows.d.mts +0 -33
  267. package/dist/db/bundleRows.mjs +0 -79
  268. package/dist/db/pluginCore.cjs +0 -242
  269. package/dist/db/pluginCore.mjs +0 -241
  270. package/dist/db/schema/sqlMigrations.cjs +0 -74
  271. package/dist/db/schema/sqlMigrations.mjs +0 -73
  272. package/dist/db/schemaEnhancements.cjs +0 -20
  273. package/dist/db/schemaEnhancements.mjs +0 -19
  274. package/dist/route.cjs +0 -7
  275. package/dist/route.mjs +0 -7
  276. package/dist/schema/v0_21_0.cjs +0 -38
  277. package/dist/schema/v0_21_0.mjs +0 -37
  278. package/dist/schema/v0_29_0.cjs +0 -45
  279. package/dist/schema/v0_29_0.mjs +0 -44
  280. package/dist/schema/v0_31_0.cjs +0 -82
  281. package/dist/schema/v0_31_0.mjs +0 -80
  282. package/src/db/pluginCore.spec.ts +0 -1095
  283. package/src/db/pluginCore.ts +0 -475
  284. package/src/db/pluginUpdateCheck.bench.ts +0 -254
  285. package/src/db/schema/sqlMigrations.ts +0 -219
  286. package/src/db/schemaEnhancements.ts +0 -55
  287. package/src/route.ts +0 -7
  288. package/src/schema/v0_21_0.ts +0 -52
  289. package/src/schema/v0_29_0.ts +0 -62
  290. package/src/schema/v0_31_0.spec.ts +0 -73
  291. package/src/schema/v0_31_0.ts +0 -120
@@ -1,14 +1,19 @@
1
- import fs from "node:fs/promises";
2
1
  import { brotliCompressSync } from "node:zlib";
3
2
 
4
3
  import type {
5
4
  Bundle,
6
5
  DatabasePlugin,
7
- NodeStoragePlugin,
8
- NodeStorageProfile,
6
+ StoragePlugin,
7
+ StoragePluginWith,
8
+ } from "@hot-updater/plugin-core";
9
+ import {
10
+ createDatabaseClient,
11
+ createStoragePlugin as createCoreStoragePlugin,
9
12
  } from "@hot-updater/plugin-core";
10
13
  import { afterEach, describe, expect, it, vi } from "vitest";
11
14
 
15
+ import { createInMemoryDatabasePlugin } from "../../../test-utils/test/inMemoryDatabasePlugin";
16
+
12
17
  vi.mock("@hot-updater/bsdiff", () => ({
13
18
  hdiff: vi.fn(async () => new Uint8Array([1, 2, 3, 4])),
14
19
  }));
@@ -16,90 +21,52 @@ vi.mock("@hot-updater/bsdiff", () => ({
16
21
  import { createBundleDiff } from "./createBundleDiff";
17
22
 
18
23
  const createBundle = (id: string, overrides: Partial<Bundle> = {}): Bundle => ({
19
- channel: "production",
20
- enabled: true,
24
+ archiveByteSize: 1_024,
21
25
  fileHash: `${id}-file-hash`,
22
- fingerprintHash: null,
23
26
  gitCommitHash: null,
24
27
  id,
25
- message: id,
26
- assetBaseStorageUri: `s3://test-bucket/releases/${id}/files`,
27
- manifestStorageUri: `s3://test-bucket/releases/${id}/manifest.json`,
28
+ assetBaseStorageUri: "s3://test-bucket/releases/assets",
29
+ manifestStorageUri: `s3://test-bucket/releases/bundles/${id}/manifest.json`,
28
30
  metadata: {},
29
31
  platform: "ios",
30
- shouldForceUpdate: false,
31
- storageUri: `s3://test-bucket/releases/${id}/bundle.zip`,
32
- targetAppVersion: "1.0.0",
32
+ storageUri: `s3://test-bucket/releases/bundles/${id}/bundle.zip`,
33
33
  ...overrides,
34
34
  });
35
35
 
36
- const createDatabasePlugin = (
37
- bundles: Map<string, Bundle>,
38
- ): DatabasePlugin => ({
39
- name: "mockDatabase",
40
- async appendBundle() {},
41
- async commitBundle() {},
42
- async deleteBundle() {},
43
- async getBundleById(bundleId) {
44
- return bundles.get(bundleId) ?? null;
45
- },
46
- async getBundles() {
47
- return {
48
- data: Array.from(bundles.values()),
49
- pagination: {
50
- currentPage: 1,
51
- hasNextPage: false,
52
- hasPreviousPage: false,
53
- total: bundles.size,
54
- totalPages: 1,
55
- },
56
- };
57
- },
58
- async getChannels() {
59
- return ["production"];
60
- },
61
- async onUnmount() {},
62
- async updateBundle(bundleId, nextBundle) {
63
- const currentBundle = bundles.get(bundleId);
64
- if (!currentBundle) {
65
- return;
66
- }
67
- bundles.set(bundleId, {
68
- ...currentBundle,
69
- ...nextBundle,
70
- metadata: {
71
- ...currentBundle.metadata,
72
- ...nextBundle.metadata,
73
- },
74
- });
75
- },
76
- });
36
+ const createDatabasePlugin = async (
37
+ bundles: readonly Bundle[],
38
+ ): Promise<DatabasePlugin> => {
39
+ const plugin = createInMemoryDatabasePlugin();
40
+ const client = createDatabaseClient(plugin);
41
+ for (const bundle of bundles) await client.insertBundle(bundle);
42
+ return plugin;
43
+ };
77
44
 
78
45
  const createStoragePlugin = (
79
- upload: NodeStorageProfile["upload"],
80
- ): NodeStoragePlugin => ({
81
- name: "mockStorage",
82
- supportedProtocol: "s3",
83
- profiles: {
84
- node: {
85
- async delete() {},
86
- async downloadFile(storageUri, filePath) {
46
+ put: NonNullable<StoragePlugin["put"]>,
47
+ options: {
48
+ get?: NonNullable<StoragePlugin["get"]>;
49
+ protocol?: string;
50
+ } = {},
51
+ ): StoragePluginWith<"get" | "put" | "delete"> =>
52
+ createCoreStoragePlugin({
53
+ name: "mockStorage",
54
+ protocol: options.protocol ?? "s3",
55
+ async delete({ storageUri }) {
56
+ void storageUri;
57
+ return { deleted: true };
58
+ },
59
+ get:
60
+ options.get ??
61
+ (async ({ storageUri }) => {
87
62
  const storageUrl = new URL(storageUri);
88
63
  const response = await fetch(
89
64
  `https://assets.example.com${storageUrl.pathname}`,
90
65
  );
91
- await fs.writeFile(
92
- filePath,
93
- new Uint8Array(await response.arrayBuffer()),
94
- );
95
- },
96
- async exists() {
97
- return false;
98
- },
99
- upload,
100
- },
101
- },
102
- });
66
+ return { response: response.ok ? response : null };
67
+ }),
68
+ put,
69
+ });
103
70
 
104
71
  describe("createBundleDiff", () => {
105
72
  afterEach(() => {
@@ -107,20 +74,20 @@ describe("createBundleDiff", () => {
107
74
  });
108
75
 
109
76
  it("uploads a Hermes patch and stores patch metadata on the target bundle", async () => {
110
- const baseBundle = createBundle("00000000-0000-0000-0000-000000000001", {
111
- message: "base",
112
- });
113
- const targetBundle = createBundle("00000000-0000-0000-0000-000000000002", {
114
- message: "target",
115
- });
116
- const bundles = new Map([
117
- [baseBundle.id, baseBundle],
118
- [targetBundle.id, targetBundle],
119
- ]);
120
- const upload = vi.fn<NodeStorageProfile["upload"]>(
121
- async (key, filePath) => ({
122
- storageUri: `s3://test-bucket/${key}/${filePath.split("/").pop()}`,
123
- }),
77
+ const baseBundle = createBundle("00000000-0000-0000-0000-000000000001");
78
+ const targetBundle = createBundle("00000000-0000-0000-0000-000000000002");
79
+ const baseDownloadFileHash = "a".repeat(64);
80
+ const targetDownloadFileHash = "b".repeat(64);
81
+ const plugin = await createDatabasePlugin([baseBundle, targetBundle]);
82
+ const databasePlugin: DatabasePlugin = plugin;
83
+ const commit = vi.spyOn(databasePlugin, "commit");
84
+ const upload = vi.fn<NonNullable<StoragePlugin["put"]>>(
85
+ async ({ key, body, contentLength }) => {
86
+ const bytes = new Uint8Array(await new Response(body).arrayBuffer());
87
+ expect(bytes).toEqual(new Uint8Array([1, 2, 3, 4]));
88
+ expect(contentLength).toBe(bytes.byteLength);
89
+ return { storageUri: `s3://test-bucket/${key}` };
90
+ },
124
91
  );
125
92
 
126
93
  vi.stubGlobal(
@@ -133,6 +100,8 @@ describe("createBundleDiff", () => {
133
100
  JSON.stringify({
134
101
  assets: {
135
102
  "index.ios.bundle": {
103
+ downloadByteSize: 7,
104
+ downloadFileHash: baseDownloadFileHash,
136
105
  fileHash: "hash-old",
137
106
  },
138
107
  },
@@ -146,6 +115,8 @@ describe("createBundleDiff", () => {
146
115
  JSON.stringify({
147
116
  assets: {
148
117
  "index.ios.bundle": {
118
+ downloadByteSize: 7,
119
+ downloadFileHash: targetDownloadFileHash,
149
120
  fileHash: "hash-new",
150
121
  },
151
122
  },
@@ -154,19 +125,19 @@ describe("createBundleDiff", () => {
154
125
  );
155
126
  }
156
127
 
157
- if (url.endsWith(`${baseBundle.id}/files/index.ios.bundle.br`)) {
128
+ if (url.endsWith(`/assets/sha256/aa/${baseDownloadFileHash}.br`)) {
158
129
  return new Response(brotliCompressSync(new Uint8Array([1, 2, 3])));
159
130
  }
160
131
 
161
- if (url.endsWith(`${baseBundle.id}/files/index.ios.bundle`)) {
132
+ if (url.endsWith("/assets/sha256/ha/hash-old.bundle")) {
162
133
  return new Response(new Uint8Array([1, 2, 3]));
163
134
  }
164
135
 
165
- if (url.endsWith(`${targetBundle.id}/files/index.ios.bundle.br`)) {
136
+ if (url.endsWith(`/assets/sha256/bb/${targetDownloadFileHash}.br`)) {
166
137
  return new Response(brotliCompressSync(new Uint8Array([1, 9, 3])));
167
138
  }
168
139
 
169
- if (url.endsWith(`${targetBundle.id}/files/index.ios.bundle`)) {
140
+ if (url.endsWith("/assets/sha256/ha/hash-new.bundle")) {
170
141
  return new Response(new Uint8Array([1, 9, 3]));
171
142
  }
172
143
 
@@ -181,26 +152,28 @@ describe("createBundleDiff", () => {
181
152
  bundleId: targetBundle.id,
182
153
  },
183
154
  {
184
- databasePlugin: createDatabasePlugin(bundles),
155
+ databasePlugin,
185
156
  storagePlugin: createStoragePlugin(upload),
186
157
  },
187
158
  );
188
159
 
189
160
  expect(upload).toHaveBeenCalledOnce();
190
- expect(updatedBundle).toMatchObject({
191
- patchBaseBundleId: baseBundle.id,
192
- patchBaseFileHash: "hash-old",
193
- });
194
- expect(updatedBundle.patchFileHash).toMatch(/[a-f0-9]{64}/);
195
- expect(updatedBundle.patchStorageUri).toContain(
196
- `bundles/${targetBundle.id}/patches/${baseBundle.id}`,
161
+ expect(commit).toHaveBeenCalledOnce();
162
+ const [patch] = updatedBundle.patches ?? [];
163
+ expect(patch?.baseBundleId).toBe(baseBundle.id);
164
+ expect(patch?.baseFileHash).toBe("hash-old");
165
+ expect(patch?.byteSize).toBe(4);
166
+ expect(patch?.patchFileHash).toMatch(/[a-f0-9]{64}/);
167
+ expect(patch?.patchStorageUri).toContain(
168
+ `bundles/${targetBundle.id}/patches/${baseBundle.id}/${patch?.patchFileHash}/`,
197
169
  );
198
170
  expect(updatedBundle.patches).toEqual([
199
171
  {
200
172
  baseBundleId: baseBundle.id,
201
173
  baseFileHash: "hash-old",
202
- patchFileHash: updatedBundle.patchFileHash,
203
- patchStorageUri: updatedBundle.patchStorageUri,
174
+ byteSize: 4,
175
+ patchFileHash: patch?.patchFileHash,
176
+ patchStorageUri: patch?.patchStorageUri,
204
177
  },
205
178
  ]);
206
179
  } finally {
@@ -211,13 +184,13 @@ describe("createBundleDiff", () => {
211
184
  it("rejects ambiguous Hermes bundle assets in manifests", async () => {
212
185
  const baseBundle = createBundle("00000000-0000-0000-0000-000000000001");
213
186
  const targetBundle = createBundle("00000000-0000-0000-0000-000000000002");
214
- const bundles = new Map([
215
- [baseBundle.id, baseBundle],
216
- [targetBundle.id, targetBundle],
187
+ const databasePlugin = await createDatabasePlugin([
188
+ baseBundle,
189
+ targetBundle,
217
190
  ]);
218
- const upload = vi.fn<NodeStorageProfile["upload"]>(
219
- async (key, filePath) => ({
220
- storageUri: `s3://test-bucket/${key}/${filePath.split("/").pop()}`,
191
+ const upload = vi.fn<NonNullable<StoragePlugin["put"]>>(
192
+ async ({ key }) => ({
193
+ storageUri: `s3://test-bucket/${key}`,
221
194
  }),
222
195
  );
223
196
 
@@ -270,7 +243,7 @@ describe("createBundleDiff", () => {
270
243
  bundleId: targetBundle.id,
271
244
  },
272
245
  {
273
- databasePlugin: createDatabasePlugin(bundles),
246
+ databasePlugin,
274
247
  storagePlugin: createStoragePlugin(upload),
275
248
  },
276
249
  ),
@@ -281,6 +254,84 @@ describe("createBundleDiff", () => {
281
254
  }
282
255
  });
283
256
 
257
+ it("uses a matching HTTPS storage plugin before direct fetch", async () => {
258
+ const baseBundle = createBundle("00000000-0000-0000-0000-000000000001", {
259
+ assetBaseStorageUri: "https://storage.example.com/releases/assets",
260
+ manifestStorageUri:
261
+ "https://storage.example.com/releases/base/manifest.json",
262
+ });
263
+ const targetBundle = createBundle("00000000-0000-0000-0000-000000000002", {
264
+ assetBaseStorageUri: "https://storage.example.com/releases/assets",
265
+ manifestStorageUri:
266
+ "https://storage.example.com/releases/target/manifest.json",
267
+ });
268
+ const databasePlugin = await createDatabasePlugin([
269
+ baseBundle,
270
+ targetBundle,
271
+ ]);
272
+ const responses = new Map<string, string | Uint8Array>([
273
+ [
274
+ baseBundle.manifestStorageUri!,
275
+ JSON.stringify({
276
+ assets: { "index.ios.bundle": { fileHash: "hash-old" } },
277
+ bundleId: baseBundle.id,
278
+ }),
279
+ ],
280
+ [
281
+ targetBundle.manifestStorageUri!,
282
+ JSON.stringify({
283
+ assets: { "index.ios.bundle": { fileHash: "hash-new" } },
284
+ bundleId: targetBundle.id,
285
+ }),
286
+ ],
287
+ [
288
+ `${baseBundle.assetBaseStorageUri}/sha256/ha/hash-old.bundle`,
289
+ new Uint8Array([1, 2, 3]),
290
+ ],
291
+ [
292
+ `${targetBundle.assetBaseStorageUri}/sha256/ha/hash-new.bundle`,
293
+ new Uint8Array([1, 9, 3]),
294
+ ],
295
+ ]);
296
+ const get = vi.fn<NonNullable<StoragePlugin["get"]>>(
297
+ async ({ storageUri }) => ({
298
+ response: responses.has(storageUri)
299
+ ? new Response(responses.get(storageUri))
300
+ : null,
301
+ }),
302
+ );
303
+ const directFetch = vi.fn<typeof fetch>();
304
+ vi.stubGlobal("fetch", directFetch);
305
+ const upload = vi.fn<NonNullable<StoragePlugin["put"]>>(
306
+ async ({ key }) => ({
307
+ storageUri: `https://storage.example.com/${key}`,
308
+ }),
309
+ );
310
+
311
+ try {
312
+ await createBundleDiff(
313
+ { baseBundleId: baseBundle.id, bundleId: targetBundle.id },
314
+ {
315
+ databasePlugin,
316
+ storagePlugin: createStoragePlugin(upload, {
317
+ get,
318
+ protocol: "https",
319
+ }),
320
+ },
321
+ );
322
+
323
+ expect(get).toHaveBeenCalledWith({
324
+ storageUri: baseBundle.manifestStorageUri,
325
+ });
326
+ expect(get).toHaveBeenCalledWith({
327
+ storageUri: targetBundle.manifestStorageUri,
328
+ });
329
+ expect(directFetch).not.toHaveBeenCalled();
330
+ } finally {
331
+ vi.unstubAllGlobals();
332
+ }
333
+ });
334
+
284
335
  it("appends additional patch bases without replacing the primary patch when requested", async () => {
285
336
  const primaryBaseBundle = createBundle(
286
337
  "00000000-0000-0000-0000-000000000001",
@@ -293,23 +344,20 @@ describe("createBundleDiff", () => {
293
344
  {
294
345
  baseBundleId: primaryBaseBundle.id,
295
346
  baseFileHash: "hash-primary-old",
347
+ byteSize: 4,
296
348
  patchFileHash: "hash-primary-patch",
297
349
  patchStorageUri: `s3://test-bucket/${primaryBaseBundle.id}/existing.bsdiff`,
298
350
  },
299
351
  ],
300
- patchBaseBundleId: primaryBaseBundle.id,
301
- patchBaseFileHash: "hash-primary-old",
302
- patchFileHash: "hash-primary-patch",
303
- patchStorageUri: `s3://test-bucket/${primaryBaseBundle.id}/existing.bsdiff`,
304
352
  });
305
- const bundles = new Map([
306
- [primaryBaseBundle.id, primaryBaseBundle],
307
- [secondaryBaseBundle.id, secondaryBaseBundle],
308
- [targetBundle.id, targetBundle],
353
+ const databasePlugin = await createDatabasePlugin([
354
+ primaryBaseBundle,
355
+ secondaryBaseBundle,
356
+ targetBundle,
309
357
  ]);
310
- const upload = vi.fn<NodeStorageProfile["upload"]>(
311
- async (key, filePath) => ({
312
- storageUri: `s3://test-bucket/${key}/${filePath.split("/").pop()}`,
358
+ const upload = vi.fn<NonNullable<StoragePlugin["put"]>>(
359
+ async ({ key }) => ({
360
+ storageUri: `s3://test-bucket/${key}`,
313
361
  }),
314
362
  );
315
363
 
@@ -344,21 +392,19 @@ describe("createBundleDiff", () => {
344
392
  );
345
393
  }
346
394
 
347
- if (
348
- url.endsWith(`${secondaryBaseBundle.id}/files/index.ios.bundle.br`)
349
- ) {
395
+ if (url.endsWith("/assets/sha256/ha/hash-secondary-old.br")) {
350
396
  return new Response(brotliCompressSync(new Uint8Array([1, 2, 3])));
351
397
  }
352
398
 
353
- if (url.endsWith(`${secondaryBaseBundle.id}/files/index.ios.bundle`)) {
399
+ if (url.endsWith("/assets/sha256/ha/hash-secondary-old.bundle")) {
354
400
  return new Response(new Uint8Array([1, 2, 3]));
355
401
  }
356
402
 
357
- if (url.endsWith(`${targetBundle.id}/files/index.ios.bundle.br`)) {
403
+ if (url.endsWith("/assets/sha256/ha/hash-target-new.br")) {
358
404
  return new Response(brotliCompressSync(new Uint8Array([1, 4, 3])));
359
405
  }
360
406
 
361
- if (url.endsWith(`${targetBundle.id}/files/index.ios.bundle`)) {
407
+ if (url.endsWith("/assets/sha256/ha/hash-target-new.bundle")) {
362
408
  return new Response(new Uint8Array([1, 4, 3]));
363
409
  }
364
410
 
@@ -373,7 +419,7 @@ describe("createBundleDiff", () => {
373
419
  bundleId: targetBundle.id,
374
420
  },
375
421
  {
376
- databasePlugin: createDatabasePlugin(bundles),
422
+ databasePlugin,
377
423
  storagePlugin: createStoragePlugin(upload),
378
424
  },
379
425
  {
@@ -381,7 +427,6 @@ describe("createBundleDiff", () => {
381
427
  },
382
428
  );
383
429
 
384
- expect(updatedBundle.patchBaseBundleId).toBe(primaryBaseBundle.id);
385
430
  expect(updatedBundle.patches).toMatchObject([
386
431
  {
387
432
  baseBundleId: primaryBaseBundle.id,