sovrium 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (527) hide show
  1. package/CHANGELOG.md +3497 -0
  2. package/LICENSE.md +147 -0
  3. package/LICENSE_EE.md +297 -0
  4. package/README.md +321 -0
  5. package/drizzle/0000_melted_kabuki.sql +163 -0
  6. package/drizzle/meta/0000_snapshot.json +1216 -0
  7. package/drizzle/meta/_journal.json +13 -0
  8. package/package.json +167 -0
  9. package/schemas/0.0.1/app.openapi.json +70 -0
  10. package/schemas/0.0.1/app.schema.json +7961 -0
  11. package/schemas/0.0.2/app.openapi.json +80 -0
  12. package/schemas/0.0.2/app.schema.json +8829 -0
  13. package/schemas/development/app.openapi.json +70 -0
  14. package/schemas/development/app.schema.json +7456 -0
  15. package/src/application/errors/app-validation-error.ts +14 -0
  16. package/src/application/errors/static-generation-error.ts +16 -0
  17. package/src/application/metadata/favicon-transformer.ts +127 -0
  18. package/src/application/models/server.ts +27 -0
  19. package/src/application/ports/models/user-metadata.ts +36 -0
  20. package/src/application/ports/models/user-session.ts +34 -0
  21. package/src/application/ports/repositories/activity-log-repository.ts +68 -0
  22. package/src/application/ports/repositories/activity-repository.ts +49 -0
  23. package/src/application/ports/repositories/analytics-repository.ts +164 -0
  24. package/src/application/ports/repositories/auth-repository.ts +33 -0
  25. package/src/application/ports/repositories/batch-repository.ts +86 -0
  26. package/src/application/ports/repositories/comment-repository.ts +150 -0
  27. package/src/application/ports/repositories/index.ts +41 -0
  28. package/src/application/ports/repositories/table-repository.ts +139 -0
  29. package/src/application/ports/services/css-compiler.ts +55 -0
  30. package/src/application/ports/services/index.ts +16 -0
  31. package/src/application/ports/services/page-renderer.ts +79 -0
  32. package/src/application/ports/services/server-factory.ts +80 -0
  33. package/src/application/ports/services/static-site-generator.ts +82 -0
  34. package/src/application/use-cases/activity/programs.ts +66 -0
  35. package/src/application/use-cases/analytics/collect-page-view.ts +114 -0
  36. package/src/application/use-cases/analytics/purge-old-data.ts +40 -0
  37. package/src/application/use-cases/analytics/query-campaigns.ts +43 -0
  38. package/src/application/use-cases/analytics/query-devices.ts +36 -0
  39. package/src/application/use-cases/analytics/query-overview.ts +50 -0
  40. package/src/application/use-cases/analytics/query-pages.ts +40 -0
  41. package/src/application/use-cases/analytics/query-referrers.ts +43 -0
  42. package/src/application/use-cases/analytics/ua-parser.ts +89 -0
  43. package/src/application/use-cases/analytics/visitor-hash.ts +77 -0
  44. package/src/application/use-cases/auth/bootstrap-admin.ts +270 -0
  45. package/src/application/use-cases/list-activity-logs.ts +123 -0
  46. package/src/application/use-cases/server/generate-static-helpers.ts +374 -0
  47. package/src/application/use-cases/server/generate-static.ts +287 -0
  48. package/src/application/use-cases/server/start-server.ts +118 -0
  49. package/src/application/use-cases/server/startup-error-handler.ts +69 -0
  50. package/src/application/use-cases/server/static-content-generators.ts +182 -0
  51. package/src/application/use-cases/server/static-language-generators.ts +181 -0
  52. package/src/application/use-cases/server/static-url-rewriter.ts +237 -0
  53. package/src/application/use-cases/server/translation-replacer.ts +164 -0
  54. package/src/application/use-cases/tables/activity-programs.ts +93 -0
  55. package/src/application/use-cases/tables/batch-operations.ts +156 -0
  56. package/src/application/use-cases/tables/comment-programs.ts +436 -0
  57. package/src/application/use-cases/tables/permissions/permissions.ts +25 -0
  58. package/src/application/use-cases/tables/programs.ts +435 -0
  59. package/src/application/use-cases/tables/table-operations.ts +412 -0
  60. package/src/application/use-cases/tables/user-role.ts +52 -0
  61. package/src/application/use-cases/tables/utils/display-formatter.ts +471 -0
  62. package/src/application/use-cases/tables/utils/field-read-filter.ts +189 -0
  63. package/src/application/use-cases/tables/utils/list-helpers.ts +122 -0
  64. package/src/application/use-cases/tables/utils/record-transformer.ts +319 -0
  65. package/src/cli.ts +370 -0
  66. package/src/domain/errors/create-tagged-error.ts +36 -0
  67. package/src/domain/errors/index.ts +78 -0
  68. package/src/domain/models/api/analytics.ts +179 -0
  69. package/src/domain/models/api/auth.ts +231 -0
  70. package/src/domain/models/api/common.ts +60 -0
  71. package/src/domain/models/api/error.ts +89 -0
  72. package/src/domain/models/api/health.ts +38 -0
  73. package/src/domain/models/api/index.ts +42 -0
  74. package/src/domain/models/api/request.ts +132 -0
  75. package/src/domain/models/api/tables.ts +444 -0
  76. package/src/domain/models/app/analytics/index.ts +129 -0
  77. package/src/domain/models/app/auth/config.ts +116 -0
  78. package/src/domain/models/app/auth/index.ts +230 -0
  79. package/src/domain/models/app/auth/methods/email-and-password.ts +67 -0
  80. package/src/domain/models/app/auth/methods/index.ts +11 -0
  81. package/src/domain/models/app/auth/methods/magic-link.ts +54 -0
  82. package/src/domain/models/app/auth/oauth/index.ts +8 -0
  83. package/src/domain/models/app/auth/oauth/providers.ts +105 -0
  84. package/src/domain/models/app/auth/plugins/admin.ts +130 -0
  85. package/src/domain/models/app/auth/plugins/index.ts +74 -0
  86. package/src/domain/models/app/auth/plugins/two-factor.ts +63 -0
  87. package/src/domain/models/app/auth/roles.ts +179 -0
  88. package/src/domain/models/app/auth/strategies.ts +191 -0
  89. package/src/domain/models/app/auth/validation.ts +127 -0
  90. package/src/domain/models/app/common/branded-ids.ts +200 -0
  91. package/src/domain/models/app/common/definitions.ts +187 -0
  92. package/src/domain/models/app/component/common/component-children.ts +119 -0
  93. package/src/domain/models/app/component/common/component-props.ts +89 -0
  94. package/src/domain/models/app/component/common/component-reference.ts +170 -0
  95. package/src/domain/models/app/component/component.ts +81 -0
  96. package/src/domain/models/app/components.ts +65 -0
  97. package/src/domain/models/app/description.ts +83 -0
  98. package/src/domain/models/app/index.ts +258 -0
  99. package/src/domain/models/app/language/language-config.ts +200 -0
  100. package/src/domain/models/app/languages.ts +205 -0
  101. package/src/domain/models/app/name.ts +66 -0
  102. package/src/domain/models/app/page/common/interactions/click-interaction.ts +116 -0
  103. package/src/domain/models/app/page/common/interactions/entrance-animation.ts +84 -0
  104. package/src/domain/models/app/page/common/interactions/hover-interaction.ts +144 -0
  105. package/src/domain/models/app/page/common/interactions/interactions.ts +64 -0
  106. package/src/domain/models/app/page/common/interactions/scroll-interaction.ts +93 -0
  107. package/src/domain/models/app/page/common/responsive.ts +114 -0
  108. package/src/domain/models/app/page/common/url.ts +35 -0
  109. package/src/domain/models/app/page/common/variable-reference.ts +53 -0
  110. package/src/domain/models/app/page/id.ts +44 -0
  111. package/src/domain/models/app/page/index.ts +270 -0
  112. package/src/domain/models/app/page/meta/analytics.ts +248 -0
  113. package/src/domain/models/app/page/meta/custom-elements.ts +180 -0
  114. package/src/domain/models/app/page/meta/dns-prefetch.ts +77 -0
  115. package/src/domain/models/app/page/meta/favicon-set.ts +203 -0
  116. package/src/domain/models/app/page/meta/favicon.ts +50 -0
  117. package/src/domain/models/app/page/meta/favicons-config.ts +73 -0
  118. package/src/domain/models/app/page/meta/index.ts +278 -0
  119. package/src/domain/models/app/page/meta/open-graph.ts +166 -0
  120. package/src/domain/models/app/page/meta/preload.ts +190 -0
  121. package/src/domain/models/app/page/meta/structured-data/article.ts +211 -0
  122. package/src/domain/models/app/page/meta/structured-data/breadcrumb.ts +115 -0
  123. package/src/domain/models/app/page/meta/structured-data/common-fields.ts +201 -0
  124. package/src/domain/models/app/page/meta/structured-data/education-event.ts +256 -0
  125. package/src/domain/models/app/page/meta/structured-data/faq-page.ts +127 -0
  126. package/src/domain/models/app/page/meta/structured-data/index.ts +95 -0
  127. package/src/domain/models/app/page/meta/structured-data/local-business.ts +247 -0
  128. package/src/domain/models/app/page/meta/structured-data/organization.ts +171 -0
  129. package/src/domain/models/app/page/meta/structured-data/person.ts +138 -0
  130. package/src/domain/models/app/page/meta/structured-data/postal-address.ts +106 -0
  131. package/src/domain/models/app/page/meta/structured-data/product.ts +214 -0
  132. package/src/domain/models/app/page/meta/twitter-card.ts +217 -0
  133. package/src/domain/models/app/page/name.ts +38 -0
  134. package/src/domain/models/app/page/path.ts +21 -0
  135. package/src/domain/models/app/page/scripts/external-scripts.ts +163 -0
  136. package/src/domain/models/app/page/scripts/features.ts +135 -0
  137. package/src/domain/models/app/page/scripts/inline-scripts.ts +114 -0
  138. package/src/domain/models/app/page/scripts/scripts.ts +102 -0
  139. package/src/domain/models/app/page/sections.ts +298 -0
  140. package/src/domain/models/app/pages.ts +61 -0
  141. package/src/domain/models/app/permissions/index.ts +61 -0
  142. package/src/domain/models/app/permissions/resource-action.ts +114 -0
  143. package/src/domain/models/app/permissions/roles.ts +120 -0
  144. package/src/domain/models/app/table/check-constraints.ts +105 -0
  145. package/src/domain/models/app/table/cycle-detection.ts +124 -0
  146. package/src/domain/models/app/table/database-identifier.ts +153 -0
  147. package/src/domain/models/app/table/field-name.ts +36 -0
  148. package/src/domain/models/app/table/field-types/advanced/array-field.ts +33 -0
  149. package/src/domain/models/app/table/field-types/advanced/autonumber-field.ts +54 -0
  150. package/src/domain/models/app/table/field-types/advanced/button-field.ts +56 -0
  151. package/src/domain/models/app/table/field-types/advanced/color-field.ts +57 -0
  152. package/src/domain/models/app/table/field-types/advanced/count-field.ts +54 -0
  153. package/src/domain/models/app/table/field-types/advanced/formula-field.ts +58 -0
  154. package/src/domain/models/app/table/field-types/advanced/geolocation-field.ts +49 -0
  155. package/src/domain/models/app/table/field-types/advanced/index.ts +16 -0
  156. package/src/domain/models/app/table/field-types/advanced/json-field.ts +25 -0
  157. package/src/domain/models/app/table/field-types/advanced/unknown-field.ts +85 -0
  158. package/src/domain/models/app/table/field-types/base-field.ts +42 -0
  159. package/src/domain/models/app/table/field-types/date-time/created-at-field.ts +49 -0
  160. package/src/domain/models/app/table/field-types/date-time/date-field.ts +95 -0
  161. package/src/domain/models/app/table/field-types/date-time/deleted-at-field.ts +56 -0
  162. package/src/domain/models/app/table/field-types/date-time/duration-field.ts +73 -0
  163. package/src/domain/models/app/table/field-types/date-time/index.ts +12 -0
  164. package/src/domain/models/app/table/field-types/date-time/updated-at-field.ts +50 -0
  165. package/src/domain/models/app/table/field-types/index.ts +19 -0
  166. package/src/domain/models/app/table/field-types/media/barcode-field.ts +58 -0
  167. package/src/domain/models/app/table/field-types/media/index.ts +10 -0
  168. package/src/domain/models/app/table/field-types/media/multiple-attachments-field.ts +80 -0
  169. package/src/domain/models/app/table/field-types/media/single-attachment-field.ts +81 -0
  170. package/src/domain/models/app/table/field-types/numeric/currency-field.ts +144 -0
  171. package/src/domain/models/app/table/field-types/numeric/decimal-field.ts +113 -0
  172. package/src/domain/models/app/table/field-types/numeric/index.ts +13 -0
  173. package/src/domain/models/app/table/field-types/numeric/integer-field.ts +98 -0
  174. package/src/domain/models/app/table/field-types/numeric/percentage-field.ts +115 -0
  175. package/src/domain/models/app/table/field-types/numeric/progress-field.ts +71 -0
  176. package/src/domain/models/app/table/field-types/numeric/rating-field.ts +74 -0
  177. package/src/domain/models/app/table/field-types/relational/index.ts +10 -0
  178. package/src/domain/models/app/table/field-types/relational/lookup-field.ts +46 -0
  179. package/src/domain/models/app/table/field-types/relational/relationship-field.ts +112 -0
  180. package/src/domain/models/app/table/field-types/relational/rollup-field.ts +58 -0
  181. package/src/domain/models/app/table/field-types/selection/checkbox-field.ts +51 -0
  182. package/src/domain/models/app/table/field-types/selection/index.ts +11 -0
  183. package/src/domain/models/app/table/field-types/selection/multi-select-field.ts +68 -0
  184. package/src/domain/models/app/table/field-types/selection/single-select-field.ts +54 -0
  185. package/src/domain/models/app/table/field-types/selection/status-field.ts +37 -0
  186. package/src/domain/models/app/table/field-types/text/email-field.ts +80 -0
  187. package/src/domain/models/app/table/field-types/text/index.ts +13 -0
  188. package/src/domain/models/app/table/field-types/text/long-text-field.ts +77 -0
  189. package/src/domain/models/app/table/field-types/text/phone-number-field.ts +82 -0
  190. package/src/domain/models/app/table/field-types/text/rich-text-field.ts +66 -0
  191. package/src/domain/models/app/table/field-types/text/single-line-text-field.ts +79 -0
  192. package/src/domain/models/app/table/field-types/text/url-field.ts +81 -0
  193. package/src/domain/models/app/table/field-types/user/created-by-field.ts +50 -0
  194. package/src/domain/models/app/table/field-types/user/deleted-by-field.ts +57 -0
  195. package/src/domain/models/app/table/field-types/user/index.ts +11 -0
  196. package/src/domain/models/app/table/field-types/user/updated-by-field.ts +51 -0
  197. package/src/domain/models/app/table/field-types/user/user-field.ts +52 -0
  198. package/src/domain/models/app/table/field-types/validation-utils.ts +166 -0
  199. package/src/domain/models/app/table/fields.ts +216 -0
  200. package/src/domain/models/app/table/foreign-keys.ts +111 -0
  201. package/src/domain/models/app/table/formula-keywords.ts +326 -0
  202. package/src/domain/models/app/table/id.ts +31 -0
  203. package/src/domain/models/app/table/index.ts +290 -0
  204. package/src/domain/models/app/table/indexes.ts +80 -0
  205. package/src/domain/models/app/table/name.ts +37 -0
  206. package/src/domain/models/app/table/permissions/field-permission.ts +83 -0
  207. package/src/domain/models/app/table/permissions/index.ts +167 -0
  208. package/src/domain/models/app/table/permissions/permission-evaluator.ts +372 -0
  209. package/src/domain/models/app/table/permissions/permission.ts +49 -0
  210. package/src/domain/models/app/table/primary-key.ts +62 -0
  211. package/src/domain/models/app/table/table-formula-validation.ts +168 -0
  212. package/src/domain/models/app/table/table-indexes-validation.ts +38 -0
  213. package/src/domain/models/app/table/table-permissions-validation.ts +77 -0
  214. package/src/domain/models/app/table/table-primary-key-validation.ts +49 -0
  215. package/src/domain/models/app/table/table-views-validation.ts +408 -0
  216. package/src/domain/models/app/table/unique-constraints.ts +79 -0
  217. package/src/domain/models/app/table/views/fields.ts +28 -0
  218. package/src/domain/models/app/table/views/filters.ts +162 -0
  219. package/src/domain/models/app/table/views/group-by.ts +32 -0
  220. package/src/domain/models/app/table/views/id.ts +50 -0
  221. package/src/domain/models/app/table/views/index.ts +177 -0
  222. package/src/domain/models/app/table/views/name.ts +32 -0
  223. package/src/domain/models/app/table/views/permissions.ts +98 -0
  224. package/src/domain/models/app/table/views/sorts.ts +31 -0
  225. package/src/domain/models/app/tables.ts +695 -0
  226. package/src/domain/models/app/theme/animations.ts +208 -0
  227. package/src/domain/models/app/theme/border-radius.ts +58 -0
  228. package/src/domain/models/app/theme/breakpoints.ts +62 -0
  229. package/src/domain/models/app/theme/colors.ts +110 -0
  230. package/src/domain/models/app/theme/fonts.ts +164 -0
  231. package/src/domain/models/app/theme/shadows.ts +61 -0
  232. package/src/domain/models/app/theme/spacing.ts +115 -0
  233. package/src/domain/models/app/theme.ts +66 -0
  234. package/src/domain/models/app/version.ts +87 -0
  235. package/src/domain/models/record-comment.ts +91 -0
  236. package/src/domain/utils/content-parsing.ts +49 -0
  237. package/src/domain/utils/format-detection.ts +69 -0
  238. package/src/domain/utils/index.ts +9 -0
  239. package/src/domain/utils/route-matcher.ts +184 -0
  240. package/src/domain/utils/translation-resolver.ts +170 -0
  241. package/src/index.ts +208 -0
  242. package/src/infrastructure/analytics/tracking-script.ts +48 -0
  243. package/src/infrastructure/auth/better-auth/auth.ts +216 -0
  244. package/src/infrastructure/auth/better-auth/email-handlers.ts +162 -0
  245. package/src/infrastructure/auth/better-auth/index.ts +16 -0
  246. package/src/infrastructure/auth/better-auth/layer.ts +97 -0
  247. package/src/infrastructure/auth/better-auth/plugins/admin.ts +56 -0
  248. package/src/infrastructure/auth/better-auth/plugins/magic-link.ts +31 -0
  249. package/src/infrastructure/auth/better-auth/plugins/two-factor.ts +19 -0
  250. package/src/infrastructure/auth/better-auth/schema.ts +152 -0
  251. package/src/infrastructure/auth/index.ts +27 -0
  252. package/src/infrastructure/css/cache/css-cache-service.ts +130 -0
  253. package/src/infrastructure/css/compiler.ts +210 -0
  254. package/src/infrastructure/css/css-compiler-live.ts +20 -0
  255. package/src/infrastructure/css/index.ts +25 -0
  256. package/src/infrastructure/css/styles/animation-styles-generator.ts +177 -0
  257. package/src/infrastructure/css/styles/click-animations.ts +147 -0
  258. package/src/infrastructure/css/styles/component-layer-generators.ts +147 -0
  259. package/src/infrastructure/css/theme/theme-generators.ts +130 -0
  260. package/src/infrastructure/css/theme/theme-layer-generators.ts +219 -0
  261. package/src/infrastructure/css/theme/theme-token-resolver.ts +76 -0
  262. package/src/infrastructure/database/activity-queries.ts +111 -0
  263. package/src/infrastructure/database/auth/auth-validation.ts +101 -0
  264. package/src/infrastructure/database/drizzle/db-bun.ts +17 -0
  265. package/src/infrastructure/database/drizzle/db.ts +17 -0
  266. package/src/infrastructure/database/drizzle/index.ts +16 -0
  267. package/src/infrastructure/database/drizzle/layer.ts +34 -0
  268. package/src/infrastructure/database/drizzle/migrate.ts +77 -0
  269. package/src/infrastructure/database/drizzle/schema/activity-log.ts +111 -0
  270. package/src/infrastructure/database/drizzle/schema/analytics-page-views.ts +116 -0
  271. package/src/infrastructure/database/drizzle/schema/migration-audit.ts +68 -0
  272. package/src/infrastructure/database/drizzle/schema/record-comments.ts +79 -0
  273. package/src/infrastructure/database/drizzle/schema.ts +12 -0
  274. package/src/infrastructure/database/field-utils.ts +87 -0
  275. package/src/infrastructure/database/filter-operators.ts +136 -0
  276. package/src/infrastructure/database/formula/formula-trigger-generators.ts +114 -0
  277. package/src/infrastructure/database/formula/formula-utils.ts +440 -0
  278. package/src/infrastructure/database/generators/index-generators.ts +152 -0
  279. package/src/infrastructure/database/generators/trigger-generators.ts +154 -0
  280. package/src/infrastructure/database/index.ts +35 -0
  281. package/src/infrastructure/database/lookup/lookup-expression-generators.ts +356 -0
  282. package/src/infrastructure/database/lookup/lookup-expressions.ts +116 -0
  283. package/src/infrastructure/database/lookup/lookup-view-generators.ts +403 -0
  284. package/src/infrastructure/database/lookup/lookup-view-helpers.ts +65 -0
  285. package/src/infrastructure/database/lookup/lookup-view-triggers.ts +121 -0
  286. package/src/infrastructure/database/migration-audit-trail.ts +375 -0
  287. package/src/infrastructure/database/repositories/activity-log-repository-live.ts +99 -0
  288. package/src/infrastructure/database/repositories/activity-repository-live.ts +21 -0
  289. package/src/infrastructure/database/repositories/analytics-repository-live.ts +316 -0
  290. package/src/infrastructure/database/repositories/auth-repository-live.ts +42 -0
  291. package/src/infrastructure/database/repositories/batch-repository-live.ts +29 -0
  292. package/src/infrastructure/database/repositories/comment-repository-live.ts +39 -0
  293. package/src/infrastructure/database/repositories/table-repository-live.ts +38 -0
  294. package/src/infrastructure/database/schema/schema-dependency-sorting.ts +142 -0
  295. package/src/infrastructure/database/schema/schema-initializer.ts +598 -0
  296. package/src/infrastructure/database/schema-migration/column-detection.ts +286 -0
  297. package/src/infrastructure/database/schema-migration/constants.ts +31 -0
  298. package/src/infrastructure/database/schema-migration/constraint-sync.ts +288 -0
  299. package/src/infrastructure/database/schema-migration/index-sync.ts +108 -0
  300. package/src/infrastructure/database/schema-migration/index.ts +66 -0
  301. package/src/infrastructure/database/schema-migration/migration-statements.ts +106 -0
  302. package/src/infrastructure/database/schema-migration/rename-detection.ts +87 -0
  303. package/src/infrastructure/database/schema-migration/table-operations.ts +65 -0
  304. package/src/infrastructure/database/schema-migration/type-utils.ts +98 -0
  305. package/src/infrastructure/database/schema-migration/types.ts +14 -0
  306. package/src/infrastructure/database/schema-migration-helpers.ts +53 -0
  307. package/src/infrastructure/database/session-context.ts +20 -0
  308. package/src/infrastructure/database/sql/sql-check-constraints.ts +252 -0
  309. package/src/infrastructure/database/sql/sql-column-generators.ts +174 -0
  310. package/src/infrastructure/database/sql/sql-execution.ts +245 -0
  311. package/src/infrastructure/database/sql/sql-field-predicates.ts +81 -0
  312. package/src/infrastructure/database/sql/sql-generators.ts +91 -0
  313. package/src/infrastructure/database/sql/sql-junction-tables.ts +79 -0
  314. package/src/infrastructure/database/sql/sql-key-constraints.ts +210 -0
  315. package/src/infrastructure/database/sql/sql-type-mappings.ts +106 -0
  316. package/src/infrastructure/database/sql/sql-utils.ts +53 -0
  317. package/src/infrastructure/database/table-live-layers.ts +30 -0
  318. package/src/infrastructure/database/table-operations/column-generators.ts +82 -0
  319. package/src/infrastructure/database/table-operations/create-table-sql.ts +81 -0
  320. package/src/infrastructure/database/table-operations/index.ts +55 -0
  321. package/src/infrastructure/database/table-operations/migration-utils.ts +157 -0
  322. package/src/infrastructure/database/table-operations/table-effects.ts +234 -0
  323. package/src/infrastructure/database/table-operations/table-features.ts +96 -0
  324. package/src/infrastructure/database/table-operations/type-compatibility.ts +58 -0
  325. package/src/infrastructure/database/table-operations.ts +47 -0
  326. package/src/infrastructure/database/table-queries/batch/batch-create.ts +80 -0
  327. package/src/infrastructure/database/table-queries/batch/batch-delete.ts +212 -0
  328. package/src/infrastructure/database/table-queries/batch/batch-helpers.ts +124 -0
  329. package/src/infrastructure/database/table-queries/batch/batch-restore.ts +161 -0
  330. package/src/infrastructure/database/table-queries/batch/batch-update.ts +146 -0
  331. package/src/infrastructure/database/table-queries/batch/batch-upsert.ts +357 -0
  332. package/src/infrastructure/database/table-queries/batch/batch.ts +14 -0
  333. package/src/infrastructure/database/table-queries/crud/crud-read.ts +351 -0
  334. package/src/infrastructure/database/table-queries/crud/crud-write.ts +399 -0
  335. package/src/infrastructure/database/table-queries/crud/crud.ts +16 -0
  336. package/src/infrastructure/database/table-queries/index.ts +11 -0
  337. package/src/infrastructure/database/table-queries/mutation-helpers/authorship-helpers.ts +152 -0
  338. package/src/infrastructure/database/table-queries/mutation-helpers/create-record-helpers.ts +90 -0
  339. package/src/infrastructure/database/table-queries/mutation-helpers/delete-helpers.ts +163 -0
  340. package/src/infrastructure/database/table-queries/mutation-helpers/record-fetch-helpers.ts +79 -0
  341. package/src/infrastructure/database/table-queries/mutation-helpers/update-helpers.ts +74 -0
  342. package/src/infrastructure/database/table-queries/query-helpers/activity-log-helpers.ts +53 -0
  343. package/src/infrastructure/database/table-queries/query-helpers/activity-queries.ts +106 -0
  344. package/src/infrastructure/database/table-queries/query-helpers/aggregation-helpers.ts +314 -0
  345. package/src/infrastructure/database/table-queries/query-helpers/comment-queries.ts +414 -0
  346. package/src/infrastructure/database/table-queries/query-helpers/record-validation-queries.ts +126 -0
  347. package/src/infrastructure/database/table-queries/query-helpers/trash-helpers.ts +58 -0
  348. package/src/infrastructure/database/table-queries/shared/error-handling.ts +47 -0
  349. package/src/infrastructure/database/table-queries/shared/typed-execute.ts +27 -0
  350. package/src/infrastructure/database/table-queries/shared/user-join-helpers.ts +38 -0
  351. package/src/infrastructure/database/table-queries/shared/validation.ts +39 -0
  352. package/src/infrastructure/database/views/view-generators.ts +258 -0
  353. package/src/infrastructure/devtools/devtools-layer.ts +43 -0
  354. package/src/infrastructure/devtools/index.ts +8 -0
  355. package/src/infrastructure/email/email-config.ts +103 -0
  356. package/src/infrastructure/email/email-service.ts +152 -0
  357. package/src/infrastructure/email/index.ts +107 -0
  358. package/src/infrastructure/email/nodemailer.ts +125 -0
  359. package/src/infrastructure/email/templates.ts +244 -0
  360. package/src/infrastructure/errors/auth-config-required-error.ts +21 -0
  361. package/src/infrastructure/errors/auth-error.ts +16 -0
  362. package/src/infrastructure/errors/css-compilation-error.ts +14 -0
  363. package/src/infrastructure/errors/index.ts +26 -0
  364. package/src/infrastructure/errors/schema-initialization-error.ts +19 -0
  365. package/src/infrastructure/errors/server-creation-error.ts +14 -0
  366. package/src/infrastructure/filesystem/copy-directory.ts +136 -0
  367. package/src/infrastructure/layers/app-layer.ts +61 -0
  368. package/src/infrastructure/layers/page-renderer-layer.ts +41 -0
  369. package/src/infrastructure/logging/index.ts +8 -0
  370. package/src/infrastructure/logging/logger.ts +204 -0
  371. package/src/infrastructure/schema/file-loader.ts +53 -0
  372. package/src/infrastructure/schema/index.ts +15 -0
  373. package/src/infrastructure/schema/remote-loader.ts +48 -0
  374. package/src/infrastructure/server/index.ts +26 -0
  375. package/src/infrastructure/server/language-detection.ts +87 -0
  376. package/src/infrastructure/server/lifecycle.ts +67 -0
  377. package/src/infrastructure/server/route-setup/api-routes.ts +310 -0
  378. package/src/infrastructure/server/route-setup/auth-route-utils.ts +399 -0
  379. package/src/infrastructure/server/route-setup/auth-routes.ts +245 -0
  380. package/src/infrastructure/server/route-setup/openapi-routes.ts +45 -0
  381. package/src/infrastructure/server/route-setup/openapi-schema.ts +120 -0
  382. package/src/infrastructure/server/route-setup/page-routes.ts +219 -0
  383. package/src/infrastructure/server/route-setup/static-assets.ts +191 -0
  384. package/src/infrastructure/server/server-factory-live.ts +45 -0
  385. package/src/infrastructure/server/server.ts +275 -0
  386. package/src/infrastructure/server/ssg-adapter.ts +196 -0
  387. package/src/infrastructure/server/static-site-generator-live.ts +20 -0
  388. package/src/infrastructure/utils/accept-language-parser.ts +106 -0
  389. package/src/infrastructure/utils/glob-matcher.ts +50 -0
  390. package/src/presentation/api/client.ts +114 -0
  391. package/src/presentation/api/middleware/auth.ts +233 -0
  392. package/src/presentation/api/middleware/table.ts +155 -0
  393. package/src/presentation/api/middleware/validation.ts +88 -0
  394. package/src/presentation/api/routes/activity/get-activity-by-id-handler.ts +77 -0
  395. package/src/presentation/api/routes/activity/index.ts +28 -0
  396. package/src/presentation/api/routes/activity.ts +339 -0
  397. package/src/presentation/api/routes/analytics.ts +328 -0
  398. package/src/presentation/api/routes/auth.ts +169 -0
  399. package/src/presentation/api/routes/index.ts +11 -0
  400. package/src/presentation/api/routes/tables/activity-handlers.ts +57 -0
  401. package/src/presentation/api/routes/tables/batch-permission-helpers.ts +163 -0
  402. package/src/presentation/api/routes/tables/batch-routes.ts +355 -0
  403. package/src/presentation/api/routes/tables/comment-handlers.ts +377 -0
  404. package/src/presentation/api/routes/tables/create-record-helpers.ts +179 -0
  405. package/src/presentation/api/routes/tables/effect-runner.ts +58 -0
  406. package/src/presentation/api/routes/tables/error-handlers.ts +53 -0
  407. package/src/presentation/api/routes/tables/field-permission-validation.ts +167 -0
  408. package/src/presentation/api/routes/tables/filter-parser.ts +75 -0
  409. package/src/presentation/api/routes/tables/formula-parser.ts +118 -0
  410. package/src/presentation/api/routes/tables/index.ts +113 -0
  411. package/src/presentation/api/routes/tables/list-records-filter.ts +54 -0
  412. package/src/presentation/api/routes/tables/param-parsers.ts +59 -0
  413. package/src/presentation/api/routes/tables/record-handlers.ts +484 -0
  414. package/src/presentation/api/routes/tables/record-routes.ts +53 -0
  415. package/src/presentation/api/routes/tables/record-update-handler.ts +200 -0
  416. package/src/presentation/api/routes/tables/sort-validation.ts +85 -0
  417. package/src/presentation/api/routes/tables/table-routes.ts +76 -0
  418. package/src/presentation/api/routes/tables/timezone-validation.ts +41 -0
  419. package/src/presentation/api/routes/tables/upsert-helpers.ts +471 -0
  420. package/src/presentation/api/routes/tables/utils.ts +159 -0
  421. package/src/presentation/api/routes/tables/view-routes.ts +51 -0
  422. package/src/presentation/api/routes/tables.ts +9 -0
  423. package/src/presentation/api/utils/context-helpers.ts +43 -0
  424. package/src/presentation/api/utils/error-sanitizer.ts +235 -0
  425. package/src/presentation/api/utils/field-permission-validator.ts +53 -0
  426. package/src/presentation/api/utils/filter-field-validator.ts +90 -0
  427. package/src/presentation/api/utils/index.ts +13 -0
  428. package/src/presentation/api/utils/run-effect.ts +94 -0
  429. package/src/presentation/api/utils/validate-request.ts +89 -0
  430. package/src/presentation/api/validation/index.ts +29 -0
  431. package/src/presentation/api/validation/rules/field-rules.ts +158 -0
  432. package/src/presentation/api/validation/rules/record-rules.ts +73 -0
  433. package/src/presentation/cli/index.ts +19 -0
  434. package/src/presentation/cli/schema-loader.ts +172 -0
  435. package/src/presentation/hooks/use-breakpoint.ts +155 -0
  436. package/src/presentation/rendering/render-error-pages.tsx +60 -0
  437. package/src/presentation/rendering/render-homepage.tsx +137 -0
  438. package/src/presentation/scripts/script-renderers.ts +112 -0
  439. package/src/presentation/styling/animation-composer.ts +117 -0
  440. package/src/presentation/styling/index.ts +13 -0
  441. package/src/presentation/styling/parse-style.ts +243 -0
  442. package/src/presentation/styling/style-utils.ts +50 -0
  443. package/src/presentation/styling/theme-colors.ts +53 -0
  444. package/src/presentation/translations/component-utils.ts +54 -0
  445. package/src/presentation/translations/index.ts +16 -0
  446. package/src/presentation/translations/translation-resolver.ts +22 -0
  447. package/src/presentation/ui/languages/language-switcher.tsx +119 -0
  448. package/src/presentation/ui/metadata/analytics-builders.tsx +174 -0
  449. package/src/presentation/ui/metadata/analytics-head.tsx +39 -0
  450. package/src/presentation/ui/metadata/custom-elements-builders.tsx +157 -0
  451. package/src/presentation/ui/metadata/extract-component-meta.ts +108 -0
  452. package/src/presentation/ui/metadata/head-elements.tsx +164 -0
  453. package/src/presentation/ui/metadata/index.tsx +35 -0
  454. package/src/presentation/ui/metadata/meta-utils.tsx +42 -0
  455. package/src/presentation/ui/metadata/open-graph-meta.tsx +57 -0
  456. package/src/presentation/ui/metadata/structured-data-from-component.tsx +134 -0
  457. package/src/presentation/ui/metadata/structured-data.tsx +88 -0
  458. package/src/presentation/ui/metadata/twitter-card-meta.tsx +80 -0
  459. package/src/presentation/ui/pages/DefaultHomePage.tsx +43 -0
  460. package/src/presentation/ui/pages/DefaultPageConfigs.ts +220 -0
  461. package/src/presentation/ui/pages/DynamicPage.tsx +307 -0
  462. package/src/presentation/ui/pages/ErrorPage.tsx +25 -0
  463. package/src/presentation/ui/pages/NotFoundPage.tsx +25 -0
  464. package/src/presentation/ui/pages/PageBodyScripts.tsx +242 -0
  465. package/src/presentation/ui/pages/PageBodyStyles.ts +52 -0
  466. package/src/presentation/ui/pages/PageHead.tsx +380 -0
  467. package/src/presentation/ui/pages/PageLangResolver.ts +58 -0
  468. package/src/presentation/ui/pages/PageMain.tsx +58 -0
  469. package/src/presentation/ui/pages/PageMetadata.ts +168 -0
  470. package/src/presentation/ui/pages/PageMetadataI18n.ts +169 -0
  471. package/src/presentation/ui/pages/PageScripts.ts +78 -0
  472. package/src/presentation/ui/pages/SectionRenderer.tsx +67 -0
  473. package/src/presentation/ui/pages/SectionSpacing.tsx +131 -0
  474. package/src/presentation/ui/sections/component-renderer.tsx +426 -0
  475. package/src/presentation/ui/sections/component-renderer.types.ts +33 -0
  476. package/src/presentation/ui/sections/components/component-reference-handler.tsx +74 -0
  477. package/src/presentation/ui/sections/components/component-resolution.ts +65 -0
  478. package/src/presentation/ui/sections/components/index.ts +9 -0
  479. package/src/presentation/ui/sections/hero.tsx +394 -0
  480. package/src/presentation/ui/sections/props/component-builder.ts +183 -0
  481. package/src/presentation/ui/sections/props/element-props.ts +179 -0
  482. package/src/presentation/ui/sections/props/index.ts +9 -0
  483. package/src/presentation/ui/sections/props/prop-conversion.ts +171 -0
  484. package/src/presentation/ui/sections/props/props-builder-config.ts +42 -0
  485. package/src/presentation/ui/sections/props/props-builder.ts +296 -0
  486. package/src/presentation/ui/sections/renderers/element-renderers/html-element-renderer.tsx +124 -0
  487. package/src/presentation/ui/sections/renderers/element-renderers/index.ts +59 -0
  488. package/src/presentation/ui/sections/renderers/element-renderers/interactive-renderers.tsx +231 -0
  489. package/src/presentation/ui/sections/renderers/element-renderers/media-renderers.tsx +102 -0
  490. package/src/presentation/ui/sections/renderers/element-renderers/text-content-renderers.tsx +42 -0
  491. package/src/presentation/ui/sections/renderers/element-renderers.ts +53 -0
  492. package/src/presentation/ui/sections/renderers/html-element-helpers.ts +100 -0
  493. package/src/presentation/ui/sections/renderers/specialized-renderers.tsx +212 -0
  494. package/src/presentation/ui/sections/rendering/component-dispatch-config.ts +31 -0
  495. package/src/presentation/ui/sections/rendering/component-registry/index.ts +39 -0
  496. package/src/presentation/ui/sections/rendering/component-registry/interactive-components.ts +54 -0
  497. package/src/presentation/ui/sections/rendering/component-registry/media-components.ts +36 -0
  498. package/src/presentation/ui/sections/rendering/component-registry/special-components.tsx +153 -0
  499. package/src/presentation/ui/sections/rendering/component-registry/structural-components.ts +215 -0
  500. package/src/presentation/ui/sections/rendering/component-registry/text-components.ts +57 -0
  501. package/src/presentation/ui/sections/rendering/component-registry-helpers.tsx +29 -0
  502. package/src/presentation/ui/sections/rendering/component-registry.tsx +21 -0
  503. package/src/presentation/ui/sections/rendering/component-type-dispatcher.tsx +33 -0
  504. package/src/presentation/ui/sections/rendering/index.ts +9 -0
  505. package/src/presentation/ui/sections/responsive/responsive-children-builder.tsx +96 -0
  506. package/src/presentation/ui/sections/responsive/responsive-content-builder.tsx +95 -0
  507. package/src/presentation/ui/sections/responsive/responsive-props-merger.ts +195 -0
  508. package/src/presentation/ui/sections/responsive/responsive-resolver.ts +213 -0
  509. package/src/presentation/ui/sections/styling/animation-composer-wrapper.ts +65 -0
  510. package/src/presentation/ui/sections/styling/class-builders.ts +45 -0
  511. package/src/presentation/ui/sections/styling/color-resolver.ts +43 -0
  512. package/src/presentation/ui/sections/styling/hover-interaction-handler.ts +107 -0
  513. package/src/presentation/ui/sections/styling/index.ts +9 -0
  514. package/src/presentation/ui/sections/styling/interaction-props-builder.ts +55 -0
  515. package/src/presentation/ui/sections/styling/shadow-resolver.ts +83 -0
  516. package/src/presentation/ui/sections/styling/spacing-resolver.ts +104 -0
  517. package/src/presentation/ui/sections/styling/style-processor.ts +170 -0
  518. package/src/presentation/ui/sections/styling/theme-tokens.ts +184 -0
  519. package/src/presentation/ui/sections/translations/i18n-content-resolver.ts +198 -0
  520. package/src/presentation/ui/sections/translations/index.ts +9 -0
  521. package/src/presentation/ui/sections/translations/translation-handler.ts +143 -0
  522. package/src/presentation/ui/sections/translations/variable-substitution.ts +225 -0
  523. package/src/presentation/ui/sections/utils/time-parser.ts +82 -0
  524. package/src/presentation/utils/link-attributes.ts +50 -0
  525. package/src/presentation/utils/string-utils.ts +58 -0
  526. package/src/presentation/utils/styles.ts +50 -0
  527. package/tsconfig.json +46 -0
