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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (291) hide show
  1. package/dist/adapters/databaseConstraintErrors.cjs +23 -0
  2. package/dist/adapters/databaseConstraintErrors.mjs +21 -0
  3. package/dist/adapters/databasePluginUtils.cjs +117 -0
  4. package/dist/adapters/databasePluginUtils.mjs +106 -0
  5. package/dist/adapters/drizzle.cjs +53 -176
  6. package/dist/adapters/drizzle.d.cts +6 -4
  7. package/dist/adapters/drizzle.d.mts +6 -4
  8. package/dist/adapters/drizzle.mjs +54 -177
  9. package/dist/adapters/drizzleCrud.cjs +250 -0
  10. package/dist/adapters/drizzleCrud.mjs +249 -0
  11. package/dist/adapters/drizzleLazyDB.cjs +84 -36
  12. package/dist/adapters/drizzleLazyDB.mjs +84 -36
  13. package/dist/adapters/drizzleQuery.cjs +68 -0
  14. package/dist/adapters/drizzleQuery.mjs +67 -0
  15. package/dist/adapters/kysely.cjs +18 -138
  16. package/dist/adapters/kysely.d.cts +3 -12
  17. package/dist/adapters/kysely.d.mts +3 -12
  18. package/dist/adapters/kysely.mjs +19 -139
  19. package/dist/adapters/kyselyCrud.cjs +254 -0
  20. package/dist/adapters/kyselyCrud.mjs +253 -0
  21. package/dist/adapters/kyselyQuery.cjs +56 -0
  22. package/dist/adapters/kyselyQuery.mjs +55 -0
  23. package/dist/adapters/mongodb.cjs +21 -174
  24. package/dist/adapters/mongodb.d.cts +3 -2
  25. package/dist/adapters/mongodb.d.mts +3 -2
  26. package/dist/adapters/mongodb.mjs +21 -174
  27. package/dist/adapters/mongodbCollections.cjs +36 -0
  28. package/dist/adapters/mongodbCollections.mjs +30 -0
  29. package/dist/adapters/mongodbQuery.cjs +77 -0
  30. package/dist/adapters/mongodbQuery.mjs +70 -0
  31. package/dist/adapters/mongodbReads.cjs +148 -0
  32. package/dist/adapters/mongodbReads.mjs +147 -0
  33. package/dist/adapters/mongodbWrites.cjs +134 -0
  34. package/dist/adapters/mongodbWrites.mjs +133 -0
  35. package/dist/adapters/prisma.cjs +205 -180
  36. package/dist/adapters/prisma.d.cts +3 -3
  37. package/dist/adapters/prisma.d.mts +3 -3
  38. package/dist/adapters/prisma.mjs +206 -181
  39. package/dist/adapters/prismaQuery.cjs +58 -0
  40. package/dist/adapters/prismaQuery.mjs +56 -0
  41. package/dist/adapters/prismaRows.cjs +198 -0
  42. package/dist/adapters/prismaRows.mjs +188 -0
  43. package/dist/apiKeys.cjs +139 -0
  44. package/dist/apiKeys.d.cts +50 -0
  45. package/dist/apiKeys.d.mts +50 -0
  46. package/dist/apiKeys.mjs +133 -0
  47. package/dist/createHotUpdaterCore.cjs +48 -26
  48. package/dist/createHotUpdaterCore.d.cts +49 -17
  49. package/dist/createHotUpdaterCore.d.mts +49 -17
  50. package/dist/createHotUpdaterCore.mjs +51 -29
  51. package/dist/db/createBundleDiff.cjs +45 -68
  52. package/dist/db/createBundleDiff.d.cts +3 -3
  53. package/dist/db/createBundleDiff.d.mts +3 -3
  54. package/dist/db/createBundleDiff.mjs +47 -68
  55. package/dist/db/databasePluginCore.cjs +138 -0
  56. package/dist/db/databasePluginCore.mjs +137 -0
  57. package/dist/db/fixedMigrator.cjs +36 -93
  58. package/dist/db/fixedMigrator.mjs +36 -93
  59. package/dist/db/fixedMigratorMongo.cjs +319 -0
  60. package/dist/db/fixedMigratorMongo.mjs +319 -0
  61. package/dist/db/fixedMigratorShared.cjs +23 -0
  62. package/dist/db/fixedMigratorShared.mjs +18 -0
  63. package/dist/db/mongoMigrationExecution.cjs +11 -0
  64. package/dist/db/mongoMigrationExecution.mjs +11 -0
  65. package/dist/db/releaseCatalog.cjs +66 -0
  66. package/dist/db/releaseCatalog.d.cts +17 -0
  67. package/dist/db/releaseCatalog.d.mts +17 -0
  68. package/dist/db/releaseCatalog.mjs +64 -0
  69. package/dist/db/schema/mongodb.cjs +14 -6
  70. package/dist/db/schema/mongodb.mjs +14 -6
  71. package/dist/db/schema/registry.cjs +0 -2
  72. package/dist/db/schema/registry.mjs +1 -2
  73. package/dist/db/schema/sql.cjs +31 -14
  74. package/dist/db/schema/sql.mjs +32 -13
  75. package/dist/db/schema/sqlOperations.cjs +4 -4
  76. package/dist/db/schema/sqlOperations.mjs +5 -5
  77. package/dist/db/schemaGenerators.cjs +52 -18
  78. package/dist/db/schemaGenerators.mjs +52 -18
  79. package/dist/db/schemaReadiness.cjs +4 -3
  80. package/dist/db/schemaReadiness.mjs +4 -3
  81. package/dist/db/sqlMigrationExecution.cjs +67 -0
  82. package/dist/db/sqlMigrationExecution.mjs +66 -0
  83. package/dist/db/types.cjs +2 -6
  84. package/dist/db/types.d.cts +60 -16
  85. package/dist/db/types.d.mts +60 -16
  86. package/dist/db/types.mjs +3 -6
  87. package/dist/db/updateArtifacts.cjs +114 -60
  88. package/dist/db/updateArtifacts.mjs +117 -62
  89. package/dist/handler.cjs +108 -282
  90. package/dist/handler.d.cts +3 -46
  91. package/dist/handler.d.mts +3 -46
  92. package/dist/handler.mjs +107 -281
  93. package/dist/handlerBundleRoutes.cjs +155 -0
  94. package/dist/handlerBundleRoutes.mjs +155 -0
  95. package/dist/handlerErrors.cjs +9 -0
  96. package/dist/handlerErrors.mjs +9 -0
  97. package/dist/handlerParameters.cjs +39 -0
  98. package/dist/handlerParameters.mjs +34 -0
  99. package/dist/handlerReleaseCatalogRoutes.cjs +111 -0
  100. package/dist/handlerReleaseCatalogRoutes.mjs +110 -0
  101. package/dist/handlerReleaseManagementRoutes.cjs +125 -0
  102. package/dist/handlerReleaseManagementRoutes.mjs +124 -0
  103. package/dist/handlerTypes.d.cts +63 -0
  104. package/dist/handlerTypes.d.mts +63 -0
  105. package/dist/handlerVersionRoutes.cjs +10 -0
  106. package/dist/handlerVersionRoutes.d.cts +4 -0
  107. package/dist/handlerVersionRoutes.d.mts +4 -0
  108. package/dist/handlerVersionRoutes.mjs +9 -0
  109. package/dist/index.cjs +10 -1
  110. package/dist/index.d.cts +9 -3
  111. package/dist/index.d.mts +9 -3
  112. package/dist/index.mjs +5 -2
  113. package/dist/insights/bounded/activeOverview.cjs +87 -0
  114. package/dist/insights/bounded/activeOverview.mjs +85 -0
  115. package/dist/insights/bounded/installationSearch.cjs +42 -0
  116. package/dist/insights/bounded/installationSearch.mjs +42 -0
  117. package/dist/insights/bounded/persistence.cjs +39 -0
  118. package/dist/insights/bounded/persistence.mjs +38 -0
  119. package/dist/insights/bounded/provider.cjs +170 -0
  120. package/dist/insights/bounded/provider.d.cts +7 -0
  121. package/dist/insights/bounded/provider.d.mts +7 -0
  122. package/dist/insights/bounded/provider.mjs +170 -0
  123. package/dist/insights/bounded/scan.cjs +144 -0
  124. package/dist/insights/bounded/scan.mjs +138 -0
  125. package/dist/insights/domain.d.cts +121 -0
  126. package/dist/insights/domain.d.mts +121 -0
  127. package/dist/insights/errors.cjs +22 -0
  128. package/dist/insights/errors.mjs +20 -0
  129. package/dist/insights/eventInput.cjs +111 -0
  130. package/dist/insights/eventInput.mjs +111 -0
  131. package/dist/insights/persistence.d.cts +6 -0
  132. package/dist/insights/persistence.d.mts +6 -0
  133. package/dist/insights/queryInput.cjs +58 -0
  134. package/dist/insights/queryInput.mjs +55 -0
  135. package/dist/insights/routes.cjs +63 -0
  136. package/dist/insights/routes.mjs +61 -0
  137. package/dist/insights/types.d.cts +17 -0
  138. package/dist/insights/types.d.mts +17 -0
  139. package/dist/node.cjs +19 -12
  140. package/dist/node.d.cts +15 -10
  141. package/dist/node.d.mts +15 -10
  142. package/dist/node.mjs +19 -13
  143. package/dist/package.cjs +1 -1
  144. package/dist/package.mjs +1 -1
  145. package/dist/schema/dsl.cjs +19 -2
  146. package/dist/schema/dsl.mjs +17 -3
  147. package/dist/schema/index.cjs +1 -8
  148. package/dist/schema/index.mjs +2 -8
  149. package/dist/schema/types.cjs +3 -1
  150. package/dist/schema/types.mjs +3 -2
  151. package/dist/schema/v1_0_0.cjs +364 -0
  152. package/dist/schema/v1_0_0.mjs +357 -0
  153. package/dist/storageAccess.cjs +69 -25
  154. package/dist/storageAccess.mjs +68 -25
  155. package/dist/types/index.d.cts +4 -3
  156. package/dist/types/index.d.mts +5 -4
  157. package/package.json +8 -7
  158. package/src/adapters/databaseConstraintErrors.ts +25 -0
  159. package/src/adapters/databasePluginTestDatabase.ts +106 -0
  160. package/src/adapters/databasePluginUtils.spec.ts +29 -0
  161. package/src/adapters/databasePluginUtils.ts +214 -0
  162. package/src/adapters/drizzle.spec.ts +208 -47
  163. package/src/adapters/drizzle.ts +106 -293
  164. package/src/adapters/drizzleCrud.ts +419 -0
  165. package/src/adapters/drizzleLazyDB.ts +206 -100
  166. package/src/adapters/drizzleQuery.ts +164 -0
  167. package/src/adapters/kysely.spec.ts +126 -88
  168. package/src/adapters/kysely.ts +76 -334
  169. package/src/adapters/kyselyCrud.ts +588 -0
  170. package/src/adapters/kyselyQuery.ts +128 -0
  171. package/src/adapters/mongodb.spec.ts +141 -0
  172. package/src/adapters/mongodb.ts +60 -288
  173. package/src/adapters/mongodbCollections.ts +62 -0
  174. package/src/adapters/mongodbQuery.ts +226 -0
  175. package/src/adapters/mongodbReads.ts +311 -0
  176. package/src/adapters/mongodbRows.spec.ts +64 -0
  177. package/src/adapters/mongodbRows.ts +118 -0
  178. package/src/adapters/mongodbTestClient.ts +342 -0
  179. package/src/adapters/mongodbTestFilter.ts +166 -0
  180. package/src/adapters/mongodbWrites.ts +260 -0
  181. package/src/adapters/prisma.spec.ts +196 -0
  182. package/src/adapters/prisma.ts +417 -292
  183. package/src/adapters/prismaQuery.ts +129 -0
  184. package/src/adapters/prismaRows.spec.ts +98 -0
  185. package/src/adapters/prismaRows.ts +301 -0
  186. package/src/adapters/prismaTestClient.ts +396 -0
  187. package/src/apiKeys.integration.spec.ts +192 -0
  188. package/src/apiKeys.spec.ts +162 -0
  189. package/src/apiKeys.ts +220 -0
  190. package/src/createHotUpdaterCore.ts +164 -100
  191. package/src/db/bundleRows.spec.ts +164 -0
  192. package/src/db/bundleRows.ts +10 -140
  193. package/src/db/createBundleDiff.spec.ts +172 -127
  194. package/src/db/createBundleDiff.ts +91 -110
  195. package/src/db/databasePluginCore.spec.ts +62 -0
  196. package/src/db/databasePluginCore.testFixtures.ts +76 -0
  197. package/src/db/databasePluginCore.ts +185 -0
  198. package/src/db/databasePluginUpdateCheck.bench.ts +107 -0
  199. package/src/db/fixedMigrator.spec.ts +184 -42
  200. package/src/db/fixedMigrator.ts +64 -205
  201. package/src/db/fixedMigratorMongo.spec.ts +192 -0
  202. package/src/db/fixedMigratorMongo.ts +425 -0
  203. package/src/db/fixedMigratorShared.ts +36 -0
  204. package/src/db/hotUpdaterSchema.ts +0 -1
  205. package/src/db/index.spec.ts +333 -1025
  206. package/src/db/mongoMigrationExecution.ts +23 -0
  207. package/src/db/node-sqlite.d.ts +25 -0
  208. package/src/db/releaseCatalog.ts +148 -0
  209. package/src/db/schema/mongodb.ts +20 -6
  210. package/src/db/schema/sql.ts +52 -11
  211. package/src/db/schema/sqlOperations.ts +5 -4
  212. package/src/db/schemaGenerators.ts +107 -23
  213. package/src/db/schemaReadiness.ts +2 -1
  214. package/src/db/sqlMigrationExecution.spec.ts +65 -0
  215. package/src/db/sqlMigrationExecution.ts +108 -0
  216. package/src/db/types.spec.ts +55 -0
  217. package/src/db/types.ts +129 -65
  218. package/src/db/updateArtifacts.spec.ts +359 -0
  219. package/src/db/updateArtifacts.ts +290 -126
  220. package/src/handler-standalone.integration.spec.ts +401 -364
  221. package/src/handler.spec.ts +137 -582
  222. package/src/handler.testFixtures.ts +30 -0
  223. package/src/handler.ts +218 -616
  224. package/src/handlerAuth.integration.spec.ts +33 -0
  225. package/src/handlerBundleRoutes.ts +259 -0
  226. package/src/handlerErrors.ts +7 -0
  227. package/src/handlerManagement.spec.ts +262 -0
  228. package/src/handlerParameters.ts +105 -0
  229. package/src/handlerReleaseCatalogRoutes.ts +154 -0
  230. package/src/handlerReleaseManagementRoutes.ts +213 -0
  231. package/src/handlerTypes.ts +96 -0
  232. package/src/handlerVersionRoutes.ts +12 -0
  233. package/src/index.ts +22 -2
  234. package/src/insights/bounded/activeOverview.spec.ts +189 -0
  235. package/src/insights/bounded/activeOverview.ts +148 -0
  236. package/src/insights/bounded/installationSearch.spec.ts +130 -0
  237. package/src/insights/bounded/installationSearch.ts +69 -0
  238. package/src/insights/bounded/persistence.ts +43 -0
  239. package/src/insights/bounded/provider.spec.ts +219 -0
  240. package/src/insights/bounded/provider.ts +229 -0
  241. package/src/insights/bounded/scan.ts +235 -0
  242. package/src/insights/domain.ts +139 -0
  243. package/src/insights/errors.ts +18 -0
  244. package/src/insights/eventInput.ts +168 -0
  245. package/src/insights/insights.integration.spec.ts +144 -0
  246. package/src/insights/persistence.ts +10 -0
  247. package/src/insights/queryInput.ts +102 -0
  248. package/src/insights/routes.ts +123 -0
  249. package/src/insights/types.ts +44 -0
  250. package/src/node.spec.ts +79 -10
  251. package/src/node.ts +20 -17
  252. package/src/releaseCatalogRoutes.integration.spec.ts +238 -0
  253. package/src/runtime.spec.ts +126 -959
  254. package/src/runtime.testFixtures.ts +71 -0
  255. package/src/schema/dsl-all-versions.spec.ts +26 -17
  256. package/src/schema/dsl.ts +41 -2
  257. package/src/schema/index.ts +3 -9
  258. package/src/schema/types.ts +9 -3
  259. package/src/schema/v1_0_0.spec.ts +146 -0
  260. package/src/schema/v1_0_0.ts +465 -0
  261. package/src/storageAccess.spec.ts +127 -32
  262. package/src/storageAccess.ts +106 -55
  263. package/src/types/index.ts +2 -1
  264. package/dist/db/bundleRows.cjs +0 -82
  265. package/dist/db/bundleRows.d.cts +0 -33
  266. package/dist/db/bundleRows.d.mts +0 -33
  267. package/dist/db/bundleRows.mjs +0 -79
  268. package/dist/db/pluginCore.cjs +0 -242
  269. package/dist/db/pluginCore.mjs +0 -241
  270. package/dist/db/schema/sqlMigrations.cjs +0 -74
  271. package/dist/db/schema/sqlMigrations.mjs +0 -73
  272. package/dist/db/schemaEnhancements.cjs +0 -20
  273. package/dist/db/schemaEnhancements.mjs +0 -19
  274. package/dist/route.cjs +0 -7
  275. package/dist/route.mjs +0 -7
  276. package/dist/schema/v0_21_0.cjs +0 -38
  277. package/dist/schema/v0_21_0.mjs +0 -37
  278. package/dist/schema/v0_29_0.cjs +0 -45
  279. package/dist/schema/v0_29_0.mjs +0 -44
  280. package/dist/schema/v0_31_0.cjs +0 -82
  281. package/dist/schema/v0_31_0.mjs +0 -80
  282. package/src/db/pluginCore.spec.ts +0 -1095
  283. package/src/db/pluginCore.ts +0 -475
  284. package/src/db/pluginUpdateCheck.bench.ts +0 -254
  285. package/src/db/schema/sqlMigrations.ts +0 -219
  286. package/src/db/schemaEnhancements.ts +0 -55
  287. package/src/route.ts +0 -7
  288. package/src/schema/v0_21_0.ts +0 -52
  289. package/src/schema/v0_29_0.ts +0 -62
  290. package/src/schema/v0_31_0.spec.ts +0 -73
  291. package/src/schema/v0_31_0.ts +0 -120
