@hot-updater/server 0.36.6 → 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.
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/analytics/bounded/activeOverview.cjs +87 -0
  44. package/dist/analytics/bounded/activeOverview.mjs +85 -0
  45. package/dist/analytics/bounded/installationSearch.cjs +42 -0
  46. package/dist/analytics/bounded/installationSearch.mjs +42 -0
  47. package/dist/analytics/bounded/persistence.cjs +39 -0
  48. package/dist/analytics/bounded/persistence.mjs +38 -0
  49. package/dist/analytics/bounded/provider.cjs +170 -0
  50. package/dist/analytics/bounded/provider.d.cts +7 -0
  51. package/dist/analytics/bounded/provider.d.mts +7 -0
  52. package/dist/analytics/bounded/provider.mjs +170 -0
  53. package/dist/analytics/bounded/scan.cjs +144 -0
  54. package/dist/analytics/bounded/scan.mjs +138 -0
  55. package/dist/analytics/domain.d.cts +121 -0
  56. package/dist/analytics/domain.d.mts +121 -0
  57. package/dist/analytics/errors.cjs +22 -0
  58. package/dist/analytics/errors.mjs +20 -0
  59. package/dist/analytics/eventInput.cjs +111 -0
  60. package/dist/analytics/eventInput.mjs +111 -0
  61. package/dist/analytics/persistence.d.cts +6 -0
  62. package/dist/analytics/persistence.d.mts +6 -0
  63. package/dist/analytics/queryInput.cjs +58 -0
  64. package/dist/analytics/queryInput.mjs +55 -0
  65. package/dist/analytics/routes.cjs +63 -0
  66. package/dist/analytics/routes.mjs +61 -0
  67. package/dist/analytics/types.d.cts +17 -0
  68. package/dist/analytics/types.d.mts +17 -0
  69. package/dist/apiKeys.cjs +139 -0
  70. package/dist/apiKeys.d.cts +50 -0
  71. package/dist/apiKeys.d.mts +50 -0
  72. package/dist/apiKeys.mjs +133 -0
  73. package/dist/createHotUpdaterCore.cjs +48 -26
  74. package/dist/createHotUpdaterCore.d.cts +49 -17
  75. package/dist/createHotUpdaterCore.d.mts +49 -17
  76. package/dist/createHotUpdaterCore.mjs +51 -29
  77. package/dist/db/createBundleDiff.cjs +45 -68
  78. package/dist/db/createBundleDiff.d.cts +3 -3
  79. package/dist/db/createBundleDiff.d.mts +3 -3
  80. package/dist/db/createBundleDiff.mjs +47 -68
  81. package/dist/db/databasePluginCore.cjs +138 -0
  82. package/dist/db/databasePluginCore.mjs +137 -0
  83. package/dist/db/fixedMigrator.cjs +36 -93
  84. package/dist/db/fixedMigrator.mjs +36 -93
  85. package/dist/db/fixedMigratorMongo.cjs +319 -0
  86. package/dist/db/fixedMigratorMongo.mjs +319 -0
  87. package/dist/db/fixedMigratorShared.cjs +23 -0
  88. package/dist/db/fixedMigratorShared.mjs +18 -0
  89. package/dist/db/mongoMigrationExecution.cjs +11 -0
  90. package/dist/db/mongoMigrationExecution.mjs +11 -0
  91. package/dist/db/releaseCatalog.cjs +66 -0
  92. package/dist/db/releaseCatalog.d.cts +17 -0
  93. package/dist/db/releaseCatalog.d.mts +17 -0
  94. package/dist/db/releaseCatalog.mjs +64 -0
  95. package/dist/db/schema/mongodb.cjs +14 -6
  96. package/dist/db/schema/mongodb.mjs +14 -6
  97. package/dist/db/schema/registry.cjs +0 -2
  98. package/dist/db/schema/registry.mjs +1 -2
  99. package/dist/db/schema/sql.cjs +31 -14
  100. package/dist/db/schema/sql.mjs +32 -13
  101. package/dist/db/schema/sqlOperations.cjs +4 -4
  102. package/dist/db/schema/sqlOperations.mjs +5 -5
  103. package/dist/db/schemaGenerators.cjs +52 -18
  104. package/dist/db/schemaGenerators.mjs +52 -18
  105. package/dist/db/schemaReadiness.cjs +4 -3
  106. package/dist/db/schemaReadiness.mjs +4 -3
  107. package/dist/db/sqlMigrationExecution.cjs +67 -0
  108. package/dist/db/sqlMigrationExecution.mjs +66 -0
  109. package/dist/db/types.cjs +2 -6
  110. package/dist/db/types.d.cts +60 -16
  111. package/dist/db/types.d.mts +60 -16
  112. package/dist/db/types.mjs +3 -6
  113. package/dist/db/updateArtifacts.cjs +114 -60
  114. package/dist/db/updateArtifacts.mjs +117 -62
  115. package/dist/handler.cjs +108 -282
  116. package/dist/handler.d.cts +3 -46
  117. package/dist/handler.d.mts +3 -46
  118. package/dist/handler.mjs +107 -281
  119. package/dist/handlerBundleRoutes.cjs +155 -0
  120. package/dist/handlerBundleRoutes.mjs +155 -0
  121. package/dist/handlerErrors.cjs +9 -0
  122. package/dist/handlerErrors.mjs +9 -0
  123. package/dist/handlerParameters.cjs +39 -0
  124. package/dist/handlerParameters.mjs +34 -0
  125. package/dist/handlerReleaseCatalogRoutes.cjs +111 -0
  126. package/dist/handlerReleaseCatalogRoutes.mjs +110 -0
  127. package/dist/handlerReleaseManagementRoutes.cjs +125 -0
  128. package/dist/handlerReleaseManagementRoutes.mjs +124 -0
  129. package/dist/handlerTypes.d.cts +63 -0
  130. package/dist/handlerTypes.d.mts +63 -0
  131. package/dist/handlerVersionRoutes.cjs +10 -0
  132. package/dist/handlerVersionRoutes.d.cts +4 -0
  133. package/dist/handlerVersionRoutes.d.mts +4 -0
  134. package/dist/handlerVersionRoutes.mjs +9 -0
  135. package/dist/index.cjs +10 -1
  136. package/dist/index.d.cts +9 -3
  137. package/dist/index.d.mts +9 -3
  138. package/dist/index.mjs +5 -2
  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/analytics/analytics.integration.spec.ts +144 -0
  188. package/src/analytics/bounded/activeOverview.spec.ts +189 -0
  189. package/src/analytics/bounded/activeOverview.ts +148 -0
  190. package/src/analytics/bounded/installationSearch.spec.ts +130 -0
  191. package/src/analytics/bounded/installationSearch.ts +69 -0
  192. package/src/analytics/bounded/persistence.ts +43 -0
  193. package/src/analytics/bounded/provider.spec.ts +219 -0
  194. package/src/analytics/bounded/provider.ts +229 -0
  195. package/src/analytics/bounded/scan.ts +235 -0
  196. package/src/analytics/domain.ts +139 -0
  197. package/src/analytics/errors.ts +18 -0
  198. package/src/analytics/eventInput.ts +168 -0
  199. package/src/analytics/persistence.ts +10 -0
  200. package/src/analytics/queryInput.ts +102 -0
  201. package/src/analytics/routes.ts +123 -0
  202. package/src/analytics/types.ts +44 -0
  203. package/src/apiKeys.integration.spec.ts +192 -0
  204. package/src/apiKeys.spec.ts +162 -0
  205. package/src/apiKeys.ts +220 -0
  206. package/src/createHotUpdaterCore.ts +164 -100
  207. package/src/db/bundleRows.spec.ts +164 -0
  208. package/src/db/bundleRows.ts +10 -140
  209. package/src/db/createBundleDiff.spec.ts +172 -127
  210. package/src/db/createBundleDiff.ts +91 -110
  211. package/src/db/databasePluginCore.spec.ts +62 -0
  212. package/src/db/databasePluginCore.testFixtures.ts +76 -0
  213. package/src/db/databasePluginCore.ts +185 -0
  214. package/src/db/databasePluginUpdateCheck.bench.ts +107 -0
  215. package/src/db/fixedMigrator.spec.ts +184 -42
  216. package/src/db/fixedMigrator.ts +64 -205
  217. package/src/db/fixedMigratorMongo.spec.ts +192 -0
  218. package/src/db/fixedMigratorMongo.ts +425 -0
  219. package/src/db/fixedMigratorShared.ts +36 -0
  220. package/src/db/hotUpdaterSchema.ts +0 -1
  221. package/src/db/index.spec.ts +333 -1025
  222. package/src/db/mongoMigrationExecution.ts +23 -0
  223. package/src/db/node-sqlite.d.ts +25 -0
  224. package/src/db/releaseCatalog.ts +148 -0
  225. package/src/db/schema/mongodb.ts +20 -6
  226. package/src/db/schema/sql.ts +52 -11
  227. package/src/db/schema/sqlOperations.ts +5 -4
  228. package/src/db/schemaGenerators.ts +107 -23
  229. package/src/db/schemaReadiness.ts +2 -1
  230. package/src/db/sqlMigrationExecution.spec.ts +65 -0
  231. package/src/db/sqlMigrationExecution.ts +108 -0
  232. package/src/db/types.spec.ts +55 -0
  233. package/src/db/types.ts +129 -65
  234. package/src/db/updateArtifacts.spec.ts +359 -0
  235. package/src/db/updateArtifacts.ts +290 -126
  236. package/src/handler-standalone.integration.spec.ts +401 -364
  237. package/src/handler.spec.ts +137 -582
  238. package/src/handler.testFixtures.ts +30 -0
  239. package/src/handler.ts +218 -616
  240. package/src/handlerAuth.integration.spec.ts +33 -0
  241. package/src/handlerBundleRoutes.ts +259 -0
  242. package/src/handlerErrors.ts +7 -0
  243. package/src/handlerManagement.spec.ts +262 -0
  244. package/src/handlerParameters.ts +105 -0
  245. package/src/handlerReleaseCatalogRoutes.ts +154 -0
  246. package/src/handlerReleaseManagementRoutes.ts +213 -0
  247. package/src/handlerTypes.ts +96 -0
  248. package/src/handlerVersionRoutes.ts +12 -0
  249. package/src/index.ts +22 -2
  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
