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,598 @@
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 { SQL } from 'bun'
9
+ import { Config, Effect, Console, Data, Runtime, type ConfigError } from 'effect'
10
+ import { AuthConfigRequiredForUserFields } from '@/infrastructure/errors/auth-config-required-error'
11
+ import { SchemaInitializationError } from '@/infrastructure/errors/schema-initialization-error'
12
+ import { logInfo } from '@/infrastructure/logging/logger'
13
+ import {
14
+ needsUsersTable,
15
+ needsUpdatedByTrigger,
16
+ ensureBetterAuthUsersTable,
17
+ ensureUpdatedByTriggerFunction,
18
+ type BetterAuthUsersTableRequired,
19
+ } from '../auth/auth-validation'
20
+ import { sanitizeTableName, isManyToManyRelationship } from '../field-utils'
21
+ import * as lookupViewGenerators from '../lookup/lookup-view-generators'
22
+ import {
23
+ getPreviousSchema,
24
+ logRollbackOperation,
25
+ recordMigration,
26
+ storeSchemaChecksum,
27
+ generateSchemaChecksum,
28
+ validateStoredChecksum,
29
+ } from '../migration-audit-trail'
30
+ import {
31
+ dropObsoleteTables,
32
+ renameTablesIfNeeded,
33
+ syncForeignKeyConstraints,
34
+ } from '../schema-migration-helpers'
35
+ import {
36
+ tableExists,
37
+ executeSQL,
38
+ type SQLExecutionError,
39
+ type TransactionLike,
40
+ } from '../sql/sql-execution'
41
+ import { generateJunctionTableDDL, generateJunctionTableName } from '../sql/sql-generators'
42
+ import {
43
+ createOrMigrateTableEffect,
44
+ createLookupViewsEffect,
45
+ createTableViewsEffect,
46
+ } from '../table-operations'
47
+ import * as viewGenerators from '../views/view-generators'
48
+ import {
49
+ detectCircularDependenciesWithOptionalFK,
50
+ sortTablesByDependencies,
51
+ } from './schema-dependency-sorting'
52
+ import type { App } from '@/domain/models/app'
53
+ import type { Table } from '@/domain/models/app/table'
54
+
55
+ // Re-export error types for convenience
56
+ export { AuthConfigRequiredForUserFields } from '@/infrastructure/errors/auth-config-required-error'
57
+ export { SchemaInitializationError } from '@/infrastructure/errors/schema-initialization-error'
58
+ export { BetterAuthUsersTableRequired } from '../auth/auth-validation'
59
+
60
+ export class NoDatabaseUrlError extends Data.TaggedError('NoDatabaseUrlError')<{
61
+ readonly message: string
62
+ }> {}
63
+
64
+ /** Ensure Better Auth prerequisites exist (users table + updated-by trigger) */
65
+ const ensureAuthPrerequisites = (
66
+ tx: TransactionLike,
67
+ tables: readonly Table[],
68
+ hasAuthConfig: boolean
69
+ ): Effect.Effect<void, never, never> =>
70
+ Effect.gen(function* () {
71
+ logInfo('[executeSchemaInit] Checking if Better Auth users table is needed...')
72
+ const needs = needsUsersTable(tables)
73
+ logInfo(`[executeSchemaInit] needsUsersTable: ${needs}`)
74
+ logInfo(`[executeSchemaInit] hasAuthConfig: ${hasAuthConfig}`)
75
+
76
+ // Only enforce users table existence if auth is configured
77
+ // If auth is NOT configured, authorship fields will be NULL
78
+ if (needs && hasAuthConfig) {
79
+ logInfo('[executeSchemaInit] Better Auth users table is needed, verifying it exists...')
80
+ yield* Effect.promise(() => ensureBetterAuthUsersTable(tx))
81
+ } else if (needs && !hasAuthConfig) {
82
+ logInfo(
83
+ '[executeSchemaInit] User fields present but auth not configured - fields will be NULL'
84
+ )
85
+ } else {
86
+ logInfo('[executeSchemaInit] Better Auth users table not needed')
87
+ }
88
+
89
+ if (needsUpdatedByTrigger(tables)) {
90
+ yield* Effect.promise(() => ensureUpdatedByTriggerFunction(tx))
91
+ }
92
+ })
93
+
94
+ /** Build map of which tables use VIEWs (have lookup fields) */
95
+ const buildTableUsesViewMap = (
96
+ tables: readonly Table[],
97
+ lookupViewModule: typeof lookupViewGenerators
98
+ ): ReadonlyMap<string, boolean> =>
99
+ new Map(tables.map((table) => [table.name, lookupViewModule.shouldUseView(table)]))
100
+
101
+ // Configuration for createMigrateTables
102
+ type CreateMigrateTablesConfig = {
103
+ readonly tx: TransactionLike
104
+ readonly sortedTables: readonly Table[]
105
+ readonly tableUsesView: ReadonlyMap<string, boolean>
106
+ readonly circularTables: ReadonlySet<string>
107
+ readonly previousSchema: { readonly tables: readonly object[] } | undefined
108
+ readonly lookupViewModule: typeof lookupViewGenerators
109
+ readonly hasAuthConfig: boolean
110
+ }
111
+
112
+ /** Create or migrate each table in sorted order */
113
+ const createMigrateTables = (
114
+ config: CreateMigrateTablesConfig
115
+ ): Effect.Effect<void, SQLExecutionError, never> =>
116
+ Effect.gen(function* () {
117
+ const {
118
+ tx,
119
+ sortedTables,
120
+ tableUsesView,
121
+ circularTables,
122
+ previousSchema,
123
+ lookupViewModule,
124
+ hasAuthConfig,
125
+ } = config
126
+ /* eslint-disable functional/no-loop-statements */
127
+ for (const table of sortedTables) {
128
+ const sanitized = sanitizeTableName(table.name)
129
+ const physicalTableName = lookupViewModule.shouldUseView(table)
130
+ ? lookupViewModule.getBaseTableName(sanitized)
131
+ : sanitized
132
+ const exists = yield* tableExists(tx, physicalTableName)
133
+ logInfo(`[Creating/migrating table] ${table.name} (exists: ${exists})`)
134
+ yield* createOrMigrateTableEffect({
135
+ tx,
136
+ table,
137
+ exists,
138
+ tableUsesView,
139
+ previousSchema,
140
+ skipForeignKeys: circularTables.has(table.name),
141
+ hasAuthConfig,
142
+ })
143
+ logInfo(`[Created/migrated table] ${table.name}`)
144
+ }
145
+ /* eslint-enable functional/no-loop-statements */
146
+ })
147
+
148
+ /** Add foreign key constraints for tables with circular dependencies */
149
+ const addCircularFKConstraints = (
150
+ tx: TransactionLike,
151
+ sortedTables: readonly Table[],
152
+ circularTables: ReadonlySet<string>,
153
+ tableUsesView: ReadonlyMap<string, boolean>
154
+ ): Effect.Effect<void, SQLExecutionError, never> =>
155
+ Effect.gen(function* () {
156
+ if (circularTables.size === 0) return
157
+ logInfo(`[Adding FK constraints for circular dependencies]`)
158
+ /* eslint-disable functional/no-loop-statements */
159
+ for (const table of sortedTables.filter((t) => circularTables.has(t.name))) {
160
+ yield* syncForeignKeyConstraints(tx, table, tableUsesView)
161
+ logInfo(`[Added FK constraints] ${table.name}`)
162
+ }
163
+ /* eslint-enable functional/no-loop-statements */
164
+ })
165
+
166
+ /** Collect junction table specs for many-to-many relationships (functional construction with deduplication) */
167
+ const collectJunctionTableSpecs = (
168
+ sortedTables: readonly Table[],
169
+ tableUsesView: ReadonlyMap<string, boolean>
170
+ ): ReadonlyMap<string, { readonly name: string; readonly ddl: string }> => {
171
+ const junctionSpecs = sortedTables.flatMap((table) => {
172
+ const manyToManyFields = table.fields.filter(isManyToManyRelationship)
173
+ return manyToManyFields.map((field) => {
174
+ const junctionTableName = generateJunctionTableName(table.name, field.relatedTable)
175
+ const ddl = generateJunctionTableDDL(table.name, field.relatedTable, tableUsesView)
176
+ return [junctionTableName, { name: junctionTableName, ddl }] as const
177
+ })
178
+ })
179
+
180
+ // Deduplicate by junction table name (keep first occurrence)
181
+ return new Map(junctionSpecs)
182
+ }
183
+
184
+ /** Create junction tables for many-to-many relationships */
185
+ const createJunctionTables = (
186
+ tx: TransactionLike,
187
+ junctionTableSpecs: ReadonlyMap<string, { readonly name: string; readonly ddl: string }>
188
+ ): Effect.Effect<void, SQLExecutionError, never> =>
189
+ Effect.gen(function* () {
190
+ if (junctionTableSpecs.size === 0) return
191
+ logInfo(`[Creating junction tables] ${Array.from(junctionTableSpecs.keys()).join(', ')}`)
192
+ yield* Effect.all(
193
+ Array.from(junctionTableSpecs.values()).map((spec) =>
194
+ executeSQL(tx, spec.ddl).pipe(
195
+ Effect.tap(() => logInfo(`[Created junction table] ${spec.name}`))
196
+ )
197
+ ),
198
+ { concurrency: 'unbounded' }
199
+ )
200
+ })
201
+
202
+ /** Drop obsolete views and create all views (lookup + user-defined) */
203
+ const createAllViews = (
204
+ tx: TransactionLike,
205
+ sortedTables: readonly Table[],
206
+ viewGeneratorsModule: typeof viewGenerators
207
+ ): Effect.Effect<void, SQLExecutionError, never> =>
208
+ Effect.gen(function* () {
209
+ yield* Effect.promise(() => viewGeneratorsModule.dropAllObsoleteViews(tx, sortedTables))
210
+ yield* Effect.all(
211
+ sortedTables.map((table) => createLookupViewsEffect(tx, table)),
212
+ { concurrency: 'unbounded' }
213
+ )
214
+ yield* Effect.all(
215
+ sortedTables.map((table) => createTableViewsEffect(tx, table)),
216
+ { concurrency: 'unbounded' }
217
+ )
218
+ })
219
+
220
+ /** Execute all migration steps within a transaction */
221
+ const executeMigrationSteps = (
222
+ tx: TransactionLike,
223
+ tables: readonly Table[],
224
+ app: App
225
+ ): Effect.Effect<void, SQLExecutionError, never> =>
226
+ Effect.gen(function* () {
227
+ // Step 0: Validate stored checksum to detect tampering
228
+ yield* validateStoredChecksum(tx)
229
+
230
+ // Steps 1-2: Ensure Better Auth prerequisites
231
+ yield* ensureAuthPrerequisites(tx, tables, !!app.auth)
232
+
233
+ // Step 3: Load previous schema for field rename detection
234
+ const previousSchema = yield* getPreviousSchema(tx)
235
+
236
+ // Step 3.5: Rename tables that have changed names
237
+ yield* renameTablesIfNeeded(tx, tables, previousSchema)
238
+
239
+ // Step 4: Drop tables that exist in database but not in schema
240
+ yield* dropObsoleteTables(tx, tables)
241
+
242
+ // Step 5: Build view map and detect circular dependencies
243
+ const tableUsesView = buildTableUsesViewMap(tables, lookupViewGenerators)
244
+ const circularTables = detectCircularDependenciesWithOptionalFK(tables)
245
+ if (circularTables.size > 0) {
246
+ logInfo(`[Circular dependencies detected] ${Array.from(circularTables).join(', ')}`)
247
+ }
248
+
249
+ // Sort and log table creation order
250
+ const sortedTables = sortTablesByDependencies(tables)
251
+ logInfo(`[Table creation order] ${sortedTables.map((t) => t.name).join(' → ')}`)
252
+
253
+ // Step 6: Create or migrate tables
254
+ yield* createMigrateTables({
255
+ tx,
256
+ sortedTables,
257
+ tableUsesView,
258
+ circularTables,
259
+ previousSchema,
260
+ lookupViewModule: lookupViewGenerators,
261
+ hasAuthConfig: !!app.auth,
262
+ })
263
+
264
+ // Step 7: Add FK constraints for circular dependencies
265
+ yield* addCircularFKConstraints(tx, sortedTables, circularTables, tableUsesView)
266
+
267
+ // Step 8: Create junction tables for many-to-many relationships
268
+ const junctionTableSpecs = collectJunctionTableSpecs(sortedTables, tableUsesView)
269
+ yield* createJunctionTables(tx, junctionTableSpecs)
270
+
271
+ // Steps 9-11: Create all views
272
+ yield* createAllViews(tx, sortedTables, viewGenerators)
273
+
274
+ // Steps 12-13: Record migration and store checksum
275
+ yield* recordMigration(tx, app)
276
+ yield* storeSchemaChecksum(tx, app)
277
+ })
278
+
279
+ /** Log rollback operation in a separate transaction */
280
+ const logRollbackError = (
281
+ databaseUrl: string,
282
+ errorMessage: string,
283
+ runtime: Runtime.Runtime<never>
284
+ ): Effect.Effect<void, never, never> =>
285
+ Effect.gen(function* () {
286
+ logInfo(`[executeSchemaInit] CATCH HANDLER - Error caught: ${errorMessage}`)
287
+ const logDb = new SQL(databaseUrl)
288
+
289
+ yield* Effect.tryPromise({
290
+ try: async () => {
291
+ /* eslint-disable-next-line functional/no-expression-statements */
292
+ await logDb.begin(async (logTx) => {
293
+ /* eslint-disable-next-line functional/no-expression-statements */
294
+ await Runtime.runPromise(runtime)(
295
+ logRollbackOperation(logTx, errorMessage).pipe(
296
+ Effect.catchAll((logError) => {
297
+ logInfo(`[executeSchemaInit] Failed to log rollback: ${logError.message}`)
298
+ return Effect.void
299
+ })
300
+ )
301
+ )
302
+ })
303
+ logInfo('[executeSchemaInit] CATCH HANDLER - Rollback logged and committed')
304
+ },
305
+ catch: () => undefined, // Non-fatal
306
+ }).pipe(
307
+ Effect.ensuring(
308
+ Effect.gen(function* () {
309
+ yield* Effect.promise(() => logDb.close())
310
+ logInfo('[executeSchemaInit] CATCH HANDLER - Log DB connection closed')
311
+ })
312
+ ),
313
+ Effect.ignore
314
+ )
315
+ })
316
+
317
+ /**
318
+ * Execute schema initialization using bun:sql with transaction support
319
+ * Uses Bun's native SQL driver for optimal performance
320
+ *
321
+ * Now supports incremental schema migrations:
322
+ * - For new tables: CREATE TABLE
323
+ * - For existing tables: ALTER TABLE ADD COLUMN for new fields
324
+ *
325
+ * SECURITY NOTE: tx.unsafe() is intentionally used here for DDL execution.
326
+ *
327
+ * This is SAFE because:
328
+ * 1. SQL is generated from validated Effect Schema objects, not user input
329
+ * - Table names come from schema definitions validated at startup
330
+ * - Field names/types are constrained by the domain model (Fields type)
331
+ * 2. DDL statements (CREATE TABLE, CREATE INDEX) cannot use parameterized queries
332
+ * - PostgreSQL does not support $1 placeholders in DDL statements
333
+ * - Table and column names must be interpolated directly
334
+ * 3. All identifiers come from validated schema definitions
335
+ * - The App schema is validated via Effect Schema before reaching this code
336
+ * - Invalid identifiers would fail schema validation, not reach SQL execution
337
+ * 4. Transaction boundary provides atomicity
338
+ * - If any statement fails, the entire transaction rolls back
339
+ * - No partial schema state is possible
340
+ *
341
+ * This pattern is standard for schema migration tools (Drizzle, Prisma, etc.)
342
+ * which all generate and execute DDL strings directly.
343
+ */
344
+ const executeSchemaInit = (
345
+ databaseUrl: string,
346
+ tables: readonly Table[],
347
+ app: App
348
+ ): Effect.Effect<void, SchemaInitializationError, never> =>
349
+ Effect.gen(function* () {
350
+ const db = new SQL({ url: databaseUrl, max: 1 })
351
+ const runtime = yield* Effect.runtime<never>()
352
+
353
+ try {
354
+ yield* Effect.tryPromise({
355
+ try: async () => {
356
+ /* eslint-disable-next-line functional/no-expression-statements */
357
+ await db.begin(async (tx) => {
358
+ /* eslint-disable-next-line functional/no-expression-statements */
359
+ await Runtime.runPromise(runtime)(executeMigrationSteps(tx, tables, app))
360
+ logInfo('[executeSchemaInit] Transaction completed successfully (auto-commit)')
361
+ })
362
+ },
363
+ catch: (error) =>
364
+ new SchemaInitializationError({
365
+ message: `Schema initialization failed: ${String(error)}`,
366
+ cause: error,
367
+ }),
368
+ }).pipe(
369
+ Effect.catchAll((error) =>
370
+ Effect.gen(function* () {
371
+ yield* logRollbackError(databaseUrl, error.message, runtime)
372
+ return yield* error
373
+ })
374
+ )
375
+ )
376
+ } finally {
377
+ yield* Effect.promise(() => db.close())
378
+ }
379
+ })
380
+
381
+ /**
382
+ * Check if schema checksum matches saved checksum (fast path optimization)
383
+ * Returns true if migration should be skipped, false otherwise
384
+ *
385
+ * IMPORTANT: Also verifies that expected tables actually exist.
386
+ * This prevents skipping migration when template databases have checksum but no tables.
387
+ */
388
+ const checkShouldSkipMigration = (
389
+ databaseUrl: string,
390
+ currentChecksum: string,
391
+ tables: readonly Table[]
392
+ ): Effect.Effect<boolean, SchemaInitializationError> =>
393
+ Effect.tryPromise({
394
+ try: async () => {
395
+ const quickDb = new SQL(databaseUrl)
396
+ try {
397
+ // Quick read-only query to check checksum (no transaction needed)
398
+ const result = (await quickDb.unsafe(
399
+ `SELECT checksum FROM system.schema_checksum WHERE id = 'singleton'`
400
+ )) as readonly { checksum: string }[]
401
+
402
+ // Early return if checksum doesn't match
403
+ if (result.length === 0 || result[0]?.checksum !== currentChecksum) {
404
+ logInfo(
405
+ '[checkShouldSkipMigration] Schema checksum differs or missing - running full migration'
406
+ )
407
+ return false
408
+ }
409
+
410
+ // Checksum matches, but verify tables actually exist
411
+ // This prevents skipping migration when template DBs have checksum but no tables
412
+ if (tables.length === 0) {
413
+ logInfo(
414
+ '[checkShouldSkipMigration] Schema checksum matches and no tables expected - skipping migration (fast path)'
415
+ )
416
+ return true
417
+ }
418
+
419
+ // Check if the first table exists (as a sanity check)
420
+ // Note: Table names come from validated schema, not user input (see SECURITY NOTE above)
421
+ const firstTableName = tables[0]?.name
422
+ if (!firstTableName) {
423
+ logInfo(
424
+ '[checkShouldSkipMigration] Schema checksum matches and tables verified - skipping migration (fast path)'
425
+ )
426
+ return true
427
+ }
428
+
429
+ // Sanitize table name for PostgreSQL check
430
+ const sanitizedTableName = sanitizeTableName(firstTableName)
431
+
432
+ const tableCheck = (await quickDb.unsafe(`
433
+ SELECT EXISTS (
434
+ SELECT FROM information_schema.tables
435
+ WHERE table_schema = 'public'
436
+ AND table_name = '${sanitizedTableName}'
437
+ )
438
+ `)) as readonly { exists: boolean }[]
439
+
440
+ if (!tableCheck[0]?.exists) {
441
+ logInfo(
442
+ `[checkShouldSkipMigration] Checksum matches but table '${sanitizedTableName}' does not exist - running full migration (template DB detected)`
443
+ )
444
+ return false
445
+ }
446
+
447
+ logInfo(
448
+ '[checkShouldSkipMigration] Schema checksum matches and tables verified - skipping migration (fast path)'
449
+ )
450
+ return true
451
+ } catch {
452
+ // Table might not exist yet (first run) - proceed with full migration
453
+ logInfo('[checkShouldSkipMigration] Checksum table not found - running full migration')
454
+ return false
455
+ } finally {
456
+ /* eslint-disable-next-line functional/no-expression-statements */
457
+ await quickDb.close()
458
+ }
459
+ },
460
+ catch: () =>
461
+ new SchemaInitializationError({
462
+ message: 'Failed to check schema checksum',
463
+ cause: undefined,
464
+ }),
465
+ }).pipe(Effect.catchAll(() => Effect.succeed(false))) // Non-fatal - if check fails, proceed with full migration
466
+
467
+ /**
468
+ * Error type union for schema initialization
469
+ */
470
+ export type SchemaError =
471
+ | SchemaInitializationError
472
+ | NoDatabaseUrlError
473
+ | BetterAuthUsersTableRequired
474
+ | AuthConfigRequiredForUserFields
475
+
476
+ /**
477
+ * Initialize database schema from app configuration (internal with error handling)
478
+ *
479
+ * Uses Bun's native SQL driver (bun:sql) for:
480
+ * - Zero-dependency PostgreSQL access
481
+ * - Optimal performance on Bun runtime
482
+ * - Built-in connection pooling
483
+ * - Transaction support with automatic rollback
484
+ *
485
+ * Errors are logged and handled internally - returns Effect<void, never>
486
+ * for simpler composition in application layer.
487
+ *
488
+ * @see docs/infrastructure/database/runtime-sql-migrations/04-migration-executor.md
489
+ */
490
+ const initializeSchemaInternal = (
491
+ app: App
492
+ ): Effect.Effect<void, SchemaError | ConfigError.ConfigError> =>
493
+ Effect.gen(function* () {
494
+ logInfo('[initializeSchemaInternal] Starting schema initialization...')
495
+ logInfo(`[initializeSchemaInternal] App tables count: ${app.tables?.length || 0}`)
496
+
497
+ // Normalize tables to empty array if undefined
498
+ const tables = app.tables ?? []
499
+
500
+ // Check if tables require user fields and auth configuration status
501
+ // Note: Authorship fields (created-by, updated-by, deleted-by) are allowed without auth config
502
+ // When auth is not configured, these fields will be NULL
503
+ const tablesNeedUsersTable = needsUsersTable(tables)
504
+ const hasAuthConfig = !!app.auth
505
+ logInfo(`[initializeSchemaInternal] Tables need users table: ${tablesNeedUsersTable}`)
506
+ logInfo(`[initializeSchemaInternal] Auth config present: ${hasAuthConfig}`)
507
+
508
+ // No validation error - authorship fields are allowed without auth (they'll be NULL)
509
+
510
+ // Get database URL from Effect Config (reads from environment)
511
+ const databaseUrlConfig = yield* Config.string('DATABASE_URL').pipe(Config.withDefault(''))
512
+ logInfo(`[initializeSchemaInternal] DATABASE_URL: ${databaseUrlConfig ? 'present' : 'missing'}`)
513
+
514
+ // Skip if no DATABASE_URL configured
515
+ if (!databaseUrlConfig) {
516
+ yield* Console.log('No DATABASE_URL found, skipping schema initialization')
517
+ return
518
+ }
519
+
520
+ yield* Console.log('Initializing database schema...')
521
+
522
+ // Fast path: Check if schema checksum matches (before opening transaction)
523
+ const currentChecksum = generateSchemaChecksum(app)
524
+ const shouldSkipMigration = yield* checkShouldSkipMigration(
525
+ databaseUrlConfig,
526
+ currentChecksum,
527
+ tables
528
+ )
529
+
530
+ // Even if migration is skipped, we need to clean up obsolete views
531
+ // Views might be created manually via SQL and need cleanup
532
+ if (shouldSkipMigration) {
533
+ yield* Console.log('✓ Schema unchanged, cleaning up obsolete views...')
534
+ // Quick cleanup of views not in schema (separate transaction)
535
+ const db = new SQL({ url: databaseUrlConfig, max: 1 })
536
+ try {
537
+ yield* Effect.tryPromise({
538
+ try: async () => {
539
+ // Side effect: Drop obsolete views in database transaction
540
+ /* eslint-disable functional/no-expression-statements */
541
+ await db.begin(async (tx) => {
542
+ await viewGenerators.dropAllObsoleteViews(tx, tables)
543
+ })
544
+ /* eslint-enable functional/no-expression-statements */
545
+ },
546
+ catch: (error) =>
547
+ new SchemaInitializationError({
548
+ message: `View cleanup failed: ${String(error)}`,
549
+ cause: error,
550
+ }),
551
+ })
552
+ } finally {
553
+ yield* Effect.promise(() => db.close())
554
+ }
555
+ yield* Console.log('✓ Schema unchanged, view cleanup complete')
556
+ return
557
+ }
558
+
559
+ // Execute schema initialization with bun:sql (even if tables is empty - to drop obsolete tables)
560
+ yield* executeSchemaInit(databaseUrlConfig, tables, app)
561
+
562
+ yield* Console.log('✓ Database schema initialized successfully')
563
+ })
564
+
565
+ /**
566
+ * Initialize database schema from app configuration
567
+ *
568
+ * Public API that handles errors internally to maintain backward compatibility.
569
+ * Configuration errors are propagated, other errors are logged.
570
+ *
571
+ * Propagated errors:
572
+ * - AuthConfigRequiredForUserFields: auth not configured but user fields used
573
+ * - SchemaInitializationError: schema creation failed (database likely required)
574
+ *
575
+ * @param app - Application configuration with tables
576
+ * @returns Effect that propagates configuration errors but logs optional failures
577
+ */
578
+ export const initializeSchema = (
579
+ app: App
580
+ ): Effect.Effect<void, AuthConfigRequiredForUserFields | SchemaInitializationError> =>
581
+ initializeSchemaInternal(app).pipe(
582
+ Effect.catchAll(
583
+ (error): Effect.Effect<void, AuthConfigRequiredForUserFields | SchemaInitializationError> => {
584
+ // Re-throw auth config errors - these are fatal configuration issues
585
+ if (error instanceof AuthConfigRequiredForUserFields) {
586
+ return Effect.fail(error)
587
+ }
588
+ // Re-throw schema initialization errors - database is required when tables are defined
589
+ if (error instanceof SchemaInitializationError) {
590
+ return Effect.fail(error)
591
+ }
592
+ // Log other errors but don't fail
593
+ return Console.error(`Error initializing database schema: ${error._tag}`).pipe(
594
+ Effect.flatMap(() => Effect.void)
595
+ )
596
+ }
597
+ )
598
+ )