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

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 (191) hide show
  1. package/dist/_virtual/_rolldown/runtime.cjs +1 -1
  2. package/dist/adapters/databaseConstraintErrors.cjs +0 -1
  3. package/dist/adapters/databasePluginUtils.cjs +0 -1
  4. package/dist/adapters/drizzle.cjs +10 -3
  5. package/dist/adapters/drizzle.d.cts +0 -1
  6. package/dist/adapters/drizzle.d.mts +0 -1
  7. package/dist/adapters/drizzle.mjs +11 -3
  8. package/dist/adapters/drizzleCrud.cjs +62 -1
  9. package/dist/adapters/drizzleCrud.mjs +61 -2
  10. package/dist/adapters/drizzleLazyDB.cjs +6 -1
  11. package/dist/adapters/drizzleLazyDB.mjs +6 -1
  12. package/dist/adapters/drizzleQuery.cjs +3 -2
  13. package/dist/adapters/drizzleQuery.mjs +3 -1
  14. package/dist/adapters/insightsTransaction.cjs +22 -0
  15. package/dist/adapters/insightsTransaction.mjs +22 -0
  16. package/dist/adapters/kysely.cjs +2 -1
  17. package/dist/adapters/kysely.d.cts +0 -1
  18. package/dist/adapters/kysely.d.mts +0 -1
  19. package/dist/adapters/kysely.mjs +3 -1
  20. package/dist/adapters/kyselyCrud.cjs +54 -3
  21. package/dist/adapters/kyselyCrud.mjs +54 -3
  22. package/dist/adapters/kyselyQuery.cjs +2 -2
  23. package/dist/adapters/kyselyQuery.mjs +2 -1
  24. package/dist/adapters/mongodb.cjs +27 -1
  25. package/dist/adapters/mongodb.d.cts +1 -1
  26. package/dist/adapters/mongodb.d.mts +1 -1
  27. package/dist/adapters/mongodb.mjs +28 -1
  28. package/dist/adapters/mongodbCollections.cjs +2 -0
  29. package/dist/adapters/mongodbCollections.mjs +2 -0
  30. package/dist/adapters/mongodbQuery.cjs +21 -4
  31. package/dist/adapters/mongodbQuery.mjs +21 -5
  32. package/dist/adapters/mongodbReads.cjs +91 -29
  33. package/dist/adapters/mongodbReads.mjs +92 -29
  34. package/dist/adapters/mongodbWrites.cjs +16 -2
  35. package/dist/adapters/mongodbWrites.mjs +18 -3
  36. package/dist/adapters/prisma.cjs +55 -7
  37. package/dist/adapters/prisma.d.cts +0 -1
  38. package/dist/adapters/prisma.d.mts +0 -1
  39. package/dist/adapters/prisma.mjs +56 -7
  40. package/dist/adapters/prismaQuery.cjs +0 -1
  41. package/dist/adapters/prismaRows.cjs +22 -1
  42. package/dist/adapters/prismaRows.mjs +22 -1
  43. package/dist/apiKeys.cjs +6 -2
  44. package/dist/apiKeys.d.cts +0 -1
  45. package/dist/apiKeys.d.mts +0 -1
  46. package/dist/apiKeys.mjs +3 -3
  47. package/dist/createHotUpdaterCore.cjs +24 -9
  48. package/dist/createHotUpdaterCore.d.cts +1 -1
  49. package/dist/createHotUpdaterCore.d.mts +1 -1
  50. package/dist/createHotUpdaterCore.mjs +23 -9
  51. package/dist/db/createBundleDiff.cjs +2 -2
  52. package/dist/db/createBundleDiff.d.cts +1 -5
  53. package/dist/db/createBundleDiff.d.mts +1 -5
  54. package/dist/db/databasePluginCore.cjs +0 -1
  55. package/dist/db/fixedMigrator.cjs +2 -2
  56. package/dist/db/fixedMigrator.mjs +2 -2
  57. package/dist/db/fixedMigratorMongo.cjs +6 -4
  58. package/dist/db/fixedMigratorMongo.mjs +6 -4
  59. package/dist/db/index.d.cts +0 -1
  60. package/dist/db/index.d.mts +0 -1
  61. package/dist/db/releaseCatalog.cjs +0 -1
  62. package/dist/db/releaseCatalog.d.cts +0 -1
  63. package/dist/db/releaseCatalog.d.mts +0 -1
  64. package/dist/db/schema/insightsCollation.cjs +14 -0
  65. package/dist/db/schema/insightsCollation.mjs +14 -0
  66. package/dist/db/schema/sql.cjs +11 -7
  67. package/dist/db/schema/sql.mjs +8 -8
  68. package/dist/db/schemaGenerators.cjs +28 -9
  69. package/dist/db/schemaGenerators.mjs +28 -9
  70. package/dist/db/schemaReadiness.cjs +2 -0
  71. package/dist/db/schemaReadiness.d.cts +1 -0
  72. package/dist/db/schemaReadiness.d.mts +1 -0
  73. package/dist/db/schemaReadiness.mjs +2 -0
  74. package/dist/db/sqlMigrationExecution.cjs +1 -1
  75. package/dist/db/sqlMigrationExecution.mjs +1 -0
  76. package/dist/db/types.cjs +3 -1
  77. package/dist/db/types.d.cts +3 -6
  78. package/dist/db/types.d.mts +3 -6
  79. package/dist/db/types.mjs +2 -2
  80. package/dist/db/updateArtifacts.cjs +0 -1
  81. package/dist/handler.d.cts +1 -2
  82. package/dist/handler.d.mts +1 -2
  83. package/dist/handlerReleaseCatalogRoutes.cjs +0 -1
  84. package/dist/handlerReleaseManagementRoutes.cjs +0 -1
  85. package/dist/handlerTypes.d.cts +0 -1
  86. package/dist/handlerTypes.d.mts +0 -1
  87. package/dist/handlerVersionRoutes.d.cts +1 -0
  88. package/dist/handlerVersionRoutes.d.mts +1 -0
  89. package/dist/index.cjs +1 -1
  90. package/dist/index.d.cts +4 -4
  91. package/dist/index.d.mts +4 -4
  92. package/dist/index.mjs +1 -1
  93. package/dist/insights/domain.d.cts +37 -69
  94. package/dist/insights/domain.d.mts +37 -69
  95. package/dist/insights/errors.cjs +1 -8
  96. package/dist/insights/errors.mjs +2 -8
  97. package/dist/insights/eventInput.cjs +49 -5
  98. package/dist/insights/eventInput.mjs +48 -6
  99. package/dist/insights/provider.cjs +314 -0
  100. package/dist/insights/provider.d.cts +6 -0
  101. package/dist/insights/provider.d.mts +6 -0
  102. package/dist/insights/provider.mjs +314 -0
  103. package/dist/insights/queryInput.cjs +78 -43
  104. package/dist/insights/queryInput.mjs +76 -40
  105. package/dist/insights/routes.cjs +20 -25
  106. package/dist/insights/routes.mjs +22 -27
  107. package/dist/insights/types.d.cts +27 -12
  108. package/dist/insights/types.d.mts +27 -12
  109. package/dist/node.d.cts +0 -1
  110. package/dist/node.d.mts +0 -1
  111. package/dist/package.cjs +1 -1
  112. package/dist/package.mjs +1 -1
  113. package/dist/schema/index.cjs +17 -3
  114. package/dist/schema/index.mjs +4 -4
  115. package/dist/schema/v1_0_0.cjs +72 -30
  116. package/dist/schema/v1_0_0.mjs +72 -31
  117. package/dist/storageAccess.cjs +0 -1
  118. package/dist/types/index.d.cts +0 -1
  119. package/dist/types/index.d.mts +0 -1
  120. package/package.json +7 -7
  121. package/src/adapters/databasePluginTestDatabase.ts +14 -0
  122. package/src/adapters/drizzle.spec.ts +99 -0
  123. package/src/adapters/drizzle.ts +21 -3
  124. package/src/adapters/drizzleCrud.ts +154 -2
  125. package/src/adapters/drizzleLazyDB.ts +17 -3
  126. package/src/adapters/insightsTransaction.ts +32 -0
  127. package/src/adapters/kysely.spec.ts +29 -0
  128. package/src/adapters/kysely.ts +10 -1
  129. package/src/adapters/kyselyCrud.ts +121 -0
  130. package/src/adapters/mongodb.integration.spec.ts +210 -0
  131. package/src/adapters/mongodb.spec.ts +69 -1
  132. package/src/adapters/mongodb.ts +66 -2
  133. package/src/adapters/mongodbCollections.ts +5 -0
  134. package/src/adapters/mongodbQuery.ts +32 -3
  135. package/src/adapters/mongodbReads.ts +69 -0
  136. package/src/adapters/mongodbTestClient.ts +41 -6
  137. package/src/adapters/mongodbTestFilter.ts +4 -1
  138. package/src/adapters/mongodbWrites.ts +27 -0
  139. package/src/adapters/prisma.spec.ts +97 -0
  140. package/src/adapters/prisma.ts +103 -3
  141. package/src/adapters/prismaRows.ts +37 -1
  142. package/src/adapters/prismaTestClient.ts +22 -5
  143. package/src/apiKeys.integration.spec.ts +1 -1
  144. package/src/createHotUpdaterCore.ts +21 -7
  145. package/src/db/databasePluginUpdateCheck.bench.ts +3 -0
  146. package/src/db/fixedMigrator.spec.ts +65 -0
  147. package/src/db/fixedMigratorMongo.spec.ts +56 -9
  148. package/src/db/fixedMigratorMongo.ts +10 -10
  149. package/src/db/index.spec.ts +8 -1
  150. package/src/db/schema/insightsCollation.ts +21 -0
  151. package/src/db/schema/sql.ts +8 -5
  152. package/src/db/schemaGenerators.ts +41 -13
  153. package/src/db/types.spec.ts +8 -0
  154. package/src/db/types.ts +10 -4
  155. package/src/handler-standalone.integration.spec.ts +5 -7
  156. package/src/index.ts +2 -2
  157. package/src/insights/domain.ts +39 -75
  158. package/src/insights/errors.ts +0 -7
  159. package/src/insights/eventInput.ts +60 -3
  160. package/src/insights/insights.integration.spec.ts +254 -17
  161. package/src/insights/provider.spec.ts +385 -0
  162. package/src/insights/provider.ts +591 -0
  163. package/src/insights/queryInput.ts +112 -73
  164. package/src/insights/routes.ts +37 -56
  165. package/src/insights/types.ts +47 -36
  166. package/src/schema/dsl-all-versions.spec.ts +3 -1
  167. package/src/schema/v1_0_0.spec.ts +77 -3
  168. package/src/schema/v1_0_0.ts +94 -30
  169. package/dist/insights/bounded/activeOverview.cjs +0 -87
  170. package/dist/insights/bounded/activeOverview.mjs +0 -85
  171. package/dist/insights/bounded/installationSearch.cjs +0 -42
  172. package/dist/insights/bounded/installationSearch.mjs +0 -42
  173. package/dist/insights/bounded/persistence.cjs +0 -39
  174. package/dist/insights/bounded/persistence.mjs +0 -38
  175. package/dist/insights/bounded/provider.cjs +0 -170
  176. package/dist/insights/bounded/provider.d.cts +0 -7
  177. package/dist/insights/bounded/provider.d.mts +0 -7
  178. package/dist/insights/bounded/provider.mjs +0 -170
  179. package/dist/insights/bounded/scan.cjs +0 -144
  180. package/dist/insights/bounded/scan.mjs +0 -138
  181. package/dist/insights/persistence.d.cts +0 -6
  182. package/dist/insights/persistence.d.mts +0 -6
  183. package/src/insights/bounded/activeOverview.spec.ts +0 -189
  184. package/src/insights/bounded/activeOverview.ts +0 -148
  185. package/src/insights/bounded/installationSearch.spec.ts +0 -130
  186. package/src/insights/bounded/installationSearch.ts +0 -69
  187. package/src/insights/bounded/persistence.ts +0 -43
  188. package/src/insights/bounded/provider.spec.ts +0 -219
  189. package/src/insights/bounded/provider.ts +0 -229
  190. package/src/insights/bounded/scan.ts +0 -235
  191. package/src/insights/persistence.ts +0 -10