@@ -0,0 +1,155 @@
1
+ import { HandlerBadRequestError } from "./handlerErrors.mjs";
2
+ import { decodeMaybe, isPlatform, parsePositiveIntegerSearchParam, parseStringArraySearchParam, requireRouteParam } from "./handlerParameters.mjs";
3
+ //#region src/handlerBundleRoutes.ts
4
+ const BUNDLE_LIST_BOUNDS = {
5
+ defaultValue: 50,
6
+ maxValue: 100
7
+ };
8
+ const isChannelText = (value) => {
9
+ if (typeof value !== "string" || value.length === 0) return false;
10
+ let codePoints = 0;
11
+ for (const _codePoint of value) {
12
+ codePoints += 1;
13
+ if (codePoints > 255) return false;
14
+ }
15
+ return true;
16
+ };
17
+ const requireChannelInsertInput = (value) => {
18
+ if (!value || typeof value !== "object" || Array.isArray(value) || !("row" in value) || !value.row || typeof value.row !== "object" || Array.isArray(value.row) || !("id" in value.row) || !isChannelText(value.row.id) || !("name" in value.row) || !isChannelText(value.row.name) || !("onConflict" in value) || value.onConflict !== "returnExisting") throw new HandlerBadRequestError("Invalid Channel insert payload");
19
+ return {
20
+ row: {
21
+ id: value.row.id,
22
+ name: value.row.name
23
+ },
24
+ onConflict: value.onConflict
25
+ };
26
+ };
27
+ const requireBundlePatchPayload = (payload, bundleId) => {
28
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) throw new HandlerBadRequestError("Invalid bundle payload");
29
+ const bundlePatch = payload;
30
+ if (bundlePatch.id !== void 0 && bundlePatch.id !== bundleId) throw new HandlerBadRequestError("Bundle id mismatch");
31
+ const { id: _ignoredId, ...rest } = bundlePatch;
32
+ return rest;
33
+ };
34
+ const createBundleRouteHandlers = () => ({
35
+ getBundle: async (params, _request, api) => {
36
+ const bundle = await api.getBundleById(requireRouteParam(params, "id"));
37
+ if (!bundle) return new Response(JSON.stringify({ error: "Bundle not found" }), {
38
+ status: 404,
39
+ headers: { "Content-Type": "application/json" }
40
+ });
41
+ return new Response(JSON.stringify(bundle), {
42
+ status: 200,
43
+ headers: { "Content-Type": "application/json" }
44
+ });
45
+ },
46
+ getBundles: async (_params, request, api) => {
47
+ const url = new URL(request.url);
48
+ const platform = url.searchParams.get("platform");
49
+ const limit = parsePositiveIntegerSearchParam(url, "limit", BUNDLE_LIST_BOUNDS);
50
+ const pageParam = url.searchParams.get("page");
51
+ const offset = url.searchParams.get("offset");
52
+ const after = url.searchParams.get("after") ?? void 0;
53
+ const before = url.searchParams.get("before") ?? void 0;
54
+ const orderDirection = url.searchParams.get("orderDirection");
55
+ const idEq = url.searchParams.get("idEq") ?? void 0;
56
+ const idGt = url.searchParams.get("idGt") ?? void 0;
57
+ const idGte = url.searchParams.get("idGte") ?? void 0;
58
+ const idLt = url.searchParams.get("idLt") ?? void 0;
59
+ const idLte = url.searchParams.get("idLte") ?? void 0;
60
+ const idIn = parseStringArraySearchParam(url, "idIn");
61
+ const page = pageParam === null ? void 0 : Number.isSafeInteger(Number(pageParam)) && Number(pageParam) > 0 && Number.isSafeInteger((Number(pageParam) - 1) * limit) ? Number(pageParam) : null;
62
+ if (offset !== null) throw new HandlerBadRequestError("The 'offset' query parameter has been removed. Use 'after' or 'before' cursor pagination instead.");
63
+ if (page === null) throw new HandlerBadRequestError("The 'page' query parameter must be a positive integer.");
64
+ if (after !== void 0 && before !== void 0) throw new HandlerBadRequestError("The 'after' and 'before' query parameters cannot be combined.");
65
+ if (page !== void 0 && (after !== void 0 || before !== void 0)) throw new HandlerBadRequestError("The 'page' query parameter cannot be combined with 'after' or 'before'.");
66
+ if (platform !== null && !isPlatform(platform)) throw new HandlerBadRequestError(`Invalid platform: ${platform}. Expected 'ios' or 'android'.`);
67
+ if (orderDirection !== null && orderDirection !== "asc" && orderDirection !== "desc") throw new HandlerBadRequestError("The 'orderDirection' query parameter must be 'asc' or 'desc'.");
68
+ const pagination = page === void 0 ? {
69
+ cursor: after !== void 0 ? { after } : before !== void 0 ? { before } : void 0,
70
+ page: void 0
71
+ } : {
72
+ cursor: void 0,
73
+ page
74
+ };
75
+ const result = await api.getBundles({
76
+ where: {
77
+ ...platform && { platform },
78
+ ...idEq || idGt || idGte || idLt || idLte || idIn?.length ? { id: {
79
+ ...idEq && { eq: idEq },
80
+ ...idGt && { gt: idGt },
81
+ ...idGte && { gte: idGte },
82
+ ...idLt && { lt: idLt },
83
+ ...idLte && { lte: idLte },
84
+ ...idIn?.length && { in: idIn }
85
+ } } : {}
86
+ },
87
+ limit,
88
+ ...pagination,
89
+ ...orderDirection && { orderBy: {
90
+ field: "id",
91
+ direction: orderDirection
92
+ } }
93
+ });
94
+ return new Response(JSON.stringify(result), {
95
+ status: 200,
96
+ headers: { "Content-Type": "application/json" }
97
+ });
98
+ },
99
+ createBundles: async (_params, request, api) => {
100
+ const body = await request.json();
101
+ const bundles = Array.isArray(body) ? body : [body];
102
+ if (api.insertBundles) await api.insertBundles(bundles);
103
+ else {
104
+ if (bundles.length > 1) throw new HandlerBadRequestError("This server does not support atomic bundle batches");
105
+ const bundle = bundles[0];
106
+ if (bundle !== void 0) await api.insertBundle(bundle);
107
+ }
108
+ return new Response(JSON.stringify({ success: true }), {
109
+ status: 201,
110
+ headers: { "Content-Type": "application/json" }
111
+ });
112
+ },
113
+ updateBundle: async (params, request, api) => {
114
+ const bundleId = requireRouteParam(params, "id");
115
+ const body = await request.json();
116
+ const payload = Array.isArray(body) ? body[0] : body;
117
+ await api.updateBundleById(bundleId, requireBundlePatchPayload(payload, bundleId));
118
+ return new Response(JSON.stringify({ success: true }), {
119
+ status: 200,
120
+ headers: { "Content-Type": "application/json" }
121
+ });
122
+ },
123
+ deleteBundle: async (params, _request, api) => {
124
+ await api.deleteBundleById(requireRouteParam(params, "id"));
125
+ return new Response(JSON.stringify({ success: true }), {
126
+ status: 200,
127
+ headers: { "Content-Type": "application/json" }
128
+ });
129
+ },
130
+ getChannels: async (_params, _request, api) => {
131
+ const response = { data: { channels: await api.getChannels() } };
132
+ return new Response(JSON.stringify(response), {
133
+ status: 200,
134
+ headers: { "Content-Type": "application/json" }
135
+ });
136
+ },
137
+ createChannel: async (_params, request, api) => {
138
+ const result = await api.insertChannel(requireChannelInsertInput(await request.json()));
139
+ return new Response(JSON.stringify({ data: result }), {
140
+ status: result.inserted ? 201 : 200,
141
+ headers: { "Content-Type": "application/json" }
142
+ });
143
+ },
144
+ deleteChannel: async (params, _request, api) => {
145
+ const result = await api.deleteChannel({ id: decodeMaybe(requireRouteParam(params, "id")) ?? "" });
146
+ if (result.deleted) return new Response(null, { status: 204 });
147
+ const status = result.reason === "not_found" ? 404 : 409;
148
+ return new Response(JSON.stringify({ data: result }), {
149
+ status,
150
+ headers: { "Content-Type": "application/json" }
151
+ });
152
+ }
153
+ });
154
+ //#endregion
155
+ export { createBundleRouteHandlers };
@@ -0,0 +1,9 @@
1
+ //#region src/handlerErrors.ts
2
+ var HandlerBadRequestError = class extends Error {
3
+ name = "HandlerBadRequestError";
4
+ constructor(message) {
5
+ super(message);
6
+ }
7
+ };
8
+ //#endregion
9
+ exports.HandlerBadRequestError = HandlerBadRequestError;
@@ -0,0 +1,9 @@
1
+ //#region src/handlerErrors.ts
2
+ var HandlerBadRequestError = class extends Error {
3
+ name = "HandlerBadRequestError";
4
+ constructor(message) {
5
+ super(message);
6
+ }
7
+ };
8
+ //#endregion
9
+ export { HandlerBadRequestError };
@@ -0,0 +1,39 @@
1
+ const require_handlerErrors = require("./handlerErrors.cjs");
2
+ //#region src/handlerParameters.ts
3
+ const decodeMaybe = (value) => {
4
+ if (value === void 0) return void 0;
5
+ try {
6
+ return decodeURIComponent(value);
7
+ } catch {
8
+ return value;
9
+ }
10
+ };
11
+ const isPlatform = (value) => value === "ios" || value === "android";
12
+ const requireRouteParam = (params, key) => {
13
+ const value = params[key];
14
+ if (!value) throw new require_handlerErrors.HandlerBadRequestError(`Missing route parameter: ${key}`);
15
+ return value;
16
+ };
17
+ const requirePlatformParam = (params) => {
18
+ const platform = requireRouteParam(params, "platform");
19
+ if (!isPlatform(platform)) throw new require_handlerErrors.HandlerBadRequestError(`Invalid platform: ${platform}. Expected 'ios' or 'android'.`);
20
+ return platform;
21
+ };
22
+ const parseStringArraySearchParam = (url, key) => {
23
+ const values = url.searchParams.getAll(key);
24
+ return values.length > 0 ? values : void 0;
25
+ };
26
+ const parsePositiveIntegerSearchParam = (url, key, bounds) => {
27
+ const value = url.searchParams.get(key);
28
+ if (value === null) return bounds.defaultValue;
29
+ const parsed = Number(value);
30
+ if (!Number.isInteger(parsed) || parsed < 1 || parsed > bounds.maxValue) throw new require_handlerErrors.HandlerBadRequestError(`The '${key}' query parameter must be a positive integer between 1 and ${bounds.maxValue}.`);
31
+ return parsed;
32
+ };
33
+ //#endregion
34
+ exports.decodeMaybe = decodeMaybe;
35
+ exports.isPlatform = isPlatform;
36
+ exports.parsePositiveIntegerSearchParam = parsePositiveIntegerSearchParam;
37
+ exports.parseStringArraySearchParam = parseStringArraySearchParam;
38
+ exports.requirePlatformParam = requirePlatformParam;
39
+ exports.requireRouteParam = requireRouteParam;
@@ -0,0 +1,34 @@
1
+ import { HandlerBadRequestError } from "./handlerErrors.mjs";
2
+ //#region src/handlerParameters.ts
3
+ const decodeMaybe = (value) => {
4
+ if (value === void 0) return void 0;
5
+ try {
6
+ return decodeURIComponent(value);
7
+ } catch {
8
+ return value;
9
+ }
10
+ };
11
+ const isPlatform = (value) => value === "ios" || value === "android";
12
+ const requireRouteParam = (params, key) => {
13
+ const value = params[key];
14
+ if (!value) throw new HandlerBadRequestError(`Missing route parameter: ${key}`);
15
+ return value;
16
+ };
17
+ const requirePlatformParam = (params) => {
18
+ const platform = requireRouteParam(params, "platform");
19
+ if (!isPlatform(platform)) throw new HandlerBadRequestError(`Invalid platform: ${platform}. Expected 'ios' or 'android'.`);
20
+ return platform;
21
+ };
22
+ const parseStringArraySearchParam = (url, key) => {
23
+ const values = url.searchParams.getAll(key);
24
+ return values.length > 0 ? values : void 0;
25
+ };
26
+ const parsePositiveIntegerSearchParam = (url, key, bounds) => {
27
+ const value = url.searchParams.get(key);
28
+ if (value === null) return bounds.defaultValue;
29
+ const parsed = Number(value);
30
+ if (!Number.isInteger(parsed) || parsed < 1 || parsed > bounds.maxValue) throw new HandlerBadRequestError(`The '${key}' query parameter must be a positive integer between 1 and ${bounds.maxValue}.`);
31
+ return parsed;
32
+ };
33
+ //#endregion
34
+ export { decodeMaybe, isPlatform, parsePositiveIntegerSearchParam, parseStringArraySearchParam, requirePlatformParam, requireRouteParam };
@@ -0,0 +1,111 @@
1
+ require("./_virtual/_rolldown/runtime.cjs");
2
+ const require_handlerParameters = require("./handlerParameters.cjs");
3
+ let _hot_updater_plugin_core = require("@hot-updater/plugin-core");
4
+ //#region src/handlerReleaseCatalogRoutes.ts
5
+ const CATALOG_CONTENT_TYPE = "application/vnd.hot-updater.release-catalog+json; version=1";
6
+ const ORIGIN_CACHE_TTL_MS = 5e3;
7
+ const ORIGIN_CACHE_MAX_ENTRIES = 128;
8
+ const privateNotFound = () => Response.json({ error: "Not found" }, {
9
+ status: 404,
10
+ headers: { "cache-control": "private, no-store" }
11
+ });
12
+ const responseHash = async (body) => {
13
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(body));
14
+ return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
15
+ };
16
+ const catalogResponse = async (catalog, request, clientAccessHeaderName) => {
17
+ if (catalog === null) return privateNotFound();
18
+ const body = JSON.stringify(catalog);
19
+ const etag = `"sha256:${await responseHash(body)}"`;
20
+ const headers = {
21
+ "cache-control": "public, max-age=0, s-maxage=5",
22
+ "content-type": CATALOG_CONTENT_TYPE,
23
+ etag,
24
+ vary: `Accept-Encoding, ${clientAccessHeaderName}`
25
+ };
26
+ if (request.headers.get("if-none-match") === etag) return new Response(null, {
27
+ headers,
28
+ status: 304
29
+ });
30
+ return new Response(body, {
31
+ headers,
32
+ status: 200
33
+ });
34
+ };
35
+ const createReleaseCatalogRouteHandlers = (clientAccessHeaderName = "x-api-key") => {
36
+ const cache = /* @__PURE__ */ new Map();
37
+ const inFlight = /* @__PURE__ */ new Map();
38
+ const loadCatalog = async (key, load) => {
39
+ const cached = cache.get(key);
40
+ if (cached !== void 0 && cached.expiresAt > Date.now()) {
41
+ cache.delete(key);
42
+ cache.set(key, cached);
43
+ return cached.catalog;
44
+ }
45
+ if (cached !== void 0) cache.delete(key);
46
+ const pending = inFlight.get(key);
47
+ if (pending !== void 0) return pending;
48
+ const next = load().then((catalog) => {
49
+ if (catalog !== null) {
50
+ cache.set(key, {
51
+ catalog,
52
+ expiresAt: Date.now() + ORIGIN_CACHE_TTL_MS
53
+ });
54
+ while (cache.size > ORIGIN_CACHE_MAX_ENTRIES) {
55
+ const oldestKey = cache.keys().next().value;
56
+ if (oldestKey === void 0) break;
57
+ cache.delete(oldestKey);
58
+ }
59
+ }
60
+ return catalog;
61
+ });
62
+ inFlight.set(key, next);
63
+ try {
64
+ return await next;
65
+ } finally {
66
+ inFlight.delete(key);
67
+ }
68
+ };
69
+ return {
70
+ appVersionReleaseCatalog: async (params, request, api) => {
71
+ if (api.getReleaseCatalog === void 0) return privateNotFound();
72
+ const rawAppVersion = require_handlerParameters.requireRouteParam(params, "appVersion");
73
+ const appVersion = (0, _hot_updater_plugin_core.canonicalizeAppVersion)(rawAppVersion);
74
+ if (appVersion === null || appVersion !== rawAppVersion) return Response.json({ error: "Invalid app version" }, {
75
+ status: 400,
76
+ headers: { "cache-control": "private, no-store" }
77
+ });
78
+ const input = {
79
+ appVersion,
80
+ channelKey: require_handlerParameters.requireRouteParam(params, "channelKey"),
81
+ platform: require_handlerParameters.requirePlatformParam(params),
82
+ strategy: "APP_VERSION"
83
+ };
84
+ return catalogResponse(await loadCatalog(`app-version:${JSON.stringify(input)}`, () => api.getReleaseCatalog(input)), request, clientAccessHeaderName);
85
+ },
86
+ fingerprintReleaseCatalog: async (params, request, api) => {
87
+ if (api.getReleaseCatalog === void 0) return privateNotFound();
88
+ const input = {
89
+ channelKey: require_handlerParameters.requireRouteParam(params, "channelKey"),
90
+ fingerprintHash: require_handlerParameters.requireRouteParam(params, "fingerprintHash"),
91
+ platform: require_handlerParameters.requirePlatformParam(params),
92
+ strategy: "FINGERPRINT"
93
+ };
94
+ return catalogResponse(await loadCatalog(`fingerprint:${JSON.stringify(input)}`, () => api.getReleaseCatalog(input)), request, clientAccessHeaderName);
95
+ },
96
+ artifact: async (params, _request, api) => {
97
+ if (api.getArtifactInfo === void 0) return privateNotFound();
98
+ const info = await api.getArtifactInfo(require_handlerParameters.requireRouteParam(params, "targetBundleId"), require_handlerParameters.requireRouteParam(params, "currentBundleId"));
99
+ if (info === null) return privateNotFound();
100
+ return new Response(JSON.stringify(info), {
101
+ status: 200,
102
+ headers: {
103
+ "cache-control": "private, no-store",
104
+ "content-type": "application/json"
105
+ }
106
+ });
107
+ }
108
+ };
109
+ };
110
+ //#endregion
111
+ exports.createReleaseCatalogRouteHandlers = createReleaseCatalogRouteHandlers;
@@ -0,0 +1,110 @@
1
+ import { requirePlatformParam, requireRouteParam } from "./handlerParameters.mjs";
2
+ import { canonicalizeAppVersion } from "@hot-updater/plugin-core";
3
+ //#region src/handlerReleaseCatalogRoutes.ts
4
+ const CATALOG_CONTENT_TYPE = "application/vnd.hot-updater.release-catalog+json; version=1";
5
+ const ORIGIN_CACHE_TTL_MS = 5e3;
6
+ const ORIGIN_CACHE_MAX_ENTRIES = 128;
7
+ const privateNotFound = () => Response.json({ error: "Not found" }, {
8
+ status: 404,
9
+ headers: { "cache-control": "private, no-store" }
10
+ });
11
+ const responseHash = async (body) => {
12
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(body));
13
+ return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
14
+ };
15
+ const catalogResponse = async (catalog, request, clientAccessHeaderName) => {
16
+ if (catalog === null) return privateNotFound();
17
+ const body = JSON.stringify(catalog);
18
+ const etag = `"sha256:${await responseHash(body)}"`;
19
+ const headers = {
20
+ "cache-control": "public, max-age=0, s-maxage=5",
21
+ "content-type": CATALOG_CONTENT_TYPE,
22
+ etag,
23
+ vary: `Accept-Encoding, ${clientAccessHeaderName}`
24
+ };
25
+ if (request.headers.get("if-none-match") === etag) return new Response(null, {
26
+ headers,
27
+ status: 304
28
+ });
29
+ return new Response(body, {
30
+ headers,
31
+ status: 200
32
+ });
33
+ };
34
+ const createReleaseCatalogRouteHandlers = (clientAccessHeaderName = "x-api-key") => {
35
+ const cache = /* @__PURE__ */ new Map();
36
+ const inFlight = /* @__PURE__ */ new Map();
37
+ const loadCatalog = async (key, load) => {
38
+ const cached = cache.get(key);
39
+ if (cached !== void 0 && cached.expiresAt > Date.now()) {
40
+ cache.delete(key);
41
+ cache.set(key, cached);
42
+ return cached.catalog;
43
+ }
44
+ if (cached !== void 0) cache.delete(key);
45
+ const pending = inFlight.get(key);
46
+ if (pending !== void 0) return pending;
47
+ const next = load().then((catalog) => {
48
+ if (catalog !== null) {
49
+ cache.set(key, {
50
+ catalog,
51
+ expiresAt: Date.now() + ORIGIN_CACHE_TTL_MS
52
+ });
53
+ while (cache.size > ORIGIN_CACHE_MAX_ENTRIES) {
54
+ const oldestKey = cache.keys().next().value;
55
+ if (oldestKey === void 0) break;
56
+ cache.delete(oldestKey);
57
+ }
58
+ }
59
+ return catalog;
60
+ });
61
+ inFlight.set(key, next);
62
+ try {
63
+ return await next;
64
+ } finally {
65
+ inFlight.delete(key);
66
+ }
67
+ };
68
+ return {
69
+ appVersionReleaseCatalog: async (params, request, api) => {
70
+ if (api.getReleaseCatalog === void 0) return privateNotFound();
71
+ const rawAppVersion = requireRouteParam(params, "appVersion");
72
+ const appVersion = canonicalizeAppVersion(rawAppVersion);
73
+ if (appVersion === null || appVersion !== rawAppVersion) return Response.json({ error: "Invalid app version" }, {
74
+ status: 400,
75
+ headers: { "cache-control": "private, no-store" }
76
+ });
77
+ const input = {
78
+ appVersion,
79
+ channelKey: requireRouteParam(params, "channelKey"),
80
+ platform: requirePlatformParam(params),
81
+ strategy: "APP_VERSION"
82
+ };
83
+ return catalogResponse(await loadCatalog(`app-version:${JSON.stringify(input)}`, () => api.getReleaseCatalog(input)), request, clientAccessHeaderName);
84
+ },
85
+ fingerprintReleaseCatalog: async (params, request, api) => {
86
+ if (api.getReleaseCatalog === void 0) return privateNotFound();
87
+ const input = {
88
+ channelKey: requireRouteParam(params, "channelKey"),
89
+ fingerprintHash: requireRouteParam(params, "fingerprintHash"),
90
+ platform: requirePlatformParam(params),
91
+ strategy: "FINGERPRINT"
92
+ };
93
+ return catalogResponse(await loadCatalog(`fingerprint:${JSON.stringify(input)}`, () => api.getReleaseCatalog(input)), request, clientAccessHeaderName);
94
+ },
95
+ artifact: async (params, _request, api) => {
96
+ if (api.getArtifactInfo === void 0) return privateNotFound();
97
+ const info = await api.getArtifactInfo(requireRouteParam(params, "targetBundleId"), requireRouteParam(params, "currentBundleId"));
98
+ if (info === null) return privateNotFound();
99
+ return new Response(JSON.stringify(info), {
100
+ status: 200,
101
+ headers: {
102
+ "cache-control": "private, no-store",
103
+ "content-type": "application/json"
104
+ }
105
+ });
106
+ }
107
+ };
108
+ };
109
+ //#endregion
110
+ export { createReleaseCatalogRouteHandlers };
@@ -0,0 +1,125 @@
1
+ require("./_virtual/_rolldown/runtime.cjs");
2
+ const require_handlerErrors = require("./handlerErrors.cjs");
3
+ const require_handlerParameters = require("./handlerParameters.cjs");
4
+ let _hot_updater_plugin_core = require("@hot-updater/plugin-core");
5
+ //#region src/handlerReleaseManagementRoutes.ts
6
+ const unavailable = () => Response.json({ error: "Not found" }, { status: 404 });
7
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
8
+ const parseRevision = (value) => {
9
+ if (value === void 0 || value === null || value === "") return void 0;
10
+ const revision = typeof value === "number" ? value : Number(value);
11
+ if (!Number.isSafeInteger(revision) || revision < 1) throw new require_handlerErrors.HandlerBadRequestError("Invalid expected Release revision");
12
+ return revision;
13
+ };
14
+ const parsePolicyInput = async (request) => {
15
+ const body = await request.json();
16
+ if (!isRecord(body) || !isRecord(body.patch)) throw new require_handlerErrors.HandlerBadRequestError("Invalid Release policy mutation");
17
+ return {
18
+ expectedRevision: parseRevision(body.expectedRevision),
19
+ patch: body.patch
20
+ };
21
+ };
22
+ const mutationResponse = async (operation) => {
23
+ try {
24
+ return Response.json({ data: await operation() });
25
+ } catch (error) {
26
+ if (error instanceof _hot_updater_plugin_core.ReleaseManagementError || error instanceof _hot_updater_plugin_core.ReleaseCatalogMutationError) {
27
+ const status = error.code === "RELEASE_NOT_FOUND" ? 404 : error.code === "VERSION_CONFLICT" ? 409 : 400;
28
+ return Response.json({
29
+ code: error.code,
30
+ error: error.message
31
+ }, { status });
32
+ }
33
+ throw error;
34
+ }
35
+ };
36
+ const createReleaseManagementRouteHandlers = () => ({
37
+ getRelease: async (params, _request, api) => {
38
+ if (api.getReleaseById === void 0) return unavailable();
39
+ const row = await api.getReleaseById(require_handlerParameters.requireRouteParam(params, "id"));
40
+ return row === null ? unavailable() : Response.json({ data: row }, { status: 200 });
41
+ },
42
+ getReleases: async (_params, request, api) => {
43
+ const url = new URL(request.url);
44
+ const scopeKey = url.searchParams.get("scopeKey");
45
+ const limit = Number(url.searchParams.get("limit") ?? "100");
46
+ if (!Number.isSafeInteger(limit) || limit <= 0 || limit > 1e3) throw new require_handlerErrors.HandlerBadRequestError("Invalid Release scope query");
47
+ if (scopeKey !== null) {
48
+ if (scopeKey.length === 0 || api.getReleasesByScope === void 0) throw new require_handlerErrors.HandlerBadRequestError("Invalid Release scope query");
49
+ const afterReleaseId = url.searchParams.get("afterReleaseId") ?? void 0;
50
+ return Response.json({ data: await api.getReleasesByScope({
51
+ ...afterReleaseId === void 0 ? {} : { afterReleaseId },
52
+ limit,
53
+ scopeKey
54
+ }) });
55
+ }
56
+ if (api.getReleases === void 0) return unavailable();
57
+ const enabledValue = url.searchParams.get("enabled");
58
+ const enabled = enabledValue === null ? void 0 : enabledValue === "true" ? true : enabledValue === "false" ? false : null;
59
+ const platformValue = url.searchParams.get("platform");
60
+ const platform = platformValue === null ? void 0 : platformValue === "ios" || platformValue === "android" ? platformValue : null;
61
+ if (enabled === null || platform === null) throw new require_handlerErrors.HandlerBadRequestError("Invalid Release list filter");
62
+ return Response.json({ data: await api.getReleases({
63
+ afterReleaseId: url.searchParams.get("afterReleaseId") ?? void 0,
64
+ beforeReleaseId: url.searchParams.get("beforeReleaseId") ?? void 0,
65
+ bundleId: url.searchParams.get("bundleId") ?? void 0,
66
+ channelId: url.searchParams.get("channelId") ?? void 0,
67
+ enabled,
68
+ limit,
69
+ platform,
70
+ targetAppVersion: url.searchParams.get("targetAppVersion") ?? void 0
71
+ }) });
72
+ },
73
+ getReleaseCatalogRow: async (params, _request, api) => {
74
+ if (api.getReleaseCatalogByScopeKey === void 0) return unavailable();
75
+ const row = await api.getReleaseCatalogByScopeKey(require_handlerParameters.decodeMaybe(require_handlerParameters.requireRouteParam(params, "scopeKey")) ?? "");
76
+ return row === null ? unavailable() : Response.json({ data: row }, { status: 200 });
77
+ },
78
+ getReleaseCatalogs: async (_params, request, api) => {
79
+ if (api.getReleaseCatalogs === void 0) return unavailable();
80
+ const url = new URL(request.url);
81
+ const limit = Number(url.searchParams.get("limit") ?? "100");
82
+ if (!Number.isSafeInteger(limit) || limit <= 0 || limit > 1e3) throw new require_handlerErrors.HandlerBadRequestError("Invalid Release catalog list query");
83
+ return Response.json({ data: await api.getReleaseCatalogs({
84
+ afterScopeKey: url.searchParams.get("afterScopeKey") ?? void 0,
85
+ limit
86
+ }) });
87
+ },
88
+ updateRelease: async (params, request, api) => {
89
+ if (api.updateReleasePolicy === void 0) return unavailable();
90
+ const input = await parsePolicyInput(request);
91
+ return mutationResponse(() => api.updateReleasePolicy({
92
+ ...input,
93
+ releaseId: require_handlerParameters.requireRouteParam(params, "id")
94
+ }));
95
+ },
96
+ preflightRelease: async (params, request, api) => {
97
+ if (api.preflightReleasePolicy === void 0) return unavailable();
98
+ const input = await parsePolicyInput(request);
99
+ return mutationResponse(() => api.preflightReleasePolicy({
100
+ ...input,
101
+ releaseId: require_handlerParameters.requireRouteParam(params, "id")
102
+ }));
103
+ },
104
+ deleteRelease: async (params, request, api) => {
105
+ if (api.deleteRelease === void 0) return unavailable();
106
+ const releaseId = require_handlerParameters.requireRouteParam(params, "id");
107
+ const url = new URL(request.url);
108
+ if (url.searchParams.get("confirm") !== releaseId) throw new require_handlerErrors.HandlerBadRequestError("Release hard deletion requires confirm=<release-id>");
109
+ return mutationResponse(() => api.deleteRelease({
110
+ expectedRevision: parseRevision(url.searchParams.get("expectedRevision")),
111
+ releaseId
112
+ }));
113
+ },
114
+ rebuildReleaseCatalog: async (params, _request, api) => {
115
+ if (api.rebuildReleaseCatalog === void 0) return unavailable();
116
+ return mutationResponse(() => api.rebuildReleaseCatalog(require_handlerParameters.decodeMaybe(require_handlerParameters.requireRouteParam(params, "scopeKey")) ?? ""));
117
+ },
118
+ commitDatabase: async (_params, request, api) => {
119
+ if (api.commitDatabase === void 0) return unavailable();
120
+ const input = await request.json();
121
+ return Response.json({ data: await api.commitDatabase(input) });
122
+ }
123
+ });
124
+ //#endregion
125
+ exports.createReleaseManagementRouteHandlers = createReleaseManagementRouteHandlers;