@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
@@ -17,6 +17,9 @@ import { getSQLProvider } from "./types";
17
17
 
18
18
  const literal = (value: string): string => JSON.stringify(value);
19
19
 
20
+ const varcharLength = (type: HotUpdaterColumnType): number =>
21
+ Number(type.slice("varchar(".length, -1));
22
+
20
23
  const toPascalCase = (value: string): string =>
21
24
  value
22
25
  .split("_")
@@ -27,11 +30,16 @@ const toPascalCase = (value: string): string =>
27
30
  const prismaDb = (type: HotUpdaterColumnType, provider: ORMProvider) => {
28
31
  if (provider === "postgresql") {
29
32
  if (type === "uuid") return " @db.Uuid";
30
- if (type.startsWith("varchar")) return " @db.VarChar(255)";
33
+ if (type.startsWith("varchar")) {
34
+ return ` @db.VarChar(${varcharLength(type)})`;
35
+ }
31
36
  }
32
37
  if (provider === "mysql") {
33
38
  if (type === "uuid") return " @db.Char(36)";
34
- if (type.startsWith("varchar")) return " @db.VarChar(255)";
39
+ if (type === "large-string") return " @db.MediumText";
40
+ if (type.startsWith("varchar")) {
41
+ return ` @db.VarChar(${varcharLength(type)})`;
42
+ }
35
43
  }
36
44
  return "";
37
45
  };
