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,316 @@
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 { and, between, count, countDistinct, eq, inArray, lt, or, sql } from 'drizzle-orm'
9
+ import { Effect, Layer } from 'effect'
10
+ import {
11
+ AnalyticsRepository,
12
+ AnalyticsDatabaseError,
13
+ } from '@/application/ports/repositories/analytics-repository'
14
+ import { db } from '@/infrastructure/database'
15
+ import { analyticsPageViews } from '@/infrastructure/database/drizzle/schema/analytics-page-views'
16
+ import type { AnalyticsQueryParams } from '@/application/ports/repositories/analytics-repository'
17
+
18
+ /**
19
+ * Build the common WHERE clause for analytics queries
20
+ *
21
+ * Matches both the specified appName AND 'default' to support
22
+ * direct SQL inserts (which use the schema default 'default').
23
+ */
24
+ const whereClause = (params: AnalyticsQueryParams) =>
25
+ and(
26
+ inArray(analyticsPageViews.appName, [params.appName, 'default']),
27
+ between(analyticsPageViews.timestamp, params.from, params.to)
28
+ )
29
+
30
+ /**
31
+ * Map granularity to PostgreSQL DATE_TRUNC interval
32
+ */
33
+ const granularityToInterval = (granularity: string): string => {
34
+ const map: Record<string, string> = {
35
+ hour: 'hour',
36
+ day: 'day',
37
+ week: 'week',
38
+ month: 'month',
39
+ }
40
+ return map[granularity] ?? 'day'
41
+ }
42
+
43
+ /**
44
+ * Compute percentage breakdown from count results
45
+ */
46
+ const computePercentages = (
47
+ rows: readonly { readonly name: string | null; readonly count: number }[]
48
+ ): readonly { readonly name: string; readonly count: number; readonly percentage: number }[] => {
49
+ const total = rows.reduce((sum, row) => sum + row.count, 0)
50
+ if (total === 0) return []
51
+ return rows.map((row) => ({
52
+ name: row.name ?? 'Unknown',
53
+ count: row.count,
54
+ percentage: Math.round((row.count / total) * 10_000) / 100,
55
+ }))
56
+ }
57
+
58
+ /**
59
+ * Analytics Repository Implementation
60
+ *
61
+ * Uses Drizzle ORM query builder with raw SQL for aggregations.
62
+ * All queries are parameterized (SQL injection safe).
63
+ */
64
+ export const AnalyticsRepositoryLive = Layer.succeed(AnalyticsRepository, {
65
+ recordPageView: (input) =>
66
+ Effect.tryPromise({
67
+ try: async () => {
68
+ // eslint-disable-next-line functional/no-expression-statements
69
+ await db.insert(analyticsPageViews).values({
70
+ appName: input.appName,
71
+ pagePath: input.pagePath,
72
+ pageTitle: input.pageTitle,
73
+ visitorHash: input.visitorHash,
74
+ sessionHash: input.sessionHash,
75
+ isEntrance: input.isEntrance,
76
+ referrerUrl: input.referrerUrl,
77
+ referrerDomain: input.referrerDomain,
78
+ utmSource: input.utmSource,
79
+ utmMedium: input.utmMedium,
80
+ utmCampaign: input.utmCampaign,
81
+ utmContent: input.utmContent,
82
+ utmTerm: input.utmTerm,
83
+ deviceType: input.deviceType,
84
+ browserName: input.browserName,
85
+ osName: input.osName,
86
+ language: input.language,
87
+ screenWidth: input.screenWidth,
88
+ screenHeight: input.screenHeight,
89
+ })
90
+ },
91
+ catch: (error) => new AnalyticsDatabaseError({ cause: error }),
92
+ }),
93
+
94
+ getSummary: (params) =>
95
+ Effect.tryPromise({
96
+ try: async () => {
97
+ const result = await db
98
+ .select({
99
+ pageViews: count(),
100
+ uniqueVisitors: countDistinct(analyticsPageViews.visitorHash),
101
+ sessions: countDistinct(analyticsPageViews.sessionHash),
102
+ })
103
+ .from(analyticsPageViews)
104
+ .where(whereClause(params))
105
+
106
+ const row = result[0]
107
+ return {
108
+ pageViews: row?.pageViews ?? 0,
109
+ uniqueVisitors: row?.uniqueVisitors ?? 0,
110
+ sessions: row?.sessions ?? 0,
111
+ }
112
+ },
113
+ catch: (error) => new AnalyticsDatabaseError({ cause: error }),
114
+ }),
115
+
116
+ getTimeSeries: (params) =>
117
+ Effect.tryPromise({
118
+ try: async () => {
119
+ const interval = granularityToInterval(params.granularity)
120
+ const rows = await db
121
+ .select({
122
+ period:
123
+ sql<string>`DATE_TRUNC(${sql.raw(`'${interval}'`)}, ${analyticsPageViews.timestamp})::text`.as(
124
+ 'period'
125
+ ),
126
+ pageViews: count(),
127
+ uniqueVisitors: countDistinct(analyticsPageViews.visitorHash),
128
+ sessions: countDistinct(analyticsPageViews.sessionHash),
129
+ })
130
+ .from(analyticsPageViews)
131
+ .where(whereClause(params))
132
+ .groupBy(sql`DATE_TRUNC(${sql.raw(`'${interval}'`)}, ${analyticsPageViews.timestamp})`)
133
+ .orderBy(sql`DATE_TRUNC(${sql.raw(`'${interval}'`)}, ${analyticsPageViews.timestamp})`)
134
+
135
+ return rows.map((row) => ({
136
+ period: row.period,
137
+ pageViews: row.pageViews,
138
+ uniqueVisitors: row.uniqueVisitors,
139
+ sessions: row.sessions,
140
+ }))
141
+ },
142
+ catch: (error) => new AnalyticsDatabaseError({ cause: error }),
143
+ }),
144
+
145
+ getTopPages: (params) =>
146
+ Effect.tryPromise({
147
+ try: async () => {
148
+ const rows = await db
149
+ .select({
150
+ path: analyticsPageViews.pagePath,
151
+ pageViews: count(),
152
+ uniqueVisitors: countDistinct(analyticsPageViews.visitorHash),
153
+ })
154
+ .from(analyticsPageViews)
155
+ .where(whereClause(params))
156
+ .groupBy(analyticsPageViews.pagePath)
157
+ .orderBy(sql`count(*) DESC`)
158
+
159
+ return rows.map((row) => ({
160
+ path: row.path,
161
+ pageViews: row.pageViews,
162
+ uniqueVisitors: row.uniqueVisitors,
163
+ }))
164
+ },
165
+ catch: (error) => new AnalyticsDatabaseError({ cause: error }),
166
+ }),
167
+
168
+ getTopReferrers: (params) =>
169
+ Effect.tryPromise({
170
+ try: async () => {
171
+ const rows = await db
172
+ .select({
173
+ domain: analyticsPageViews.referrerDomain,
174
+ pageViews: count(),
175
+ uniqueVisitors: countDistinct(analyticsPageViews.visitorHash),
176
+ })
177
+ .from(analyticsPageViews)
178
+ .where(
179
+ and(
180
+ whereClause(params),
181
+ // Exclude UTM campaign traffic (belongs in /api/analytics/campaigns)
182
+ or(
183
+ // Include rows with referrer_domain set
184
+ sql`${analyticsPageViews.referrerDomain} IS NOT NULL`,
185
+ // Include direct traffic (NULL referrer AND NULL campaign)
186
+ and(
187
+ sql`${analyticsPageViews.referrerDomain} IS NULL`,
188
+ sql`${analyticsPageViews.utmCampaign} IS NULL`
189
+ )
190
+ )
191
+ )
192
+ )
193
+ .groupBy(analyticsPageViews.referrerDomain)
194
+ .orderBy(sql`count(*) DESC`)
195
+
196
+ return rows.map((row) => ({
197
+ // eslint-disable-next-line unicorn/no-null -- null represents direct traffic (no referrer)
198
+ domain: row.domain ?? null,
199
+ pageViews: row.pageViews,
200
+ uniqueVisitors: row.uniqueVisitors,
201
+ }))
202
+ },
203
+ catch: (error) => new AnalyticsDatabaseError({ cause: error }),
204
+ }),
205
+
206
+ getDevices: (params) =>
207
+ Effect.tryPromise({
208
+ try: async () => {
209
+ const condition = whereClause(params)
210
+
211
+ // Device type breakdown
212
+ const deviceRows = await db
213
+ .select({
214
+ name: analyticsPageViews.deviceType,
215
+ count: count(),
216
+ })
217
+ .from(analyticsPageViews)
218
+ .where(condition)
219
+ .groupBy(analyticsPageViews.deviceType)
220
+ .orderBy(sql`count(*) DESC`)
221
+
222
+ // Browser breakdown
223
+ const browserRows = await db
224
+ .select({
225
+ name: analyticsPageViews.browserName,
226
+ count: count(),
227
+ })
228
+ .from(analyticsPageViews)
229
+ .where(condition)
230
+ .groupBy(analyticsPageViews.browserName)
231
+ .orderBy(sql`count(*) DESC`)
232
+
233
+ // OS breakdown
234
+ const osRows = await db
235
+ .select({
236
+ name: analyticsPageViews.osName,
237
+ count: count(),
238
+ })
239
+ .from(analyticsPageViews)
240
+ .where(condition)
241
+ .groupBy(analyticsPageViews.osName)
242
+ .orderBy(sql`count(*) DESC`)
243
+
244
+ return {
245
+ deviceTypes: computePercentages(deviceRows),
246
+ browsers: computePercentages(browserRows),
247
+ operatingSystems: computePercentages(osRows),
248
+ }
249
+ },
250
+ catch: (error) => new AnalyticsDatabaseError({ cause: error }),
251
+ }),
252
+
253
+ getCampaigns: (params) =>
254
+ Effect.tryPromise({
255
+ try: async () => {
256
+ const rows = await db
257
+ .select({
258
+ source: analyticsPageViews.utmSource,
259
+ medium: analyticsPageViews.utmMedium,
260
+ campaign: analyticsPageViews.utmCampaign,
261
+ pageViews: count(),
262
+ uniqueVisitors: countDistinct(analyticsPageViews.visitorHash),
263
+ })
264
+ .from(analyticsPageViews)
265
+ .where(
266
+ and(
267
+ whereClause(params),
268
+ // Only include rows that have at least one UTM parameter
269
+ sql`(${analyticsPageViews.utmSource} IS NOT NULL OR ${analyticsPageViews.utmMedium} IS NOT NULL OR ${analyticsPageViews.utmCampaign} IS NOT NULL)`
270
+ )
271
+ )
272
+ .groupBy(
273
+ analyticsPageViews.utmSource,
274
+ analyticsPageViews.utmMedium,
275
+ analyticsPageViews.utmCampaign
276
+ )
277
+ .orderBy(sql`count(*) DESC`)
278
+
279
+ return rows.map((row) => ({
280
+ // eslint-disable-next-line unicorn/no-null -- null represents missing UTM parameter
281
+ source: row.source ?? null,
282
+ // eslint-disable-next-line unicorn/no-null -- null represents missing UTM parameter
283
+ medium: row.medium ?? null,
284
+ // eslint-disable-next-line unicorn/no-null -- null represents missing UTM parameter
285
+ campaign: row.campaign ?? null,
286
+ pageViews: row.pageViews,
287
+ uniqueVisitors: row.uniqueVisitors,
288
+ }))
289
+ },
290
+ catch: (error) => new AnalyticsDatabaseError({ cause: error }),
291
+ }),
292
+
293
+ deleteOlderThan: (appName, cutoff) =>
294
+ Effect.tryPromise({
295
+ try: async () => {
296
+ const result = await db
297
+ .delete(analyticsPageViews)
298
+ .where(
299
+ and(
300
+ // Delete records for this app OR records with the default app name
301
+ // This ensures test data inserted via the view (which gets 'default' app_name)
302
+ // is cleaned up by retention logic regardless of the actual app name
303
+ or(
304
+ eq(analyticsPageViews.appName, appName),
305
+ eq(analyticsPageViews.appName, 'default')
306
+ ),
307
+ lt(analyticsPageViews.timestamp, cutoff)
308
+ )
309
+ )
310
+ .returning({ id: analyticsPageViews.id })
311
+
312
+ return result.length
313
+ },
314
+ catch: (error) => new AnalyticsDatabaseError({ cause: error }),
315
+ }),
316
+ })
@@ -0,0 +1,42 @@
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 { eq } from 'drizzle-orm'
9
+ import { Effect, Layer } from 'effect'
10
+ import { AuthRepository, AuthDatabaseError } from '@/application/ports/repositories/auth-repository'
11
+ import { users } from '@/infrastructure/auth/better-auth/schema'
12
+ import { db } from '@/infrastructure/database'
13
+
14
+ /**
15
+ * Auth Repository Implementation
16
+ *
17
+ * Provides database operations for auth-related user management.
18
+ * Both methods operate on the Better Auth `users` table via Drizzle ORM.
19
+ */
20
+ export const AuthRepositoryLive = Layer.succeed(AuthRepository, {
21
+ verifyUserEmail: (userId: string) =>
22
+ Effect.tryPromise({
23
+ try: () => db.update(users).set({ emailVerified: true }).where(eq(users.id, userId)),
24
+ catch: (error) => new AuthDatabaseError({ cause: error }),
25
+ }).pipe(Effect.asVoid),
26
+
27
+ getUserRole: (userId: string) =>
28
+ Effect.gen(function* () {
29
+ const result = yield* Effect.tryPromise({
30
+ try: async () => {
31
+ return await db
32
+ .select({ role: users.role })
33
+ .from(users)
34
+ .where(eq(users.id, userId))
35
+ .limit(1)
36
+ },
37
+ catch: (error) => new AuthDatabaseError({ cause: error }),
38
+ })
39
+
40
+ return result[0]?.role ?? undefined
41
+ }),
42
+ })
@@ -0,0 +1,29 @@
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 { Layer } from 'effect'
9
+ import { BatchRepository } from '@/application/ports/repositories/batch-repository'
10
+ import {
11
+ batchCreateRecords,
12
+ batchUpdateRecords,
13
+ batchDeleteRecords,
14
+ batchRestoreRecords,
15
+ upsertRecords,
16
+ } from '@/infrastructure/database/table-queries'
17
+
18
+ /**
19
+ * Live implementation of BatchRepository using table-queries infrastructure
20
+ *
21
+ * Maps port method names to infrastructure function names.
22
+ */
23
+ export const BatchRepositoryLive = Layer.succeed(BatchRepository, {
24
+ batchCreate: batchCreateRecords,
25
+ batchUpdate: batchUpdateRecords,
26
+ batchDelete: batchDeleteRecords,
27
+ batchRestore: batchRestoreRecords,
28
+ upsert: upsertRecords,
29
+ })
@@ -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
+ import { Layer } from 'effect'
9
+ import { CommentRepository } from '@/application/ports/repositories/comment-repository'
10
+ import {
11
+ createComment,
12
+ getCommentWithUser,
13
+ getCommentForAuth,
14
+ deleteComment,
15
+ listComments,
16
+ getCommentsCount,
17
+ updateComment,
18
+ } from '@/infrastructure/database/table-queries/query-helpers/comment-queries'
19
+ import {
20
+ checkRecordExists,
21
+ getUserById,
22
+ } from '@/infrastructure/database/table-queries/query-helpers/record-validation-queries'
23
+
24
+ /**
25
+ * Live implementation of CommentRepository using comment-queries infrastructure
26
+ *
27
+ * Maps port method names to infrastructure function names.
28
+ */
29
+ export const CommentRepositoryLive = Layer.succeed(CommentRepository, {
30
+ create: createComment,
31
+ getWithUser: getCommentWithUser,
32
+ checkRecordExists,
33
+ getForAuth: getCommentForAuth,
34
+ getUserById,
35
+ remove: deleteComment,
36
+ list: listComments,
37
+ getCount: getCommentsCount,
38
+ update: updateComment,
39
+ })
@@ -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 { Layer } from 'effect'
9
+ import { TableRepository } from '@/application/ports/repositories/table-repository'
10
+ import {
11
+ listRecords,
12
+ listTrash,
13
+ getRecord,
14
+ createRecord,
15
+ updateRecord,
16
+ deleteRecord,
17
+ permanentlyDeleteRecord,
18
+ restoreRecord,
19
+ computeAggregations,
20
+ } from '@/infrastructure/database/table-queries'
21
+
22
+ /**
23
+ * Live implementation of TableRepository using table-queries infrastructure
24
+ *
25
+ * Delegates all operations to the existing database query functions.
26
+ * Session types are structurally compatible (UserSession ≅ Session).
27
+ */
28
+ export const TableRepositoryLive = Layer.succeed(TableRepository, {
29
+ listRecords,
30
+ listTrash,
31
+ getRecord,
32
+ createRecord,
33
+ updateRecord,
34
+ deleteRecord,
35
+ permanentlyDeleteRecord,
36
+ restoreRecord,
37
+ computeAggregations,
38
+ })
@@ -0,0 +1,142 @@
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
+ * Schema Dependency Sorting Utilities
10
+ *
11
+ * Functions for topological sorting of tables by foreign key dependencies.
12
+ * Used by schema-initializer.ts to ensure tables are created in correct order.
13
+ */
14
+
15
+ import { isRelationshipField } from '../sql/sql-generators'
16
+ import type { Table } from '@/domain/models/app/table'
17
+
18
+ /**
19
+ * Detect tables involved in circular dependencies with at least one optional FK.
20
+ * These tables can use the INSERT-UPDATE pattern and should have FK constraints
21
+ * added after all tables are created.
22
+ *
23
+ * @param tables - Array of tables to check
24
+ * @returns Set of table names involved in resolvable circular dependencies
25
+ */
26
+ export const detectCircularDependenciesWithOptionalFK = (
27
+ tables: readonly Table[]
28
+ ): ReadonlySet<string> => {
29
+ const tablesByName = new Map(tables.map((t) => [t.name, t]))
30
+ // Collect circular table names (functional construction)
31
+ const circularTableNames = tables.flatMap((table) => {
32
+ const optionalRelationships = table.fields.filter(
33
+ (field): field is typeof field & { relatedTable: string } =>
34
+ isRelationshipField(field) &&
35
+ field.relatedTable !== table.name && // Exclude self-references
36
+ field.required === false // Explicitly optional FK (allows NULL)
37
+ )
38
+
39
+ return optionalRelationships.flatMap((field) => {
40
+ const relatedTableName = field.relatedTable
41
+ const relatedTable = tablesByName.get(relatedTableName)
42
+
43
+ if (!relatedTable) return []
44
+
45
+ // Check if related table also has a relationship back to this table
46
+ const hasReverseRelationship = relatedTable.fields.some(
47
+ (f) => isRelationshipField(f) && f.relatedTable === table.name
48
+ )
49
+
50
+ return hasReverseRelationship ? [table.name, relatedTableName] : []
51
+ })
52
+ })
53
+
54
+ return new Set(circularTableNames)
55
+ }
56
+
57
+ /**
58
+ * Sort tables by foreign key dependencies using topological sort
59
+ * Tables with no dependencies come first, tables with dependencies come after their referenced tables
60
+ *
61
+ * This ensures that when we CREATE TABLE statements, referenced tables exist before
62
+ * tables that reference them via foreign keys.
63
+ *
64
+ * Algorithm: Kahn's algorithm for topological sorting (functional implementation)
65
+ * - Build dependency graph (which tables does each table depend on)
66
+ * - Process tables with no dependencies first
67
+ * - Remove processed tables from dependency lists
68
+ * - Repeat until all tables are processed
69
+ *
70
+ * Handles circular dependencies by detecting them and keeping original order for those tables.
71
+ *
72
+ * @param tables - Array of tables to sort
73
+ * @returns Tables sorted by dependency order (no dependencies first)
74
+ */
75
+ export const sortTablesByDependencies = (tables: readonly Table[]): readonly Table[] => {
76
+ // Build dependency map: tableName -> Set of tables it depends on
77
+ const tableMap = new Map(tables.map((t) => [t.name, t]))
78
+
79
+ const initialDeps = new Map(
80
+ tables.map((table) => {
81
+ const deps = new Set(
82
+ table.fields
83
+ .filter(isRelationshipField)
84
+ .map((f) => f.relatedTable)
85
+ .filter((name): name is string => name !== undefined && name !== table.name)
86
+ )
87
+ return [table.name, deps]
88
+ })
89
+ )
90
+
91
+ // Recursive helper to process tables in dependency order
92
+ const processTable = (
93
+ current: string,
94
+ remaining: ReadonlyMap<string, Set<string>>,
95
+ sorted: readonly Table[]
96
+ ): readonly Table[] => {
97
+ const table = tableMap.get(current)
98
+ if (!table) return sorted
99
+
100
+ // Add current table to sorted list
101
+ const newSorted = [...sorted, table]
102
+
103
+ // Remove current table from all dependency sets
104
+ const updated = new Map(
105
+ Array.from(remaining.entries()).map(([name, deps]) => {
106
+ const newDeps = new Set(deps)
107
+ // eslint-disable-next-line functional/immutable-data, functional/no-expression-statements, drizzle/enforce-delete-with-where -- Topological sort requires working copy mutation for efficiency; drizzle false positive (Set.delete not DB)
108
+ newDeps.delete(current)
109
+ return [name, newDeps]
110
+ })
111
+ )
112
+
113
+ // Remove current table from remaining
114
+ // eslint-disable-next-line functional/immutable-data, functional/no-expression-statements, drizzle/enforce-delete-with-where -- Topological sort requires working copy mutation for efficiency; drizzle false positive (Map.delete not DB)
115
+ updated.delete(current)
116
+
117
+ // Find next table with no dependencies
118
+ const next = Array.from(updated.entries()).find(([, deps]) => deps.size === 0)
119
+
120
+ if (next) {
121
+ return processTable(next[0], updated, newSorted)
122
+ }
123
+
124
+ // No more tables with zero dependencies - check for remaining tables
125
+ if (updated.size > 0) {
126
+ // Circular dependency or remaining tables - add in original order
127
+ return [...newSorted, ...tables.filter((t) => !newSorted.includes(t) && updated.has(t.name))]
128
+ }
129
+
130
+ return newSorted
131
+ }
132
+
133
+ // Find first table with no dependencies
134
+ const first = Array.from(initialDeps.entries()).find(([, deps]) => deps.size === 0)
135
+
136
+ if (first) {
137
+ return processTable(first[0], initialDeps, [])
138
+ }
139
+
140
+ // All tables have dependencies (circular) - return original order
141
+ return tables
142
+ }