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,154 @@
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 { sanitizeTableName } from '../field-utils'
9
+ import type { Table } from '@/domain/models/app/table'
10
+
11
+ /**
12
+ * Generate triggers for created-at fields (set on INSERT, prevent UPDATE)
13
+ * Includes intrinsic created_at column if not explicitly defined
14
+ */
15
+ export const generateCreatedAtTriggers = (table: Table): readonly string[] => {
16
+ const createdAtFields = table.fields.filter((field) => field.type === 'created-at')
17
+ const hasCreatedAtField = table.fields.some((field) => field.name === 'created_at')
18
+
19
+ // Collect all created_at field names
20
+ const fieldNames = [
21
+ ...createdAtFields.map((f) => f.name),
22
+ // Include intrinsic created_at column if not explicitly defined
23
+ ...(!hasCreatedAtField ? ['created_at'] : []),
24
+ ]
25
+
26
+ if (fieldNames.length === 0) return []
27
+
28
+ const sanitized = sanitizeTableName(table.name)
29
+ const setFunctionName = `set_${sanitized}_created_at`
30
+ const setTriggerName = `a_trigger_${sanitized}_set_created_at` // Prefix with 'a_' to ensure it runs before formula triggers
31
+ const preventFunctionName = `prevent_${sanitized}_created_at_update`
32
+ const preventTriggerName = `a_trigger_${sanitized}_created_at_immutable` // Prefix with 'a_' to ensure it runs before formula triggers
33
+
34
+ return [
35
+ // Create trigger function to set created_at on INSERT
36
+ `CREATE OR REPLACE FUNCTION ${setFunctionName}()
37
+ RETURNS TRIGGER AS $$
38
+ BEGIN
39
+ ${fieldNames.map((name) => `IF NEW.${name} IS NULL THEN NEW.${name} = CURRENT_TIMESTAMP; END IF;`).join('\n ')}
40
+ RETURN NEW;
41
+ END;
42
+ $$ LANGUAGE plpgsql`,
43
+ // Create INSERT trigger
44
+ `DROP TRIGGER IF EXISTS ${setTriggerName} ON ${sanitized}`,
45
+ `CREATE TRIGGER ${setTriggerName}
46
+ BEFORE INSERT ON ${sanitized}
47
+ FOR EACH ROW
48
+ EXECUTE FUNCTION ${setFunctionName}()`,
49
+ // Create trigger function to prevent updates
50
+ `CREATE OR REPLACE FUNCTION ${preventFunctionName}()
51
+ RETURNS TRIGGER AS $$
52
+ BEGIN
53
+ ${fieldNames.map((name) => `NEW.${name} = OLD.${name};`).join('\n ')}
54
+ RETURN NEW;
55
+ END;
56
+ $$ LANGUAGE plpgsql`,
57
+ // Create UPDATE trigger
58
+ `DROP TRIGGER IF EXISTS ${preventTriggerName} ON ${sanitized}`,
59
+ `CREATE TRIGGER ${preventTriggerName}
60
+ BEFORE UPDATE ON ${sanitized}
61
+ FOR EACH ROW
62
+ EXECUTE FUNCTION ${preventFunctionName}()`,
63
+ ]
64
+ }
65
+
66
+ /**
67
+ * Generate trigger to prevent updates to autonumber fields (immutability)
68
+ */
69
+ export const generateAutonumberTriggers = (table: Table): readonly string[] => {
70
+ const autonumberFields = table.fields.filter((field) => field.type === 'autonumber')
71
+
72
+ if (autonumberFields.length === 0) return []
73
+
74
+ const sanitized = sanitizeTableName(table.name)
75
+ const fieldNames = autonumberFields.map((f) => f.name)
76
+ const triggerFunctionName = `prevent_${sanitized}_autonumber_update`
77
+ const triggerName = `trigger_${sanitized}_autonumber_immutable`
78
+
79
+ return [
80
+ // Create trigger function
81
+ `CREATE OR REPLACE FUNCTION ${triggerFunctionName}()
82
+ RETURNS TRIGGER AS $$
83
+ BEGIN
84
+ ${fieldNames.map((name) => `NEW.${name} = OLD.${name};`).join('\n ')}
85
+ RETURN NEW;
86
+ END;
87
+ $$ LANGUAGE plpgsql`,
88
+ // Create trigger
89
+ `DROP TRIGGER IF EXISTS ${triggerName} ON ${sanitized}`,
90
+ `CREATE TRIGGER ${triggerName}
91
+ BEFORE UPDATE ON ${sanitized}
92
+ FOR EACH ROW
93
+ EXECUTE FUNCTION ${triggerFunctionName}()`,
94
+ ]
95
+ }
96
+
97
+ /**
98
+ * Generate trigger to automatically update updated-by fields on UPDATE
99
+ */
100
+ export const generateUpdatedByTriggers = (table: Table): readonly string[] => {
101
+ const updatedByFields = table.fields.filter((field) => field.type === 'updated-by')
102
+
103
+ if (updatedByFields.length === 0) return []
104
+
105
+ const sanitized = sanitizeTableName(table.name)
106
+
107
+ return [
108
+ // Create trigger (fires on UPDATE only - updated_by should be NULL on INSERT)
109
+ `DROP TRIGGER IF EXISTS set_updated_by ON ${sanitized}`,
110
+ `CREATE TRIGGER set_updated_by
111
+ BEFORE UPDATE ON ${sanitized}
112
+ FOR EACH ROW
113
+ EXECUTE FUNCTION set_updated_by()`,
114
+ ]
115
+ }
116
+
117
+ /**
118
+ * Generate triggers to automatically set/update updated-at fields on INSERT/UPDATE
119
+ * Includes intrinsic updated_at column if not explicitly defined
120
+ */
121
+ export const generateUpdatedAtTriggers = (table: Table): readonly string[] => {
122
+ const updatedAtFields = table.fields.filter((field) => field.type === 'updated-at')
123
+ const hasUpdatedAtField = table.fields.some((field) => field.name === 'updated_at')
124
+
125
+ // Collect all updated_at field names
126
+ const fieldNames = [
127
+ ...updatedAtFields.map((f) => f.name),
128
+ // Include intrinsic updated_at column if not explicitly defined
129
+ ...(!hasUpdatedAtField ? ['updated_at'] : []),
130
+ ]
131
+
132
+ if (fieldNames.length === 0) return []
133
+
134
+ const sanitized = sanitizeTableName(table.name)
135
+ const triggerFunctionName = `update_${sanitized}_updated_at`
136
+ const triggerName = `a_trigger_${sanitized}_updated_at` // Prefix with 'a_' to ensure it runs before formula triggers
137
+
138
+ return [
139
+ // Create trigger function (handles both INSERT and UPDATE)
140
+ `CREATE OR REPLACE FUNCTION ${triggerFunctionName}()
141
+ RETURNS TRIGGER AS $$
142
+ BEGIN
143
+ ${fieldNames.map((name) => `NEW.${name} = CURRENT_TIMESTAMP;`).join('\n ')}
144
+ RETURN NEW;
145
+ END;
146
+ $$ LANGUAGE plpgsql`,
147
+ // Create trigger for both INSERT and UPDATE
148
+ `DROP TRIGGER IF EXISTS ${triggerName} ON ${sanitized}`,
149
+ `CREATE TRIGGER ${triggerName}
150
+ BEFORE INSERT OR UPDATE ON ${sanitized}
151
+ FOR EACH ROW
152
+ EXECUTE FUNCTION ${triggerFunctionName}()`,
153
+ ]
154
+ }
@@ -0,0 +1,35 @@
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
+ * Database Infrastructure Module
10
+ *
11
+ * Provides database access and ORM utilities.
12
+ * Currently uses Drizzle ORM with PostgreSQL via Bun SQL.
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * import { Database, DatabaseLive } from '@/infrastructure/database'
17
+ * import { users } from '@/infrastructure/database/schema'
18
+ *
19
+ * const program = Effect.gen(function* () {
20
+ * const db = yield* Database
21
+ * const allUsers = yield* Effect.tryPromise(() => db.select().from(users))
22
+ * return allUsers
23
+ * }).pipe(Effect.provide(DatabaseLive))
24
+ * ```
25
+ */
26
+
27
+ export { db, type DrizzleDB, type DrizzleTransaction } from './drizzle/db'
28
+ export { Database, DatabaseLive } from './drizzle/layer'
29
+ export * from './drizzle/schema'
30
+ export {
31
+ SessionContextError,
32
+ ForbiddenError,
33
+ UniqueConstraintViolationError,
34
+ ValidationError,
35
+ } from './session-context'
@@ -0,0 +1,356 @@
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
+ * Expression generators for lookup, rollup, and count fields
10
+ *
11
+ * These functions generate SQL subquery expressions for computed columns
12
+ * used in lookup VIEWs.
13
+ */
14
+
15
+ import { generateSqlCondition } from '../filter-operators'
16
+ import { toSingular, generateJunctionTableName } from '../sql/sql-generators'
17
+ import type { Fields } from '@/domain/models/app/table/fields'
18
+ import type { ViewFilterCondition } from '@/domain/models/app/table/views/filters'
19
+
20
+ /**
21
+ * Build WHERE clause from filter condition
22
+ */
23
+ const buildWhereClause = (filter: ViewFilterCondition, aliasPrefix: string): string => {
24
+ const { field, operator, value } = filter
25
+ const column = `${aliasPrefix}.${field}`
26
+ // Use legacy string escaping mode for backward compatibility
27
+ return generateSqlCondition(column, operator, value, { useEscapeSqlString: true })
28
+ }
29
+
30
+ /**
31
+ * Options for generating lookup expressions
32
+ */
33
+ interface LookupExpressionOptions {
34
+ readonly lookupName: string
35
+ readonly relationshipField: string
36
+ readonly relatedField: string
37
+ readonly filters: ViewFilterCondition | undefined
38
+ readonly tableAlias: string
39
+ readonly actualTableName: string
40
+ }
41
+
42
+ /**
43
+ * Generate reverse lookup expression (one-to-many)
44
+ */
45
+ const generateReverseLookupExpression = (options: LookupExpressionOptions): string => {
46
+ const { lookupName, relationshipField, relatedField, filters, tableAlias, actualTableName } =
47
+ options
48
+ // Infer table name from lookup field name (e.g., "active_tasks" → "tasks")
49
+ const lookupNameParts = lookupName.split('_')
50
+ const relatedTable = lookupNameParts[lookupNameParts.length - 1] ?? actualTableName
51
+
52
+ const alias = `${relatedTable}_for_${lookupName}`
53
+ const baseCondition = `${alias}.${relationshipField} = ${tableAlias}.id`
54
+ const whereConditions = filters
55
+ ? [baseCondition, buildWhereClause(filters, alias)]
56
+ : [baseCondition]
57
+ const whereClause = whereConditions.join(' AND ')
58
+
59
+ return `(
60
+ SELECT STRING_AGG(${alias}.${relatedField}::TEXT, ', ' ORDER BY ${alias}.${relatedField})
61
+ FROM ${relatedTable} AS ${alias}
62
+ WHERE ${whereClause}
63
+ ) AS ${lookupName}`
64
+ }
65
+
66
+ /**
67
+ * Options for many-to-many lookup expressions
68
+ */
69
+ interface ManyToManyLookupOptions {
70
+ readonly lookupName: string
71
+ readonly relatedTable: string
72
+ readonly relatedField: string
73
+ readonly filters: ViewFilterCondition | undefined
74
+ readonly tableAlias: string
75
+ readonly actualTableName: string
76
+ }
77
+
78
+ /**
79
+ * Generate many-to-many lookup expression (through junction table)
80
+ */
81
+ const generateManyToManyLookupExpression = (options: ManyToManyLookupOptions): string => {
82
+ const { lookupName, relatedTable, relatedField, filters, tableAlias, actualTableName } = options
83
+ const alias = `${relatedTable}_for_${lookupName}`
84
+ const junctionTable = generateJunctionTableName(actualTableName, relatedTable)
85
+ const junctionAlias = `junction_${lookupName}`
86
+ const foreignKeyInJunction = `${toSingular(actualTableName)}_id`
87
+ const relatedForeignKeyInJunction = `${toSingular(relatedTable)}_id`
88
+
89
+ const baseCondition = `${junctionAlias}.${foreignKeyInJunction} = ${tableAlias}.id`
90
+ const joinCondition = `${alias}.id = ${junctionAlias}.${relatedForeignKeyInJunction}`
91
+ const whereConditions = filters
92
+ ? [baseCondition, buildWhereClause(filters, alias)]
93
+ : [baseCondition]
94
+ const whereClause = whereConditions.join(' AND ')
95
+
96
+ return `(
97
+ SELECT STRING_AGG(${alias}.${relatedField}::TEXT, ', ' ORDER BY ${alias}.${relatedField})
98
+ FROM ${junctionTable} AS ${junctionAlias}
99
+ INNER JOIN ${relatedTable} AS ${alias} ON ${joinCondition}
100
+ WHERE ${whereClause}
101
+ ) AS ${lookupName}`
102
+ }
103
+
104
+ /**
105
+ * Options for forward lookup expressions
106
+ */
107
+ interface ForwardLookupOptions {
108
+ readonly lookupName: string
109
+ readonly relationshipField: string
110
+ readonly relatedTable: string
111
+ readonly relatedField: string
112
+ readonly filters: ViewFilterCondition | undefined
113
+ readonly tableAlias: string
114
+ }
115
+
116
+ /**
117
+ * Generate forward lookup expression (many-to-one)
118
+ */
119
+ const generateForwardLookupExpression = (options: ForwardLookupOptions): string => {
120
+ const { lookupName, relationshipField, relatedTable, relatedField, filters, tableAlias } = options
121
+ const alias = `${relatedTable}_for_${lookupName}`
122
+
123
+ if (filters) {
124
+ const whereClause = buildWhereClause(filters, alias)
125
+ return `(
126
+ SELECT ${alias}.${relatedField}
127
+ FROM ${relatedTable} AS ${alias}
128
+ WHERE ${alias}.id = ${tableAlias}.${relationshipField} AND ${whereClause}
129
+ ) AS ${lookupName}`
130
+ }
131
+
132
+ // Direct column reference via LEFT JOIN (handled in main VIEW SELECT)
133
+ return `${alias}.${relatedField} AS ${lookupName}`
134
+ }
135
+
136
+ /**
137
+ * Check if field is a many-to-many relationship with related table
138
+ */
139
+ const isManyToManyWithRelatedTable = (
140
+ field: Fields[number]
141
+ ): field is Fields[number] & { relationType: 'many-to-many'; relatedTable: string } =>
142
+ 'relationType' in field &&
143
+ field.relationType === 'many-to-many' &&
144
+ 'relatedTable' in field &&
145
+ typeof field.relatedTable === 'string'
146
+
147
+ /**
148
+ * Check if field has a related table
149
+ */
150
+ const hasRelatedTable = (
151
+ field: Fields[number]
152
+ ): field is Fields[number] & { relatedTable: string } =>
153
+ 'relatedTable' in field && typeof field.relatedTable === 'string'
154
+
155
+ /**
156
+ * Generate lookup column expression
157
+ * Handles forward (many-to-one), reverse (one-to-many), and many-to-many lookups
158
+ */
159
+ export const generateLookupExpression = (
160
+ lookupField: Fields[number] & {
161
+ readonly type: 'lookup'
162
+ readonly relationshipField: string
163
+ readonly relatedField: string
164
+ readonly filters?: ViewFilterCondition
165
+ },
166
+ tableAlias: string,
167
+ allFields: readonly Fields[number][],
168
+ actualTableName: string
169
+ ): string => {
170
+ const { name: lookupName, relationshipField, relatedField, filters } = lookupField
171
+ const relationshipFieldDef = allFields.find((f) => f.name === relationshipField)
172
+ const baseOptions = { lookupName, relationshipField, relatedField, filters, tableAlias }
173
+
174
+ // Reverse lookup (relationship field not in current table)
175
+ if (!relationshipFieldDef || relationshipFieldDef.type !== 'relationship') {
176
+ return generateReverseLookupExpression({ ...baseOptions, actualTableName })
177
+ }
178
+
179
+ // Many-to-many lookup (via junction table)
180
+ if (isManyToManyWithRelatedTable(relationshipFieldDef)) {
181
+ return generateManyToManyLookupExpression({
182
+ ...baseOptions,
183
+ relatedTable: relationshipFieldDef.relatedTable,
184
+ actualTableName,
185
+ })
186
+ }
187
+
188
+ // Forward lookup (many-to-one)
189
+ if (hasRelatedTable(relationshipFieldDef)) {
190
+ return generateForwardLookupExpression({
191
+ ...baseOptions,
192
+ relatedTable: relationshipFieldDef.relatedTable,
193
+ })
194
+ }
195
+
196
+ // Fallback: NULL if relationship is invalid
197
+ return `NULL AS ${lookupName}`
198
+ }
199
+
200
+ /**
201
+ * Map aggregation function name to PostgreSQL aggregate function
202
+ */
203
+ const mapAggregationToPostgres = (aggregation: string, relatedField: string): string => {
204
+ const upperAgg = aggregation.toUpperCase()
205
+
206
+ switch (upperAgg) {
207
+ case 'SUM':
208
+ return `SUM(${relatedField})`
209
+ case 'COUNT':
210
+ return `COUNT(${relatedField})`
211
+ case 'AVG':
212
+ return `AVG(${relatedField})`
213
+ case 'MIN':
214
+ return `MIN(${relatedField})`
215
+ case 'MAX':
216
+ return `MAX(${relatedField})`
217
+ case 'COUNTA':
218
+ return `COUNT(CASE WHEN ${relatedField} IS NOT NULL AND ${relatedField} != '' THEN 1 END)`
219
+ case 'COUNTALL':
220
+ return `COUNT(*)`
221
+ case 'ARRAYUNIQUE':
222
+ return `ARRAY_AGG(DISTINCT ${relatedField} ORDER BY ${relatedField})`
223
+ default:
224
+ return `SUM(${relatedField})`
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Generate default value for empty aggregation results
230
+ */
231
+ const getDefaultValueForAggregation = (aggregation: string): string => {
232
+ const upperAgg = aggregation.toUpperCase()
233
+
234
+ switch (upperAgg) {
235
+ case 'AVG':
236
+ case 'MIN':
237
+ case 'MAX':
238
+ return 'NULL'
239
+ case 'ARRAYUNIQUE':
240
+ return 'ARRAY[]::TEXT[]'
241
+ default:
242
+ return '0'
243
+ }
244
+ }
245
+
246
+ /**
247
+ * Generate rollup column expression with aggregation
248
+ */
249
+ export const generateRollupExpression = (
250
+ rollupField: Fields[number] & {
251
+ readonly type: 'rollup'
252
+ readonly relationshipField: string
253
+ readonly relatedField: string
254
+ readonly aggregation: string
255
+ readonly filters?: ViewFilterCondition
256
+ },
257
+ tableName: string,
258
+ allFields: readonly Fields[number][]
259
+ ): string => {
260
+ const { name: rollupName, relationshipField, relatedField, aggregation, filters } = rollupField
261
+
262
+ const relationshipFieldDef = allFields.find((f) => f.name === relationshipField)
263
+
264
+ if (!relationshipFieldDef || relationshipFieldDef.type !== 'relationship') {
265
+ return `${getDefaultValueForAggregation(aggregation)} AS ${rollupName}`
266
+ }
267
+
268
+ if (
269
+ !('relatedTable' in relationshipFieldDef) ||
270
+ typeof relationshipFieldDef.relatedTable !== 'string'
271
+ ) {
272
+ return `${getDefaultValueForAggregation(aggregation)} AS ${rollupName}`
273
+ }
274
+
275
+ const { relatedTable } = relationshipFieldDef
276
+ const alias = `${relatedTable}_for_${rollupName}`
277
+
278
+ const aggregationExpr = mapAggregationToPostgres(aggregation, `${alias}.${relatedField}`)
279
+ const defaultValue = getDefaultValueForAggregation(aggregation)
280
+
281
+ // Determine the foreign key column in the related table
282
+ const foreignKeyColumn =
283
+ 'foreignKey' in relationshipFieldDef && typeof relationshipFieldDef.foreignKey === 'string'
284
+ ? relationshipFieldDef.foreignKey
285
+ : relationshipField
286
+
287
+ const baseCondition = `${alias}.${foreignKeyColumn} = ${tableName}.id`
288
+ const whereConditions = filters
289
+ ? [baseCondition, buildWhereClause(filters, alias)]
290
+ : [baseCondition]
291
+
292
+ const whereClause = whereConditions.join(' AND ')
293
+
294
+ return `COALESCE(
295
+ (SELECT ${aggregationExpr}
296
+ FROM ${relatedTable} AS ${alias}
297
+ WHERE ${whereClause}),
298
+ ${defaultValue}
299
+ ) AS ${rollupName}`
300
+ }
301
+
302
+ /**
303
+ * Generate count column expression with optional filtering
304
+ * Counts linked records from a relationship field, with optional conditions
305
+ */
306
+ export const generateCountExpression = (
307
+ countField: Fields[number] & {
308
+ readonly type: 'count'
309
+ readonly relationshipField: string
310
+ readonly conditions?: readonly ViewFilterCondition[]
311
+ },
312
+ tableName: string,
313
+ allFields: readonly Fields[number][]
314
+ ): string => {
315
+ const { name: countName, relationshipField, conditions } = countField
316
+
317
+ const relationshipFieldDef = allFields.find((f) => f.name === relationshipField)
318
+
319
+ // Count field must reference a valid relationship field in same table
320
+ if (!relationshipFieldDef || relationshipFieldDef.type !== 'relationship') {
321
+ return `0 AS ${countName}`
322
+ }
323
+
324
+ if (
325
+ !('relatedTable' in relationshipFieldDef) ||
326
+ typeof relationshipFieldDef.relatedTable !== 'string'
327
+ ) {
328
+ return `0 AS ${countName}`
329
+ }
330
+
331
+ const { relatedTable } = relationshipFieldDef
332
+ const alias = `${relatedTable}_for_${countName}`
333
+
334
+ // Determine the foreign key column in the related table
335
+ const foreignKeyColumn =
336
+ 'foreignKey' in relationshipFieldDef && typeof relationshipFieldDef.foreignKey === 'string'
337
+ ? relationshipFieldDef.foreignKey
338
+ : relationshipField
339
+
340
+ // Build WHERE clause with base condition + optional filter conditions
341
+ const baseCondition = `${alias}.${foreignKeyColumn} = ${tableName}.id`
342
+
343
+ // Convert conditions array to WHERE clauses
344
+ const filterConditions = conditions?.map((condition) => buildWhereClause(condition, alias)) ?? []
345
+
346
+ const whereConditions = [baseCondition, ...filterConditions]
347
+ const whereClause = whereConditions.join(' AND ')
348
+
349
+ // Use COALESCE to ensure 0 instead of NULL when no records match
350
+ return `COALESCE(
351
+ (SELECT COUNT(*)
352
+ FROM ${relatedTable} AS ${alias}
353
+ WHERE ${whereClause}),
354
+ 0
355
+ ) AS ${countName}`
356
+ }
@@ -0,0 +1,116 @@
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 { toSingular, generateJunctionTableName } from '../sql/sql-generators'
9
+ import { buildWhereClause } from './lookup-view-helpers'
10
+ import type { ViewFilterCondition } from '@/domain/models/app/table/views/filters'
11
+
12
+ /**
13
+ * Configuration for lookup expression generation
14
+ */
15
+ export type LookupExpressionConfig = {
16
+ readonly lookupName: string
17
+ readonly relationshipField: string
18
+ readonly relatedField: string
19
+ readonly filters: ViewFilterCondition | undefined
20
+ readonly tableAlias: string
21
+ readonly actualTableName: string
22
+ }
23
+
24
+ /**
25
+ * Configuration for many-to-many lookup expression
26
+ */
27
+ export type ManyToManyLookupConfig = {
28
+ readonly lookupName: string
29
+ readonly relatedTable: string
30
+ readonly relatedField: string
31
+ readonly filters: ViewFilterCondition | undefined
32
+ readonly tableAlias: string
33
+ readonly actualTableName: string
34
+ }
35
+
36
+ /**
37
+ * Configuration for forward lookup expression
38
+ */
39
+ export type ForwardLookupConfig = {
40
+ readonly lookupName: string
41
+ readonly relationshipField: string
42
+ readonly relatedTable: string
43
+ readonly relatedField: string
44
+ readonly filters: ViewFilterCondition | undefined
45
+ readonly tableAlias: string
46
+ }
47
+
48
+ /**
49
+ * Generate reverse lookup expression (one-to-many)
50
+ */
51
+ export const generateReverseLookupExpression = (config: LookupExpressionConfig): string => {
52
+ const { lookupName, relationshipField, relatedField, filters, tableAlias, actualTableName } =
53
+ config
54
+ // Infer table name from lookup field name (e.g., "active_tasks" → "tasks")
55
+ const lookupNameParts = lookupName.split('_')
56
+ const relatedTable = lookupNameParts[lookupNameParts.length - 1] ?? actualTableName
57
+
58
+ const alias = `${relatedTable}_for_${lookupName}`
59
+ const baseCondition = `${alias}.${relationshipField} = ${tableAlias}.id`
60
+ const whereConditions = filters
61
+ ? [baseCondition, buildWhereClause(filters, alias)]
62
+ : [baseCondition]
63
+ const whereClause = whereConditions.join(' AND ')
64
+
65
+ return `(
66
+ SELECT STRING_AGG(${alias}.${relatedField}::TEXT, ', ' ORDER BY ${alias}.${relatedField})
67
+ FROM ${relatedTable} AS ${alias}
68
+ WHERE ${whereClause}
69
+ ) AS ${lookupName}`
70
+ }
71
+
72
+ /**
73
+ * Generate many-to-many lookup expression (through junction table)
74
+ */
75
+ export const generateManyToManyLookupExpression = (config: ManyToManyLookupConfig): string => {
76
+ const { lookupName, relatedTable, relatedField, filters, tableAlias, actualTableName } = config
77
+ const alias = `${relatedTable}_for_${lookupName}`
78
+ const junctionTable = generateJunctionTableName(actualTableName, relatedTable)
79
+ const junctionAlias = `junction_${lookupName}`
80
+ const foreignKeyInJunction = `${toSingular(actualTableName)}_id`
81
+ const relatedForeignKeyInJunction = `${toSingular(relatedTable)}_id`
82
+
83
+ const baseCondition = `${junctionAlias}.${foreignKeyInJunction} = ${tableAlias}.id`
84
+ const joinCondition = `${alias}.id = ${junctionAlias}.${relatedForeignKeyInJunction}`
85
+ const whereConditions = filters
86
+ ? [baseCondition, buildWhereClause(filters, alias)]
87
+ : [baseCondition]
88
+ const whereClause = whereConditions.join(' AND ')
89
+
90
+ return `(
91
+ SELECT STRING_AGG(${alias}.${relatedField}::TEXT, ', ' ORDER BY ${alias}.${relatedField})
92
+ FROM ${junctionTable} AS ${junctionAlias}
93
+ INNER JOIN ${relatedTable} AS ${alias} ON ${joinCondition}
94
+ WHERE ${whereClause}
95
+ ) AS ${lookupName}`
96
+ }
97
+
98
+ /**
99
+ * Generate forward lookup expression (many-to-one)
100
+ */
101
+ export const generateForwardLookupExpression = (config: ForwardLookupConfig): string => {
102
+ const { lookupName, relationshipField, relatedTable, relatedField, filters, tableAlias } = config
103
+ const alias = `${relatedTable}_for_${lookupName}`
104
+
105
+ if (filters) {
106
+ const whereClause = buildWhereClause(filters, alias)
107
+ return `(
108
+ SELECT ${alias}.${relatedField}
109
+ FROM ${relatedTable} AS ${alias}
110
+ WHERE ${alias}.id = ${tableAlias}.${relationshipField} AND ${whereClause}
111
+ ) AS ${lookupName}`
112
+ }
113
+
114
+ // Direct column reference via LEFT JOIN (handled in main VIEW SELECT)
115
+ return `${alias}.${relatedField} AS ${lookupName}`
116
+ }