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,38 @@
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 type { UserMetadataWithOptionalImage } from '@/application/ports/models/user-metadata'
9
+
10
+ /**
11
+ * Row shape from a LEFT JOIN on the users table
12
+ *
13
+ * Fields are nullable because LEFT JOIN returns NULL when no user matches.
14
+ */
15
+ export interface UserJoinRow {
16
+ readonly userId: string | null | undefined
17
+ readonly userName: string | null | undefined
18
+ readonly userEmail: string | null | undefined
19
+ readonly userImage: string | null | undefined
20
+ }
21
+
22
+ /**
23
+ * Extract user metadata from a LEFT JOIN row
24
+ *
25
+ * Returns undefined when required fields (userId, userName, userEmail) are missing,
26
+ * which happens when the LEFT JOIN finds no matching user.
27
+ */
28
+ export function extractUserFromRow(row: UserJoinRow): UserMetadataWithOptionalImage | undefined {
29
+ if (row.userId && row.userName && row.userEmail) {
30
+ return {
31
+ id: row.userId,
32
+ name: row.userName,
33
+ email: row.userEmail,
34
+ image: row.userImage ?? undefined,
35
+ }
36
+ }
37
+ return undefined
38
+ }
@@ -0,0 +1,39 @@
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
+ * Validate a table name to prevent SQL injection
10
+ *
11
+ * PostgreSQL identifiers cannot be fully parameterized, so we validate them.
12
+ * This function ensures table names:
13
+ * - Only contain alphanumeric characters, underscores
14
+ * - Start with a letter or underscore
15
+ * - Are within PostgreSQL's 63-character limit
16
+ *
17
+ * @param tableName - Raw table name from user input
18
+ * @throws Error if table name contains invalid characters
19
+ */
20
+ export const validateTableName = (tableName: string): void => {
21
+ // PostgreSQL identifier rules: start with letter/underscore, contain alphanumeric/underscore
22
+ // Max 63 characters (PostgreSQL limit)
23
+ const validIdentifier = /^[a-z_][a-z0-9_]*$/i
24
+ if (!validIdentifier.test(tableName) || tableName.length > 63) {
25
+ // eslint-disable-next-line functional/no-throw-statements -- Validation requires throwing for invalid input
26
+ throw new Error(`Invalid table name: ${tableName}`)
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Validate a column name to prevent SQL injection
32
+ */
33
+ export const validateColumnName = (columnName: string): void => {
34
+ const validIdentifier = /^[a-z_][a-z0-9_]*$/i
35
+ if (!validIdentifier.test(columnName) || columnName.length > 63) {
36
+ // eslint-disable-next-line functional/no-throw-statements -- Validation requires throwing for invalid input
37
+ throw new Error(`Invalid column name: ${columnName}`)
38
+ }
39
+ }
@@ -0,0 +1,258 @@
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 { generateSqlCondition } from '../filter-operators'
10
+ import {
11
+ getExistingViews,
12
+ getExistingMaterializedViews,
13
+ executeSQLStatements,
14
+ executeSQLStatementsParallel,
15
+ type TransactionLike,
16
+ } from '../sql/sql-execution'
17
+ import type { Table } from '@/domain/models/app/table'
18
+ import type { View } from '@/domain/models/app/table/views'
19
+ import type { ViewFilterNode } from '@/domain/models/app/table/views/filters'
20
+
21
+ /**
22
+ * Map filter nodes to SQL condition strings
23
+ * Extracts field, operator, value from each condition and generates SQL
24
+ * Handles leaf conditions only (not nested AND/OR groups)
25
+ */
26
+ const mapFilterConditions = (nodes: readonly ViewFilterNode[]): readonly string[] => {
27
+ return nodes
28
+ .map((node) => {
29
+ if ('field' in node && 'operator' in node && 'value' in node) {
30
+ return generateSqlCondition(node.field, node.operator, node.value)
31
+ }
32
+ return ''
33
+ })
34
+ .filter((c) => c !== '')
35
+ }
36
+
37
+ /**
38
+ * Generate SQL WHERE clause from view filters
39
+ * Supports comparison operators (equals, greaterThan, lessThan, etc.)
40
+ * Values are properly escaped to prevent SQL injection
41
+ */
42
+ const generateWhereClause = (filters: View['filters']): string => {
43
+ if (!filters) return ''
44
+
45
+ // Handle AND filters
46
+ if ('and' in filters && filters.and) {
47
+ const conditions = mapFilterConditions(filters.and)
48
+ return conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : ''
49
+ }
50
+
51
+ // Handle OR filters
52
+ if ('or' in filters && filters.or) {
53
+ const conditions = mapFilterConditions(filters.or)
54
+ return conditions.length > 0 ? `WHERE ${conditions.join(' OR ')}` : ''
55
+ }
56
+
57
+ return ''
58
+ }
59
+
60
+ /**
61
+ * Generate SQL ORDER BY clause from view sorts and groupBy
62
+ * GroupBy takes precedence - when present, it's used for ordering
63
+ * If both groupBy and sorts are present, groupBy is applied first
64
+ */
65
+ const generateOrderByClause = (sorts: View['sorts'], groupBy: View['groupBy']): string => {
66
+ // Build order items immutably
67
+ const groupByItems = groupBy
68
+ ? [`${groupBy.field} ${(groupBy.direction || 'asc').toUpperCase()}`]
69
+ : []
70
+
71
+ const sortItems =
72
+ sorts && sorts.length > 0 && !groupBy
73
+ ? sorts.map((sort) => `${sort.field} ${sort.direction.toUpperCase()}`)
74
+ : []
75
+
76
+ const orderItems = [...groupByItems, ...sortItems]
77
+
78
+ return orderItems.length > 0 ? `ORDER BY ${orderItems.join(', ')}` : ''
79
+ }
80
+
81
+ /**
82
+ * Generate CREATE VIEW or CREATE MATERIALIZED VIEW statement for a table view
83
+ * PostgreSQL doesn't support IF NOT EXISTS for CREATE VIEW, so we drop first
84
+ */
85
+ export const generateViewSQL = (table: Table, view: View): string => {
86
+ const viewType = view.materialized ? 'MATERIALIZED VIEW' : 'VIEW'
87
+ // Convert view.id to string (ViewId can be number or string)
88
+ const viewIdStr = String(view.id)
89
+
90
+ // If view has a custom query, use it directly
91
+ if (view.query) {
92
+ return `CREATE ${viewType} ${viewIdStr} AS ${view.query}`
93
+ }
94
+
95
+ // Otherwise, build query from filters, sorts, fields, groupBy
96
+ const fields = view.fields && view.fields.length > 0 ? view.fields.join(', ') : '*'
97
+ const whereClause = generateWhereClause(view.filters)
98
+ const orderByClause = generateOrderByClause(view.sorts, view.groupBy)
99
+
100
+ const clauses = [`SELECT ${fields}`, `FROM ${table.name}`, whereClause, orderByClause].filter(
101
+ (clause) => clause !== ''
102
+ )
103
+
104
+ const query = clauses.join(' ')
105
+
106
+ return `CREATE ${viewType} ${viewIdStr} AS ${query}`
107
+ }
108
+
109
+ /**
110
+ * Generate all CREATE VIEW statements for a table
111
+ */
112
+ export const generateTableViewStatements = (table: Table): readonly string[] => {
113
+ if (!table.views || table.views.length === 0) return []
114
+
115
+ return table.views.map((view) => generateViewSQL(table, view))
116
+ }
117
+
118
+ /**
119
+ * Generate trigger to make a view read-only
120
+ * PostgreSQL views can be automatically updatable if they meet certain criteria
121
+ * To ensure views are truly read-only, we create INSTEAD OF triggers that reject modifications
122
+ */
123
+ export const generateReadOnlyViewTrigger = (viewId: string | number): readonly string[] => {
124
+ const viewIdStr = String(viewId)
125
+ const triggerBaseName = `${viewIdStr}_readonly`
126
+
127
+ return [
128
+ // INSTEAD OF INSERT trigger
129
+ `CREATE OR REPLACE FUNCTION ${triggerBaseName}_insert_fn()
130
+ RETURNS TRIGGER AS $$
131
+ BEGIN
132
+ RAISE EXCEPTION 'cannot insert into view "%"', TG_TABLE_NAME;
133
+ END;
134
+ $$ LANGUAGE plpgsql`,
135
+ `CREATE TRIGGER ${triggerBaseName}_insert
136
+ INSTEAD OF INSERT ON ${viewIdStr}
137
+ FOR EACH ROW EXECUTE FUNCTION ${triggerBaseName}_insert_fn()`,
138
+
139
+ // INSTEAD OF UPDATE trigger
140
+ `CREATE OR REPLACE FUNCTION ${triggerBaseName}_update_fn()
141
+ RETURNS TRIGGER AS $$
142
+ BEGIN
143
+ RAISE EXCEPTION 'cannot update view "%"', TG_TABLE_NAME;
144
+ END;
145
+ $$ LANGUAGE plpgsql`,
146
+ `CREATE TRIGGER ${triggerBaseName}_update
147
+ INSTEAD OF UPDATE ON ${viewIdStr}
148
+ FOR EACH ROW EXECUTE FUNCTION ${triggerBaseName}_update_fn()`,
149
+
150
+ // INSTEAD OF DELETE trigger
151
+ `CREATE OR REPLACE FUNCTION ${triggerBaseName}_delete_fn()
152
+ RETURNS TRIGGER AS $$
153
+ BEGIN
154
+ RAISE EXCEPTION 'cannot delete from view "%"', TG_TABLE_NAME;
155
+ END;
156
+ $$ LANGUAGE plpgsql`,
157
+ `CREATE TRIGGER ${triggerBaseName}_delete
158
+ INSTEAD OF DELETE ON ${viewIdStr}
159
+ FOR EACH ROW EXECUTE FUNCTION ${triggerBaseName}_delete_fn()`,
160
+ ]
161
+ }
162
+
163
+ /**
164
+ * Drop views that no longer exist in the schema
165
+ * Called once for all tables to ensure clean state before creating views
166
+ */
167
+ /* eslint-disable functional/no-expression-statements */
168
+ export const generateDropObsoleteViewsSQL = async (
169
+ tx: TransactionLike,
170
+ tables: readonly Table[]
171
+ ): Promise<void> => {
172
+ // Get existing views using Effect-based queries
173
+ const program = Effect.gen(function* () {
174
+ // Query both view types in parallel
175
+ const [existingViewNames, existingMatViewNames] = yield* Effect.all(
176
+ [getExistingViews(tx), getExistingMaterializedViews(tx)],
177
+ { concurrency: 2 }
178
+ )
179
+
180
+ const existingViews = new Set(existingViewNames)
181
+ const existingMatViews = new Set(existingMatViewNames)
182
+
183
+ // Collect ALL view IDs from schema (across all tables)
184
+ const allSchemaViewIds = new Set<string>(
185
+ tables.flatMap((table) =>
186
+ table.views && table.views.length > 0 ? table.views.map((view) => String(view.id)) : []
187
+ )
188
+ )
189
+
190
+ // Find views to drop: exist in DB but not in schema
191
+ const viewsToDrop = Array.from(existingViews).filter(
192
+ (viewName) => !allSchemaViewIds.has(viewName)
193
+ )
194
+ const matViewsToDrop = Array.from(existingMatViews).filter(
195
+ (viewName) => !allSchemaViewIds.has(viewName)
196
+ )
197
+
198
+ // Generate DROP statements
199
+ const dropViewStatements = viewsToDrop.map(
200
+ (viewName) => `DROP VIEW IF EXISTS ${viewName} CASCADE`
201
+ )
202
+ const dropMatViewStatements = matViewsToDrop.map(
203
+ (viewName) => `DROP MATERIALIZED VIEW IF EXISTS ${viewName} CASCADE`
204
+ )
205
+
206
+ // Execute all DROP statements sequentially (not in parallel)
207
+ // Sequential execution ensures CASCADE dependencies are handled correctly
208
+ // When dropping view A that view B depends on, CASCADE will drop B automatically
209
+ // If we then try to drop B, IF EXISTS makes it a no-op
210
+ yield* executeSQLStatements(tx, [...dropViewStatements, ...dropMatViewStatements])
211
+ })
212
+
213
+ await Effect.runPromise(program)
214
+ }
215
+ /* eslint-enable functional/no-expression-statements */
216
+
217
+ /**
218
+ * Drop all views that are not defined in any table's schema
219
+ * This ensures orphaned views (manually created or from previous schemas) are cleaned up
220
+ */
221
+ /* eslint-disable functional/no-expression-statements */
222
+ export const dropAllObsoleteViews = async (
223
+ tx: TransactionLike,
224
+ tables: readonly Table[]
225
+ ): Promise<void> => {
226
+ const program = Effect.gen(function* () {
227
+ // Query both view types in parallel
228
+ const [existingViewNames, existingMatViewNames] = yield* Effect.all(
229
+ [getExistingViews(tx), getExistingMaterializedViews(tx)],
230
+ { concurrency: 2 }
231
+ )
232
+
233
+ // Collect all view IDs from all tables (functional construction)
234
+ const allSchemaViews = new Set<string>(
235
+ tables.flatMap((table) => (table.views ? table.views.map((view) => String(view.id)) : []))
236
+ )
237
+
238
+ // Find views to drop - any view in DB that's not in schema
239
+ const viewsToDrop = existingViewNames.filter((viewName) => !allSchemaViews.has(viewName))
240
+ const matViewsToDrop = existingMatViewNames.filter((viewName) => !allSchemaViews.has(viewName))
241
+
242
+ // Generate DROP statements with CASCADE
243
+ const dropViewStatements = viewsToDrop.map(
244
+ (viewName) => `DROP VIEW IF EXISTS ${viewName} CASCADE`
245
+ )
246
+ const dropMatViewStatements = matViewsToDrop.map(
247
+ (viewName) => `DROP MATERIALIZED VIEW IF EXISTS ${viewName} CASCADE`
248
+ )
249
+
250
+ // Execute all DROP statements in parallel
251
+ if (dropViewStatements.length > 0 || dropMatViewStatements.length > 0) {
252
+ yield* executeSQLStatementsParallel(tx, [...dropViewStatements, ...dropMatViewStatements])
253
+ }
254
+ })
255
+
256
+ await Effect.runPromise(program)
257
+ }
258
+ /* eslint-enable functional/no-expression-statements */
@@ -0,0 +1,43 @@
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 { DevTools } from '@effect/experimental'
9
+ import { Layer } from 'effect'
10
+
11
+ /**
12
+ * Effect DevTools layer for development debugging
13
+ *
14
+ * Provides runtime tracing and telemetry via the Effect DevTools extension.
15
+ * Enable by setting EFFECT_DEVTOOLS=1 environment variable.
16
+ *
17
+ * Requirements:
18
+ * - Install VS Code/Cursor Effect extension: https://marketplace.visualstudio.com/items?itemName=effectful-tech.effect-vscode
19
+ * - Set EFFECT_DEVTOOLS=1 in your environment
20
+ * - Run the application
21
+ * - Open Effect panel in your editor to see telemetry
22
+ *
23
+ * @example
24
+ * ```bash
25
+ * EFFECT_DEVTOOLS=1 bun run start
26
+ * ```
27
+ *
28
+ * @see https://effect.website/docs/getting-started/devtools/
29
+ */
30
+ export const DevToolsLayer = DevTools.layer()
31
+
32
+ /**
33
+ * Check if DevTools should be enabled based on environment variable
34
+ */
35
+ export const isDevToolsEnabled = (): boolean => process.env['EFFECT_DEVTOOLS'] === '1'
36
+
37
+ /**
38
+ * Conditionally provide DevTools layer based on environment
39
+ *
40
+ * Returns the DevTools layer if EFFECT_DEVTOOLS=1, otherwise returns an empty layer.
41
+ * This allows safe integration without runtime overhead when disabled.
42
+ */
43
+ export const DevToolsLayerOptional = isDevToolsEnabled() ? DevToolsLayer : Layer.empty
@@ -0,0 +1,8 @@
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
+ export { DevToolsLayer, DevToolsLayerOptional, isDevToolsEnabled } from './devtools-layer'
@@ -0,0 +1,103 @@
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 { logError, logWarning } from '../logging'
9
+ import type { EmailConfig } from './nodemailer'
10
+
11
+ /**
12
+ * SMTP Configuration using type-safe environment variable access
13
+ *
14
+ * This module provides email configuration with:
15
+ * 1. Type-safe environment variable access
16
+ * 2. Sensible defaults for development (Mailpit)
17
+ * 3. Structured logging for missing configuration
18
+ * 4. Clear separation of production vs development modes
19
+ *
20
+ * Environment Variables:
21
+ * - SMTP_HOST: SMTP server hostname (required in production)
22
+ * - SMTP_PORT: SMTP server port (default: 587)
23
+ * - SMTP_SECURE: Use SSL/TLS (default: false for port 587, true for port 465)
24
+ * - SMTP_USER: SMTP authentication username
25
+ * - SMTP_PASS: SMTP authentication password
26
+ * - SMTP_FROM: Default "from" email address (default: noreply@sovrium.com)
27
+ * - SMTP_FROM_NAME: Default "from" display name (default: 'Sovrium')
28
+ *
29
+ * @see https://mailpit.axllent.org/ for local development email testing
30
+ */
31
+
32
+ /**
33
+ * Read optional string from environment
34
+ */
35
+ const getEnvString = (key: string, defaultValue: string): string => process.env[key] ?? defaultValue
36
+
37
+ /**
38
+ * Read optional number from environment
39
+ */
40
+ const getEnvNumber = (key: string, defaultValue: number): number => {
41
+ const value = process.env[key]
42
+ return value ? parseInt(value, 10) : defaultValue
43
+ }
44
+
45
+ /**
46
+ * Read optional boolean from environment
47
+ */
48
+ const getEnvBoolean = (key: string, defaultValue: boolean): boolean => {
49
+ const value = process.env[key]
50
+ return value ? value === 'true' : defaultValue
51
+ }
52
+
53
+ /**
54
+ * Get email configuration from environment variables
55
+ *
56
+ * In production, SMTP_HOST is required. In development, falls back to
57
+ * localhost:1025 (Mailpit) for local email testing.
58
+ */
59
+ export const getEmailConfigFromEffect = (): EmailConfig => {
60
+ const host = process.env.SMTP_HOST
61
+ const isProduction = process.env.NODE_ENV === 'production'
62
+
63
+ // Use real SMTP when host is configured
64
+ if (host) {
65
+ const port = getEnvNumber('SMTP_PORT', 587)
66
+ return {
67
+ host,
68
+ port,
69
+ secure: getEnvBoolean('SMTP_SECURE', false) || port === 465,
70
+ auth: {
71
+ user: getEnvString('SMTP_USER', ''),
72
+ pass: getEnvString('SMTP_PASS', ''),
73
+ },
74
+ from: {
75
+ email: getEnvString('SMTP_FROM', 'noreply@sovrium.com'),
76
+ name: getEnvString('SMTP_FROM_NAME', 'Sovrium'),
77
+ },
78
+ }
79
+ }
80
+
81
+ // Log warnings for missing SMTP config
82
+ if (isProduction) {
83
+ logError('[EMAIL] SMTP_HOST not configured in production mode')
84
+ } else {
85
+ logWarning('[EMAIL] SMTP_HOST not configured, using 127.0.0.1:1025 (Mailpit)')
86
+ }
87
+
88
+ // Development fallback - Mailpit on localhost
89
+ // Use explicit IPv4 address to avoid IPv6 resolution issues
90
+ return {
91
+ host: '127.0.0.1',
92
+ port: 1025,
93
+ secure: false,
94
+ auth: {
95
+ user: '',
96
+ pass: '',
97
+ },
98
+ from: {
99
+ email: getEnvString('SMTP_FROM', 'noreply@sovrium.local'),
100
+ name: getEnvString('SMTP_FROM_NAME', 'Sovrium (Dev)'),
101
+ },
102
+ }
103
+ }
@@ -0,0 +1,152 @@
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 { Context, Effect, Layer, Data } from 'effect'
9
+ import { transporter, getDefaultFrom, type SendMailOptions } from './nodemailer'
10
+
11
+ /**
12
+ * Email service error types
13
+ */
14
+ export class EmailError extends Data.TaggedError('EmailError')<{
15
+ readonly message: string
16
+ readonly cause?: unknown
17
+ }> {}
18
+
19
+ export class EmailConnectionError extends Data.TaggedError('EmailConnectionError')<{
20
+ readonly message: string
21
+ readonly cause?: unknown
22
+ }> {}
23
+
24
+ /**
25
+ * Email service interface
26
+ *
27
+ * Provides a functional interface for sending emails with Effect-based
28
+ * error handling and composition.
29
+ */
30
+ export interface EmailService {
31
+ /**
32
+ * Send an email
33
+ *
34
+ * @param options - Nodemailer mail options
35
+ * @returns Effect that resolves with message info or fails with EmailError
36
+ */
37
+ readonly send: (options: Readonly<SendMailOptions>) => Effect.Effect<string, EmailError>
38
+
39
+ /**
40
+ * Send an email with the default "from" address
41
+ *
42
+ * @param options - Mail options without "from" field
43
+ * @returns Effect that resolves with message info or fails with EmailError
44
+ */
45
+ readonly sendWithDefaultFrom: (
46
+ options: Readonly<Omit<SendMailOptions, 'from'>>
47
+ ) => Effect.Effect<string, EmailError>
48
+
49
+ /**
50
+ * Verify SMTP connection
51
+ *
52
+ * @returns Effect that resolves with true or fails with EmailConnectionError
53
+ */
54
+ readonly verifyConnection: () => Effect.Effect<boolean, EmailConnectionError>
55
+ }
56
+
57
+ /**
58
+ * Email service tag for Effect dependency injection
59
+ */
60
+ export class Email extends Context.Tag('Email')<Email, EmailService>() {}
61
+
62
+ /**
63
+ * Live implementation of EmailService using Nodemailer
64
+ */
65
+ export const EmailLive = Layer.succeed(
66
+ Email,
67
+ Email.of({
68
+ send: (options) =>
69
+ Effect.tryPromise({
70
+ try: async () => {
71
+ const info = await transporter.sendMail(options)
72
+ return info.messageId
73
+ },
74
+ catch: (error) =>
75
+ new EmailError({
76
+ message: `Failed to send email: ${error instanceof Error ? error.message : String(error)}`,
77
+ cause: error,
78
+ }),
79
+ }),
80
+
81
+ sendWithDefaultFrom: (options) =>
82
+ Effect.tryPromise({
83
+ try: async () => {
84
+ const info = await transporter.sendMail({
85
+ from: getDefaultFrom(),
86
+ ...options,
87
+ })
88
+ return info.messageId
89
+ },
90
+ catch: (error) =>
91
+ new EmailError({
92
+ message: `Failed to send email: ${error instanceof Error ? error.message : String(error)}`,
93
+ cause: error,
94
+ }),
95
+ }),
96
+
97
+ verifyConnection: () =>
98
+ Effect.tryPromise({
99
+ try: () => transporter.verify(),
100
+ catch: (error) =>
101
+ new EmailConnectionError({
102
+ message: `SMTP connection failed: ${error instanceof Error ? error.message : String(error)}`,
103
+ cause: error,
104
+ }),
105
+ }),
106
+ })
107
+ )
108
+
109
+ /**
110
+ * Send email helper function (for use outside Effect context)
111
+ *
112
+ * This is a convenience function for Better Auth integration where
113
+ * we need to use async/await directly instead of Effect.
114
+ *
115
+ * @example
116
+ * ```typescript
117
+ * import { sendEmail } from '@/infrastructure/email/email-service'
118
+ *
119
+ * await sendEmail({
120
+ * to: 'user@example.com',
121
+ * subject: 'Welcome',
122
+ * html: '<h1>Welcome!</h1>',
123
+ * })
124
+ * ```
125
+ */
126
+ export async function sendEmail(options: Readonly<Omit<SendMailOptions, 'from'>>): Promise<string> {
127
+ const info = await transporter.sendMail({
128
+ from: getDefaultFrom(),
129
+ ...options,
130
+ })
131
+ return info.messageId
132
+ }
133
+
134
+ /**
135
+ * Send email with full options (including custom "from")
136
+ *
137
+ * @example
138
+ * ```typescript
139
+ * import { sendEmailWithOptions } from '@/infrastructure/email/email-service'
140
+ *
141
+ * await sendEmailWithOptions({
142
+ * from: '"Custom Sender" <custom@example.com>',
143
+ * to: 'user@example.com',
144
+ * subject: 'Hello',
145
+ * text: 'Hello World',
146
+ * })
147
+ * ```
148
+ */
149
+ export async function sendEmailWithOptions(options: Readonly<SendMailOptions>): Promise<string> {
150
+ const info = await transporter.sendMail(options)
151
+ return info.messageId
152
+ }