@@ -1,4 +1,3 @@
1
- require("../_virtual/_rolldown/runtime.cjs");
2
1
  const require_databaseConstraintErrors = require("./databaseConstraintErrors.cjs");
3
2
  const require_databasePluginUtils = require("./databasePluginUtils.cjs");
4
3
  const require_kyselyQuery = require("./kyselyQuery.cjs");
@@ -6,6 +5,7 @@ let _hot_updater_plugin_core = require("@hot-updater/plugin-core");
6
5
  let kysely = require("kysely");
7
6
  //#region src/adapters/kyselyCrud.ts
8
7
  var KyselyAdapterInvariantError = class extends Error {
8
+ reason;
9
9
  name = "KyselyAdapterInvariantError";
10
10
  constructor(reason) {
11
11
  super(`Kysely plugin invariant failed: ${reason}`);
@@ -20,7 +20,10 @@ const orderClause = (input) => {
20
20
  if (clauses === void 0 || clauses.length === 0) return empty;
21
21
  return kysely.sql` order by ${kysely.sql.join(clauses.map((clause) => {
22
22
  const field = kysely.sql.ref(clause.field);
23
- return kysely.sql`${(clause.nulls ?? (clause.direction === "asc" ? "last" : "first")) === "first" ? kysely.sql`${field} is null desc` : kysely.sql`${field} is null asc`}, ${clause.direction === "asc" ? kysely.sql`${field} asc` : kysely.sql`${field} desc`}`;
23
+ if (input?.model === "bundle_events" || input?.model === "bundle_installations") return clause.direction === "asc" ? kysely.sql`${field} asc` : kysely.sql`${field} desc`;
24
+ const nullOrder = (clause.nulls ?? (clause.direction === "asc" ? "last" : "first")) === "first" ? kysely.sql`${field} is null desc` : kysely.sql`${field} is null asc`;
25
+ const valueOrder = clause.direction === "asc" ? kysely.sql`${field} asc` : kysely.sql`${field} desc`;
26
+ return kysely.sql`${nullOrder}, ${valueOrder}`;
24
27
  }), kysely.sql`, `)}`;
25
28
  };
26
29
  const insertRow = async (executor, table, row, provider, onConflict = void 0) => {
@@ -30,6 +33,26 @@ const insertRow = async (executor, table, row, provider, onConflict = void 0) =>
30
33
  const result = onConflict === void 0 ? await kysely.sql`insert into ${kysely.sql.table(table)} (${columns}) values (${values})`.execute(executor) : provider === "mysql" ? await kysely.sql`insert ignore into ${kysely.sql.table(table)} (${columns}) values (${values})`.execute(executor) : provider === "sqlite" ? await kysely.sql`insert or ignore into ${kysely.sql.table(table)} (${columns}) values (${values})`.execute(executor) : await kysely.sql`insert into ${kysely.sql.table(table)} (${columns}) values (${values}) on conflict do nothing`.execute(executor);
31
34
  return Number(result.numAffectedRows ?? 0) > 0;
32
35
  };
36
+ const recordKyselyInsights = async (executor, provider, { event, installation }) => {
37
+ const insert = (table, row) => {
38
+ const entries = Object.entries(row);
39
+ return kysely.sql`insert into ${kysely.sql.table(table)} (${kysely.sql.join(entries.map(([field]) => kysely.sql.ref(field)))}) values (${kysely.sql.join(entries.map(([, value]) => value))})`;
40
+ };
41
+ if (provider === "mysql") {
42
+ if ((await kysely.sql`select id from bundle_events where id = ${event.id} for update`.execute(executor)).rows.length > 0) return;
43
+ await insert("bundle_events", event).execute(executor);
44
+ await kysely.sql`${insert("bundle_installations", installation)} on duplicate key update install_id = ${installation.install_id}`.execute(executor);
45
+ } else {
46
+ if ((await kysely.sql`${insert("bundle_events", event)} on conflict (id) do nothing returning id`.execute(executor)).rows.length === 0) return;
47
+ await kysely.sql`${insert("bundle_installations", installation)} on conflict (install_id) do nothing`.execute(executor);
48
+ }
49
+ const assignments = Object.entries(installation).map(([field, value]) => kysely.sql`${kysely.sql.ref(field)} = ${value}`);
50
+ await kysely.sql`update ${kysely.sql.table("bundle_installations")} set ${kysely.sql.join(assignments)}
51
+ where ${kysely.sql.ref("install_id")} = ${installation.install_id}
52
+ and (${kysely.sql.ref("received_at_ms")} < ${installation.received_at_ms}
53
+ or (${kysely.sql.ref("received_at_ms")} = ${installation.received_at_ms}
54
+ and ${kysely.sql.ref("id")} < ${installation.id}))`.execute(executor);
55
+ };
33
56
  const updateBundle = async (executor, id, update) => {
34
57
  const assignments = Object.entries(update).filter(([, value]) => value !== void 0).map(([field, value]) => kysely.sql`${kysely.sql.ref(field)} = ${value}`);
35
58
  if (assignments.length === 0) return;
@@ -44,7 +67,7 @@ const updateRow = async (executor, table, keyField, key, update) => {
44
67
  await kysely.sql`update ${kysely.sql.table(table)} set ${kysely.sql.join(assignments)} where ${kysely.sql.ref(keyField)} = ${key}`.execute(executor);
45
68
  };
46
69
  const assertBundleReferences = async (executor, provider, relationMode, bundleId, baseBundleId) => {
47
- const ids = [...new Set([bundleId, baseBundleId])].sort((left, right) => left.localeCompare(right));
70
+ const ids = [.../* @__PURE__ */ new Set([bundleId, baseBundleId])].sort((left, right) => left.localeCompare(right));
48
71
  const result = await kysely.sql`select ${kysely.sql.ref("id")} from ${kysely.sql.table("bundles")} where ${kysely.sql.ref("id")} in (${kysely.sql.join(ids)}) order by ${kysely.sql.ref("id")}${lockClause(provider, relationMode)}`.execute(executor);
49
72
  const storedIds = new Set(result.rows.map(({ id }) => id));
50
73
  if (!storedIds.has(bundleId)) throw new KyselyAdapterInvariantError("bundle_patches.bundle_id.foreign-key");
@@ -96,6 +119,9 @@ const createKyselyCrud = (executor, provider, relationMode = "foreign-keys") =>
96
119
  case "bundle_events":
97
120
  await insertRow(executor, "bundle_events", input.data, provider);
98
121
  return input.data;
122
+ case "bundle_installations":
123
+ await insertRow(executor, "bundle_installations", input.data, provider, input.onConflict);
124
+ return input.data;
99
125
  case "releases":
100
126
  await insertRow(executor, "releases", require_databasePluginUtils.toStoredReleaseRow(input.data, provider), provider);
101
127
  return input.data;
@@ -111,6 +137,19 @@ const createKyselyCrud = (executor, provider, relationMode = "foreign-keys") =>
111
137
  }
112
138
  },
113
139
  async update(input) {
140
+ if (input.model === "bundle_installations") {
141
+ const installId = input.where.find((item) => item.field === "install_id" && (item.operator === void 0 || item.operator === "eq") && typeof item.value === "string")?.value;
142
+ if (typeof installId !== "string") throw new KyselyAdapterInvariantError("bundle_installations.update.selector");
143
+ const assignments = Object.entries(input.update).filter(([, value]) => value !== void 0).map(([field, value]) => kysely.sql`${kysely.sql.ref(field)} = ${value}`);
144
+ const where = require_kyselyQuery.buildKyselyWhere(provider, input.where);
145
+ let updated = false;
146
+ if (assignments.length > 0 && where !== void 0) {
147
+ const result = await kysely.sql`update ${kysely.sql.table("bundle_installations")} set ${kysely.sql.join(assignments)} where ${where}`.execute(executor);
148
+ updated = Number(result.numAffectedRows ?? 0) > 0;
149
+ }
150
+ if (!updated) return null;
151
+ return (await kysely.sql`select * from ${kysely.sql.table("bundle_installations")} where ${kysely.sql.ref("install_id")} = ${installId} limit 1`.execute(executor)).rows[0] ?? null;
152
+ }
114
153
  const selector = input.where[0];
115
154
  if (selector === void 0 || typeof selector.value !== "string") throw new KyselyAdapterInvariantError(`${input.model}.update.selector`);
116
155
  if (input.model === "api_keys") {
@@ -172,9 +211,11 @@ const createKyselyCrud = (executor, provider, relationMode = "foreign-keys") =>
172
211
  async count(input) {
173
212
  if (input.distinct !== void 0) throw new _hot_updater_plugin_core.DatabasePluginInputError("invalid-operation");
174
213
  switch (input.model) {
214
+ case "bundle_events": return countRows(executor, "bundle_events", require_kyselyQuery.buildKyselyWhere(provider, input.where));
175
215
  case "bundles": return countRows(executor, "bundles", require_kyselyQuery.buildKyselyWhere(provider, input.where));
176
216
  case "bundle_patches": return countRows(executor, "bundle_patches", require_kyselyQuery.buildKyselyWhere(provider, input.where));
177
217
  case "releases": return countRows(executor, "releases", require_kyselyQuery.buildKyselyWhere(provider, input.where));
218
+ case "bundle_installations": return countRows(executor, "bundle_installations", require_kyselyQuery.buildKyselyWhere(provider, input.where));
178
219
  }
179
220
  },
180
221
  async findOne(input) {
@@ -206,6 +247,10 @@ const createKyselyCrud = (executor, provider, relationMode = "foreign-keys") =>
206
247
  const row = (await kysely.sql`select * from ${kysely.sql.table("release_catalogs")}${where} limit 1`.execute(executor)).rows[0];
207
248
  return row === void 0 ? null : require_databasePluginUtils.fromStoredReleaseCatalogRow(row);
208
249
  }
250
+ case "bundle_installations": {
251
+ const where = whereClause(require_kyselyQuery.buildKyselyWhere(provider, input.where));
252
+ return (await kysely.sql`select * from ${kysely.sql.table("bundle_installations")}${where} limit 1`.execute(executor)).rows[0] ?? null;
253
+ }
209
254
  }
210
255
  },
211
256
  async findMany(input) {
@@ -227,6 +272,11 @@ const createKyselyCrud = (executor, provider, relationMode = "foreign-keys") =>
227
272
  const order = orderClause(input);
228
273
  return [...(await kysely.sql`select * from ${kysely.sql.table("bundle_events")}${where}${order}${pagination}`.execute(executor)).rows];
229
274
  }
275
+ case "bundle_installations": {
276
+ const where = whereClause(require_kyselyQuery.buildKyselyWhere(provider, input.where));
277
+ const order = orderClause(input);
278
+ return [...(await kysely.sql`select * from ${kysely.sql.table("bundle_installations")}${where}${order}${pagination}`.execute(executor)).rows];
279
+ }
230
280
  case "api_keys": {
231
281
  const where = whereClause(require_kyselyQuery.buildKyselyWhere(provider, input.where));
232
282
  const order = orderClause(input);
@@ -252,3 +302,4 @@ const createKyselyCrud = (executor, provider, relationMode = "foreign-keys") =>
252
302
  });
253
303
  //#endregion
254
304
  exports.createKyselyCrud = createKyselyCrud;
305
+ exports.recordKyselyInsights = recordKyselyInsights;
@@ -5,6 +5,7 @@ import { DatabasePluginInputError } from "@hot-updater/plugin-core";
5
5
  import { sql } from "kysely";
6
6
  //#region src/adapters/kyselyCrud.ts
7
7
  var KyselyAdapterInvariantError = class extends Error {
8
+ reason;
8
9
  name = "KyselyAdapterInvariantError";
9
10
  constructor(reason) {
10
11
  super(`Kysely plugin invariant failed: ${reason}`);
@@ -19,7 +20,10 @@ const orderClause = (input) => {
19
20
  if (clauses === void 0 || clauses.length === 0) return empty;
20
21
  return sql` order by ${sql.join(clauses.map((clause) => {
21
22
  const field = sql.ref(clause.field);
22
- return sql`${(clause.nulls ?? (clause.direction === "asc" ? "last" : "first")) === "first" ? sql`${field} is null desc` : sql`${field} is null asc`}, ${clause.direction === "asc" ? sql`${field} asc` : sql`${field} desc`}`;
23
+ if (input?.model === "bundle_events" || input?.model === "bundle_installations") return clause.direction === "asc" ? sql`${field} asc` : sql`${field} desc`;
24
+ const nullOrder = (clause.nulls ?? (clause.direction === "asc" ? "last" : "first")) === "first" ? sql`${field} is null desc` : sql`${field} is null asc`;
25
+ const valueOrder = clause.direction === "asc" ? sql`${field} asc` : sql`${field} desc`;
26
+ return sql`${nullOrder}, ${valueOrder}`;
23
27
  }), sql`, `)}`;
24
28
  };
25
29
  const insertRow = async (executor, table, row, provider, onConflict = void 0) => {
@@ -29,6 +33,26 @@ const insertRow = async (executor, table, row, provider, onConflict = void 0) =>
29
33
  const result = onConflict === void 0 ? await sql`insert into ${sql.table(table)} (${columns}) values (${values})`.execute(executor) : provider === "mysql" ? await sql`insert ignore into ${sql.table(table)} (${columns}) values (${values})`.execute(executor) : provider === "sqlite" ? await sql`insert or ignore into ${sql.table(table)} (${columns}) values (${values})`.execute(executor) : await sql`insert into ${sql.table(table)} (${columns}) values (${values}) on conflict do nothing`.execute(executor);
30
34
  return Number(result.numAffectedRows ?? 0) > 0;
31
35
  };
36
+ const recordKyselyInsights = async (executor, provider, { event, installation }) => {
37
+ const insert = (table, row) => {
38
+ const entries = Object.entries(row);
39
+ return sql`insert into ${sql.table(table)} (${sql.join(entries.map(([field]) => sql.ref(field)))}) values (${sql.join(entries.map(([, value]) => value))})`;
40
+ };
41
+ if (provider === "mysql") {
42
+ if ((await sql`select id from bundle_events where id = ${event.id} for update`.execute(executor)).rows.length > 0) return;
43
+ await insert("bundle_events", event).execute(executor);
44
+ await sql`${insert("bundle_installations", installation)} on duplicate key update install_id = ${installation.install_id}`.execute(executor);
45
+ } else {
46
+ if ((await sql`${insert("bundle_events", event)} on conflict (id) do nothing returning id`.execute(executor)).rows.length === 0) return;
47
+ await sql`${insert("bundle_installations", installation)} on conflict (install_id) do nothing`.execute(executor);
48
+ }
49
+ const assignments = Object.entries(installation).map(([field, value]) => sql`${sql.ref(field)} = ${value}`);
50
+ await sql`update ${sql.table("bundle_installations")} set ${sql.join(assignments)}
51
+ where ${sql.ref("install_id")} = ${installation.install_id}
52
+ and (${sql.ref("received_at_ms")} < ${installation.received_at_ms}
53
+ or (${sql.ref("received_at_ms")} = ${installation.received_at_ms}
54
+ and ${sql.ref("id")} < ${installation.id}))`.execute(executor);
55
+ };
32
56
  const updateBundle = async (executor, id, update) => {
33
57
  const assignments = Object.entries(update).filter(([, value]) => value !== void 0).map(([field, value]) => sql`${sql.ref(field)} = ${value}`);
34
58
  if (assignments.length === 0) return;
@@ -43,7 +67,7 @@ const updateRow = async (executor, table, keyField, key, update) => {
43
67
  await sql`update ${sql.table(table)} set ${sql.join(assignments)} where ${sql.ref(keyField)} = ${key}`.execute(executor);
44
68
  };
45
69
  const assertBundleReferences = async (executor, provider, relationMode, bundleId, baseBundleId) => {
46
- const ids = [...new Set([bundleId, baseBundleId])].sort((left, right) => left.localeCompare(right));
70
+ const ids = [.../* @__PURE__ */ new Set([bundleId, baseBundleId])].sort((left, right) => left.localeCompare(right));
47
71
  const result = await sql`select ${sql.ref("id")} from ${sql.table("bundles")} where ${sql.ref("id")} in (${sql.join(ids)}) order by ${sql.ref("id")}${lockClause(provider, relationMode)}`.execute(executor);
48
72
  const storedIds = new Set(result.rows.map(({ id }) => id));
49
73
  if (!storedIds.has(bundleId)) throw new KyselyAdapterInvariantError("bundle_patches.bundle_id.foreign-key");
@@ -95,6 +119,9 @@ const createKyselyCrud = (executor, provider, relationMode = "foreign-keys") =>
95
119
  case "bundle_events":
96
120
  await insertRow(executor, "bundle_events", input.data, provider);
97
121
  return input.data;
122
+ case "bundle_installations":
123
+ await insertRow(executor, "bundle_installations", input.data, provider, input.onConflict);
124
+ return input.data;
98
125
  case "releases":
99
126
  await insertRow(executor, "releases", toStoredReleaseRow(input.data, provider), provider);
100
127
  return input.data;
@@ -110,6 +137,19 @@ const createKyselyCrud = (executor, provider, relationMode = "foreign-keys") =>
110
137
  }
111
138
  },
112
139
  async update(input) {
140
+ if (input.model === "bundle_installations") {
141
+ const installId = input.where.find((item) => item.field === "install_id" && (item.operator === void 0 || item.operator === "eq") && typeof item.value === "string")?.value;
142
+ if (typeof installId !== "string") throw new KyselyAdapterInvariantError("bundle_installations.update.selector");
143
+ const assignments = Object.entries(input.update).filter(([, value]) => value !== void 0).map(([field, value]) => sql`${sql.ref(field)} = ${value}`);
144
+ const where = buildKyselyWhere(provider, input.where);
145
+ let updated = false;
146
+ if (assignments.length > 0 && where !== void 0) {
147
+ const result = await sql`update ${sql.table("bundle_installations")} set ${sql.join(assignments)} where ${where}`.execute(executor);
148
+ updated = Number(result.numAffectedRows ?? 0) > 0;
149
+ }
150
+ if (!updated) return null;
151
+ return (await sql`select * from ${sql.table("bundle_installations")} where ${sql.ref("install_id")} = ${installId} limit 1`.execute(executor)).rows[0] ?? null;
152
+ }
113
153
  const selector = input.where[0];
114
154
  if (selector === void 0 || typeof selector.value !== "string") throw new KyselyAdapterInvariantError(`${input.model}.update.selector`);
115
155
  if (input.model === "api_keys") {
@@ -171,9 +211,11 @@ const createKyselyCrud = (executor, provider, relationMode = "foreign-keys") =>
171
211
  async count(input) {
172
212
  if (input.distinct !== void 0) throw new DatabasePluginInputError("invalid-operation");
173
213
  switch (input.model) {
214
+ case "bundle_events": return countRows(executor, "bundle_events", buildKyselyWhere(provider, input.where));
174
215
  case "bundles": return countRows(executor, "bundles", buildKyselyWhere(provider, input.where));
175
216
  case "bundle_patches": return countRows(executor, "bundle_patches", buildKyselyWhere(provider, input.where));
176
217
  case "releases": return countRows(executor, "releases", buildKyselyWhere(provider, input.where));
218
+ case "bundle_installations": return countRows(executor, "bundle_installations", buildKyselyWhere(provider, input.where));
177
219
  }
178
220
  },
179
221
  async findOne(input) {
@@ -205,6 +247,10 @@ const createKyselyCrud = (executor, provider, relationMode = "foreign-keys") =>
205
247
  const row = (await sql`select * from ${sql.table("release_catalogs")}${where} limit 1`.execute(executor)).rows[0];
206
248
  return row === void 0 ? null : fromStoredReleaseCatalogRow(row);
207
249
  }
250
+ case "bundle_installations": {
251
+ const where = whereClause(buildKyselyWhere(provider, input.where));
252
+ return (await sql`select * from ${sql.table("bundle_installations")}${where} limit 1`.execute(executor)).rows[0] ?? null;
253
+ }
208
254
  }
209
255
  },
210
256
  async findMany(input) {
@@ -226,6 +272,11 @@ const createKyselyCrud = (executor, provider, relationMode = "foreign-keys") =>
226
272
  const order = orderClause(input);
227
273
  return [...(await sql`select * from ${sql.table("bundle_events")}${where}${order}${pagination}`.execute(executor)).rows];
228
274
  }
275
+ case "bundle_installations": {
276
+ const where = whereClause(buildKyselyWhere(provider, input.where));
277
+ const order = orderClause(input);
278
+ return [...(await sql`select * from ${sql.table("bundle_installations")}${where}${order}${pagination}`.execute(executor)).rows];
279
+ }
229
280
  case "api_keys": {
230
281
  const where = whereClause(buildKyselyWhere(provider, input.where));
231
282
  const order = orderClause(input);
@@ -250,4 +301,4 @@ const createKyselyCrud = (executor, provider, relationMode = "foreign-keys") =>
250
301
  }
251
302
  });
252
303
  //#endregion
253
- export { createKyselyCrud };
304
+ export { createKyselyCrud, recordKyselyInsights };
@@ -1,4 +1,3 @@
1
- require("../_virtual/_rolldown/runtime.cjs");
2
1
  const require_databasePluginUtils = require("./databasePluginUtils.cjs");
3
2
  let kysely = require("kysely");
4
3
  //#region src/adapters/kyselyQuery.ts
@@ -9,7 +8,8 @@ const stringPredicate = (provider, field, operator, value, insensitive) => {
9
8
  const column = kysely.sql.ref(field);
10
9
  if (provider === "sqlite" && !insensitive) {
11
10
  const literal = require_databasePluginUtils.escapeGlobPattern(value);
12
- return kysely.sql`${column} glob ${operator === "contains" ? `*${literal}*` : operator === "starts_with" ? `${literal}*` : `*${literal}`}`;
11
+ const pattern = operator === "contains" ? `*${literal}*` : operator === "starts_with" ? `${literal}*` : `*${literal}`;
12
+ return kysely.sql`${column} glob ${pattern}`;
13
13
  }
14
14
  const literal = require_databasePluginUtils.escapeLikePattern(value);
15
15
  const pattern = operator === "contains" ? `%${literal}%` : operator === "starts_with" ? `${literal}%` : `%${literal}`;
@@ -8,7 +8,8 @@ const stringPredicate = (provider, field, operator, value, insensitive) => {
8
8
  const column = sql.ref(field);
9
9
  if (provider === "sqlite" && !insensitive) {
10
10
  const literal = escapeGlobPattern(value);
11
- return sql`${column} glob ${operator === "contains" ? `*${literal}*` : operator === "starts_with" ? `${literal}*` : `*${literal}`}`;
11
+ const pattern = operator === "contains" ? `*${literal}*` : operator === "starts_with" ? `${literal}*` : `*${literal}`;
12
+ return sql`${column} glob ${pattern}`;
12
13
  }
13
14
  const literal = escapeLikePattern(value);
14
15
  const pattern = operator === "contains" ? `%${literal}%` : operator === "starts_with" ? `${literal}%` : `%${literal}`;
@@ -1,15 +1,41 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- require("../_virtual/_rolldown/runtime.cjs");
3
2
  const require_fixedMigratorMongo = require("../db/fixedMigratorMongo.cjs");
4
3
  const require_mongodbCollections = require("./mongodbCollections.cjs");
5
4
  const require_mongodbReads = require("./mongodbReads.cjs");
6
5
  const require_mongodbWrites = require("./mongodbWrites.cjs");
7
6
  let _hot_updater_plugin_core = require("@hot-updater/plugin-core");
8
7
  let _hot_updater_plugin_core_internal = require("@hot-updater/plugin-core/internal");
8
+ let mongodb = require("mongodb");
9
9
  //#region src/adapters/mongodb.ts
10
10
  const createMongoImplementation = (client, session) => {
11
11
  const collections = require_mongodbCollections.createMongoCollections(client);
12
12
  return {
13
+ recordInsights: async (input) => {
14
+ const record = () => client.withSession((recordSession) => recordSession.withTransaction(async () => {
15
+ const records = require_mongodbCollections.createMongoCollections(client);
16
+ const options = {
17
+ session: recordSession,
18
+ collation: { locale: "simple" }
19
+ };
20
+ if ((await records.bundleEvents.updateOne({ id: input.event.id }, { $setOnInsert: input.event }, {
21
+ ...options,
22
+ upsert: true
23
+ })).upsertedCount === 0) return;
24
+ const current = await records.bundleInstallations.findOne({ install_id: input.installation.install_id }, options);
25
+ if (current === null) await records.bundleInstallations.insertOne({ ...input.installation }, { session: recordSession });
26
+ else if (input.installation.received_at_ms > current.received_at_ms || input.installation.received_at_ms === current.received_at_ms && (0, _hot_updater_plugin_core.compareInsightsText)(input.installation.id, current.id) > 0) await records.bundleInstallations.updateOne({ install_id: input.installation.install_id }, { $set: input.installation }, options);
27
+ }, {
28
+ readConcern: { level: "snapshot" },
29
+ readPreference: "primary",
30
+ writeConcern: { w: "majority" }
31
+ }));
32
+ try {
33
+ await record();
34
+ } catch (error) {
35
+ if (!(error instanceof mongodb.MongoServerError) || error.code !== 11e3) throw error;
36
+ await record();
37
+ }
38
+ },
13
39
  ...require_mongodbWrites.createMongoWrites(collections, session),
14
40
  ...require_mongodbReads.createMongoReads(collections, session)
15
41
  };
@@ -1,9 +1,9 @@
1
1
  import { DatabaseAdapterWithCapabilities } from "../db/types.cjs";
2
2
  import { MongoClient } from "mongodb";
3
-
4
3
  //#region src/adapters/mongodb.d.ts
5
4
  interface MongoDBConfig {
6
5
  readonly client: MongoClient;
6
+ /** Enables atomic catalog commits. Insights always requires MongoDB 5+ on a replica set or sharded cluster. */
7
7
  readonly transactions?: boolean;
8
8
  }
9
9
  declare const mongoAdapter: (config: MongoDBConfig) => DatabaseAdapterWithCapabilities;
@@ -1,9 +1,9 @@
1
1
  import { DatabaseAdapterWithCapabilities } from "../db/types.mjs";
2
2
  import { MongoClient } from "mongodb";
3
-
4
3
  //#region src/adapters/mongodb.d.ts
5
4
  interface MongoDBConfig {
6
5
  readonly client: MongoClient;
6
+ /** Enables atomic catalog commits. Insights always requires MongoDB 5+ on a replica set or sharded cluster. */
7
7
  readonly transactions?: boolean;
8
8
  }
9
9
  declare const mongoAdapter: (config: MongoDBConfig) => DatabaseAdapterWithCapabilities;
@@ -2,12 +2,39 @@ import { createMongoMigrator } from "../db/fixedMigratorMongo.mjs";
2
2
  import { createMongoCollections } from "./mongodbCollections.mjs";
3
3
  import { createMongoReads } from "./mongodbReads.mjs";
4
4
  import { createMongoWrites } from "./mongodbWrites.mjs";
5
- import { createDatabasePlugin } from "@hot-updater/plugin-core";
5
+ import { compareInsightsText, createDatabasePlugin } from "@hot-updater/plugin-core";
6
6
  import { createDatabasePluginAdapter } from "@hot-updater/plugin-core/internal";
7
+ import { MongoServerError } from "mongodb";
7
8
  //#region src/adapters/mongodb.ts
8
9
  const createMongoImplementation = (client, session) => {
9
10
  const collections = createMongoCollections(client);
10
11
  return {
12
+ recordInsights: async (input) => {
13
+ const record = () => client.withSession((recordSession) => recordSession.withTransaction(async () => {
14
+ const records = createMongoCollections(client);
15
+ const options = {
16
+ session: recordSession,
17
+ collation: { locale: "simple" }
18
+ };
19
+ if ((await records.bundleEvents.updateOne({ id: input.event.id }, { $setOnInsert: input.event }, {
20
+ ...options,
21
+ upsert: true
22
+ })).upsertedCount === 0) return;
23
+ const current = await records.bundleInstallations.findOne({ install_id: input.installation.install_id }, options);
24
+ if (current === null) await records.bundleInstallations.insertOne({ ...input.installation }, { session: recordSession });
25
+ else if (input.installation.received_at_ms > current.received_at_ms || input.installation.received_at_ms === current.received_at_ms && compareInsightsText(input.installation.id, current.id) > 0) await records.bundleInstallations.updateOne({ install_id: input.installation.install_id }, { $set: input.installation }, options);
26
+ }, {
27
+ readConcern: { level: "snapshot" },
28
+ readPreference: "primary",
29
+ writeConcern: { w: "majority" }
30
+ }));
31
+ try {
32
+ await record();
33
+ } catch (error) {
34
+ if (!(error instanceof MongoServerError) || error.code !== 11e3) throw error;
35
+ await record();
36
+ }
37
+ },
11
38
  ...createMongoWrites(collections, session),
12
39
  ...createMongoReads(collections, session)
13
40
  };
@@ -1,5 +1,6 @@
1
1
  //#region src/adapters/mongodbCollections.ts
2
2
  var MongoAdapterConstraintError = class extends Error {
3
+ reason;
3
4
  name = "MongoAdapterConstraintError";
4
5
  constructor(reason) {
5
6
  super(`MongoDB adapter constraint failed: ${reason}`);
@@ -19,6 +20,7 @@ const createMongoCollections = (client) => {
19
20
  bundles: database.collection("bundles"),
20
21
  bundlePatches: database.collection("bundle_patches"),
21
22
  bundleEvents: database.collection("bundle_events"),
23
+ bundleInstallations: database.collection("bundle_installations"),
22
24
  channels: database.collection("channels"),
23
25
  apiKeys: database.collection("api_keys"),
24
26
  releases: database.collection("releases"),
@@ -1,5 +1,6 @@
1
1
  //#region src/adapters/mongodbCollections.ts
2
2
  var MongoAdapterConstraintError = class extends Error {
3
+ reason;
3
4
  name = "MongoAdapterConstraintError";
4
5
  constructor(reason) {
5
6
  super(`MongoDB adapter constraint failed: ${reason}`);
@@ -19,6 +20,7 @@ const createMongoCollections = (client) => {
19
20
  bundles: database.collection("bundles"),
20
21
  bundlePatches: database.collection("bundle_patches"),
21
22
  bundleEvents: database.collection("bundle_events"),
23
+ bundleInstallations: database.collection("bundle_installations"),
22
24
  channels: database.collection("channels"),
23
25
  apiKeys: database.collection("api_keys"),
24
26
  releases: database.collection("releases"),
@@ -32,14 +32,27 @@ const predicate = (where) => {
32
32
  case "ends_with": return { $expr: stringExpression(where.field, where.operator, where.value, where.mode) };
33
33
  }
34
34
  };
35
- const createMongoWhereDocument = (where) => {
35
+ const createMongoWhereDocument = (where, toPredicate = predicate) => {
36
36
  const items = Array.isArray(where) ? where : [];
37
37
  const first = items[0];
38
38
  if (first === void 0) return {};
39
- let result = predicate(first);
40
- for (const item of items.slice(1)) result = { [item.connector === "OR" ? "$or" : "$and"]: [result, predicate(item)] };
39
+ let result = toPredicate(first);
40
+ for (const item of items.slice(1)) result = { [item.connector === "OR" ? "$or" : "$and"]: [result, toPredicate(item)] };
41
41
  return result;
42
42
  };
43
+ const insightsPredicate = (where) => {
44
+ if ("mode" in where && where.mode === "insensitive") return predicate(where);
45
+ switch (where.operator) {
46
+ case void 0:
47
+ case "eq": return { [where.field]: { $eq: where.value } };
48
+ case "gt":
49
+ case "gte":
50
+ case "lt":
51
+ case "lte":
52
+ case "in": return { [where.field]: { [`$${where.operator}`]: where.value } };
53
+ default: return predicate(where);
54
+ }
55
+ };
43
56
  function createMongoBundleWhere(where) {
44
57
  return createMongoWhereDocument(where);
45
58
  }
@@ -47,7 +60,10 @@ function createMongoPatchWhere(where) {
47
60
  return createMongoWhereDocument(where);
48
61
  }
49
62
  function createMongoEventWhere(where) {
50
- return createMongoWhereDocument(where);
63
+ return createMongoWhereDocument(where, insightsPredicate);
64
+ }
65
+ function createMongoInstallationWhere(where) {
66
+ return createMongoWhereDocument(where, insightsPredicate);
51
67
  }
52
68
  function createMongoApiKeyWhere(where) {
53
69
  return createMongoWhereDocument(where);
@@ -71,6 +87,7 @@ exports.createMongoApiKeyWhere = createMongoApiKeyWhere;
71
87
  exports.createMongoBundleWhere = createMongoBundleWhere;
72
88
  exports.createMongoChannelWhere = createMongoChannelWhere;
73
89
  exports.createMongoEventWhere = createMongoEventWhere;
90
+ exports.createMongoInstallationWhere = createMongoInstallationWhere;
74
91
  exports.createMongoPatchWhere = createMongoPatchWhere;
75
92
  exports.createMongoReleaseCatalogWhere = createMongoReleaseCatalogWhere;
76
93
  exports.createMongoReleaseWhere = createMongoReleaseWhere;
@@ -32,14 +32,27 @@ const predicate = (where) => {
32
32
  case "ends_with": return { $expr: stringExpression(where.field, where.operator, where.value, where.mode) };
33
33
  }
34
34
  };
35
- const createMongoWhereDocument = (where) => {
35
+ const createMongoWhereDocument = (where, toPredicate = predicate) => {
36
36
  const items = Array.isArray(where) ? where : [];
37
37
  const first = items[0];
38
38
  if (first === void 0) return {};
39
- let result = predicate(first);
40
- for (const item of items.slice(1)) result = { [item.connector === "OR" ? "$or" : "$and"]: [result, predicate(item)] };
39
+ let result = toPredicate(first);
40
+ for (const item of items.slice(1)) result = { [item.connector === "OR" ? "$or" : "$and"]: [result, toPredicate(item)] };
41
41
  return result;
42
42
  };
43
+ const insightsPredicate = (where) => {
44
+ if ("mode" in where && where.mode === "insensitive") return predicate(where);
45
+ switch (where.operator) {
46
+ case void 0:
47
+ case "eq": return { [where.field]: { $eq: where.value } };
48
+ case "gt":
49
+ case "gte":
50
+ case "lt":
51
+ case "lte":
52
+ case "in": return { [where.field]: { [`$${where.operator}`]: where.value } };
53
+ default: return predicate(where);
54
+ }
55
+ };
43
56
  function createMongoBundleWhere(where) {
44
57
  return createMongoWhereDocument(where);
45
58
  }
@@ -47,7 +60,10 @@ function createMongoPatchWhere(where) {
47
60
  return createMongoWhereDocument(where);
48
61
  }
49
62
  function createMongoEventWhere(where) {
50
- return createMongoWhereDocument(where);
63
+ return createMongoWhereDocument(where, insightsPredicate);
64
+ }
65
+ function createMongoInstallationWhere(where) {
66
+ return createMongoWhereDocument(where, insightsPredicate);
51
67
  }
52
68
  function createMongoApiKeyWhere(where) {
53
69
  return createMongoWhereDocument(where);
@@ -67,4 +83,4 @@ const createMongoSort = (input) => {
67
83
  return Object.fromEntries(clauses.map((clause) => [clause.field, clause.direction === "asc" ? 1 : -1]));
68
84
  };
69
85
  //#endregion
70
- export { createMongoApiKeyWhere, createMongoBundleWhere, createMongoChannelWhere, createMongoEventWhere, createMongoPatchWhere, createMongoReleaseCatalogWhere, createMongoReleaseWhere, createMongoSort };
86
+ export { createMongoApiKeyWhere, createMongoBundleWhere, createMongoChannelWhere, createMongoEventWhere, createMongoInstallationWhere, createMongoPatchWhere, createMongoReleaseCatalogWhere, createMongoReleaseWhere, createMongoSort };