@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
@@ -0,0 +1,226 @@
1
+ import type {
2
+ ApiKeyRow,
3
+ BundleEventRow,
4
+ BundlePatchRow,
5
+ BundleRow,
6
+ ChannelRow,
7
+ ReleaseCatalogRow,
8
+ ReleaseRow,
9
+ } from "@hot-updater/plugin-core";
10
+ import type {
11
+ DatabaseModel,
12
+ DatabaseOrderBy,
13
+ DatabaseWhere,
14
+ } from "@hot-updater/plugin-core/internal";
15
+ import type { Document, Filter, Sort } from "mongodb";
16
+
17
+ type AnyDatabaseWhere = {
18
+ readonly [TModel in DatabaseModel]: DatabaseWhere<TModel>;
19
+ }[DatabaseModel];
20
+ type AnyDatabaseOrderBy = {
21
+ readonly [TModel in DatabaseModel]: DatabaseOrderBy<TModel>;
22
+ }[DatabaseModel];
23
+
24
+ const escapeRegularExpression = (value: string): string =>
25
+ value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
26
+
27
+ function literal(value: unknown): Document {
28
+ return { $literal: value };
29
+ }
30
+
31
+ const stringExpression = (
32
+ field: string,
33
+ operator: "contains" | "ends_with" | "starts_with",
34
+ value: string,
35
+ mode: "insensitive" | "sensitive" | undefined,
36
+ ): Document => {
37
+ const escaped = escapeRegularExpression(value);
38
+ const regex =
39
+ operator === "starts_with"
40
+ ? `^${escaped}`
41
+ : operator === "ends_with"
42
+ ? `${escaped}$`
43
+ : escaped;
44
+ return {
45
+ $regexMatch: {
46
+ input: { $ifNull: [`$${field}`, ""] },
47
+ regex: literal(regex),
48
+ ...(mode === "insensitive" ? { options: "i" } : {}),
49
+ },
50
+ };
51
+ };
52
+
53
+ const predicate = (where: AnyDatabaseWhere): Document => {
54
+ const field = `$${where.field}`;
55
+ switch (where.operator) {
56
+ case undefined:
57
+ case "eq":
58
+ return {
59
+ $expr:
60
+ "mode" in where &&
61
+ where.mode === "insensitive" &&
62
+ typeof where.value === "string"
63
+ ? {
64
+ $eq: [
65
+ { $toLower: { $ifNull: [field, ""] } },
66
+ literal(where.value.toLocaleLowerCase()),
67
+ ],
68
+ }
69
+ : { $eq: [field, literal(where.value)] },
70
+ };
71
+ case "ne": {
72
+ const comparison =
73
+ "mode" in where && where.mode === "insensitive"
74
+ ? {
75
+ $ne: [
76
+ { $toLower: { $ifNull: [field, ""] } },
77
+ literal(where.value.toLocaleLowerCase()),
78
+ ],
79
+ }
80
+ : { $ne: [field, literal(where.value)] };
81
+ return where.value === null
82
+ ? { $expr: comparison }
83
+ : {
84
+ $and: [{ $expr: { $ne: [field, null] } }, { $expr: comparison }],
85
+ };
86
+ }
87
+ case "gt":
88
+ case "gte":
89
+ case "lt":
90
+ case "lte":
91
+ return {
92
+ $and: [
93
+ { $expr: { $ne: [field, null] } },
94
+ { $expr: { [`$${where.operator}`]: [field, literal(where.value)] } },
95
+ ],
96
+ };
97
+ case "in":
98
+ return { $expr: { $in: [field, literal(where.value)] } };
99
+ case "not_in":
100
+ return where.value.length === 0
101
+ ? { $expr: { $not: [{ $in: [field, literal(where.value)] }] } }
102
+ : {
103
+ $and: [
104
+ { $expr: { $ne: [field, null] } },
105
+ { $expr: { $not: [{ $in: [field, literal(where.value)] }] } },
106
+ ],
107
+ };
108
+ case "contains":
109
+ case "starts_with":
110
+ case "ends_with":
111
+ return {
112
+ $expr: stringExpression(
113
+ where.field,
114
+ where.operator,
115
+ where.value,
116
+ where.mode,
117
+ ),
118
+ };
119
+ }
120
+ };
121
+
122
+ const createMongoWhereDocument = (
123
+ where: readonly AnyDatabaseWhere[] | undefined,
124
+ ): Document => {
125
+ const items = Array.isArray(where) ? where : [];
126
+ const first = items[0];
127
+ if (first === undefined) return {};
128
+
129
+ let result = predicate(first);
130
+ for (const item of items.slice(1)) {
131
+ result = {
132
+ [item.connector === "OR" ? "$or" : "$and"]: [result, predicate(item)],
133
+ };
134
+ }
135
+ return result;
136
+ };
137
+
138
+ export function createMongoBundleWhere(
139
+ where: readonly DatabaseWhere<"bundles">[] | undefined,
140
+ ): Filter<BundleRow>;
141
+ export function createMongoBundleWhere(
142
+ where: readonly DatabaseWhere<"bundles">[] | undefined,
143
+ ): Document {
144
+ return createMongoWhereDocument(where);
145
+ }
146
+
147
+ export function createMongoPatchWhere(
148
+ where: readonly DatabaseWhere<"bundle_patches">[] | undefined,
149
+ ): Filter<BundlePatchRow>;
150
+ export function createMongoPatchWhere(
151
+ where: readonly DatabaseWhere<"bundle_patches">[] | undefined,
152
+ ): Document {
153
+ return createMongoWhereDocument(where);
154
+ }
155
+
156
+ export function createMongoEventWhere(
157
+ where: readonly DatabaseWhere<"bundle_events">[] | undefined,
158
+ ): Filter<BundleEventRow>;
159
+ export function createMongoEventWhere(
160
+ where: readonly DatabaseWhere<"bundle_events">[] | undefined,
161
+ ): Document {
162
+ return createMongoWhereDocument(where);
163
+ }
164
+
165
+ export function createMongoApiKeyWhere(
166
+ where: readonly DatabaseWhere<"api_keys">[] | undefined,
167
+ ): Filter<ApiKeyRow>;
168
+ export function createMongoApiKeyWhere(
169
+ where: readonly DatabaseWhere<"api_keys">[] | undefined,
170
+ ): Document {
171
+ return createMongoWhereDocument(where);
172
+ }
173
+
174
+ export function createMongoChannelWhere(
175
+ where: readonly DatabaseWhere<"channels">[] | undefined,
176
+ ): Filter<ChannelRow>;
177
+ export function createMongoChannelWhere(
178
+ where: readonly DatabaseWhere<"channels">[] | undefined,
179
+ ): Document {
180
+ return createMongoWhereDocument(where);
181
+ }
182
+
183
+ export function createMongoReleaseWhere(
184
+ where: readonly DatabaseWhere<"releases">[] | undefined,
185
+ ): Filter<ReleaseRow>;
186
+ export function createMongoReleaseWhere(
187
+ where: readonly DatabaseWhere<"releases">[] | undefined,
188
+ ): Document {
189
+ return createMongoWhereDocument(where);
190
+ }
191
+
192
+ export function createMongoReleaseCatalogWhere(
193
+ where: readonly DatabaseWhere<"release_catalogs">[] | undefined,
194
+ ): Filter<ReleaseCatalogRow>;
195
+ export function createMongoReleaseCatalogWhere(
196
+ where: readonly DatabaseWhere<"release_catalogs">[] | undefined,
197
+ ): Document {
198
+ return createMongoWhereDocument(where);
199
+ }
200
+
201
+ export const createMongoSort = (
202
+ input:
203
+ | {
204
+ readonly orderBy?: AnyDatabaseOrderBy;
205
+ }
206
+ | AnyDatabaseOrderBy
207
+ | undefined,
208
+ ): Sort | undefined => {
209
+ const clauses:
210
+ | readonly { field: string; direction: "asc" | "desc" }[]
211
+ | undefined = Array.isArray(input)
212
+ ? (input as readonly { field: string; direction: "asc" | "desc" }[])
213
+ : input && "orderBy" in input && Array.isArray(input.orderBy)
214
+ ? (input.orderBy as readonly {
215
+ field: string;
216
+ direction: "asc" | "desc";
217
+ }[])
218
+ : undefined;
219
+ if (clauses === undefined || clauses.length === 0) return undefined;
220
+ return Object.fromEntries(
221
+ clauses.map((clause) => [
222
+ clause.field,
223
+ clause.direction === "asc" ? 1 : -1,
224
+ ]),
225
+ );
226
+ };
@@ -0,0 +1,311 @@
1
+ import { DatabasePluginInputError } from "@hot-updater/plugin-core";
2
+ import type {
3
+ DatabaseImplementationResult,
4
+ DatabasePluginImplementation,
5
+ FindManyDatabaseImplementationInput,
6
+ } from "@hot-updater/plugin-core/internal";
7
+ import type { ClientSession } from "mongodb";
8
+
9
+ import { hasNullOrderOverrides, sortRowsByOrder } from "./databasePluginUtils";
10
+ import {
11
+ activeBundleFilter,
12
+ type MongoCollections,
13
+ mongoSessionOptions,
14
+ WITHOUT_INTERNAL_FIELDS,
15
+ WITHOUT_MONGO_ID,
16
+ } from "./mongodbCollections";
17
+ import {
18
+ createMongoBundleWhere,
19
+ createMongoChannelWhere,
20
+ createMongoApiKeyWhere,
21
+ createMongoEventWhere,
22
+ createMongoPatchWhere,
23
+ createMongoReleaseCatalogWhere,
24
+ createMongoReleaseWhere,
25
+ createMongoSort,
26
+ } from "./mongodbQuery";
27
+
28
+ const findMongoRows = async (
29
+ collections: MongoCollections,
30
+ input: FindManyDatabaseImplementationInput,
31
+ session?: ClientSession,
32
+ ): Promise<readonly DatabaseImplementationResult[]> => {
33
+ if (input.limit === 0) return [];
34
+ const rawOrderBy = input.orderBy;
35
+ const needsInMemoryOrder = hasNullOrderOverrides(rawOrderBy);
36
+ switch (input.model) {
37
+ case "bundles": {
38
+ const cursor = collections.bundles
39
+ .find(activeBundleFilter(createMongoBundleWhere(input.where)), {
40
+ projection: WITHOUT_INTERNAL_FIELDS,
41
+ ...mongoSessionOptions(session),
42
+ })
43
+ .skip(input.offset)
44
+ .limit(input.limit);
45
+ if (rawOrderBy === undefined) return cursor.toArray();
46
+ if (needsInMemoryOrder) {
47
+ const rows = await collections.bundles
48
+ .find(activeBundleFilter(createMongoBundleWhere(input.where)), {
49
+ projection: WITHOUT_INTERNAL_FIELDS,
50
+ ...mongoSessionOptions(session),
51
+ })
52
+ .toArray();
53
+ return sortRowsByOrder(rows, rawOrderBy).slice(
54
+ input.offset,
55
+ input.offset + input.limit,
56
+ );
57
+ }
58
+ const sort = createMongoSort(input);
59
+ return sort === undefined
60
+ ? cursor.toArray()
61
+ : cursor.sort(sort).toArray();
62
+ }
63
+ case "bundle_patches": {
64
+ const cursor = collections.bundlePatches
65
+ .find(createMongoPatchWhere(input.where), {
66
+ projection: WITHOUT_MONGO_ID,
67
+ ...mongoSessionOptions(session),
68
+ })
69
+ .skip(input.offset)
70
+ .limit(input.limit);
71
+ if (rawOrderBy === undefined) return cursor.toArray();
72
+ if (needsInMemoryOrder) {
73
+ const rows = await collections.bundlePatches
74
+ .find(createMongoPatchWhere(input.where), {
75
+ projection: WITHOUT_MONGO_ID,
76
+ ...mongoSessionOptions(session),
77
+ })
78
+ .toArray();
79
+ return sortRowsByOrder(rows, rawOrderBy).slice(
80
+ input.offset,
81
+ input.offset + input.limit,
82
+ );
83
+ }
84
+ const sort = createMongoSort(input);
85
+ return sort === undefined
86
+ ? cursor.toArray()
87
+ : cursor.sort(sort).toArray();
88
+ }
89
+ case "bundle_events": {
90
+ const cursor = collections.bundleEvents
91
+ .find(createMongoEventWhere(input.where), {
92
+ projection: WITHOUT_MONGO_ID,
93
+ ...mongoSessionOptions(session),
94
+ })
95
+ .skip(input.offset)
96
+ .limit(input.limit);
97
+ if (rawOrderBy === undefined) return cursor.toArray();
98
+ if (needsInMemoryOrder) {
99
+ const rows = await collections.bundleEvents
100
+ .find(createMongoEventWhere(input.where), {
101
+ projection: WITHOUT_MONGO_ID,
102
+ ...mongoSessionOptions(session),
103
+ })
104
+ .toArray();
105
+ return sortRowsByOrder(rows, rawOrderBy).slice(
106
+ input.offset,
107
+ input.offset + input.limit,
108
+ );
109
+ }
110
+ const sort = createMongoSort(input);
111
+ return sort === undefined
112
+ ? cursor.toArray()
113
+ : cursor.sort(sort).toArray();
114
+ }
115
+ case "api_keys": {
116
+ const cursor = collections.apiKeys
117
+ .find(createMongoApiKeyWhere(input.where), {
118
+ projection: WITHOUT_MONGO_ID,
119
+ ...mongoSessionOptions(session),
120
+ })
121
+ .skip(input.offset)
122
+ .limit(input.limit);
123
+ if (rawOrderBy === undefined) return cursor.toArray();
124
+ if (needsInMemoryOrder) {
125
+ const rows = await collections.apiKeys
126
+ .find(createMongoApiKeyWhere(input.where), {
127
+ projection: WITHOUT_MONGO_ID,
128
+ ...mongoSessionOptions(session),
129
+ })
130
+ .toArray();
131
+ return sortRowsByOrder(rows, rawOrderBy).slice(
132
+ input.offset,
133
+ input.offset + input.limit,
134
+ );
135
+ }
136
+ const sort = createMongoSort(input);
137
+ return sort === undefined
138
+ ? cursor.toArray()
139
+ : cursor.sort(sort).toArray();
140
+ }
141
+ case "channels": {
142
+ const cursor = collections.channels
143
+ .find(createMongoChannelWhere(input.where), {
144
+ projection: WITHOUT_MONGO_ID,
145
+ ...mongoSessionOptions(session),
146
+ })
147
+ .skip(input.offset)
148
+ .limit(input.limit);
149
+ if (rawOrderBy === undefined) return cursor.toArray();
150
+ if (needsInMemoryOrder) {
151
+ const rows = await collections.channels
152
+ .find(createMongoChannelWhere(input.where), {
153
+ projection: WITHOUT_MONGO_ID,
154
+ ...mongoSessionOptions(session),
155
+ })
156
+ .toArray();
157
+ return sortRowsByOrder(rows, rawOrderBy).slice(
158
+ input.offset,
159
+ input.offset + input.limit,
160
+ );
161
+ }
162
+ const sort = createMongoSort(input);
163
+ return sort === undefined
164
+ ? cursor.toArray()
165
+ : cursor.sort(sort).toArray();
166
+ }
167
+ case "releases": {
168
+ const cursor = collections.releases
169
+ .find(createMongoReleaseWhere(input.where), {
170
+ projection: WITHOUT_MONGO_ID,
171
+ ...mongoSessionOptions(session),
172
+ })
173
+ .skip(input.offset)
174
+ .limit(input.limit);
175
+ if (rawOrderBy === undefined) return cursor.toArray();
176
+ if (needsInMemoryOrder) {
177
+ const rows = await collections.releases
178
+ .find(createMongoReleaseWhere(input.where), {
179
+ projection: WITHOUT_MONGO_ID,
180
+ ...mongoSessionOptions(session),
181
+ })
182
+ .toArray();
183
+ return sortRowsByOrder(rows, rawOrderBy).slice(
184
+ input.offset,
185
+ input.offset + input.limit,
186
+ );
187
+ }
188
+ const sort = createMongoSort(input);
189
+ return sort === undefined
190
+ ? cursor.toArray()
191
+ : cursor.sort(sort).toArray();
192
+ }
193
+ case "release_catalogs": {
194
+ const cursor = collections.releaseCatalogs
195
+ .find(createMongoReleaseCatalogWhere(input.where), {
196
+ projection: WITHOUT_MONGO_ID,
197
+ ...mongoSessionOptions(session),
198
+ })
199
+ .skip(input.offset)
200
+ .limit(input.limit);
201
+ if (rawOrderBy === undefined) return cursor.toArray();
202
+ if (needsInMemoryOrder) {
203
+ const rows = await collections.releaseCatalogs
204
+ .find(createMongoReleaseCatalogWhere(input.where), {
205
+ projection: WITHOUT_MONGO_ID,
206
+ ...mongoSessionOptions(session),
207
+ })
208
+ .toArray();
209
+ return sortRowsByOrder(rows, rawOrderBy).slice(
210
+ input.offset,
211
+ input.offset + input.limit,
212
+ );
213
+ }
214
+ const sort = createMongoSort(input);
215
+ return sort === undefined
216
+ ? cursor.toArray()
217
+ : cursor.sort(sort).toArray();
218
+ }
219
+ }
220
+ };
221
+
222
+ type MongoReadImplementation = Pick<
223
+ DatabasePluginImplementation,
224
+ "count" | "findMany" | "findOne"
225
+ >;
226
+
227
+ export const createMongoReads = (
228
+ collections: MongoCollections,
229
+ session?: ClientSession,
230
+ ): MongoReadImplementation => ({
231
+ count: async (input) => {
232
+ if (input.distinct !== undefined) {
233
+ throw new DatabasePluginInputError("invalid-operation");
234
+ }
235
+ switch (input.model) {
236
+ case "bundles":
237
+ return collections.bundles.countDocuments(
238
+ activeBundleFilter(createMongoBundleWhere(input.where)),
239
+ mongoSessionOptions(session),
240
+ );
241
+ case "bundle_patches":
242
+ return collections.bundlePatches.countDocuments(
243
+ createMongoPatchWhere(input.where),
244
+ mongoSessionOptions(session),
245
+ );
246
+ case "releases":
247
+ return collections.releases.countDocuments(
248
+ createMongoReleaseWhere(input.where),
249
+ mongoSessionOptions(session),
250
+ );
251
+ }
252
+ },
253
+ findOne: async (input) => {
254
+ switch (input.model) {
255
+ case "bundles":
256
+ return collections.bundles.findOne(
257
+ activeBundleFilter(createMongoBundleWhere(input.where)),
258
+ {
259
+ projection: WITHOUT_INTERNAL_FIELDS,
260
+ ...mongoSessionOptions(session),
261
+ },
262
+ );
263
+ case "channels":
264
+ return collections.channels.findOne(
265
+ createMongoChannelWhere(input.where),
266
+ {
267
+ projection: WITHOUT_MONGO_ID,
268
+ ...mongoSessionOptions(session),
269
+ },
270
+ );
271
+ case "api_keys":
272
+ return collections.apiKeys.findOne(
273
+ createMongoApiKeyWhere(input.where),
274
+ {
275
+ projection: WITHOUT_MONGO_ID,
276
+ ...mongoSessionOptions(session),
277
+ },
278
+ );
279
+ case "bundle_patches":
280
+ return collections.bundlePatches.findOne(
281
+ createMongoPatchWhere(input.where),
282
+ {
283
+ projection: WITHOUT_MONGO_ID,
284
+ ...mongoSessionOptions(session),
285
+ },
286
+ );
287
+ case "releases":
288
+ return collections.releases.findOne(
289
+ createMongoReleaseWhere(input.where),
290
+ {
291
+ projection: WITHOUT_MONGO_ID,
292
+ ...mongoSessionOptions(session),
293
+ },
294
+ );
295
+ case "release_catalogs":
296
+ return collections.releaseCatalogs.findOne(
297
+ createMongoReleaseCatalogWhere(input.where),
298
+ {
299
+ projection: WITHOUT_MONGO_ID,
300
+ ...mongoSessionOptions(session),
301
+ },
302
+ );
303
+ }
304
+ },
305
+ findMany: (input) => {
306
+ if (input.distinctOn !== undefined) {
307
+ throw new DatabasePluginInputError("invalid-operation");
308
+ }
309
+ return findMongoRows(collections, input, session);
310
+ },
311
+ });
@@ -0,0 +1,64 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import {
4
+ createBundlePatchRowFixture,
5
+ createBundleRowFixture,
6
+ } from "../../../test-utils/src/databaseTestFixtures";
7
+ import { parseMongoBundleRow, parseMongoPatchRow } from "./mongodbRows";
8
+
9
+ describe("parseMongoBundleRow", () => {
10
+ it("rejects a missing metadata field", () => {
11
+ const { metadata: _metadata, ...row } =
12
+ createBundleRowFixture("missing-metadata");
13
+
14
+ expect(() => parseMongoBundleRow(row)).toThrow(
15
+ "Invalid MongoDB plugin data",
16
+ );
17
+ });
18
+
19
+ it("rejects explicit null metadata", () => {
20
+ expect(() =>
21
+ parseMongoBundleRow({
22
+ ...createBundleRowFixture("null-metadata"),
23
+ metadata: null,
24
+ }),
25
+ ).toThrow("Invalid MongoDB plugin data");
26
+ });
27
+
28
+ it("preserves safe archive sizes above 2 GiB", () => {
29
+ expect(parseMongoBundleRow(createBundleRowFixture("large"))).toMatchObject({
30
+ archive_byte_size: 3_000_000_001,
31
+ });
32
+ });
33
+
34
+ it.each([-1, 1.5, Number.MAX_SAFE_INTEGER + 1, Number.NaN])(
35
+ "rejects invalid archive size %s",
36
+ (archiveByteSize) => {
37
+ expect(() =>
38
+ parseMongoBundleRow({
39
+ ...createBundleRowFixture("invalid-size"),
40
+ archive_byte_size: archiveByteSize,
41
+ }),
42
+ ).toThrow("Invalid MongoDB plugin data");
43
+ },
44
+ );
45
+ });
46
+
47
+ describe("parseMongoPatchRow", () => {
48
+ const row = createBundlePatchRowFixture("large", "bundle", "base");
49
+
50
+ it("preserves safe patch sizes above 2 GiB", () => {
51
+ expect(parseMongoPatchRow(row)).toMatchObject({
52
+ byte_size: 3_000_000_002,
53
+ });
54
+ });
55
+
56
+ it.each([-1, 1.5, Number.MAX_SAFE_INTEGER + 1, Number.NaN])(
57
+ "rejects invalid patch size %s",
58
+ (patchByteSize) => {
59
+ expect(() =>
60
+ parseMongoPatchRow({ ...row, byte_size: patchByteSize }),
61
+ ).toThrow("Invalid MongoDB plugin data");
62
+ },
63
+ );
64
+ });