@hot-updater/server 1.0.0-rc.0 → 1.0.0-rc.14

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 (213) 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 +14 -1
  4. package/dist/adapters/databasePluginUtils.mjs +14 -2
  5. package/dist/adapters/drizzle.cjs +9 -4
  6. package/dist/adapters/drizzle.d.cts +0 -1
  7. package/dist/adapters/drizzle.d.mts +0 -1
  8. package/dist/adapters/drizzle.mjs +10 -4
  9. package/dist/adapters/drizzleCrud.cjs +93 -3
  10. package/dist/adapters/drizzleCrud.mjs +93 -5
  11. package/dist/adapters/drizzleLazyDB.cjs +2 -0
  12. package/dist/adapters/drizzleLazyDB.mjs +2 -0
  13. package/dist/adapters/drizzleQuery.cjs +3 -2
  14. package/dist/adapters/drizzleQuery.mjs +3 -1
  15. package/dist/adapters/kysely.cjs +1 -1
  16. package/dist/adapters/kysely.d.cts +0 -1
  17. package/dist/adapters/kysely.d.mts +0 -1
  18. package/dist/adapters/kysely.mjs +2 -1
  19. package/dist/adapters/kyselyCrud.cjs +44 -5
  20. package/dist/adapters/kyselyCrud.mjs +45 -6
  21. package/dist/adapters/kyselyQuery.cjs +2 -2
  22. package/dist/adapters/kyselyQuery.mjs +2 -1
  23. package/dist/adapters/mongodb.cjs +36 -1
  24. package/dist/adapters/mongodb.d.cts +1 -1
  25. package/dist/adapters/mongodb.d.mts +1 -1
  26. package/dist/adapters/mongodb.mjs +36 -0
  27. package/dist/adapters/mongodbCollections.cjs +2 -0
  28. package/dist/adapters/mongodbCollections.mjs +2 -0
  29. package/dist/adapters/mongodbQuery.cjs +17 -4
  30. package/dist/adapters/mongodbQuery.mjs +17 -4
  31. package/dist/adapters/mongodbReads.cjs +88 -30
  32. package/dist/adapters/mongodbReads.mjs +88 -29
  33. package/dist/adapters/mongodbWrites.cjs +1 -2
  34. package/dist/adapters/mongodbWrites.mjs +1 -1
  35. package/dist/adapters/prisma.cjs +44 -7
  36. package/dist/adapters/prisma.d.cts +0 -1
  37. package/dist/adapters/prisma.d.mts +0 -1
  38. package/dist/adapters/prisma.mjs +44 -6
  39. package/dist/adapters/prismaInsights.cjs +99 -0
  40. package/dist/adapters/prismaInsights.mjs +97 -0
  41. package/dist/adapters/prismaQuery.cjs +0 -1
  42. package/dist/adapters/prismaRows.cjs +7 -8
  43. package/dist/adapters/prismaRows.mjs +8 -8
  44. package/dist/apiKeys.cjs +6 -2
  45. package/dist/apiKeys.d.cts +0 -1
  46. package/dist/apiKeys.d.mts +0 -1
  47. package/dist/apiKeys.mjs +3 -3
  48. package/dist/createHotUpdaterCore.cjs +27 -12
  49. package/dist/createHotUpdaterCore.d.cts +8 -8
  50. package/dist/createHotUpdaterCore.d.mts +8 -8
  51. package/dist/createHotUpdaterCore.mjs +26 -12
  52. package/dist/db/createBundleDiff.cjs +2 -2
  53. package/dist/db/createBundleDiff.d.cts +1 -5
  54. package/dist/db/createBundleDiff.d.mts +1 -5
  55. package/dist/db/databasePluginCore.cjs +0 -1
  56. package/dist/db/fixedMigrator.cjs +2 -2
  57. package/dist/db/fixedMigrator.mjs +2 -2
  58. package/dist/db/fixedMigratorMongo.cjs +6 -4
  59. package/dist/db/fixedMigratorMongo.mjs +6 -4
  60. package/dist/db/index.d.cts +0 -1
  61. package/dist/db/index.d.mts +0 -1
  62. package/dist/db/releaseCatalog.cjs +0 -1
  63. package/dist/db/releaseCatalog.d.cts +0 -1
  64. package/dist/db/releaseCatalog.d.mts +0 -1
  65. package/dist/db/schema/insightsCollation.cjs +14 -0
  66. package/dist/db/schema/insightsCollation.mjs +14 -0
  67. package/dist/db/schema/sql.cjs +11 -7
  68. package/dist/db/schema/sql.mjs +8 -8
  69. package/dist/db/schemaGenerators.cjs +29 -9
  70. package/dist/db/schemaGenerators.mjs +29 -9
  71. package/dist/db/schemaReadiness.cjs +2 -0
  72. package/dist/db/schemaReadiness.d.cts +1 -0
  73. package/dist/db/schemaReadiness.d.mts +1 -0
  74. package/dist/db/schemaReadiness.mjs +2 -0
  75. package/dist/db/sqlMigrationExecution.cjs +1 -1
  76. package/dist/db/sqlMigrationExecution.mjs +1 -0
  77. package/dist/db/types.cjs +3 -1
  78. package/dist/db/types.d.cts +3 -6
  79. package/dist/db/types.d.mts +3 -6
  80. package/dist/db/types.mjs +2 -2
  81. package/dist/db/updateArtifacts.cjs +0 -1
  82. package/dist/handler.cjs +5 -5
  83. package/dist/handler.d.cts +1 -2
  84. package/dist/handler.d.mts +1 -2
  85. package/dist/handler.mjs +5 -5
  86. package/dist/handlerReleaseCatalogRoutes.cjs +0 -1
  87. package/dist/handlerReleaseManagementRoutes.cjs +0 -1
  88. package/dist/handlerTypes.d.cts +0 -1
  89. package/dist/handlerTypes.d.mts +0 -1
  90. package/dist/handlerVersionRoutes.d.cts +1 -0
  91. package/dist/handlerVersionRoutes.d.mts +1 -0
  92. package/dist/index.cjs +2 -2
  93. package/dist/index.d.cts +4 -4
  94. package/dist/index.d.mts +4 -4
  95. package/dist/index.mjs +2 -2
  96. package/dist/insights/domain.d.cts +92 -0
  97. package/dist/insights/domain.d.mts +92 -0
  98. package/dist/insights/errors.cjs +15 -0
  99. package/dist/insights/errors.mjs +14 -0
  100. package/dist/{analytics → insights}/eventInput.cjs +61 -16
  101. package/dist/{analytics → insights}/eventInput.mjs +61 -18
  102. package/dist/insights/provider.cjs +332 -0
  103. package/dist/insights/provider.d.cts +6 -0
  104. package/dist/insights/provider.d.mts +6 -0
  105. package/dist/insights/provider.mjs +332 -0
  106. package/dist/insights/queryInput.cjs +93 -0
  107. package/dist/insights/queryInput.mjs +91 -0
  108. package/dist/insights/routes.cjs +58 -0
  109. package/dist/insights/routes.mjs +56 -0
  110. package/dist/insights/types.d.cts +32 -0
  111. package/dist/insights/types.d.mts +32 -0
  112. package/dist/node.d.cts +0 -1
  113. package/dist/node.d.mts +0 -1
  114. package/dist/package.cjs +1 -1
  115. package/dist/package.mjs +1 -1
  116. package/dist/schema/index.cjs +17 -3
  117. package/dist/schema/index.mjs +4 -4
  118. package/dist/schema/v1_0_0.cjs +64 -35
  119. package/dist/schema/v1_0_0.mjs +64 -36
  120. package/dist/storageAccess.cjs +0 -1
  121. package/dist/types/index.d.cts +0 -1
  122. package/dist/types/index.d.mts +0 -1
  123. package/package.json +7 -7
  124. package/src/adapters/databasePluginTestDatabase.ts +15 -5
  125. package/src/adapters/databasePluginUtils.ts +24 -0
  126. package/src/adapters/drizzle.spec.ts +203 -5
  127. package/src/adapters/drizzle.ts +16 -4
  128. package/src/adapters/drizzleCrud.ts +200 -9
  129. package/src/adapters/drizzleLazyDB.ts +33 -6
  130. package/src/adapters/kysely.spec.ts +62 -0
  131. package/src/adapters/kysely.ts +7 -1
  132. package/src/adapters/kyselyCrud.ts +115 -7
  133. package/src/adapters/kyselyInsights.spec.ts +168 -0
  134. package/src/adapters/mongodb.integration.spec.ts +483 -0
  135. package/src/adapters/mongodb.spec.ts +89 -0
  136. package/src/adapters/mongodb.ts +59 -1
  137. package/src/adapters/mongodbCollections.ts +16 -0
  138. package/src/adapters/mongodbQuery.ts +22 -3
  139. package/src/adapters/mongodbReads.ts +78 -2
  140. package/src/adapters/mongodbTestClient.ts +107 -6
  141. package/src/adapters/mongodbTestFilter.ts +5 -1
  142. package/src/adapters/mongodbWrites.ts +1 -0
  143. package/src/adapters/prisma.spec.ts +270 -0
  144. package/src/adapters/prisma.ts +75 -2
  145. package/src/adapters/prismaInsights.ts +190 -0
  146. package/src/adapters/prismaRows.ts +17 -10
  147. package/src/adapters/prismaTestClient.ts +89 -7
  148. package/src/apiKeys.integration.spec.ts +3 -3
  149. package/src/createHotUpdaterCore.ts +30 -16
  150. package/src/db/databasePluginUpdateCheck.bench.ts +10 -0
  151. package/src/db/fixedMigrator.spec.ts +63 -0
  152. package/src/db/fixedMigratorMongo.spec.ts +70 -9
  153. package/src/db/fixedMigratorMongo.ts +10 -10
  154. package/src/db/index.spec.ts +6 -1
  155. package/src/db/schema/insightsCollation.ts +21 -0
  156. package/src/db/schema/sql.ts +8 -5
  157. package/src/db/schemaGenerators.ts +42 -13
  158. package/src/db/types.spec.ts +11 -3
  159. package/src/db/types.ts +13 -7
  160. package/src/handler-standalone.integration.spec.ts +6 -8
  161. package/src/handler.ts +12 -12
  162. package/src/index.ts +3 -3
  163. package/src/insights/domain.ts +106 -0
  164. package/src/insights/errors.ts +11 -0
  165. package/src/{analytics → insights}/eventInput.ts +66 -16
  166. package/src/insights/insights.integration.spec.ts +475 -0
  167. package/src/insights/provider.spec.ts +396 -0
  168. package/src/insights/provider.ts +611 -0
  169. package/src/insights/queryInput.ts +142 -0
  170. package/src/insights/routes.ts +104 -0
  171. package/src/insights/types.ts +55 -0
  172. package/src/schema/dsl-all-versions.spec.ts +1 -1
  173. package/src/schema/v1_0_0.spec.ts +77 -3
  174. package/src/schema/v1_0_0.ts +83 -37
  175. package/dist/analytics/bounded/activeOverview.cjs +0 -87
  176. package/dist/analytics/bounded/activeOverview.mjs +0 -85
  177. package/dist/analytics/bounded/installationSearch.cjs +0 -42
  178. package/dist/analytics/bounded/installationSearch.mjs +0 -42
  179. package/dist/analytics/bounded/persistence.cjs +0 -39
  180. package/dist/analytics/bounded/persistence.mjs +0 -38
  181. package/dist/analytics/bounded/provider.cjs +0 -170
  182. package/dist/analytics/bounded/provider.d.cts +0 -7
  183. package/dist/analytics/bounded/provider.d.mts +0 -7
  184. package/dist/analytics/bounded/provider.mjs +0 -170
  185. package/dist/analytics/bounded/scan.cjs +0 -144
  186. package/dist/analytics/bounded/scan.mjs +0 -138
  187. package/dist/analytics/domain.d.cts +0 -121
  188. package/dist/analytics/domain.d.mts +0 -121
  189. package/dist/analytics/errors.cjs +0 -22
  190. package/dist/analytics/errors.mjs +0 -20
  191. package/dist/analytics/persistence.d.cts +0 -6
  192. package/dist/analytics/persistence.d.mts +0 -6
  193. package/dist/analytics/queryInput.cjs +0 -58
  194. package/dist/analytics/queryInput.mjs +0 -55
  195. package/dist/analytics/routes.cjs +0 -63
  196. package/dist/analytics/routes.mjs +0 -61
  197. package/dist/analytics/types.d.cts +0 -17
  198. package/dist/analytics/types.d.mts +0 -17
  199. package/src/analytics/analytics.integration.spec.ts +0 -144
  200. package/src/analytics/bounded/activeOverview.spec.ts +0 -189
  201. package/src/analytics/bounded/activeOverview.ts +0 -148
  202. package/src/analytics/bounded/installationSearch.spec.ts +0 -130
  203. package/src/analytics/bounded/installationSearch.ts +0 -69
  204. package/src/analytics/bounded/persistence.ts +0 -43
  205. package/src/analytics/bounded/provider.spec.ts +0 -219
  206. package/src/analytics/bounded/provider.ts +0 -229
  207. package/src/analytics/bounded/scan.ts +0 -235
  208. package/src/analytics/domain.ts +0 -139
  209. package/src/analytics/errors.ts +0 -18
  210. package/src/analytics/persistence.ts +0 -10
  211. package/src/analytics/queryInput.ts +0 -102
  212. package/src/analytics/routes.ts +0 -123
  213. package/src/analytics/types.ts +0 -44