@@ -1,151 +1,257 @@
1
+ import type {
2
+ BundleEventRow,
3
+ BundlePatchRow,
4
+ ChannelRow,
5
+ ApiKeyRow,
6
+ ReleaseCatalogRow,
7
+ } from "@hot-updater/plugin-core";
8
+
9
+ import type { StoredBundleRow, StoredReleaseRow } from "./databasePluginUtils";
1
10
  import type { DrizzleConfig } from "./drizzle";
2
11
 
3
12
  export type DrizzleTable = Record<string, unknown>;
4
13
 
14
+ type DrizzleMutation = {
15
+ readonly execute: () => Promise<unknown>;
16
+ };
17
+
18
+ export type DrizzleInsertMutation = DrizzleMutation & {
19
+ readonly onConflictDoNothing?: () => DrizzleMutation;
20
+ };
21
+
22
+ type DrizzleInsertBuilder = {
23
+ readonly ignore?: () => Pick<DrizzleInsertBuilder, "values">;
24
+ readonly values: (value: unknown) => DrizzleInsertMutation;
25
+ };
26
+
27
+ type DrizzleQuery<TRow> = {
28
+ readonly findFirst: (args?: unknown) => Promise<TRow | undefined>;
29
+ readonly findMany: (args?: unknown) => Promise<TRow[]>;
30
+ };
31
+
5
32
  export type DrizzleDB = {
6
33
  readonly _: { readonly fullSchema: Record<string, DrizzleTable> };
7
34
  readonly $count: (table: DrizzleTable, where?: unknown) => Promise<number>;
8
35
  readonly delete: (table: DrizzleTable) => {
9
- where: (condition: unknown) => Promise<unknown>;
36
+ where: (condition: unknown) => DrizzleMutation;
10
37
  };
11
- readonly insert: (table: DrizzleTable) => {
12
- values: (value: unknown) => {
13
- onConflictDoUpdate?: (args: unknown) => Promise<unknown>;
14
- onDuplicateKeyUpdate?: (args: unknown) => Promise<unknown>;
15
- execute?: () => Promise<unknown>;
16
- };
17
- };
18
- readonly query: Record<
19
- string,
20
- {
21
- findFirst: (
22
- args?: unknown,
23
- ) => Promise<Record<string, unknown> | undefined>;
24
- findMany: (args?: unknown) => Promise<Record<string, unknown>[]>;
25
- }
26
- >;
27
- readonly select: (fields?: unknown) => {
28
- from: (table: DrizzleTable) => {
29
- where?: (condition: unknown) => unknown;
30
- orderBy?: (order: unknown) => unknown;
31
- limit?: (limit: number) => unknown;
32
- offset?: (offset: number) => Promise<Record<string, unknown>[]>;
33
- };
38
+ readonly insert: (table: DrizzleTable) => DrizzleInsertBuilder;
39
+ readonly query: {
40
+ readonly bundles: DrizzleQuery<StoredBundleRow>;
41
+ readonly channels: DrizzleQuery<ChannelRow>;
42
+ readonly bundle_patches: DrizzleQuery<BundlePatchRow>;
43
+ readonly bundle_events: DrizzleQuery<BundleEventRow>;
44
+ readonly api_keys: DrizzleQuery<ApiKeyRow>;
45
+ readonly releases: DrizzleQuery<StoredReleaseRow>;
46
+ readonly release_catalogs: DrizzleQuery<ReleaseCatalogRow>;
34
47
  };
35
48
  readonly update: (table: DrizzleTable) => {
36
49
  set: (values: unknown) => {
37
- where: (condition: unknown) => Promise<unknown>;
50
+ where: (condition: unknown) => DrizzleMutation;
38
51
  };
39
52
  };
40
- readonly transaction?: <T>(
41
- operation: (tx: DrizzleDB) => Promise<T>,
42
- ) => Promise<T>;
53
+ readonly transaction?: <TResult>(
54
+ operation: (transaction: DrizzleDB) => Promise<TResult>,
55
+ ) => Promise<TResult>;
56
+ };
57
+
58
+ class InvalidDrizzleDatabaseError extends Error {
59
+ readonly name = "InvalidDrizzleDatabaseError";
60
+
61
+ constructor(readonly reason: string) {
62
+ super(`[hot-updater] ${reason}`);
63
+ }
64
+ }
65
+
66
+ const isRecord = (value: unknown): value is Record<string, unknown> =>
67
+ typeof value === "object" && value !== null;
68
+
69
+ const hasFunction = (value: Record<string, unknown>, key: string): boolean =>
70
+ typeof value[key] === "function";
71
+
72
+ const isDrizzleQuery = (value: unknown): boolean =>
73
+ isRecord(value) &&
74
+ hasFunction(value, "findFirst") &&
75
+ hasFunction(value, "findMany");
76
+
77
+ const isDrizzleDB = (value: unknown): value is DrizzleDB => {
78
+ if (!isRecord(value)) return false;
79
+ const metadata = value["_"];
80
+ if (!isRecord(metadata) || !isRecord(metadata["fullSchema"])) return false;
81
+ const query = value["query"];
82
+ if (
83
+ !isRecord(query) ||
84
+ !isDrizzleQuery(query["bundle_events"]) ||
85
+ !isDrizzleQuery(query["bundle_patches"]) ||
86
+ !isDrizzleQuery(query["bundles"]) ||
87
+ !isDrizzleQuery(query["channels"]) ||
88
+ !isDrizzleQuery(query["api_keys"]) ||
89
+ !isDrizzleQuery(query["releases"]) ||
90
+ !isDrizzleQuery(query["release_catalogs"])
91
+ ) {
92
+ return false;
93
+ }
94
+ if (
95
+ value["transaction"] !== undefined &&
96
+ !hasFunction(value, "transaction")
97
+ ) {
98
+ return false;
99
+ }
100
+ return (
101
+ hasFunction(value, "$count") &&
102
+ hasFunction(value, "delete") &&
103
+ hasFunction(value, "insert") &&
104
+ hasFunction(value, "update")
105
+ );
43
106
  };
44
107
 
45
108
  const asDB = (db: unknown): DrizzleDB => {
46
- const typed = db as DrizzleDB;
47
- if (!typed._?.fullSchema) {
48
- throw new Error(
49
- "[hot-updater] Drizzle adapter requires query mode with schema.",
109
+ if (!isDrizzleDB(db)) {
110
+ throw new InvalidDrizzleDatabaseError(
111
+ "Drizzle adapter requires query mode with schema.",
50
112
  );
51
113
  }
52
- return typed;
114
+ return db;
53
115
  };
54
116
 
55
117
  const isDBFactory = (
56
118
  db: DrizzleConfig["db"],
57
119
  ): db is () => unknown | Promise<unknown> => typeof db === "function";
58
120
 
59
- export const createLazyDB = (config: DrizzleConfig): DrizzleDB => {
60
- const dbSource = config.db;
61
- if (!isDBFactory(dbSource)) return asDB(dbSource);
121
+ const parseSchema = (
122
+ schema: Record<string, unknown>,
123
+ ): Record<string, DrizzleTable> => {
124
+ const parsed: Record<string, DrizzleTable> = {};
125
+ for (const [name, table] of Object.entries(schema)) {
126
+ if (!isRecord(table)) {
127
+ throw new InvalidDrizzleDatabaseError(
128
+ `Drizzle schema table "${name}" is invalid.`,
129
+ );
130
+ }
131
+ parsed[name] = table;
132
+ }
133
+ return parsed;
134
+ };
62
135
 
136
+ export const createLazyDB = (config: DrizzleConfig): DrizzleDB => {
137
+ if (!isDBFactory(config.db)) return asDB(config.db);
63
138
  if (!config.schema) {
64
- throw new Error(
65
- "[hot-updater] Drizzle adapter requires schema when db is lazy.",
139
+ throw new InvalidDrizzleDatabaseError(
140
+ "Drizzle adapter requires schema when db is lazy.",
66
141
  );
67
142
  }
68
143
 
144
+ const source = config.db;
69
145
  let resolvedDB: Promise<DrizzleDB> | undefined;
70
- const getDB = async () => {
71
- resolvedDB ??= Promise.resolve(dbSource()).then(asDB);
146
+ const getDB = (): Promise<DrizzleDB> => {
147
+ resolvedDB ??= Promise.resolve(source()).then(asDB);
72
148
  return resolvedDB;
73
149
  };
74
- const fullSchema = config.schema as Record<string, DrizzleTable>;
75
- const runInserted = async (
76
- table: DrizzleTable,
77
- value: unknown,
78
- operation: (
79
- inserted: ReturnType<ReturnType<DrizzleDB["insert"]>["values"]>,
80
- ) => Promise<unknown> | unknown,
81
- ) => {
82
- const db = await getDB();
83
- return operation(db.insert(table).values(value));
84
- };
85
-
86
150
  return {
87
- _: { fullSchema },
151
+ _: { fullSchema: parseSchema(config.schema) },
88
152
  $count: async (table, where) => (await getDB()).$count(table, where),
89
153
  delete: (table) => ({
90
- where: async (condition) =>
91
- (await getDB()).delete(table).where(condition),
154
+ where: (condition) => ({
155
+ execute: async () =>
156
+ (await getDB()).delete(table).where(condition).execute(),
157
+ }),
92
158
  }),
93
159
  insert: (table) => ({
94
- values: (value) => ({
95
- execute: async () =>
96
- runInserted(table, value, async (inserted) => {
97
- if (typeof inserted.execute === "function") {
98
- return inserted.execute();
99
- }
100
- return inserted;
101
- }),
102
- onConflictDoUpdate: async (args) =>
103
- runInserted(table, value, async (inserted) => {
104
- if (typeof inserted.onConflictDoUpdate !== "function") {
105
- throw new Error(
106
- "[hot-updater] Drizzle insert does not support onConflictDoUpdate.",
160
+ ignore: () => ({
161
+ values: (value) => ({
162
+ execute: async () => {
163
+ const insert = (await getDB()).insert(table).ignore;
164
+ if (insert === undefined) {
165
+ throw new InvalidDrizzleDatabaseError(
166
+ "The resolved Drizzle database does not support insert ignore.",
107
167
  );
108
168
  }
109
- return inserted.onConflictDoUpdate(args);
110
- }),
111
- onDuplicateKeyUpdate: async (args) =>
112
- runInserted(table, value, async (inserted) => {
113
- if (typeof inserted.onDuplicateKeyUpdate !== "function") {
114
- throw new Error(
115
- "[hot-updater] Drizzle insert does not support onDuplicateKeyUpdate.",
169
+ return insert().values(value).execute();
170
+ },
171
+ }),
172
+ }),
173
+ values: (value) => ({
174
+ execute: async () =>
175
+ (await getDB()).insert(table).values(value).execute(),
176
+ onConflictDoNothing: () => ({
177
+ execute: async () => {
178
+ const insert = (await getDB()).insert(table).values(value);
179
+ if (insert.onConflictDoNothing === undefined) {
180
+ throw new InvalidDrizzleDatabaseError(
181
+ "The resolved Drizzle database does not support conflict-ignore inserts.",
116
182
  );
117
183
  }
118
- return inserted.onDuplicateKeyUpdate(args);
119
- }),
120
- }),
121
- }),
122
- query: new Proxy(
123
- {},
124
- {
125
- get: (_target, tableName) => ({
126
- findFirst: async (args?: unknown) =>
127
- (await getDB()).query[String(tableName)]?.findFirst(args),
128
- findMany: async (args?: unknown) =>
129
- (await getDB()).query[String(tableName)]?.findMany(args) ?? [],
184
+ return insert.onConflictDoNothing().execute();
185
+ },
130
186
  }),
131
- },
132
- ) as DrizzleDB["query"],
133
- select: (fields) => ({
134
- from: (table) => ({
135
- offset: async (offset) =>
136
- (await getDB()).select(fields).from(table).offset?.(offset) ?? [],
137
187
  }),
138
188
  }),
189
+ query: {
190
+ bundle_events: {
191
+ findFirst: async (args) =>
192
+ (await getDB()).query.bundle_events.findFirst(args),
193
+ findMany: async (args) =>
194
+ (await getDB()).query.bundle_events.findMany(args),
195
+ },
196
+ bundle_patches: {
197
+ findFirst: async (args) =>
198
+ (await getDB()).query.bundle_patches.findFirst(args),
199
+ findMany: async (args) =>
200
+ (await getDB()).query.bundle_patches.findMany(args),
201
+ },
202
+ bundles: {
203
+ findFirst: async (args) =>
204
+ (await getDB()).query.bundles.findFirst(args),
205
+ findMany: async (args) => (await getDB()).query.bundles.findMany(args),
206
+ },
207
+ channels: {
208
+ findFirst: async (args) =>
209
+ (await getDB()).query.channels.findFirst(args),
210
+ findMany: async (args) => (await getDB()).query.channels.findMany(args),
211
+ },
212
+ api_keys: {
213
+ findFirst: async (args) =>
214
+ (await getDB()).query.api_keys.findFirst(args),
215
+ findMany: async (args) => (await getDB()).query.api_keys.findMany(args),
216
+ },
217
+ releases: {
218
+ findFirst: async (args) =>
219
+ (await getDB()).query.releases.findFirst(args),
220
+ findMany: async (args) => (await getDB()).query.releases.findMany(args),
221
+ },
222
+ release_catalogs: {
223
+ findFirst: async (args) =>
224
+ (await getDB()).query.release_catalogs.findFirst(args),
225
+ findMany: async (args) =>
226
+ (await getDB()).query.release_catalogs.findMany(args),
227
+ },
228
+ },
139
229
  update: (table) => ({
140
230
  set: (values) => ({
141
- where: async (condition) =>
142
- (await getDB()).update(table).set(values).where(condition),
231
+ where: (condition) => ({
232
+ execute: async () =>
233
+ (await getDB())
234
+ .update(table)
235
+ .set(values)
236
+ .where(condition)
237
+ .execute(),
238
+ }),
143
239
  }),
144
240
  }),
145
- transaction: async (operation) => {
146
- const db = await getDB();
147
- if (typeof db.transaction !== "function") return operation(db);
148
- return db.transaction(operation);
149
- },
241
+ ...(config.transaction === true
242
+ ? {
243
+ transaction: async <TResult>(
244
+ operation: (transaction: DrizzleDB) => Promise<TResult>,
245
+ ): Promise<TResult> => {
246
+ const db = await getDB();
247
+ if (db.transaction === undefined) {
248
+ throw new InvalidDrizzleDatabaseError(
249
+ "The resolved Drizzle database does not support transactions.",
250
+ );
251
+ }
252
+ return db.transaction(operation);
253
+ },
254
+ }
255
+ : {}),
150
256
  };
151
257
  };
@@ -0,0 +1,164 @@
1
+ import type {
2
+ DatabaseModel,
3
+ DatabaseWhere,
4
+ } from "@hot-updater/plugin-core/internal";
5
+ import {
6
+ eq,
7
+ gt,
8
+ gte,
9
+ inArray,
10
+ isNotNull,
11
+ isNull,
12
+ lt,
13
+ lte,
14
+ ne,
15
+ notInArray,
16
+ sql,
17
+ type SQL,
18
+ type SQLWrapper,
19
+ } from "drizzle-orm";
20
+
21
+ import type { ORMSQLProvider } from "../db/types";
22
+ import { escapeGlobPattern, escapeLikePattern } from "./databasePluginUtils";
23
+ import type { DrizzleTable } from "./drizzleLazyDB";
24
+
25
+ class InvalidDatabasePredicateError extends Error {
26
+ readonly name = "InvalidDatabasePredicateError";
27
+ }
28
+
29
+ class MissingDrizzleColumnError extends Error {
30
+ readonly name = "MissingDrizzleColumnError";
31
+
32
+ constructor(readonly column: string) {
33
+ super(`Drizzle schema is missing column "${column}".`);
34
+ }
35
+ }
36
+
37
+ const isSQLWrapper = (value: unknown): value is SQLWrapper =>
38
+ typeof value === "object" &&
39
+ value !== null &&
40
+ "getSQL" in value &&
41
+ typeof value.getSQL === "function";
42
+
43
+ const column = (table: DrizzleTable, field: string): SQLWrapper => {
44
+ const value = table[field];
45
+ if (!isSQLWrapper(value)) {
46
+ throw new MissingDrizzleColumnError(field);
47
+ }
48
+ return value;
49
+ };
50
+
51
+ const stringPredicate = (
52
+ provider: ORMSQLProvider,
53
+ field: SQLWrapper,
54
+ operator: "contains" | "ends_with" | "starts_with",
55
+ value: string,
56
+ insensitive: boolean,
57
+ ): SQL => {
58
+ if (provider === "sqlite" && !insensitive) {
59
+ const literal = escapeGlobPattern(value);
60
+ const pattern =
61
+ operator === "contains"
62
+ ? `*${literal}*`
63
+ : operator === "starts_with"
64
+ ? `${literal}*`
65
+ : `*${literal}`;
66
+ return sql`${field} glob ${pattern}`;
67
+ }
68
+ const literal = escapeLikePattern(value);
69
+ const pattern =
70
+ operator === "contains"
71
+ ? `%${literal}%`
72
+ : operator === "starts_with"
73
+ ? `${literal}%`
74
+ : `%${literal}`;
75
+ if (insensitive) {
76
+ return sql`lower(${field}) like lower(${pattern}) escape '\\'`;
77
+ }
78
+ if (provider === "mysql") {
79
+ return sql`binary ${field} like binary ${pattern} escape '\\'`;
80
+ }
81
+ return sql`${field} like ${pattern} escape '\\'`;
82
+ };
83
+
84
+ const predicate = <TModel extends DatabaseModel>(
85
+ provider: ORMSQLProvider,
86
+ table: DrizzleTable,
87
+ condition: DatabaseWhere<TModel>,
88
+ ): SQL => {
89
+ const field = column(table, condition.field);
90
+ const operator = condition.operator ?? "eq";
91
+ switch (operator) {
92
+ case "eq":
93
+ case "ne": {
94
+ if (condition.value === null) {
95
+ return operator === "eq" ? isNull(field) : isNotNull(field);
96
+ }
97
+ const insensitive =
98
+ "mode" in condition && condition.mode === "insensitive";
99
+ if (insensitive) {
100
+ return operator === "eq"
101
+ ? sql`lower(${field}) = lower(${condition.value})`
102
+ : sql`lower(${field}) <> lower(${condition.value})`;
103
+ }
104
+ return operator === "eq"
105
+ ? eq(field, condition.value)
106
+ : ne(field, condition.value);
107
+ }
108
+ case "gt":
109
+ return gt(field, condition.value);
110
+ case "gte":
111
+ return gte(field, condition.value);
112
+ case "lt":
113
+ return lt(field, condition.value);
114
+ case "lte":
115
+ return lte(field, condition.value);
116
+ case "in":
117
+ case "not_in": {
118
+ if (!Array.isArray(condition.value)) {
119
+ throw new InvalidDatabasePredicateError();
120
+ }
121
+ if (operator === "in") {
122
+ return condition.value.length === 0
123
+ ? sql`false`
124
+ : inArray(field, condition.value);
125
+ }
126
+ return condition.value.length === 0
127
+ ? sql`true`
128
+ : notInArray(field, condition.value);
129
+ }
130
+ case "contains":
131
+ case "starts_with":
132
+ case "ends_with": {
133
+ if (typeof condition.value !== "string") {
134
+ throw new InvalidDatabasePredicateError();
135
+ }
136
+ return stringPredicate(
137
+ provider,
138
+ field,
139
+ operator,
140
+ condition.value,
141
+ "mode" in condition && condition.mode === "insensitive",
142
+ );
143
+ }
144
+ }
145
+ };
146
+
147
+ export const buildDrizzleWhere = <TModel extends DatabaseModel>(
148
+ provider: ORMSQLProvider,
149
+ table: DrizzleTable,
150
+ where: readonly DatabaseWhere<TModel>[] | undefined,
151
+ ): SQL | undefined => {
152
+ const items = Array.isArray(where) ? where : [];
153
+ const [first, ...rest] = items;
154
+ if (first === undefined) return undefined;
155
+ let expression = predicate<TModel>(provider, table, first);
156
+ for (const condition of rest) {
157
+ const next = predicate<TModel>(provider, table, condition);
158
+ expression =
159
+ condition.connector === "OR"
160
+ ? sql`(${expression} or ${next})`
161
+ : sql`(${expression} and ${next})`;
162
+ }
163
+ return expression;
164
+ };