@@ -0,0 +1,286 @@
1
+ /**
2
+ * Copyright (c) 2025 ESSENTIAL SERVICES
3
+ *
4
+ * This source code is licensed under the Business Source License 1.1
5
+ * found in the LICENSE.md file in the root directory of this source tree.
6
+ */
7
+
8
+ import { shouldCreateDatabaseColumn } from '../field-utils'
9
+ import { generateColumnDefinition, isFieldNotNull } from '../sql/sql-generators'
10
+ import {
11
+ normalizeDataType,
12
+ doesColumnTypeMatch,
13
+ generateAlterColumnTypeStatement,
14
+ } from './type-utils'
15
+ import type { Table } from '@/domain/models/app/table'
16
+ import type { Fields } from '@/domain/models/app/table/fields'
17
+
18
+ /** Column info from database */
19
+ export type ExistingColumnInfo = {
20
+ dataType: string
21
+ isNullable: string
22
+ columnDefault: string | null
23
+ }
24
+
25
+ /**
26
+ * Check if id column needs to be recreated due to type mismatch
27
+ */
28
+ export const needsIdColumnRecreation = (
29
+ existingColumns: ReadonlyMap<string, ExistingColumnInfo>,
30
+ shouldProtectIdColumn: boolean
31
+ ): boolean => {
32
+ if (!shouldProtectIdColumn) return false
33
+ if (!existingColumns.has('id')) return false
34
+
35
+ const idType = normalizeDataType(existingColumns.get('id')!.dataType)
36
+ return idType !== 'integer' && idType !== 'serial'
37
+ }
38
+
39
+ /**
40
+ * Find columns that should be added to the table
41
+ * Excludes columns that exist but have type mismatches (those will be altered, not dropped/added)
42
+ */
43
+ export const findColumnsToAdd = (
44
+ table: Table,
45
+ existingColumns: ReadonlyMap<string, ExistingColumnInfo>,
46
+ renamedNewNames: ReadonlySet<string>
47
+ ): readonly Fields[number][] =>
48
+ table.fields.filter((field) => {
49
+ if (!shouldCreateDatabaseColumn(field)) return false // Skip UI-only fields
50
+ if (renamedNewNames.has(field.name)) return false // Skip renamed fields
51
+ if (!existingColumns.has(field.name)) return true // New column (needs ADD COLUMN)
52
+ // Existing columns with type mismatches will be handled by ALTER COLUMN TYPE
53
+ return false
54
+ })
55
+
56
+ /**
57
+ * Find columns that should be dropped from the table
58
+ * Excludes columns with type mismatches (those will be altered, not dropped)
59
+ */
60
+ export const findColumnsToDrop = (
61
+ existingColumns: ReadonlyMap<string, ExistingColumnInfo>,
62
+ schemaFieldsByName: ReadonlyMap<string, Fields[number]>,
63
+ shouldProtectIdColumn: boolean,
64
+ renamedOldNames: ReadonlySet<string>
65
+ ): readonly string[] =>
66
+ Array.from(existingColumns.keys()).filter((columnName) => {
67
+ // Never drop protected id column (it's already the correct type at this point)
68
+ if (shouldProtectIdColumn && columnName === 'id') return false
69
+
70
+ // Never drop intrinsic special fields (APP-TABLES-SPECIAL-FIELDS-007)
71
+ // These are automatic timestamp columns managed by triggers
72
+ if (columnName === 'created_at') return false
73
+ if (columnName === 'updated_at') return false
74
+ if (columnName === 'deleted_at') return false
75
+
76
+ // Don't drop if it's being renamed
77
+ if (renamedOldNames.has(columnName)) return false
78
+
79
+ // Drop if not in schema (but only if it's not a UI-only field)
80
+ if (!schemaFieldsByName.has(columnName)) return true
81
+
82
+ const field = schemaFieldsByName.get(columnName)!
83
+
84
+ // If this is a UI-only field that shouldn't have a column, drop it
85
+ if (!shouldCreateDatabaseColumn(field)) return true
86
+
87
+ // Existing columns with type mismatches will be handled by ALTER COLUMN TYPE (not dropped)
88
+ return false
89
+ })
90
+
91
+ /**
92
+ * Filter fields that should be checked for schema modifications
93
+ * Excludes UI-only fields, renamed fields, and fields not in the database
94
+ */
95
+ export const filterModifiableFields = (
96
+ fields: readonly Fields[number][],
97
+ existingColumns: ReadonlyMap<string, ExistingColumnInfo>,
98
+ renamedNewNames: ReadonlySet<string>
99
+ ): readonly Fields[number][] =>
100
+ fields.filter((field) => {
101
+ // Skip UI-only fields, renamed fields, and fields not in database
102
+ if (!shouldCreateDatabaseColumn(field)) return false
103
+ if (renamedNewNames.has(field.name)) return false
104
+ if (!existingColumns.has(field.name)) return false
105
+ return true
106
+ })
107
+
108
+ /**
109
+ * Find columns that need type changes
110
+ * Returns ALTER COLUMN TYPE statements for type mismatches
111
+ */
112
+ export const findTypeChanges = (
113
+ table: Table,
114
+ existingColumns: ReadonlyMap<string, ExistingColumnInfo>,
115
+ renamedNewNames: ReadonlySet<string>
116
+ ): readonly string[] =>
117
+ filterModifiableFields(table.fields, existingColumns, renamedNewNames).flatMap((field) => {
118
+ const existing = existingColumns.get(field.name)!
119
+ if (doesColumnTypeMatch(field, existing.dataType)) return []
120
+
121
+ // Type mismatch detected - generate ALTER COLUMN TYPE statement
122
+ return [generateAlterColumnTypeStatement(table.name, field, existing.dataType)]
123
+ })
124
+
125
+ /**
126
+ * Generate validation query to check if existing data contains NULL values
127
+ * Returns validation query that will throw error if NULL values exist
128
+ */
129
+ export const generateNotNullValidationQuery = (tableName: string, fieldName: string): string => `
130
+ DO $$
131
+ DECLARE
132
+ null_count INTEGER;
133
+ BEGIN
134
+ SELECT COUNT(*) INTO null_count
135
+ FROM ${tableName}
136
+ WHERE ${fieldName} IS NULL;
137
+
138
+ IF null_count > 0 THEN
139
+ RAISE EXCEPTION 'Migration failed: cannot add NOT NULL constraint to column "${fieldName}" in table "${tableName}" because % existing row(s) contain null values. Either provide a default value or update existing rows first.', null_count;
140
+ END IF;
141
+ END$$;
142
+ `
143
+
144
+ /**
145
+ * Generate backfill query to update NULL values with the default
146
+ * Returns UPDATE statement that sets NULL values to the default value
147
+ */
148
+ export const generateBackfillQuery = (table: Table, field: Fields[number]): string => {
149
+ const columnDef = generateColumnDefinition(field, false, table.fields)
150
+ // Extract DEFAULT clause from column definition
151
+ // Handle quoted strings (DEFAULT 'value'), unquoted values (DEFAULT 42), and functions (DEFAULT NOW())
152
+ const defaultMatch = columnDef.match(/DEFAULT\s+('(?:[^']|'')*'|[^\s]+)/)
153
+ if (!defaultMatch) {
154
+ // This shouldn't happen if hasDefault is true, but handle gracefully
155
+ return ''
156
+ }
157
+
158
+ const defaultClause = defaultMatch[1]
159
+ return `UPDATE ${table.name} SET ${field.name} = ${defaultClause} WHERE ${field.name} IS NULL`
160
+ }
161
+
162
+ /**
163
+ * Find columns that need nullability changes
164
+ * Returns ALTER COLUMN statements for SET NOT NULL / DROP NOT NULL
165
+ * CRITICAL: Validates existing data before applying NOT NULL constraint
166
+ * Migration will FAIL if any existing data contains NULL values without a default
167
+ *
168
+ * When a default value is provided:
169
+ * 1. Set the default value on the column (handled by findDefaultValueChanges)
170
+ * 2. Backfill existing NULL values with the default
171
+ * 3. Then set NOT NULL
172
+ */
173
+ export const findNullabilityChanges = (
174
+ table: Table,
175
+ existingColumns: ReadonlyMap<string, ExistingColumnInfo>,
176
+ renamedNewNames: ReadonlySet<string>,
177
+ primaryKeyFields: readonly string[]
178
+ ): readonly string[] =>
179
+ filterModifiableFields(table.fields, existingColumns, renamedNewNames).flatMap((field) => {
180
+ const existing = existingColumns.get(field.name)!
181
+ const isPrimaryKey = primaryKeyFields.includes(field.name)
182
+ const shouldBeNotNull = isFieldNotNull(field, isPrimaryKey)
183
+ const currentlyNotNull = existing.isNullable === 'NO'
184
+
185
+ // If nullability differs, generate ALTER COLUMN statement
186
+ if (shouldBeNotNull && !currentlyNotNull) {
187
+ // Check if field has a default value
188
+ const hasDefault = 'default' in field && field.default !== undefined
189
+
190
+ if (hasDefault) {
191
+ // If has default: backfill NULL values, then set NOT NULL
192
+ const backfillQuery = generateBackfillQuery(table, field)
193
+ return [
194
+ ...(backfillQuery ? [backfillQuery] : []),
195
+ `ALTER TABLE ${table.name} ALTER COLUMN ${field.name} SET NOT NULL`,
196
+ ]
197
+ }
198
+
199
+ // If no default value, validate that existing data doesn't contain NULL
200
+ // This prevents migration from failing with PostgreSQL's "column contains null values" error
201
+ return [
202
+ generateNotNullValidationQuery(table.name, field.name),
203
+ `ALTER TABLE ${table.name} ALTER COLUMN ${field.name} SET NOT NULL`,
204
+ ]
205
+ }
206
+ if (!shouldBeNotNull && currentlyNotNull && !isPrimaryKey) {
207
+ // Only DROP NOT NULL if it's not a primary key or auto-managed field
208
+ return [`ALTER TABLE ${table.name} ALTER COLUMN ${field.name} DROP NOT NULL`]
209
+ }
210
+ return []
211
+ })
212
+
213
+ /**
214
+ * Find columns that need default value changes
215
+ * Returns ALTER COLUMN statements for SET DEFAULT or DROP DEFAULT
216
+ */
217
+ export const findDefaultValueChanges = (
218
+ table: Table,
219
+ existingColumns: ReadonlyMap<string, ExistingColumnInfo>,
220
+ renamedNewNames: ReadonlySet<string>,
221
+ previousSchema?: { readonly tables: readonly object[] }
222
+ ): readonly string[] => {
223
+ // Find previous table definition to compare default values
224
+ const previousTable = previousSchema?.tables.find(
225
+ (t: object) => 'name' in t && t.name === table.name
226
+ ) as
227
+ | {
228
+ name: string
229
+ fields?: readonly { id?: number; name?: string; default?: unknown }[]
230
+ }
231
+ | undefined
232
+
233
+ const previousFieldsByName = new Map(
234
+ previousTable?.fields?.filter((f) => f.name !== undefined).map((f) => [f.name!, f.default]) ??
235
+ []
236
+ )
237
+
238
+ return filterModifiableFields(table.fields, existingColumns, renamedNewNames).flatMap((field) => {
239
+ const currentDefault = 'default' in field ? field.default : undefined
240
+ const previousDefault = previousFieldsByName.get(field.name)
241
+
242
+ // Only generate ALTER statements if default value changed
243
+ if (currentDefault === previousDefault) return []
244
+
245
+ // Case 1: Default removed (was set, now undefined)
246
+ if (previousDefault !== undefined && currentDefault === undefined) {
247
+ return [`ALTER TABLE ${table.name} ALTER COLUMN ${field.name} DROP DEFAULT`]
248
+ }
249
+
250
+ // Case 2: Default added or modified (generate SET DEFAULT statement)
251
+ if (currentDefault !== undefined) {
252
+ const columnDef = generateColumnDefinition(field, false, table.fields)
253
+ // Extract DEFAULT clause from column definition
254
+ // Handle quoted strings (DEFAULT 'value'), unquoted values (DEFAULT 42), and functions (DEFAULT NOW())
255
+ const defaultMatch = columnDef.match(/DEFAULT\s+('(?:[^']|'')*'|[^\s]+)/)
256
+ if (defaultMatch) {
257
+ const defaultClause = defaultMatch[1]
258
+ return [`ALTER TABLE ${table.name} ALTER COLUMN ${field.name} SET DEFAULT ${defaultClause}`]
259
+ }
260
+ }
261
+
262
+ return []
263
+ })
264
+ }
265
+
266
+ /**
267
+ * Build drop/add column statements from the computed columns to modify
268
+ */
269
+ export const buildColumnStatements = (options: {
270
+ readonly tableName: string
271
+ readonly columnsToDrop: readonly string[]
272
+ readonly columnsToAdd: readonly Fields[number][]
273
+ readonly primaryKeyFields: readonly string[]
274
+ readonly allFields: readonly Fields[number][]
275
+ }): { readonly dropStatements: readonly string[]; readonly addStatements: readonly string[] } => {
276
+ const { tableName, columnsToDrop, columnsToAdd, primaryKeyFields, allFields } = options
277
+ const dropStatements = columnsToDrop.map(
278
+ (columnName) => `ALTER TABLE ${tableName} DROP COLUMN ${columnName} CASCADE`
279
+ )
280
+ const addStatements = columnsToAdd.map((field) => {
281
+ const isPrimaryKey = primaryKeyFields.includes(field.name)
282
+ const columnDef = generateColumnDefinition(field, isPrimaryKey, allFields)
283
+ return `ALTER TABLE ${tableName} ADD COLUMN ${columnDef}`
284
+ })
285
+ return { dropStatements, addStatements }
286
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Copyright (c) 2025 ESSENTIAL SERVICES
3
+ *
4
+ * This source code is licensed under the Business Source License 1.1
5
+ * found in the LICENSE.md file in the root directory of this source tree.
6
+ */
7
+
8
+ /**
9
+ * System tables that should never be dropped
10
+ * These tables are managed by Better Auth/Drizzle migrations or migration system, not by runtime schema
11
+ * Note: Better Auth tables are in the auth schema with native Better Auth table names
12
+ * System tables are in the system schema (system.*)
13
+ */
14
+ export const PROTECTED_SYSTEM_TABLES = new Set([
15
+ // Better Auth tables (in auth schema)
16
+ 'auth.user',
17
+ 'auth.session',
18
+ 'auth.account',
19
+ 'auth.verification',
20
+ 'auth.two_factor',
21
+ 'auth.team',
22
+ 'auth.team_member',
23
+ 'auth.role',
24
+ // Migration system tables (in system schema)
25
+ 'system.migration_history',
26
+ 'system.migration_log',
27
+ 'system.schema_checksum',
28
+ // Activity and comment tables (in system schema)
29
+ 'system.activity_logs',
30
+ 'system.record_comments',
31
+ ])
@@ -0,0 +1,288 @@
1
+ /**
2
+ * Copyright (c) 2025 ESSENTIAL SERVICES
3
+ *
4
+ * This source code is licensed under the Business Source License 1.1
5
+ * found in the LICENSE.md file in the root directory of this source tree.
6
+ */
7
+
8
+ import { Effect } from 'effect'
9
+ import {
10
+ executeSQLStatements,
11
+ type TransactionLike,
12
+ type SQLExecutionError,
13
+ } from '../sql/sql-execution'
14
+ import { generateForeignKeyConstraints, generateTableConstraints } from '../sql/sql-generators'
15
+ import type { Table } from '@/domain/models/app/table'
16
+
17
+ /**
18
+ * Get drop statements for removed unique constraints
19
+ */
20
+ const getUniqueConstraintDropStatements = (
21
+ table: Table,
22
+ previousSchema: { readonly tables: readonly object[] } | undefined,
23
+ currentUniqueFields: readonly string[]
24
+ ): readonly string[] => {
25
+ if (!previousSchema) return []
26
+
27
+ const previousTable = previousSchema.tables.find(
28
+ (t: object) => 'name' in t && t.name === table.name
29
+ ) as
30
+ | {
31
+ name: string
32
+ fields?: readonly { name?: string; unique?: boolean }[]
33
+ uniqueConstraints?: readonly { name: string }[]
34
+ }
35
+ | undefined
36
+
37
+ if (!previousTable) return []
38
+
39
+ // Single-field constraints that were removed
40
+ const previousUniqueFields =
41
+ previousTable.fields?.filter((f) => f.name && 'unique' in f && f.unique).map((f) => f.name!) ??
42
+ []
43
+
44
+ const removedFields = previousUniqueFields.filter(
45
+ (fieldName) => !currentUniqueFields.includes(fieldName)
46
+ )
47
+
48
+ const singleFieldDrops = removedFields.map((fieldName) => {
49
+ const constraintName = `${table.name}_${fieldName}_key`
50
+ return `ALTER TABLE ${table.name} DROP CONSTRAINT IF EXISTS ${constraintName}`
51
+ })
52
+
53
+ // Composite constraints that were removed
54
+ const previousCompositeNames = previousTable.uniqueConstraints?.map((c) => c.name) ?? []
55
+ const currentCompositeNames = table.uniqueConstraints?.map((c) => c.name) ?? []
56
+
57
+ const removedComposites = previousCompositeNames.filter(
58
+ (name) => !currentCompositeNames.includes(name)
59
+ )
60
+
61
+ const compositeDrops = removedComposites.map(
62
+ (constraintName) => `ALTER TABLE ${table.name} DROP CONSTRAINT IF EXISTS ${constraintName}`
63
+ )
64
+
65
+ return [...singleFieldDrops, ...compositeDrops]
66
+ }
67
+
68
+ /**
69
+ * Build SQL statements to add unique constraints (single-field and composite)
70
+ */
71
+ const buildUniqueConstraintAddStatements = (
72
+ table: Table,
73
+ uniqueFields: readonly string[]
74
+ ): readonly string[] => {
75
+ // Single-field constraints
76
+ const singleFieldStatements = uniqueFields.map((fieldName) => {
77
+ const constraintName = `${table.name}_${fieldName}_key`
78
+ return `
79
+ DO $$
80
+ BEGIN
81
+ IF NOT EXISTS (
82
+ SELECT 1 FROM information_schema.table_constraints
83
+ WHERE table_name = '${table.name}'
84
+ AND constraint_type = 'UNIQUE'
85
+ AND constraint_name = '${constraintName}'
86
+ ) THEN
87
+ ALTER TABLE ${table.name} ADD CONSTRAINT ${constraintName} UNIQUE (${fieldName});
88
+ END IF;
89
+ END$$;
90
+ `
91
+ })
92
+
93
+ // Composite constraints
94
+ const compositeStatements =
95
+ table.uniqueConstraints?.map((constraint) => {
96
+ const fields = constraint.fields.join(', ')
97
+ return `
98
+ DO $$
99
+ BEGIN
100
+ IF NOT EXISTS (
101
+ SELECT 1 FROM information_schema.table_constraints
102
+ WHERE table_name = '${table.name}'
103
+ AND constraint_type = 'UNIQUE'
104
+ AND constraint_name = '${constraint.name}'
105
+ ) THEN
106
+ ALTER TABLE ${table.name} ADD CONSTRAINT ${constraint.name} UNIQUE (${fields});
107
+ END IF;
108
+ END$$;
109
+ `
110
+ }) ?? []
111
+
112
+ return [...singleFieldStatements, ...compositeStatements]
113
+ }
114
+
115
+ /**
116
+ * Sync unique constraints for existing table
117
+ * Adds named UNIQUE constraints for:
118
+ * 1. Single-field constraints (fields with unique property)
119
+ * 2. Composite unique constraints (table.uniqueConstraints)
120
+ * Removes constraints that are no longer in the schema
121
+ * Uses PostgreSQL default naming convention: {table}_{column}_key for single fields
122
+ */
123
+ export const syncUniqueConstraints = (
124
+ tx: TransactionLike,
125
+ table: Table,
126
+ previousSchema?: { readonly tables: readonly object[] }
127
+ ): Effect.Effect<void, SQLExecutionError> =>
128
+ Effect.gen(function* () {
129
+ const uniqueFields = table.fields.filter((f) => 'unique' in f && f.unique).map((f) => f.name)
130
+ const dropStatements = getUniqueConstraintDropStatements(table, previousSchema, uniqueFields)
131
+ const addStatements = buildUniqueConstraintAddStatements(table, uniqueFields)
132
+
133
+ yield* executeSQLStatements(tx, [...dropStatements, ...addStatements])
134
+ })
135
+
136
+ /**
137
+ * Sync foreign key constraints for existing table
138
+ * Drops and recreates FK constraints to ensure referential actions (ON DELETE, ON UPDATE) are up-to-date
139
+ * This is needed when table schema is updated with new referential actions
140
+ *
141
+ * NOTE: After RENAME COLUMN, PostgreSQL preserves FK constraints but doesn't rename them.
142
+ * We need to drop old constraints by column name, not just by expected constraint name.
143
+ */
144
+ export const syncForeignKeyConstraints = (
145
+ tx: TransactionLike,
146
+ table: Table,
147
+ tableUsesView?: ReadonlyMap<string, boolean>
148
+ ): Effect.Effect<void, SQLExecutionError> =>
149
+ Effect.gen(function* () {
150
+ const fkConstraints = generateForeignKeyConstraints(table.name, table.fields, tableUsesView)
151
+
152
+ // Build drop and add statements for each FK constraint
153
+ const statements = fkConstraints.flatMap((constraint) => {
154
+ // Extract column name from the constraint SQL
155
+ // Format: "CONSTRAINT {constraintName} FOREIGN KEY ({columnName}) REFERENCES ..."
156
+ const match = constraint.match(/CONSTRAINT\s+\w+\s+FOREIGN KEY\s+\((\w+)\)/)
157
+ if (!match) return []
158
+
159
+ const columnName = match[1]
160
+
161
+ // Drop ALL existing FK constraints on this column (handles renamed columns)
162
+ // PostgreSQL doesn't rename constraints when column is renamed, so we need to drop by column
163
+ const dropStatement = `
164
+ DO $$
165
+ DECLARE
166
+ constraint_rec RECORD;
167
+ BEGIN
168
+ FOR constraint_rec IN
169
+ SELECT tc.constraint_name
170
+ FROM information_schema.table_constraints tc
171
+ JOIN information_schema.key_column_usage kcu
172
+ ON tc.constraint_name = kcu.constraint_name
173
+ AND tc.table_schema = kcu.table_schema
174
+ WHERE tc.table_name = '${table.name}'
175
+ AND tc.constraint_type = 'FOREIGN KEY'
176
+ AND kcu.column_name = '${columnName}'
177
+ LOOP
178
+ EXECUTE 'ALTER TABLE ${table.name} DROP CONSTRAINT ' || constraint_rec.constraint_name;
179
+ END LOOP;
180
+ END$$;
181
+ `
182
+
183
+ // Add constraint with updated referential actions
184
+ const addStatement = `ALTER TABLE ${table.name} ADD ${constraint}`
185
+
186
+ return [dropStatement, addStatement]
187
+ })
188
+
189
+ // Execute all FK constraint statements sequentially
190
+ yield* executeSQLStatements(tx, statements)
191
+ })
192
+
193
+ /**
194
+ * Validate existing data against new CHECK constraint before applying
195
+ * Returns validation query that will throw error if data violates constraint
196
+ */
197
+ const generateConstraintValidationQuery = (
198
+ tableName: string,
199
+ constraint: string
200
+ ): string | undefined => {
201
+ // Extract constraint condition from the constraint SQL
202
+ // Format: "CONSTRAINT {constraintName} CHECK ({condition})"
203
+ const match = constraint.match(/CHECK\s*\((.+)\)$/)
204
+ if (!match) return undefined
205
+
206
+ const condition = match[1]
207
+
208
+ // Generate query that will fail if any row violates the new constraint
209
+ // Using NOT ({condition}) to find rows that violate the constraint
210
+ return `
211
+ DO $$
212
+ DECLARE
213
+ violation_count INTEGER;
214
+ BEGIN
215
+ SELECT COUNT(*) INTO violation_count
216
+ FROM ${tableName}
217
+ WHERE NOT (${condition});
218
+
219
+ IF violation_count > 0 THEN
220
+ RAISE EXCEPTION 'Migration failed: existing data violates check constraint. % row(s) in table "${tableName}" violate the new constraint condition.', violation_count;
221
+ END IF;
222
+ END$$;
223
+ `
224
+ }
225
+
226
+ /**
227
+ * Sync CHECK constraints for existing table
228
+ * Adds CHECK constraints for fields with validation requirements (enum values, ranges, formats, etc.)
229
+ * Drops and recreates constraints when they are modified (e.g., min/max value changes)
230
+ * This is needed when fields are added via ALTER TABLE and need their CHECK constraints
231
+ *
232
+ * CRITICAL: Validates existing data before applying new constraints
233
+ * Migration will FAIL if any existing data violates the new constraint
234
+ */
235
+ export const syncCheckConstraints = (
236
+ tx: TransactionLike,
237
+ table: Table
238
+ ): Effect.Effect<void, SQLExecutionError> =>
239
+ Effect.gen(function* () {
240
+ const allConstraints = generateTableConstraints(table, undefined)
241
+
242
+ // Filter only CHECK constraints (not UNIQUE, FK, or PRIMARY KEY)
243
+ const checkConstraints = allConstraints.filter(
244
+ (constraint) =>
245
+ constraint.startsWith('CONSTRAINT') &&
246
+ constraint.includes('CHECK') &&
247
+ !constraint.includes('UNIQUE') &&
248
+ !constraint.includes('FOREIGN KEY') &&
249
+ !constraint.includes('PRIMARY KEY')
250
+ )
251
+
252
+ // Build statements to drop existing constraints and add new ones
253
+ // This ensures constraints are updated when validation rules change (e.g., max value increases)
254
+ const statements = checkConstraints.flatMap((constraint) => {
255
+ // Extract constraint name from the constraint SQL
256
+ // Format: "CONSTRAINT {constraintName} CHECK ..."
257
+ const match = constraint.match(/CONSTRAINT\s+(\w+)\s+CHECK/)
258
+ if (!match) return []
259
+
260
+ const constraintName = match[1]
261
+
262
+ // Generate validation query to check if existing data violates new constraint
263
+ const validationQuery = generateConstraintValidationQuery(table.name, constraint)
264
+
265
+ // Drop existing constraint if it exists, validate data, then add the new constraint
266
+ // This handles both new constraints and modified constraints
267
+ return [
268
+ `
269
+ DO $$
270
+ BEGIN
271
+ IF EXISTS (
272
+ SELECT 1 FROM information_schema.table_constraints
273
+ WHERE table_name = '${table.name}'
274
+ AND constraint_type = 'CHECK'
275
+ AND constraint_name = '${constraintName}'
276
+ ) THEN
277
+ ALTER TABLE ${table.name} DROP CONSTRAINT ${constraintName};
278
+ END IF;
279
+ END$$;
280
+ `,
281
+ ...(validationQuery ? [validationQuery] : []),
282
+ `ALTER TABLE ${table.name} ADD ${constraint}`,
283
+ ]
284
+ })
285
+
286
+ // Execute all statements sequentially (drop, validate, then add for each constraint)
287
+ yield* executeSQLStatements(tx, statements)
288
+ })