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,414 @@
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, eq, and, isNull, desc, asc } from 'drizzle-orm'
9
+ import { Effect } from 'effect'
10
+ import { users } from '@/infrastructure/auth/better-auth/schema'
11
+ import { SessionContextError } from '@/infrastructure/database'
12
+ import { db } from '@/infrastructure/database/drizzle'
13
+ import { recordComments } from '@/infrastructure/database/drizzle/schema/record-comments'
14
+ import { wrapDatabaseError } from '../shared/error-handling'
15
+ import { extractUserFromRow } from '../shared/user-join-helpers'
16
+ import type { UserMetadataWithOptionalImage } from '@/application/ports/models/user-metadata'
17
+ import type { Session } from '@/infrastructure/auth/better-auth/schema'
18
+
19
+ /**
20
+ * Create a comment on a record
21
+ */
22
+ export function createComment(config: {
23
+ readonly session: Readonly<Session>
24
+ readonly tableId: string
25
+ readonly recordId: string
26
+ readonly content: string
27
+ }): Effect.Effect<
28
+ {
29
+ readonly id: string
30
+ readonly tableId: string
31
+ readonly recordId: string
32
+ readonly userId: string
33
+ readonly content: string
34
+ readonly createdAt: Date
35
+ },
36
+ SessionContextError
37
+ > {
38
+ const { session, tableId, recordId, content } = config
39
+ return Effect.tryPromise({
40
+ try: async () => {
41
+ const now = new Date()
42
+ const values = {
43
+ id: crypto.randomUUID(),
44
+ tableId,
45
+ recordId,
46
+ userId: session.userId,
47
+ content,
48
+ createdAt: now,
49
+ updatedAt: now,
50
+ }
51
+
52
+ const result = await db.insert(recordComments).values(values).returning()
53
+
54
+ if (result.length === 0) {
55
+ // eslint-disable-next-line functional/no-throw-statements -- Required inside Effect.tryPromise for error propagation
56
+ throw new SessionContextError('Failed to create comment')
57
+ }
58
+
59
+ const comment = result[0]!
60
+ return {
61
+ id: comment.id,
62
+ tableId: comment.tableId,
63
+ recordId: comment.recordId,
64
+ userId: comment.userId,
65
+ content: comment.content,
66
+ createdAt: comment.createdAt,
67
+ }
68
+ },
69
+ catch: wrapDatabaseError('Failed to create comment'),
70
+ })
71
+ }
72
+
73
+ /**
74
+ * Transform comment query result to domain model
75
+ */
76
+ function transformCommentRow(row: {
77
+ readonly id: string
78
+ readonly tableId: string
79
+ readonly recordId: string
80
+ readonly userId: string
81
+ readonly content: string
82
+ readonly createdAt: Date
83
+ readonly updatedAt: Date
84
+ readonly userName: string | undefined
85
+ readonly userEmail: string | undefined
86
+ readonly userImage: string | undefined
87
+ }): {
88
+ readonly id: string
89
+ readonly tableId: string
90
+ readonly recordId: string
91
+ readonly userId: string
92
+ readonly content: string
93
+ readonly createdAt: Date
94
+ readonly updatedAt: Date
95
+ readonly user: UserMetadataWithOptionalImage | undefined
96
+ } {
97
+ return {
98
+ id: row.id,
99
+ tableId: row.tableId,
100
+ recordId: row.recordId,
101
+ userId: row.userId,
102
+ content: row.content,
103
+ createdAt: row.createdAt,
104
+ updatedAt: row.updatedAt,
105
+ user: extractUserFromRow(row),
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Comment query result type
111
+ */
112
+ type CommentQueryRow = {
113
+ readonly id: string
114
+ readonly tableId: string
115
+ readonly recordId: string
116
+ readonly userId: string
117
+ readonly content: string
118
+ readonly createdAt: Date
119
+ readonly updatedAt: Date
120
+ readonly userName: string | null
121
+ readonly userEmail: string | null
122
+ readonly userImage: string | null
123
+ }
124
+
125
+ /**
126
+ * Comment select fields with user join
127
+ */
128
+ const commentSelectFields = {
129
+ id: recordComments.id,
130
+ tableId: recordComments.tableId,
131
+ recordId: recordComments.recordId,
132
+ userId: recordComments.userId,
133
+ content: recordComments.content,
134
+ createdAt: recordComments.createdAt,
135
+ updatedAt: recordComments.updatedAt,
136
+ userName: users.name,
137
+ userEmail: users.email,
138
+ userImage: users.image,
139
+ }
140
+
141
+ /**
142
+ * Execute comment query with user join
143
+ */
144
+ function executeCommentQuery(commentId: string) {
145
+ return db
146
+ .select(commentSelectFields)
147
+ .from(recordComments)
148
+ .leftJoin(users, eq(recordComments.userId, users.id))
149
+ .where(and(eq(recordComments.id, commentId), isNull(recordComments.deletedAt)))
150
+ .limit(1)
151
+ }
152
+
153
+ /**
154
+ * Get comment with user metadata
155
+ */
156
+ export function getCommentWithUser(config: {
157
+ readonly session: Readonly<Session>
158
+ readonly commentId: string
159
+ }): Effect.Effect<
160
+ | {
161
+ readonly id: string
162
+ readonly tableId: string
163
+ readonly recordId: string
164
+ readonly userId: string
165
+ readonly content: string
166
+ readonly createdAt: Date
167
+ readonly updatedAt: Date
168
+ readonly user: UserMetadataWithOptionalImage | undefined
169
+ }
170
+ | undefined,
171
+ SessionContextError
172
+ > {
173
+ const { commentId } = config
174
+ return Effect.gen(function* () {
175
+ const result = yield* Effect.tryPromise<Array<CommentQueryRow>, SessionContextError>({
176
+ try: () => executeCommentQuery(commentId),
177
+ catch: (error) => new SessionContextError('Failed to get comment', error),
178
+ })
179
+
180
+ if (result.length === 0 || !result[0]) {
181
+ return undefined
182
+ }
183
+
184
+ const row = result[0]
185
+ return transformCommentRow({
186
+ ...row,
187
+ userName: row.userName ?? undefined,
188
+ userEmail: row.userEmail ?? undefined,
189
+ userImage: row.userImage ?? undefined,
190
+ })
191
+ })
192
+ }
193
+
194
+ /**
195
+ * Delete (soft delete) a comment
196
+ */
197
+ export function deleteComment(config: {
198
+ readonly session: Readonly<Session>
199
+ readonly commentId: string
200
+ }): Effect.Effect<void, SessionContextError> {
201
+ const { commentId } = config
202
+ return Effect.tryPromise({
203
+ try: async () => {
204
+ const now = new Date()
205
+
206
+ const result = await db
207
+ .update(recordComments)
208
+ .set({ deletedAt: now, updatedAt: now })
209
+ .where(and(eq(recordComments.id, commentId), isNull(recordComments.deletedAt)))
210
+ .returning()
211
+
212
+ if (result.length === 0) {
213
+ // eslint-disable-next-line functional/no-throw-statements -- Required inside Effect.tryPromise for error propagation
214
+ throw new SessionContextError('Comment not found')
215
+ }
216
+ },
217
+ catch: wrapDatabaseError('Failed to delete comment'),
218
+ })
219
+ }
220
+
221
+ /**
222
+ * Get comment by ID for authorization check
223
+ */
224
+ export function getCommentForAuth(config: {
225
+ readonly session: Readonly<Session>
226
+ readonly commentId: string
227
+ }): Effect.Effect<
228
+ | {
229
+ readonly id: string
230
+ readonly userId: string
231
+ readonly recordId: string
232
+ readonly tableId: string
233
+ }
234
+ | undefined,
235
+ SessionContextError
236
+ > {
237
+ const { commentId } = config
238
+ return Effect.gen(function* () {
239
+ const result = yield* Effect.tryPromise({
240
+ try: () =>
241
+ db
242
+ .select({
243
+ id: recordComments.id,
244
+ userId: recordComments.userId,
245
+ recordId: recordComments.recordId,
246
+ tableId: recordComments.tableId,
247
+ })
248
+ .from(recordComments)
249
+ .where(and(eq(recordComments.id, commentId), isNull(recordComments.deletedAt)))
250
+ .limit(1),
251
+ catch: (error) => new SessionContextError('Failed to get comment', error),
252
+ })
253
+
254
+ if (result.length === 0 || !result[0]) {
255
+ return undefined
256
+ }
257
+
258
+ return result[0]
259
+ })
260
+ }
261
+
262
+ /**
263
+ * Build base comments query with user join
264
+ */
265
+ function buildCommentsQuery(recordId: string) {
266
+ return db
267
+ .select(commentSelectFields)
268
+ .from(recordComments)
269
+ .leftJoin(users, eq(recordComments.userId, users.id))
270
+ .where(and(eq(recordComments.recordId, recordId), isNull(recordComments.deletedAt)))
271
+ }
272
+
273
+ /**
274
+ * Execute list comments query with sorting and pagination
275
+ */
276
+ function executeListCommentsQuery(
277
+ recordId: string,
278
+ options?: {
279
+ readonly limit?: number
280
+ readonly offset?: number
281
+ readonly sortOrder?: 'asc' | 'desc'
282
+ }
283
+ ) {
284
+ const query = buildCommentsQuery(recordId)
285
+
286
+ // Apply sorting (default: DESC for newest first)
287
+ const sortedQuery =
288
+ options?.sortOrder === 'asc'
289
+ ? query.orderBy(asc(recordComments.createdAt), asc(recordComments.id))
290
+ : query.orderBy(desc(recordComments.createdAt), desc(recordComments.id))
291
+
292
+ // Apply pagination
293
+ if (options?.limit !== undefined) {
294
+ const paginatedQuery = sortedQuery.limit(options.limit)
295
+ return options.offset !== undefined ? paginatedQuery.offset(options.offset) : paginatedQuery
296
+ }
297
+
298
+ return sortedQuery
299
+ }
300
+
301
+ /**
302
+ * List all comments for a record
303
+ */
304
+ export function listComments(config: {
305
+ readonly session: Readonly<Session>
306
+ readonly recordId: string
307
+ readonly limit?: number
308
+ readonly offset?: number
309
+ readonly sortOrder?: 'asc' | 'desc'
310
+ }): Effect.Effect<
311
+ readonly {
312
+ readonly id: string
313
+ readonly tableId: string
314
+ readonly recordId: string
315
+ readonly userId: string
316
+ readonly content: string
317
+ readonly createdAt: Date
318
+ readonly updatedAt: Date
319
+ readonly user: UserMetadataWithOptionalImage | undefined
320
+ }[],
321
+ SessionContextError
322
+ > {
323
+ const { recordId, limit, offset, sortOrder } = config
324
+ return Effect.gen(function* () {
325
+ const result = yield* Effect.tryPromise<Array<CommentQueryRow>, SessionContextError>({
326
+ try: () => executeListCommentsQuery(recordId, { limit, offset, sortOrder }),
327
+ catch: (error) => new SessionContextError('Failed to list comments', error),
328
+ })
329
+
330
+ return result.map((row) =>
331
+ transformCommentRow({
332
+ ...row,
333
+ userName: row.userName ?? undefined,
334
+ userEmail: row.userEmail ?? undefined,
335
+ userImage: row.userImage ?? undefined,
336
+ })
337
+ )
338
+ })
339
+ }
340
+
341
+ /**
342
+ * Get total count of comments for a record
343
+ */
344
+ export function getCommentsCount(config: {
345
+ readonly session: Readonly<Session>
346
+ readonly recordId: string
347
+ }): Effect.Effect<number, SessionContextError> {
348
+ const { recordId } = config
349
+ return Effect.gen(function* () {
350
+ const result = yield* Effect.tryPromise<Array<{ count: number }>, SessionContextError>({
351
+ try: () =>
352
+ db
353
+ .select({ count: sql<number>`count(*)::int` })
354
+ .from(recordComments)
355
+ .where(and(eq(recordComments.recordId, recordId), isNull(recordComments.deletedAt))),
356
+ catch: (error) => new SessionContextError('Failed to count comments', error),
357
+ })
358
+
359
+ return result[0]?.count ?? 0
360
+ })
361
+ }
362
+
363
+ /**
364
+ * Update a comment's content
365
+ */
366
+ export function updateComment(config: {
367
+ readonly session: Readonly<Session>
368
+ readonly commentId: string
369
+ readonly content: string
370
+ }): Effect.Effect<
371
+ {
372
+ readonly id: string
373
+ readonly tableId: string
374
+ readonly recordId: string
375
+ readonly userId: string
376
+ readonly content: string
377
+ readonly createdAt: Date
378
+ readonly updatedAt: Date
379
+ },
380
+ SessionContextError
381
+ > {
382
+ const { commentId, content } = config
383
+ return Effect.tryPromise({
384
+ try: async () => {
385
+ const now = new Date()
386
+
387
+ const result = await db
388
+ .update(recordComments)
389
+ .set({ content, updatedAt: now })
390
+ .where(and(eq(recordComments.id, commentId), isNull(recordComments.deletedAt)))
391
+ .returning()
392
+
393
+ if (result.length === 0) {
394
+ // eslint-disable-next-line functional/no-throw-statements -- Required inside Effect.tryPromise for error propagation
395
+ throw new SessionContextError('Comment not found')
396
+ }
397
+
398
+ const comment = result[0]!
399
+ return {
400
+ id: comment.id,
401
+ tableId: comment.tableId,
402
+ recordId: comment.recordId,
403
+ userId: comment.userId,
404
+ content: comment.content,
405
+ createdAt: comment.createdAt,
406
+ updatedAt: comment.updatedAt,
407
+ }
408
+ },
409
+ catch: (error) =>
410
+ error instanceof SessionContextError
411
+ ? error
412
+ : new SessionContextError('Failed to update comment', error),
413
+ })
414
+ }
@@ -0,0 +1,126 @@
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, eq } from 'drizzle-orm'
9
+ import { Effect } from 'effect'
10
+ import { users } from '@/infrastructure/auth/better-auth/schema'
11
+ import { SessionContextError } from '@/infrastructure/database'
12
+ import { db } from '@/infrastructure/database/drizzle'
13
+ import type { Session } from '@/infrastructure/auth/better-auth/schema'
14
+
15
+ /**
16
+ * Build SQL query to check record existence with optional deleted_at filter and owner_id check
17
+ * Admins bypass owner_id filtering to access all records
18
+ *
19
+ * owner_id filtering logic:
20
+ * - Records with owner_id = NULL are accessible to all users (unowned records)
21
+ * - Records with owner_id = <userId> are accessible only to that user (owned records)
22
+ * - Admins can access all records regardless of owner_id
23
+ */
24
+ function buildRecordCheckQuery(params: {
25
+ readonly tableName: string
26
+ readonly recordId: string
27
+ readonly userId: string
28
+ readonly hasDeletedAt: boolean
29
+ readonly hasOwnerId: boolean
30
+ readonly isAdmin: boolean
31
+ }) {
32
+ const { tableName, recordId, userId, hasDeletedAt, hasOwnerId, isAdmin } = params
33
+ // Admins bypass owner_id filtering
34
+ const shouldFilterOwner = hasOwnerId && !isAdmin
35
+
36
+ if (hasDeletedAt && shouldFilterOwner) {
37
+ return sql`SELECT id FROM ${sql.identifier(tableName)} WHERE id = ${recordId} AND deleted_at IS NULL AND (owner_id = ${userId} OR owner_id IS NULL)`
38
+ }
39
+ if (hasDeletedAt) {
40
+ return sql`SELECT id FROM ${sql.identifier(tableName)} WHERE id = ${recordId} AND deleted_at IS NULL`
41
+ }
42
+ if (shouldFilterOwner) {
43
+ return sql`SELECT id FROM ${sql.identifier(tableName)} WHERE id = ${recordId} AND (owner_id = ${userId} OR owner_id IS NULL)`
44
+ }
45
+ return sql`SELECT id FROM ${sql.identifier(tableName)} WHERE id = ${recordId}`
46
+ }
47
+
48
+ /**
49
+ * Check if a record exists in the given table (with owner_id isolation for non-admins)
50
+ * Admins can access all records regardless of owner_id
51
+ */
52
+ export function checkRecordExists(config: {
53
+ readonly session: Readonly<Session>
54
+ readonly tableName: string
55
+ readonly recordId: string
56
+ readonly isAdmin?: boolean
57
+ }): Effect.Effect<boolean, SessionContextError> {
58
+ const { session, tableName, recordId, isAdmin = false } = config
59
+ return Effect.gen(function* () {
60
+ // Check if table has deleted_at column
61
+ const columnsResult = yield* Effect.tryPromise({
62
+ try: () =>
63
+ db.execute(
64
+ sql`SELECT column_name FROM information_schema.columns WHERE table_schema = 'public' AND table_name = ${tableName} AND column_name IN ('deleted_at', 'owner_id')`
65
+ ),
66
+ catch: (error) => new SessionContextError('Failed to check table columns', error),
67
+ })
68
+
69
+ const columns = columnsResult as readonly Record<string, unknown>[]
70
+ const hasDeletedAt = columns.some((row) => row.column_name === 'deleted_at')
71
+ const hasOwnerId = columns.some((row) => row.column_name === 'owner_id')
72
+
73
+ // Check if record exists (with owner_id check for multi-tenancy isolation, bypassed for admins)
74
+ const query = buildRecordCheckQuery({
75
+ tableName,
76
+ recordId,
77
+ userId: session.userId,
78
+ hasDeletedAt,
79
+ hasOwnerId,
80
+ isAdmin,
81
+ })
82
+ const result = yield* Effect.tryPromise({
83
+ try: () => db.execute(query),
84
+ catch: (error) => new SessionContextError('Failed to check record existence', error),
85
+ })
86
+
87
+ return result.length > 0
88
+ })
89
+ }
90
+
91
+ /**
92
+ * Get user by ID
93
+ */
94
+ export function getUserById(config: {
95
+ readonly session: Readonly<Session>
96
+ readonly userId: string
97
+ }): Effect.Effect<
98
+ | {
99
+ readonly id: string
100
+ readonly role: string | undefined
101
+ }
102
+ | undefined,
103
+ SessionContextError
104
+ > {
105
+ const { userId } = config
106
+ return Effect.gen(function* () {
107
+ const result = yield* Effect.tryPromise({
108
+ try: () =>
109
+ db
110
+ .select({ id: users.id, role: users.role })
111
+ .from(users)
112
+ .where(eq(users.id, userId))
113
+ .limit(1),
114
+ catch: (error) => new SessionContextError('Failed to get user', error),
115
+ })
116
+
117
+ if (result.length === 0 || !result[0]) {
118
+ return undefined
119
+ }
120
+
121
+ return {
122
+ id: result[0].id,
123
+ role: result[0].role ?? undefined,
124
+ }
125
+ })
126
+ }
@@ -0,0 +1,58 @@
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 'drizzle-orm'
9
+ import type { SQL } from 'drizzle-orm'
10
+
11
+ /**
12
+ * Build filter conditions for trash list query
13
+ */
14
+ export function buildTrashFilters(
15
+ baseQuery: Readonly<SQL>,
16
+ filters?: readonly {
17
+ readonly field: string
18
+ readonly operator: string
19
+ readonly value: unknown
20
+ }[]
21
+ ): Readonly<SQL> {
22
+ return (filters ?? []).reduce((query, condition) => {
23
+ const { field, operator, value } = condition
24
+ const fieldIdentifier = sql.identifier(field)
25
+
26
+ switch (operator) {
27
+ case 'equals':
28
+ return sql`${query} AND ${fieldIdentifier} = ${value}`
29
+ case 'notEquals':
30
+ return sql`${query} AND ${fieldIdentifier} != ${value}`
31
+ case 'contains':
32
+ return sql`${query} AND ${fieldIdentifier} ILIKE ${'%' + String(value) + '%'}`
33
+ case 'greaterThan':
34
+ return sql`${query} AND ${fieldIdentifier} > ${value}`
35
+ case 'lessThan':
36
+ return sql`${query} AND ${fieldIdentifier} < ${value}`
37
+ case 'greaterThanOrEqual':
38
+ return sql`${query} AND ${fieldIdentifier} >= ${value}`
39
+ case 'lessThanOrEqual':
40
+ return sql`${query} AND ${fieldIdentifier} <= ${value}`
41
+ default:
42
+ return query
43
+ }
44
+ }, baseQuery)
45
+ }
46
+
47
+ /**
48
+ * Add sorting to trash list query
49
+ */
50
+ export function addTrashSorting(query: Readonly<SQL>, sort?: string): Readonly<SQL> {
51
+ if (!sort) return query
52
+
53
+ const [field, order] = sort.split(':')
54
+ if (!field) return query
55
+
56
+ const direction = order?.toLowerCase() === 'desc' ? sql`DESC` : sql`ASC`
57
+ return sql`${query} ORDER BY ${sql.identifier(field)} ${direction}`
58
+ }
@@ -0,0 +1,47 @@
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 { SessionContextError, ValidationError } from '@/infrastructure/database'
9
+
10
+ /* eslint-disable functional/prefer-immutable-types -- Error handler factories for Effect.tryPromise catch: returns mutable Error class instances, parameter signature fixed as (error: unknown) by Effect API */
11
+
12
+ /**
13
+ * Create a catch handler that preserves known error types and wraps unknowns in SessionContextError.
14
+ *
15
+ * Replaces the repeated pattern:
16
+ * ```
17
+ * catch: (error) =>
18
+ * error instanceof SessionContextError ? error
19
+ * : new SessionContextError(message, error)
20
+ * ```
21
+ *
22
+ * @param message - Error message for wrapping unknown errors
23
+ * @returns A catch handler suitable for Effect.tryPromise
24
+ */
25
+ export function wrapDatabaseError(message: string): (error: unknown) => SessionContextError {
26
+ return (error: unknown): SessionContextError =>
27
+ error instanceof SessionContextError ? error : new SessionContextError(message, error)
28
+ }
29
+
30
+ /**
31
+ * Create a catch handler that preserves SessionContextError and ValidationError,
32
+ * wrapping all other errors in SessionContextError.
33
+ *
34
+ * Used in batch operations where ValidationError may propagate from inner Effect programs.
35
+ *
36
+ * @param message - Error message for wrapping unknown errors
37
+ * @returns A catch handler suitable for Effect.tryPromise
38
+ */
39
+ export function wrapDatabaseErrorWithValidation(
40
+ message: string
41
+ ): (error: unknown) => SessionContextError | ValidationError {
42
+ return (error: unknown): SessionContextError | ValidationError => {
43
+ if (error instanceof SessionContextError) return error
44
+ if (error instanceof ValidationError) return error
45
+ return new SessionContextError(message, error)
46
+ }
47
+ }
@@ -0,0 +1,27 @@
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 { DrizzleTransaction } from '@/infrastructure/database'
9
+ import type { SQL } from 'drizzle-orm'
10
+
11
+ /**
12
+ * Execute a SQL query within a transaction and return typed results.
13
+ *
14
+ * Centralizes the type assertion needed because Drizzle's tx.execute()
15
+ * return type doesn't match Record<string, unknown>[]. By keeping the
16
+ * single `as unknown as` cast here, call sites remain cast-free.
17
+ *
18
+ * @param tx - Drizzle transaction
19
+ * @param query - SQL query (from drizzle-orm sql template tag)
20
+ * @returns Typed array of results
21
+ */
22
+ export async function typedExecute<T = Record<string, unknown>>(
23
+ tx: Readonly<DrizzleTransaction>,
24
+ query: Readonly<SQL>
25
+ ): Promise<readonly T[]> {
26
+ return (await tx.execute(query)) as unknown as readonly T[]
27
+ }