@@ -1,11 +1,12 @@
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");
5
4
  let _hot_updater_plugin_core = require("@hot-updater/plugin-core");
5
+ let _hot_updater_plugin_core_internal = require("@hot-updater/plugin-core/internal");
6
6
  let kysely = require("kysely");
7
7
  //#region src/adapters/kyselyCrud.ts
8
8
  var KyselyAdapterInvariantError = class extends Error {
9
+ reason;
9
10
  name = "KyselyAdapterInvariantError";
10
11
  constructor(reason) {
11
12
  super(`Kysely plugin invariant failed: ${reason}`);
@@ -20,7 +21,10 @@ const orderClause = (input) => {
20
21
  if (clauses === void 0 || clauses.length === 0) return empty;
21
22
  return kysely.sql` order by ${kysely.sql.join(clauses.map((clause) => {
22
23
  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`}`;
24
+ if (input?.model === "bundle_events") return clause.direction === "asc" ? kysely.sql`${field} asc` : kysely.sql`${field} desc`;
25
+ const nullOrder = (clause.nulls ?? (clause.direction === "asc" ? "last" : "first")) === "first" ? kysely.sql`${field} is null desc` : kysely.sql`${field} is null asc`;
26
+ const valueOrder = clause.direction === "asc" ? kysely.sql`${field} asc` : kysely.sql`${field} desc`;
27
+ return kysely.sql`${nullOrder}, ${valueOrder}`;
24
28
  }), kysely.sql`, `)}`;
25
29
  };
26
30
  const insertRow = async (executor, table, row, provider, onConflict = void 0) => {
@@ -30,6 +34,30 @@ const insertRow = async (executor, table, row, provider, onConflict = void 0) =>
30
34
  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
35
  return Number(result.numAffectedRows ?? 0) > 0;
32
36
  };
37
+ const recordKyselyInsights = async (executor, provider, { event }) => {
38
+ const entries = Object.entries(require_databasePluginUtils.toStoredBundleEventRow(event, provider));
39
+ const insert = kysely.sql`insert into bundle_events (${kysely.sql.join(entries.map(([field]) => kysely.sql.ref(field)))}) values (${kysely.sql.join(entries.map(([, value]) => value))})`;
40
+ await (provider === "mysql" ? kysely.sql`${insert} on duplicate key update id = id` : kysely.sql`${insert} on conflict (id) do nothing`).execute(executor);
41
+ const fields = [
42
+ "install_id",
43
+ "user_id",
44
+ "platform",
45
+ "channel",
46
+ "type",
47
+ "from_bundle_id",
48
+ "to_bundle_id",
49
+ "id",
50
+ "received_at_ms"
51
+ ];
52
+ const columns = kysely.sql.join(fields.map((field) => kysely.sql.ref(field)));
53
+ const headInsert = kysely.sql`insert into bundle_event_heads (${columns}) select ${columns} from bundle_events where id = ${event.id}`;
54
+ const newer = provider === "mysql" ? kysely.sql`(values(received_at_ms) > bundle_event_heads.received_at_ms or (values(received_at_ms) = bundle_event_heads.received_at_ms and values(id) > bundle_event_heads.id))` : kysely.sql`(excluded.received_at_ms > bundle_event_heads.received_at_ms or (excluded.received_at_ms = bundle_event_heads.received_at_ms and excluded.id > bundle_event_heads.id))`;
55
+ const assignments = kysely.sql.join(fields.filter((field) => field !== "install_id").map((field) => {
56
+ const column = kysely.sql.ref(field);
57
+ return provider === "mysql" ? kysely.sql`${column} = if(${newer}, values(${column}), ${kysely.sql.ref(`bundle_event_heads.${field}`)})` : kysely.sql`${column} = ${kysely.sql.ref(`excluded.${field}`)}`;
58
+ }));
59
+ await (provider === "mysql" ? kysely.sql`${headInsert} on duplicate key update ${assignments}` : kysely.sql`${headInsert} on conflict (install_id) do update set ${assignments} where ${newer}`).execute(executor);
60
+ };
33
61
  const updateBundle = async (executor, id, update) => {
34
62
  const assignments = Object.entries(update).filter(([, value]) => value !== void 0).map(([field, value]) => kysely.sql`${kysely.sql.ref(field)} = ${value}`);
35
63
  if (assignments.length === 0) return;
@@ -44,7 +72,7 @@ const updateRow = async (executor, table, keyField, key, update) => {
44
72
  await kysely.sql`update ${kysely.sql.table(table)} set ${kysely.sql.join(assignments)} where ${kysely.sql.ref(keyField)} = ${key}`.execute(executor);
45
73
  };
46
74
  const assertBundleReferences = async (executor, provider, relationMode, bundleId, baseBundleId) => {
47
- const ids = [...new Set([bundleId, baseBundleId])].sort((left, right) => left.localeCompare(right));
75
+ const ids = [.../* @__PURE__ */ new Set([bundleId, baseBundleId])].sort((left, right) => left.localeCompare(right));
48
76
  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
77
  const storedIds = new Set(result.rows.map(({ id }) => id));
50
78
  if (!storedIds.has(bundleId)) throw new KyselyAdapterInvariantError("bundle_patches.bundle_id.foreign-key");
@@ -55,6 +83,15 @@ const countRows = async (executor, table, where) => {
55
83
  return Number(result.rows[0]?.total ?? 0);
56
84
  };
57
85
  const createKyselyCrud = (executor, provider, relationMode = "foreign-keys") => ({
86
+ async findLatestInsightsEvents(input) {
87
+ const where = require_kyselyQuery.buildKyselyWhere(provider, (0, _hot_updater_plugin_core_internal.latestInsightsWhere)(input));
88
+ return (await kysely.sql`SELECT canonical.* FROM (SELECT id, install_id FROM bundle_event_heads WHERE ${where} ORDER BY install_id ASC LIMIT ${"installId" in input ? 1 : input.limit}) AS head JOIN bundle_events AS canonical ON canonical.id = head.id ORDER BY head.install_id ASC`.execute(executor)).rows.map(require_databasePluginUtils.fromStoredBundleEventRow);
89
+ },
90
+ async countLatestInsightsEvents(input) {
91
+ const groups = (0, _hot_updater_plugin_core_internal.latestInsightsCountGroups)(input).map((where) => kysely.sql`(${require_kyselyQuery.buildKyselyWhere(provider, where)})`);
92
+ const result = await (provider === "mysql" && groups.length === 2 ? kysely.sql`SELECT (SELECT COUNT(*) FROM bundle_event_heads WHERE ${groups[0]}) + (SELECT COUNT(*) FROM bundle_event_heads WHERE ${groups[1]} AND ${groups[0]} IS NOT TRUE) AS count` : kysely.sql`SELECT COUNT(*) AS count FROM bundle_event_heads WHERE (${kysely.sql.join(groups, kysely.sql` OR `)})`).execute(executor);
93
+ return Number(result.rows[0]?.count ?? 0);
94
+ },
58
95
  async deleteChannel({ id }) {
59
96
  if ((await kysely.sql`select ${kysely.sql.ref("id")}, ${kysely.sql.ref("name")} from ${kysely.sql.table("channels")} where ${kysely.sql.ref("id")} = ${id} limit 1${lockClause(provider, relationMode)}`.execute(executor)).rows[0] === void 0) return {
60
97
  deleted: false,
@@ -94,7 +131,7 @@ const createKyselyCrud = (executor, provider, relationMode = "foreign-keys") =>
94
131
  await insertRow(executor, "bundle_patches", input.data, provider);
95
132
  return input.data;
96
133
  case "bundle_events":
97
- await insertRow(executor, "bundle_events", input.data, provider);
134
+ await insertRow(executor, "bundle_events", require_databasePluginUtils.toStoredBundleEventRow(input.data, provider), provider);
98
135
  return input.data;
99
136
  case "releases":
100
137
  await insertRow(executor, "releases", require_databasePluginUtils.toStoredReleaseRow(input.data, provider), provider);
@@ -172,6 +209,7 @@ const createKyselyCrud = (executor, provider, relationMode = "foreign-keys") =>
172
209
  async count(input) {
173
210
  if (input.distinct !== void 0) throw new _hot_updater_plugin_core.DatabasePluginInputError("invalid-operation");
174
211
  switch (input.model) {
212
+ case "bundle_events": return countRows(executor, "bundle_events", require_kyselyQuery.buildKyselyWhere(provider, input.where));
175
213
  case "bundles": return countRows(executor, "bundles", require_kyselyQuery.buildKyselyWhere(provider, input.where));
176
214
  case "bundle_patches": return countRows(executor, "bundle_patches", require_kyselyQuery.buildKyselyWhere(provider, input.where));
177
215
  case "releases": return countRows(executor, "releases", require_kyselyQuery.buildKyselyWhere(provider, input.where));
@@ -225,7 +263,7 @@ const createKyselyCrud = (executor, provider, relationMode = "foreign-keys") =>
225
263
  case "bundle_events": {
226
264
  const where = whereClause(require_kyselyQuery.buildKyselyWhere(provider, input.where));
227
265
  const order = orderClause(input);
228
- return [...(await kysely.sql`select * from ${kysely.sql.table("bundle_events")}${where}${order}${pagination}`.execute(executor)).rows];
266
+ return (await kysely.sql`select * from ${kysely.sql.table("bundle_events")}${where}${order}${pagination}`.execute(executor)).rows.map(require_databasePluginUtils.fromStoredBundleEventRow);
229
267
  }
230
268
  case "api_keys": {
231
269
  const where = whereClause(require_kyselyQuery.buildKyselyWhere(provider, input.where));
@@ -252,3 +290,4 @@ const createKyselyCrud = (executor, provider, relationMode = "foreign-keys") =>
252
290
  });
253
291
  //#endregion
254
292
  exports.createKyselyCrud = createKyselyCrud;
293
+ exports.recordKyselyInsights = recordKyselyInsights;
@@ -1,10 +1,12 @@
1
1
  import { isChannelDeleteReferencedError, translateChannelDeleteError } from "./databaseConstraintErrors.mjs";
2
- import { fromStoredBundleRow, fromStoredReleaseCatalogRow, fromStoredReleaseRow, toStoredBundleRow, toStoredBundleUpdate, toStoredReleaseRow, toStoredReleaseUpdate } from "./databasePluginUtils.mjs";
2
+ import { fromStoredBundleEventRow, fromStoredBundleRow, fromStoredReleaseCatalogRow, fromStoredReleaseRow, toStoredBundleEventRow, toStoredBundleRow, toStoredBundleUpdate, toStoredReleaseRow, toStoredReleaseUpdate } from "./databasePluginUtils.mjs";
3
3
  import { buildKyselyWhere } from "./kyselyQuery.mjs";
4
4
  import { DatabasePluginInputError } from "@hot-updater/plugin-core";
5
+ import { latestInsightsCountGroups, latestInsightsWhere } from "@hot-updater/plugin-core/internal";
5
6
  import { sql } from "kysely";
6
7
  //#region src/adapters/kyselyCrud.ts
7
8
  var KyselyAdapterInvariantError = class extends Error {
9
+ reason;
8
10
  name = "KyselyAdapterInvariantError";
9
11
  constructor(reason) {
10
12
  super(`Kysely plugin invariant failed: ${reason}`);
@@ -19,7 +21,10 @@ const orderClause = (input) => {
19
21
  if (clauses === void 0 || clauses.length === 0) return empty;
20
22
  return sql` order by ${sql.join(clauses.map((clause) => {
21
23
  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`}`;
24
+ if (input?.model === "bundle_events") return clause.direction === "asc" ? sql`${field} asc` : sql`${field} desc`;
25
+ const nullOrder = (clause.nulls ?? (clause.direction === "asc" ? "last" : "first")) === "first" ? sql`${field} is null desc` : sql`${field} is null asc`;
26
+ const valueOrder = clause.direction === "asc" ? sql`${field} asc` : sql`${field} desc`;
27
+ return sql`${nullOrder}, ${valueOrder}`;
23
28
  }), sql`, `)}`;
24
29
  };
25
30
  const insertRow = async (executor, table, row, provider, onConflict = void 0) => {
@@ -29,6 +34,30 @@ const insertRow = async (executor, table, row, provider, onConflict = void 0) =>
29
34
  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
35
  return Number(result.numAffectedRows ?? 0) > 0;
31
36
  };
37
+ const recordKyselyInsights = async (executor, provider, { event }) => {
38
+ const entries = Object.entries(toStoredBundleEventRow(event, provider));
39
+ const insert = sql`insert into bundle_events (${sql.join(entries.map(([field]) => sql.ref(field)))}) values (${sql.join(entries.map(([, value]) => value))})`;
40
+ await (provider === "mysql" ? sql`${insert} on duplicate key update id = id` : sql`${insert} on conflict (id) do nothing`).execute(executor);
41
+ const fields = [
42
+ "install_id",
43
+ "user_id",
44
+ "platform",
45
+ "channel",
46
+ "type",
47
+ "from_bundle_id",
48
+ "to_bundle_id",
49
+ "id",
50
+ "received_at_ms"
51
+ ];
52
+ const columns = sql.join(fields.map((field) => sql.ref(field)));
53
+ const headInsert = sql`insert into bundle_event_heads (${columns}) select ${columns} from bundle_events where id = ${event.id}`;
54
+ const newer = provider === "mysql" ? sql`(values(received_at_ms) > bundle_event_heads.received_at_ms or (values(received_at_ms) = bundle_event_heads.received_at_ms and values(id) > bundle_event_heads.id))` : sql`(excluded.received_at_ms > bundle_event_heads.received_at_ms or (excluded.received_at_ms = bundle_event_heads.received_at_ms and excluded.id > bundle_event_heads.id))`;
55
+ const assignments = sql.join(fields.filter((field) => field !== "install_id").map((field) => {
56
+ const column = sql.ref(field);
57
+ return provider === "mysql" ? sql`${column} = if(${newer}, values(${column}), ${sql.ref(`bundle_event_heads.${field}`)})` : sql`${column} = ${sql.ref(`excluded.${field}`)}`;
58
+ }));
59
+ await (provider === "mysql" ? sql`${headInsert} on duplicate key update ${assignments}` : sql`${headInsert} on conflict (install_id) do update set ${assignments} where ${newer}`).execute(executor);
60
+ };
32
61
  const updateBundle = async (executor, id, update) => {
33
62
  const assignments = Object.entries(update).filter(([, value]) => value !== void 0).map(([field, value]) => sql`${sql.ref(field)} = ${value}`);
34
63
  if (assignments.length === 0) return;
@@ -43,7 +72,7 @@ const updateRow = async (executor, table, keyField, key, update) => {
43
72
  await sql`update ${sql.table(table)} set ${sql.join(assignments)} where ${sql.ref(keyField)} = ${key}`.execute(executor);
44
73
  };
45
74
  const assertBundleReferences = async (executor, provider, relationMode, bundleId, baseBundleId) => {
46
- const ids = [...new Set([bundleId, baseBundleId])].sort((left, right) => left.localeCompare(right));
75
+ const ids = [.../* @__PURE__ */ new Set([bundleId, baseBundleId])].sort((left, right) => left.localeCompare(right));
47
76
  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
77
  const storedIds = new Set(result.rows.map(({ id }) => id));
49
78
  if (!storedIds.has(bundleId)) throw new KyselyAdapterInvariantError("bundle_patches.bundle_id.foreign-key");
@@ -54,6 +83,15 @@ const countRows = async (executor, table, where) => {
54
83
  return Number(result.rows[0]?.total ?? 0);
55
84
  };
56
85
  const createKyselyCrud = (executor, provider, relationMode = "foreign-keys") => ({
86
+ async findLatestInsightsEvents(input) {
87
+ const where = buildKyselyWhere(provider, latestInsightsWhere(input));
88
+ return (await sql`SELECT canonical.* FROM (SELECT id, install_id FROM bundle_event_heads WHERE ${where} ORDER BY install_id ASC LIMIT ${"installId" in input ? 1 : input.limit}) AS head JOIN bundle_events AS canonical ON canonical.id = head.id ORDER BY head.install_id ASC`.execute(executor)).rows.map(fromStoredBundleEventRow);
89
+ },
90
+ async countLatestInsightsEvents(input) {
91
+ const groups = latestInsightsCountGroups(input).map((where) => sql`(${buildKyselyWhere(provider, where)})`);
92
+ const result = await (provider === "mysql" && groups.length === 2 ? sql`SELECT (SELECT COUNT(*) FROM bundle_event_heads WHERE ${groups[0]}) + (SELECT COUNT(*) FROM bundle_event_heads WHERE ${groups[1]} AND ${groups[0]} IS NOT TRUE) AS count` : sql`SELECT COUNT(*) AS count FROM bundle_event_heads WHERE (${sql.join(groups, sql` OR `)})`).execute(executor);
93
+ return Number(result.rows[0]?.count ?? 0);
94
+ },
57
95
  async deleteChannel({ id }) {
58
96
  if ((await sql`select ${sql.ref("id")}, ${sql.ref("name")} from ${sql.table("channels")} where ${sql.ref("id")} = ${id} limit 1${lockClause(provider, relationMode)}`.execute(executor)).rows[0] === void 0) return {
59
97
  deleted: false,
@@ -93,7 +131,7 @@ const createKyselyCrud = (executor, provider, relationMode = "foreign-keys") =>
93
131
  await insertRow(executor, "bundle_patches", input.data, provider);
94
132
  return input.data;
95
133
  case "bundle_events":
96
- await insertRow(executor, "bundle_events", input.data, provider);
134
+ await insertRow(executor, "bundle_events", toStoredBundleEventRow(input.data, provider), provider);
97
135
  return input.data;
98
136
  case "releases":
99
137
  await insertRow(executor, "releases", toStoredReleaseRow(input.data, provider), provider);
@@ -171,6 +209,7 @@ const createKyselyCrud = (executor, provider, relationMode = "foreign-keys") =>
171
209
  async count(input) {
172
210
  if (input.distinct !== void 0) throw new DatabasePluginInputError("invalid-operation");
173
211
  switch (input.model) {
212
+ case "bundle_events": return countRows(executor, "bundle_events", buildKyselyWhere(provider, input.where));
174
213
  case "bundles": return countRows(executor, "bundles", buildKyselyWhere(provider, input.where));
175
214
  case "bundle_patches": return countRows(executor, "bundle_patches", buildKyselyWhere(provider, input.where));
176
215
  case "releases": return countRows(executor, "releases", buildKyselyWhere(provider, input.where));
@@ -224,7 +263,7 @@ const createKyselyCrud = (executor, provider, relationMode = "foreign-keys") =>
224
263
  case "bundle_events": {
225
264
  const where = whereClause(buildKyselyWhere(provider, input.where));
226
265
  const order = orderClause(input);
227
- return [...(await sql`select * from ${sql.table("bundle_events")}${where}${order}${pagination}`.execute(executor)).rows];
266
+ return (await sql`select * from ${sql.table("bundle_events")}${where}${order}${pagination}`.execute(executor)).rows.map(fromStoredBundleEventRow);
228
267
  }
229
268
  case "api_keys": {
230
269
  const where = whereClause(buildKyselyWhere(provider, input.where));
@@ -250,4 +289,4 @@ const createKyselyCrud = (executor, provider, relationMode = "foreign-keys") =>
250
289
  }
251
290
  });
252
291
  //#endregion
253
- export { createKyselyCrud };
292
+ 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,50 @@
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 ({ event }) => {
14
+ const record = () => client.withSession((insightsSession) => insightsSession.withTransaction(async () => {
15
+ const insights = require_mongodbCollections.createMongoCollections(client);
16
+ const options = {
17
+ session: insightsSession,
18
+ collation: { locale: "simple" }
19
+ };
20
+ if ((await insights.bundleEvents.updateOne({ id: event.id }, { $setOnInsert: event }, {
21
+ ...options,
22
+ upsert: true
23
+ })).upsertedCount === 0) return;
24
+ const current = await insights.bundleEventHeads.findOne({ install_id: event.install_id }, options);
25
+ if (current !== null && (event.received_at_ms < current.received_at_ms || event.received_at_ms === current.received_at_ms && event.id <= current.id)) return;
26
+ await insights.bundleEventHeads.updateOne({ install_id: event.install_id }, { $set: {
27
+ install_id: event.install_id,
28
+ id: event.id,
29
+ received_at_ms: event.received_at_ms,
30
+ user_id: event.user_id,
31
+ platform: event.platform,
32
+ channel: event.channel,
33
+ type: event.type,
34
+ from_bundle_id: event.from_bundle_id,
35
+ to_bundle_id: event.to_bundle_id
36
+ } }, {
37
+ ...options,
38
+ upsert: true
39
+ });
40
+ }));
41
+ try {
42
+ await record();
43
+ } catch (error) {
44
+ if (!(error instanceof mongodb.MongoServerError) || error.code !== 11e3) throw error;
45
+ await record();
46
+ }
47
+ },
13
48
  ...require_mongodbWrites.createMongoWrites(collections, session),
14
49
  ...require_mongodbReads.createMongoReads(collections, session)
15
50
  };
@@ -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;
@@ -4,10 +4,46 @@ import { createMongoReads } from "./mongodbReads.mjs";
4
4
  import { createMongoWrites } from "./mongodbWrites.mjs";
5
5
  import { 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 ({ event }) => {
13
+ const record = () => client.withSession((insightsSession) => insightsSession.withTransaction(async () => {
14
+ const insights = createMongoCollections(client);
15
+ const options = {
16
+ session: insightsSession,
17
+ collation: { locale: "simple" }
18
+ };
19
+ if ((await insights.bundleEvents.updateOne({ id: event.id }, { $setOnInsert: event }, {
20
+ ...options,
21
+ upsert: true
22
+ })).upsertedCount === 0) return;
23
+ const current = await insights.bundleEventHeads.findOne({ install_id: event.install_id }, options);
24
+ if (current !== null && (event.received_at_ms < current.received_at_ms || event.received_at_ms === current.received_at_ms && event.id <= current.id)) return;
25
+ await insights.bundleEventHeads.updateOne({ install_id: event.install_id }, { $set: {
26
+ install_id: event.install_id,
27
+ id: event.id,
28
+ received_at_ms: event.received_at_ms,
29
+ user_id: event.user_id,
30
+ platform: event.platform,
31
+ channel: event.channel,
32
+ type: event.type,
33
+ from_bundle_id: event.from_bundle_id,
34
+ to_bundle_id: event.to_bundle_id
35
+ } }, {
36
+ ...options,
37
+ upsert: true
38
+ });
39
+ }));
40
+ try {
41
+ await record();
42
+ } catch (error) {
43
+ if (!(error instanceof MongoServerError) || error.code !== 11e3) throw error;
44
+ await record();
45
+ }
46
+ },
11
47
  ...createMongoWrites(collections, session),
12
48
  ...createMongoReads(collections, session)
13
49
  };
@@ -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
+ bundleEventHeads: database.collection("bundle_event_heads"),
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
+ bundleEventHeads: database.collection("bundle_event_heads"),
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,7 @@ function createMongoPatchWhere(where) {
47
60
  return createMongoWhereDocument(where);
48
61
  }
49
62
  function createMongoEventWhere(where) {
50
- return createMongoWhereDocument(where);
63
+ return createMongoWhereDocument(where, insightsPredicate);
51
64
  }
52
65
  function createMongoApiKeyWhere(where) {
53
66
  return createMongoWhereDocument(where);
@@ -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,7 @@ function createMongoPatchWhere(where) {
47
60
  return createMongoWhereDocument(where);
48
61
  }
49
62
  function createMongoEventWhere(where) {
50
- return createMongoWhereDocument(where);
63
+ return createMongoWhereDocument(where, insightsPredicate);
51
64
  }
52
65
  function createMongoApiKeyWhere(where) {
53
66
  return createMongoWhereDocument(where);