@@ -42,6 +50,7 @@ const prismaType = (
42
50
  ): string => {
43
51
  const base = (() => {
44
52
  if (column.type === "bool") return "Boolean";
53
+ if (column.type === "float") return "Float";
45
54
  if (column.type === "integer") return "Int";
46
55
  if (column.type === "json") return "Json";
47
56
  return "String";
@@ -80,11 +89,14 @@ const prismaField = (
80
89
  const relationTargetFields = (
81
90
  table: HotUpdaterTableSchema,
82
91
  schema: HotUpdaterVersionedSchema,
83
- ): readonly HotUpdaterRelationSchema[] =>
92
+ ): readonly {
93
+ readonly relation: HotUpdaterRelationSchema;
94
+ readonly sourceTable: HotUpdaterTableSchema;
95
+ }[] =>
84
96
  schema.tables.flatMap((sourceTable) =>
85
- (sourceTable.relations ?? []).filter(
86
- (relation) => relation.referencedTable === table.ormName,
87
- ),
97
+ (sourceTable.relations ?? [])
98
+ .filter((relation) => relation.referencedTable === table.ormName)
99
+ .map((relation) => ({ relation, sourceTable })),
88
100
  );
89
101
 
90
102
  const prismaRelationFields = (
@@ -92,14 +104,36 @@ const prismaRelationFields = (
92
104
  schema: HotUpdaterVersionedSchema,
93
105
  ): string[] => {
94
106
  const lines = relationTargetFields(table, schema).map(
95
- (relation) =>
96
- `${relation.fieldName} ${relation.referencedTable === table.ormName ? "bundle_patches" : relation.referencedTable}[] @relation(${literal(relation.relationName)})`,
107
+ ({ relation, sourceTable }) =>
108
+ `${relation.fieldName} ${sourceTable.ormName}[] @relation(${literal(relation.relationName)})`,
97
109
  );
98
110
 
99
111
  for (const relation of table.relations ?? []) {
100
112
  const targetType = toPascalCase(relation.referencedTable);
113
+ const foreignKey = table.foreignKeys?.find(
114
+ (item) =>
115
+ item.referencedTable === relation.referencedTable &&
116
+ item.columns.join("\0") === relation.columns.join("\0") &&
117
+ item.referencedColumns.join("\0") ===
118
+ relation.referencedColumns.join("\0"),
119
+ );
120
+ if (!foreignKey) {
121
+ throw new Error(
122
+ `Missing foreign key metadata for relation ${table.ormName}.${relation.name}`,
123
+ );
124
+ }
125
+ const optional = relation.columns.some(
126
+ (columnName) =>
127
+ table.columns.find(({ ormName }) => ormName === columnName)?.nullable,
128
+ );
129
+ const onDelete =
130
+ foreignKey.onDelete === "cascade"
131
+ ? "Cascade"
132
+ : foreignKey.onDelete === "set null"
133
+ ? "SetNull"
134
+ : "Restrict";
101
135
  lines.push(
102
- `${relation.targetFieldName} ${targetType === "Bundles" ? "bundles" : relation.referencedTable} @relation(${literal(relation.relationName)}, fields: [${relation.columns.join(", ")}], references: [${relation.referencedColumns.join(", ")}], onUpdate: Restrict, onDelete: Cascade)`,
136
+ `${relation.targetFieldName} ${targetType === "Bundles" ? "bundles" : relation.referencedTable}${optional ? "?" : ""} @relation(${literal(relation.relationName)}, fields: [${relation.columns.join(", ")}], references: [${relation.referencedColumns.join(", ")}], onUpdate: Restrict, onDelete: ${onDelete})`,
103
137
  );
104
138
  }
105
139
 
@@ -114,7 +148,7 @@ const prismaIndexes = (
114
148
  .filter((index) => schemaIndexAppliesToProvider(index, provider))
115
149
  .map(
116
150
  (index) =>
117
- `@@index([${index.columns.join(", ")}], map: ${literal(index.name)})`,
151
+ `@@${index.unique ? "unique" : "index"}([${index.columns.join(", ")}], map: ${literal(index.name)})`,
118
152
  );
119
153
 
120
154
  export const generatePrismaSchema = (
@@ -165,6 +199,12 @@ const drizzleColumnFn = (
165
199
  imports: ["integer"],
166
200
  };
167
201
  }
202
+ if (column.type === "float") {
203
+ return {
204
+ code: `real(${literal(column.ormName)})`,
205
+ imports: ["real"],
206
+ };
207
+ }
168
208
  if (column.type === "json") {
169
209
  return {
170
210
  code: `blob(${literal(column.ormName)}, { mode: "json" })`,
@@ -173,7 +213,7 @@ const drizzleColumnFn = (
173
213
  }
174
214
  if (column.type.startsWith("varchar")) {
175
215
  return {
176
- code: `text(${literal(column.ormName)}, { length: 255 })`,
216
+ code: `text(${literal(column.ormName)}, { length: ${varcharLength(column.type)} })`,
177
217
  imports: ["text"],
178
218
  };
179
219
  }
@@ -196,12 +236,24 @@ const drizzleColumnFn = (
196
236
  if (column.type === "integer") {
197
237
  return { code: `int(${literal(column.ormName)})`, imports: ["int"] };
198
238
  }
239
+ if (column.type === "float") {
240
+ return {
241
+ code: `double(${literal(column.ormName)})`,
242
+ imports: ["double"],
243
+ };
244
+ }
199
245
  if (column.type === "json") {
200
246
  return { code: `json(${literal(column.ormName)})`, imports: ["json"] };
201
247
  }
248
+ if (column.type === "large-string") {
249
+ return {
250
+ code: `mediumtext(${literal(column.ormName)})`,
251
+ imports: ["mediumtext"],
252
+ };
253
+ }
202
254
  if (column.type.startsWith("varchar")) {
203
255
  return {
204
- code: `varchar(${literal(column.ormName)}, { length: 255 })`,
256
+ code: `varchar(${literal(column.ormName)}, { length: ${varcharLength(column.type)} })`,
205
257
  imports: ["varchar"],
206
258
  };
207
259
  }
@@ -223,12 +275,18 @@ const drizzleColumnFn = (
223
275
  imports: ["integer"],
224
276
  };
225
277
  }
278
+ if (column.type === "float") {
279
+ return {
280
+ code: `doublePrecision(${literal(column.ormName)})`,
281
+ imports: ["doublePrecision"],
282
+ };
283
+ }
226
284
  if (column.type === "json") {
227
285
  return { code: `json(${literal(column.ormName)})`, imports: ["json"] };
228
286
  }
229
287
  if (column.type.startsWith("varchar")) {
230
288
  return {
231
- code: `varchar(${literal(column.ormName)}, { length: 255 })`,
289
+ code: `varchar(${literal(column.ormName)}, { length: ${varcharLength(column.type)} })`,
232
290
  imports: ["varchar"],
233
291
  };
234
292
  }
@@ -237,7 +295,9 @@ const drizzleColumnFn = (
237
295
 
238
296
  const drizzleDefault = (value: HotUpdaterDefault | undefined): string => {
239
297
  if (!value) return "";
240
- if (value.type === "json") return ".default({})";
298
+ if (value.type === "json") {
299
+ return `.default(${JSON.stringify(value.value)})`;
300
+ }
241
301
  return `.default(${JSON.stringify(value.value)})`;
242
302
  };
243
303
 
@@ -276,10 +336,14 @@ const drizzleTable = (
276
336
  const callbacks: string[] = [];
277
337
  for (const foreignKey of table.foreignKeys ?? []) {
278
338
  imports.add("foreignKey");
339
+ const referencedTable =
340
+ foreignKey.referencedTable === table.ormName
341
+ ? "table"
342
+ : foreignKey.referencedTable;
279
343
  callbacks.push(`foreignKey({
280
344
  columns: [table.${foreignKey.columns.join(", table.")}],
281
- foreignColumns: [${foreignKey.referencedTable}.${foreignKey.referencedColumns.join(
282
- `, ${foreignKey.referencedTable}.`,
345
+ foreignColumns: [${referencedTable}.${foreignKey.referencedColumns.join(
346
+ `, ${referencedTable}.`,
283
347
  )}],
284
348
  name: ${literal(foreignKey.name)}
285
349
  }).onUpdate(${literal(foreignKey.onUpdate)}).onDelete(${literal(
@@ -289,9 +353,10 @@ const drizzleTable = (
289
353
  for (const index of (table.indexes ?? []).filter((item) =>
290
354
  schemaIndexAppliesToProvider(item, provider),
291
355
  )) {
292
- imports.add("index");
356
+ const indexFunction = index.unique ? "uniqueIndex" : "index";
357
+ imports.add(indexFunction);
293
358
  callbacks.push(
294
- `index(${literal(index.name)}).on(${index.columns
359
+ `${indexFunction}(${literal(index.name)}).on(${index.columns
295
360
  .map((column) => `table.${column}`)
296
361
  .join(", ")})`,
297
362
  );
@@ -310,9 +375,16 @@ const drizzleTable = (
310
375
  )})`;
311
376
  };
312
377
 
313
- const drizzleRelations = (table: HotUpdaterTableSchema): string | undefined => {
314
- if (!table.relations || table.relations.length === 0) return undefined;
315
- const lines = table.relations.map(
378
+ const drizzleRelations = (
379
+ table: HotUpdaterTableSchema,
380
+ schema: HotUpdaterVersionedSchema,
381
+ ): string | undefined => {
382
+ const sourceRelations = table.relations ?? [];
383
+ const targetRelations = relationTargetFields(table, schema);
384
+ if (sourceRelations.length === 0 && targetRelations.length === 0) {
385
+ return undefined;
386
+ }
387
+ const lines = sourceRelations.map(
316
388
  (
317
389
  relation,
318
390
  ) => ` ${relation.targetFieldName}: one(${relation.referencedTable}, {
@@ -323,7 +395,19 @@ const drizzleRelations = (table: HotUpdaterTableSchema): string | undefined => {
323
395
  .join(", ")}]
324
396
  })`,
325
397
  );
326
- return `export const ${table.ormName}Relations = relations(${table.ormName}, ({ one }) => ({
398
+ lines.push(
399
+ ...targetRelations.map(
400
+ ({ relation, sourceTable }) =>
401
+ ` ${relation.fieldName}: many(${sourceTable.ormName}, {
402
+ relationName: ${literal(relation.relationName)}
403
+ })`,
404
+ ),
405
+ );
406
+ const callbacks = [
407
+ ...(sourceRelations.length > 0 ? ["one"] : []),
408
+ ...(targetRelations.length > 0 ? ["many"] : []),
409
+ ];
410
+ return `export const ${table.ormName}Relations = relations(${table.ormName}, ({ ${callbacks.join(", ")} }) => ({
327
411
  ${lines.join(",\n")}
328
412
  }))`;
329
413
  };
@@ -337,7 +421,7 @@ export const generateDrizzleSchema = (
337
421
 
338
422
  for (const table of schema.tables) {
339
423
  body.push(drizzleTable(table, provider, imports));
340
- const relations = drizzleRelations(table);
424
+ const relations = drizzleRelations(table, schema);
341
425
  if (relations) body.push(relations);
342
426
  }
343
427
 
@@ -1,4 +1,5 @@
1
1
  import { HOT_UPDATER_SCHEMA_VERSION } from "../schema/types";
2
+ import { unsupportedSchemaUpgradeMessage } from "./fixedMigratorShared";
2
3
  import type { Migrator } from "./types";
3
4
 
4
5
  export class HotUpdaterSchemaMigrationRequiredError extends Error {
@@ -9,7 +10,7 @@ export class HotUpdaterSchemaMigrationRequiredError extends Error {
9
10
  super(
10
11
  currentVersion === undefined
11
12
  ? `Hot Updater database schema is not initialized for ${adapterName}. Run \`hot-updater db migrate\` before using this adapter.`
12
- : `Hot Updater database schema version ${currentVersion} is not supported by ${adapterName}. Run \`hot-updater db migrate\` to upgrade to ${HOT_UPDATER_SCHEMA_VERSION}.`,
13
+ : unsupportedSchemaUpgradeMessage(currentVersion),
13
14
  );
14
15
  this.name = "HotUpdaterSchemaMigrationRequiredError";
15
16
  }
@@ -0,0 +1,65 @@
1
+ import { DatabaseSync, type SqliteValue } from "node:sqlite";
2
+
3
+ import { Kysely, SqliteDialect } from "kysely";
4
+ import { describe, expect, it } from "vitest";
5
+
6
+ import { executeMigrationStatements } from "./sqlMigrationExecution";
7
+
8
+ const createSqliteKysely = (database: DatabaseSync): Kysely<object> =>
9
+ new Kysely<object>({
10
+ dialect: new SqliteDialect({
11
+ database: {
12
+ close: () => database.close(),
13
+ prepare: (sqlText) => {
14
+ const statement = database.prepare(sqlText);
15
+ return {
16
+ reader: statement.columns().length > 0,
17
+ all: (parameters) =>
18
+ statement.all(...(parameters as SqliteValue[])),
19
+ run: (parameters) => {
20
+ const result = statement.run(...(parameters as SqliteValue[]));
21
+ return {
22
+ changes: result.changes,
23
+ lastInsertRowid: result.lastInsertRowid,
24
+ };
25
+ },
26
+ iterate: (parameters) =>
27
+ statement.iterate(...(parameters as SqliteValue[])),
28
+ };
29
+ },
30
+ },
31
+ }),
32
+ });
33
+
34
+ describe("executeMigrationStatements", () => {
35
+ it("rolls back SQLite statements and restores foreign keys when a statement fails", async () => {
36
+ const database = new DatabaseSync(":memory:");
37
+ database.exec("pragma foreign_keys = on");
38
+ const db = createSqliteKysely(database);
39
+
40
+ await expect(
41
+ executeMigrationStatements({
42
+ db,
43
+ provider: "sqlite",
44
+ statements: [
45
+ "create table migration_rows (id integer primary key)",
46
+ "insert into migration_rows (id) values (1)",
47
+ "insert into missing_rows (id) values (1)",
48
+ ],
49
+ }),
50
+ ).rejects.toThrow("no such table: missing_rows");
51
+
52
+ expect(
53
+ database
54
+ .prepare(
55
+ "select name from sqlite_master where type = 'table' and name = 'migration_rows'",
56
+ )
57
+ .all(),
58
+ ).toEqual([]);
59
+ expect(database.prepare("pragma foreign_keys").get()).toEqual({
60
+ foreign_keys: 1,
61
+ });
62
+
63
+ await db.destroy();
64
+ });
65
+ });
@@ -0,0 +1,108 @@
1
+ import { sql, type QueryExecutorProvider } from "kysely";
2
+
3
+ import type { ORMSQLProvider } from "./types";
4
+
5
+ class SqliteMigrationForeignKeyError extends Error {
6
+ readonly name = "SqliteMigrationForeignKeyError";
7
+
8
+ constructor(readonly violations: number) {
9
+ super(`SQLite migration produced ${violations} foreign key violation(s).`);
10
+ }
11
+ }
12
+
13
+ const executeTransactionalStatements = async (
14
+ db: QueryExecutorProvider,
15
+ statements: readonly string[],
16
+ ): Promise<void> => {
17
+ await db.getExecutor().provideConnection(async (connection) => {
18
+ const execute = (statement: string) =>
19
+ connection.executeQuery(sql.raw(statement).compile(db));
20
+ await execute("begin");
21
+ try {
22
+ for (const statement of statements) await execute(statement);
23
+ await execute("commit");
24
+ } catch (error) {
25
+ await execute("rollback");
26
+ throw error;
27
+ }
28
+ });
29
+ };
30
+
31
+ const executeSqliteStatements = async (
32
+ db: QueryExecutorProvider,
33
+ statements: readonly string[],
34
+ ): Promise<void> => {
35
+ await db.getExecutor().provideConnection(async (connection) => {
36
+ const execute = (statement: string) =>
37
+ connection.executeQuery(sql.raw(statement).compile(db));
38
+ const foreignKeys = await execute("pragma foreign_keys");
39
+ const state = foreignKeys.rows[0];
40
+ const foreignKeysWereEnabled =
41
+ typeof state === "object" &&
42
+ state !== null &&
43
+ "foreign_keys" in state &&
44
+ state.foreign_keys === 1;
45
+
46
+ await execute("pragma foreign_keys = off");
47
+ try {
48
+ await execute("begin");
49
+ try {
50
+ for (const statement of statements) {
51
+ if (
52
+ statement === "pragma foreign_keys = off" ||
53
+ statement === "pragma foreign_keys = on"
54
+ ) {
55
+ continue;
56
+ }
57
+ if (statement === "pragma foreign_key_check") {
58
+ const result = await execute(statement);
59
+ if (result.rows.length > 0) {
60
+ throw new SqliteMigrationForeignKeyError(result.rows.length);
61
+ }
62
+ continue;
63
+ }
64
+ await execute(statement);
65
+ }
66
+ await execute("commit");
67
+ } catch (error) {
68
+ await execute("rollback");
69
+ throw error;
70
+ }
71
+ } finally {
72
+ await execute(
73
+ foreignKeysWereEnabled
74
+ ? "pragma foreign_keys = on"
75
+ : "pragma foreign_keys = off",
76
+ );
77
+ }
78
+ });
79
+ };
80
+
81
+ const executeMysqlStatements = async (
82
+ db: QueryExecutorProvider,
83
+ statements: readonly string[],
84
+ ): Promise<void> => {
85
+ for (const statement of statements) {
86
+ await sql.raw(statement).execute(db);
87
+ }
88
+ };
89
+
90
+ export const executeMigrationStatements = async ({
91
+ db,
92
+ provider,
93
+ statements,
94
+ }: {
95
+ readonly db: QueryExecutorProvider;
96
+ readonly provider: ORMSQLProvider;
97
+ readonly statements: readonly string[];
98
+ }): Promise<void> => {
99
+ if (provider === "mysql") {
100
+ await executeMysqlStatements(db, statements);
101
+ return;
102
+ }
103
+ if (provider === "sqlite") {
104
+ await executeSqliteStatements(db, statements);
105
+ return;
106
+ }
107
+ await executeTransactionalStatements(db, statements);
108
+ };
@@ -0,0 +1,55 @@
1
+ import { describe, expect, it } from "vitest";
2
+
3
+ import { createInMemoryDatabasePlugin } from "../../../test-utils/test/inMemoryDatabasePlugin";
4
+ import { isDatabasePlugin } from "./types";
5
+
6
+ describe("isDatabasePlugin", () => {
7
+ it("accepts a direct fixed-model plugin object", () => {
8
+ const plugin = createInMemoryDatabasePlugin();
9
+
10
+ const result = isDatabasePlugin(plugin);
11
+
12
+ expect(result).toBe(true);
13
+ });
14
+
15
+ it("rejects a v1 factory and malformed required domains", () => {
16
+ const plugin = createInMemoryDatabasePlugin();
17
+ const factory = () => plugin;
18
+ const malformedBundles = {
19
+ ...plugin,
20
+ models: {
21
+ ...plugin.models,
22
+ bundles: { ...plugin.models.bundles, findMany: null },
23
+ },
24
+ };
25
+ const missingInsights = {
26
+ ...plugin,
27
+ models: { ...plugin.models, insights: undefined },
28
+ };
29
+ const malformedChannels = {
30
+ ...plugin,
31
+ models: {
32
+ ...plugin.models,
33
+ channels: { ...plugin.models.channels, delete: null },
34
+ },
35
+ };
36
+ const malformedApiKeys = {
37
+ ...plugin,
38
+ models: {
39
+ ...plugin.models,
40
+ apiKeys: {
41
+ ...plugin.models.apiKeys,
42
+ revoke: null,
43
+ },
44
+ },
45
+ };
46
+ const malformedDispose = { ...plugin, dispose: null };
47
+
48
+ expect(isDatabasePlugin(factory)).toBe(false);
49
+ expect(isDatabasePlugin(malformedBundles)).toBe(false);
50
+ expect(isDatabasePlugin(missingInsights)).toBe(false);
51
+ expect(isDatabasePlugin(malformedChannels)).toBe(false);
52
+ expect(isDatabasePlugin(malformedApiKeys)).toBe(false);
53
+ expect(isDatabasePlugin(malformedDispose)).toBe(false);
54
+ });
55
+ });