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,403 @@
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 {
10
+ generateReverseLookupExpression,
11
+ generateManyToManyLookupExpression,
12
+ generateForwardLookupExpression,
13
+ } from './lookup-expressions'
14
+ import {
15
+ buildWhereClause,
16
+ mapAggregationToPostgres,
17
+ getDefaultValueForAggregation,
18
+ } from './lookup-view-helpers'
19
+ import {
20
+ getBaseFields,
21
+ generateInsertTrigger,
22
+ generateUpdateTrigger,
23
+ generateDeleteTrigger,
24
+ } from './lookup-view-triggers'
25
+ import type { Table } from '@/domain/models/app/table'
26
+ import type { Fields } from '@/domain/models/app/table/fields'
27
+ import type { ViewFilterCondition } from '@/domain/models/app/table/views/filters'
28
+
29
+ /**
30
+ * Check if a field is a lookup field
31
+ */
32
+ const isLookupField = (
33
+ field: Fields[number]
34
+ ): field is Fields[number] & {
35
+ type: 'lookup'
36
+ relationshipField: string
37
+ relatedField: string
38
+ filters?: ViewFilterCondition
39
+ } =>
40
+ field.type === 'lookup' &&
41
+ 'relationshipField' in field &&
42
+ 'relatedField' in field &&
43
+ typeof field.relationshipField === 'string' &&
44
+ typeof field.relatedField === 'string'
45
+
46
+ /**
47
+ * Check if a field is a rollup field
48
+ */
49
+ const isRollupField = (
50
+ field: Fields[number]
51
+ ): field is Fields[number] & {
52
+ type: 'rollup'
53
+ relationshipField: string
54
+ relatedField: string
55
+ aggregation: string
56
+ filters?: ViewFilterCondition
57
+ } =>
58
+ field.type === 'rollup' &&
59
+ 'relationshipField' in field &&
60
+ 'relatedField' in field &&
61
+ 'aggregation' in field &&
62
+ typeof field.relationshipField === 'string' &&
63
+ typeof field.relatedField === 'string' &&
64
+ typeof field.aggregation === 'string'
65
+
66
+ /**
67
+ * Check if a field is a count field
68
+ */
69
+ const isCountField = (
70
+ field: Fields[number]
71
+ ): field is Fields[number] & {
72
+ type: 'count'
73
+ relationshipField: string
74
+ conditions?: readonly ViewFilterCondition[]
75
+ } =>
76
+ field.type === 'count' &&
77
+ 'relationshipField' in field &&
78
+ typeof field.relationshipField === 'string'
79
+
80
+ /**
81
+ * Check if a table has any lookup fields
82
+ */
83
+ export const hasLookupFields = (table: Table): boolean =>
84
+ table.fields.some((field) => isLookupField(field))
85
+
86
+ /**
87
+ * Check if a table has any rollup fields
88
+ */
89
+ export const hasRollupFields = (table: Table): boolean =>
90
+ table.fields.some((field) => isRollupField(field))
91
+
92
+ /**
93
+ * Check if a table has any count fields
94
+ */
95
+ export const hasCountFields = (table: Table): boolean =>
96
+ table.fields.some((field) => isCountField(field))
97
+
98
+ /** Check if relationship field is many-to-many with valid related table */
99
+ const isManyToMany = (field: Fields[number]): field is Fields[number] & { relatedTable: string } =>
100
+ 'relationType' in field &&
101
+ field.relationType === 'many-to-many' &&
102
+ 'relatedTable' in field &&
103
+ typeof field.relatedTable === 'string'
104
+
105
+ /** Check if relationship field has valid related table */
106
+ const hasRelatedTable = (
107
+ field: Fields[number]
108
+ ): field is Fields[number] & { relatedTable: string } =>
109
+ 'relatedTable' in field && typeof field.relatedTable === 'string'
110
+
111
+ /**
112
+ * Generate lookup column expression
113
+ * Handles forward (many-to-one), reverse (one-to-many), and many-to-many lookups
114
+ */
115
+ const generateLookupExpression = (
116
+ lookupField: Fields[number] & {
117
+ readonly type: 'lookup'
118
+ readonly relationshipField: string
119
+ readonly relatedField: string
120
+ readonly filters?: ViewFilterCondition
121
+ },
122
+ tableAlias: string,
123
+ allFields: readonly Fields[number][],
124
+ actualTableName: string
125
+ ): string => {
126
+ const { name: lookupName, relationshipField, relatedField, filters } = lookupField
127
+ const relationshipFieldDef = allFields.find((f) => f.name === relationshipField)
128
+
129
+ // Reverse lookup (relationship field not in current table)
130
+ if (!relationshipFieldDef || relationshipFieldDef.type !== 'relationship') {
131
+ return generateReverseLookupExpression({
132
+ lookupName,
133
+ relationshipField,
134
+ relatedField,
135
+ filters,
136
+ tableAlias,
137
+ actualTableName,
138
+ })
139
+ }
140
+
141
+ // Many-to-many lookup (via junction table)
142
+ if (isManyToMany(relationshipFieldDef)) {
143
+ return generateManyToManyLookupExpression({
144
+ lookupName,
145
+ relatedTable: relationshipFieldDef.relatedTable,
146
+ relatedField,
147
+ filters,
148
+ tableAlias,
149
+ actualTableName,
150
+ })
151
+ }
152
+
153
+ // Forward lookup (many-to-one)
154
+ if (hasRelatedTable(relationshipFieldDef)) {
155
+ return generateForwardLookupExpression({
156
+ lookupName,
157
+ relationshipField,
158
+ relatedTable: relationshipFieldDef.relatedTable,
159
+ relatedField,
160
+ filters,
161
+ tableAlias,
162
+ })
163
+ }
164
+
165
+ // Fallback: NULL if relationship is invalid
166
+ return `NULL AS ${lookupName}`
167
+ }
168
+
169
+ /**
170
+ * Generate rollup column expression with aggregation
171
+ */
172
+ const generateRollupExpression = (
173
+ rollupField: Fields[number] & {
174
+ readonly type: 'rollup'
175
+ readonly relationshipField: string
176
+ readonly relatedField: string
177
+ readonly aggregation: string
178
+ readonly filters?: ViewFilterCondition
179
+ },
180
+ tableName: string,
181
+ allFields: readonly Fields[number][]
182
+ ): string => {
183
+ const { name: rollupName, relationshipField, relatedField, aggregation, filters } = rollupField
184
+
185
+ const relationshipFieldDef = allFields.find((f) => f.name === relationshipField)
186
+
187
+ if (!relationshipFieldDef || relationshipFieldDef.type !== 'relationship') {
188
+ return `${getDefaultValueForAggregation(aggregation)} AS ${rollupName}`
189
+ }
190
+
191
+ if (
192
+ !('relatedTable' in relationshipFieldDef) ||
193
+ typeof relationshipFieldDef.relatedTable !== 'string'
194
+ ) {
195
+ return `${getDefaultValueForAggregation(aggregation)} AS ${rollupName}`
196
+ }
197
+
198
+ const { relatedTable } = relationshipFieldDef
199
+ const alias = `${relatedTable}_for_${rollupName}`
200
+
201
+ const aggregationExpr = mapAggregationToPostgres(aggregation, `${alias}.${relatedField}`)
202
+ const defaultValue = getDefaultValueForAggregation(aggregation)
203
+
204
+ // Determine the foreign key column in the related table
205
+ // For one-to-many relationships, the foreignKey property specifies the FK column in the related table
206
+ // For many-to-one relationships, the relationship field itself is the FK column
207
+ const foreignKeyColumn =
208
+ 'foreignKey' in relationshipFieldDef && typeof relationshipFieldDef.foreignKey === 'string'
209
+ ? relationshipFieldDef.foreignKey
210
+ : relationshipField
211
+
212
+ const baseCondition = `${alias}.${foreignKeyColumn} = ${tableName}.id`
213
+ const whereConditions = filters
214
+ ? [baseCondition, buildWhereClause(filters, alias)]
215
+ : [baseCondition]
216
+
217
+ const whereClause = whereConditions.join(' AND ')
218
+
219
+ // Use the VIEW name (not base table) for rollup queries
220
+ // The VIEW will be created after all base tables exist, so it's safe to reference
221
+ return `COALESCE(
222
+ (SELECT ${aggregationExpr}
223
+ FROM ${relatedTable} AS ${alias}
224
+ WHERE ${whereClause}),
225
+ ${defaultValue}
226
+ ) AS ${rollupName}`
227
+ }
228
+
229
+ /**
230
+ * Generate count column expression with optional filtering
231
+ * Counts linked records from a relationship field, with optional conditions
232
+ */
233
+ const generateCountExpression = (
234
+ countField: Fields[number] & {
235
+ readonly type: 'count'
236
+ readonly relationshipField: string
237
+ readonly conditions?: readonly ViewFilterCondition[]
238
+ },
239
+ tableName: string,
240
+ allFields: readonly Fields[number][]
241
+ ): string => {
242
+ const { name: countName, relationshipField, conditions } = countField
243
+
244
+ const relationshipFieldDef = allFields.find((f) => f.name === relationshipField)
245
+
246
+ // Count field must reference a valid relationship field in same table
247
+ if (!relationshipFieldDef || relationshipFieldDef.type !== 'relationship') {
248
+ return `0 AS ${countName}`
249
+ }
250
+
251
+ if (
252
+ !('relatedTable' in relationshipFieldDef) ||
253
+ typeof relationshipFieldDef.relatedTable !== 'string'
254
+ ) {
255
+ return `0 AS ${countName}`
256
+ }
257
+
258
+ const { relatedTable } = relationshipFieldDef
259
+ const alias = `${relatedTable}_for_${countName}`
260
+
261
+ // Determine the foreign key column in the related table
262
+ // For one-to-many relationships, the foreignKey property specifies the FK column in the related table
263
+ // For many-to-one relationships, the relationship field itself is the FK column
264
+ const foreignKeyColumn =
265
+ 'foreignKey' in relationshipFieldDef && typeof relationshipFieldDef.foreignKey === 'string'
266
+ ? relationshipFieldDef.foreignKey
267
+ : relationshipField
268
+
269
+ // Build WHERE clause with base condition + optional filter conditions
270
+ const baseCondition = `${alias}.${foreignKeyColumn} = ${tableName}.id`
271
+
272
+ // Convert conditions array to WHERE clauses
273
+ const filterConditions = conditions?.map((condition) => buildWhereClause(condition, alias)) ?? []
274
+
275
+ const whereConditions = [baseCondition, ...filterConditions]
276
+ const whereClause = whereConditions.join(' AND ')
277
+
278
+ // Use COALESCE to ensure 0 instead of NULL when no records match
279
+ return `COALESCE(
280
+ (SELECT COUNT(*)
281
+ FROM ${relatedTable} AS ${alias}
282
+ WHERE ${whereClause}),
283
+ 0
284
+ ) AS ${countName}`
285
+ }
286
+
287
+ /**
288
+ * Build JOIN clauses for forward lookups (many-to-one only, not many-to-many)
289
+ */
290
+ const buildForwardLookupJoins = (
291
+ lookupFields: readonly (Fields[number] & {
292
+ readonly type: 'lookup'
293
+ readonly relationshipField: string
294
+ readonly relatedField: string
295
+ readonly filters?: ViewFilterCondition
296
+ })[],
297
+ allFields: readonly Fields[number][]
298
+ ): string => {
299
+ const forwardLookups = lookupFields.filter((field) => {
300
+ const relationshipFieldDef = allFields.find((f) => f.name === field.relationshipField)
301
+ return (
302
+ relationshipFieldDef &&
303
+ relationshipFieldDef.type === 'relationship' &&
304
+ 'relatedTable' in relationshipFieldDef &&
305
+ 'relationType' in relationshipFieldDef &&
306
+ relationshipFieldDef.relationType !== 'many-to-many'
307
+ )
308
+ })
309
+
310
+ return forwardLookups
311
+ .map((field) => {
312
+ const relationshipFieldDef = allFields.find((f) => f.name === field.relationshipField)
313
+ if (!relationshipFieldDef || relationshipFieldDef.type !== 'relationship') {
314
+ return ''
315
+ }
316
+ const { relatedTable } = relationshipFieldDef as unknown as { relatedTable: string }
317
+ const alias = `${relatedTable}_for_${field.name}`
318
+ return `LEFT JOIN ${relatedTable} AS ${alias} ON ${alias}.id = base.${field.relationshipField}`
319
+ })
320
+ .filter((join) => join !== '')
321
+ .join('\n ')
322
+ }
323
+
324
+ /**
325
+ * Generate CREATE VIEW statement for a table with lookup, rollup, and/or count fields
326
+ * Replaces the base table with a VIEW that includes looked-up, aggregated, and counted columns
327
+ * Returns empty string if table has no lookup, rollup, or count fields
328
+ */
329
+ export const generateLookupViewSQL = (table: Table): string => {
330
+ const lookupFields = table.fields.filter(isLookupField)
331
+ const rollupFields = table.fields.filter(isRollupField)
332
+ const countFields = table.fields.filter(isCountField)
333
+
334
+ if (lookupFields.length === 0 && rollupFields.length === 0 && countFields.length === 0) {
335
+ return '' // No lookup, rollup, or count fields - no VIEW needed
336
+ }
337
+
338
+ const sanitized = sanitizeTableName(table.name)
339
+
340
+ // Always include base.* to get all columns from the base table (including auto-generated id)
341
+ const baseFieldsWildcard = 'base.*'
342
+
343
+ // Generate expressions for lookup, rollup, and count fields
344
+ const lookupExpressions = lookupFields.map((field) =>
345
+ generateLookupExpression(field, 'base', table.fields, sanitized)
346
+ )
347
+ const rollupExpressions = rollupFields.map((field) =>
348
+ generateRollupExpression(field, 'base', table.fields)
349
+ )
350
+ const countExpressions = countFields.map((field) =>
351
+ generateCountExpression(field, 'base', table.fields)
352
+ )
353
+
354
+ // Build JOIN clauses for forward lookups
355
+ const joins = buildForwardLookupJoins(lookupFields, table.fields)
356
+
357
+ // Assemble the final VIEW SQL
358
+ const selectClause = [
359
+ baseFieldsWildcard,
360
+ ...lookupExpressions,
361
+ ...rollupExpressions,
362
+ ...countExpressions,
363
+ ].join(',\n ')
364
+
365
+ return `CREATE OR REPLACE VIEW ${sanitized} AS
366
+ SELECT
367
+ ${selectClause}
368
+ FROM ${sanitized}_base AS base
369
+ ${joins ? joins : ''}`
370
+ }
371
+
372
+ /**
373
+ * Generate base table name for a table with lookup fields
374
+ * The actual table is named {table}_base, and the VIEW is named {table}
375
+ */
376
+ export const getBaseTableName = (tableName: string): string => `${tableName}_base`
377
+
378
+ /**
379
+ * Check if a table should use a VIEW (has lookup, rollup, or count fields)
380
+ */
381
+ export const shouldUseView = (table: Table): boolean =>
382
+ hasLookupFields(table) || hasRollupFields(table) || hasCountFields(table)
383
+
384
+ /**
385
+ * Generate INSTEAD OF triggers for a VIEW to make it writable
386
+ * These triggers redirect INSERT/UPDATE/DELETE operations to the base table
387
+ */
388
+ export const generateLookupViewTriggers = (table: Table): readonly string[] => {
389
+ if (!shouldUseView(table)) {
390
+ return [] // No VIEW, no triggers needed
391
+ }
392
+
393
+ const sanitized = sanitizeTableName(table.name)
394
+ const baseTableName = getBaseTableName(sanitized)
395
+ const viewName = sanitized
396
+ const baseFields = getBaseFields(table)
397
+
398
+ return [
399
+ ...generateInsertTrigger(viewName, baseTableName, baseFields),
400
+ ...generateUpdateTrigger(viewName, baseTableName, baseFields),
401
+ ...generateDeleteTrigger(viewName, baseTableName),
402
+ ]
403
+ }
@@ -0,0 +1,65 @@
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 { generateSqlCondition } from '../filter-operators'
9
+ import type { ViewFilterCondition } from '@/domain/models/app/table/views/filters'
10
+
11
+ /**
12
+ * Build WHERE clause from filter condition
13
+ */
14
+ export const buildWhereClause = (filter: ViewFilterCondition, aliasPrefix: string): string => {
15
+ const { field, operator, value } = filter
16
+ const column = `${aliasPrefix}.${field}`
17
+ // Use legacy string escaping mode for backward compatibility
18
+ return generateSqlCondition(column, operator, value, { useEscapeSqlString: true })
19
+ }
20
+
21
+ /**
22
+ * Map aggregation function name to PostgreSQL aggregate function
23
+ */
24
+ export const mapAggregationToPostgres = (aggregation: string, relatedField: string): string => {
25
+ const upperAgg = aggregation.toUpperCase()
26
+
27
+ switch (upperAgg) {
28
+ case 'SUM':
29
+ return `SUM(${relatedField})`
30
+ case 'COUNT':
31
+ return `COUNT(${relatedField})`
32
+ case 'AVG':
33
+ return `AVG(${relatedField})`
34
+ case 'MIN':
35
+ return `MIN(${relatedField})`
36
+ case 'MAX':
37
+ return `MAX(${relatedField})`
38
+ case 'COUNTA':
39
+ return `COUNT(CASE WHEN ${relatedField} IS NOT NULL AND ${relatedField} != '' THEN 1 END)`
40
+ case 'COUNTALL':
41
+ return `COUNT(*)`
42
+ case 'ARRAYUNIQUE':
43
+ return `ARRAY_AGG(DISTINCT ${relatedField} ORDER BY ${relatedField})`
44
+ default:
45
+ return `SUM(${relatedField})`
46
+ }
47
+ }
48
+
49
+ /**
50
+ * Generate default value for empty aggregation results
51
+ */
52
+ export const getDefaultValueForAggregation = (aggregation: string): string => {
53
+ const upperAgg = aggregation.toUpperCase()
54
+
55
+ switch (upperAgg) {
56
+ case 'AVG':
57
+ case 'MIN':
58
+ case 'MAX':
59
+ return 'NULL'
60
+ case 'ARRAYUNIQUE':
61
+ return 'ARRAY[]::TEXT[]'
62
+ default:
63
+ return '0'
64
+ }
65
+ }
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Copyright (c) 2025 ESSENTIAL SERVICES
3
+ *
4
+ * This source code is licensed under the Business Source License 1.1
5
+ * found in the LICENSE.md file in the root directory of this source tree.
6
+ */
7
+
8
+ import type { Table } from '@/domain/models/app/table'
9
+
10
+ /**
11
+ * Get base fields (non-lookup, non-rollup, non-count, non-one-to-many, non-many-to-many, non-id fields)
12
+ */
13
+ export const getBaseFields = (table: Table): readonly string[] =>
14
+ table.fields
15
+ .filter((field) => {
16
+ // Exclude lookup, rollup, and count fields (handled by VIEW)
17
+ if (field.type === 'lookup' || field.type === 'rollup' || field.type === 'count') {
18
+ return false
19
+ }
20
+ // Exclude one-to-many and many-to-many relationship fields (don't create columns)
21
+ if (
22
+ field.type === 'relationship' &&
23
+ 'relationType' in field &&
24
+ (field.relationType === 'one-to-many' || field.relationType === 'many-to-many')
25
+ ) {
26
+ return false
27
+ }
28
+ // Exclude id field (auto-generated)
29
+ if (field.name === 'id') {
30
+ return false
31
+ }
32
+ return true
33
+ })
34
+ .map((field) => field.name)
35
+
36
+ /**
37
+ * Generate INSTEAD OF INSERT trigger for a VIEW
38
+ */
39
+ export const generateInsertTrigger = (
40
+ viewName: string,
41
+ baseTableName: string,
42
+ baseFields: readonly string[]
43
+ ): readonly string[] => {
44
+ const insertTriggerFunction = `${viewName}_instead_of_insert`
45
+ const insertTrigger = `${viewName}_insert_trigger`
46
+ const insertFieldsList = baseFields.join(', ')
47
+ const insertValuesList = baseFields.map((name) => `NEW.${name}`).join(', ')
48
+
49
+ return [
50
+ `CREATE OR REPLACE FUNCTION ${insertTriggerFunction}()
51
+ RETURNS TRIGGER AS $$
52
+ BEGIN
53
+ INSERT INTO ${baseTableName} (${insertFieldsList})
54
+ VALUES (${insertValuesList});
55
+ RETURN NEW;
56
+ END;
57
+ $$ LANGUAGE plpgsql`,
58
+ `DROP TRIGGER IF EXISTS ${insertTrigger} ON ${viewName}`,
59
+ `CREATE TRIGGER ${insertTrigger}
60
+ INSTEAD OF INSERT ON ${viewName}
61
+ FOR EACH ROW
62
+ EXECUTE FUNCTION ${insertTriggerFunction}()`,
63
+ ]
64
+ }
65
+
66
+ /**
67
+ * Generate INSTEAD OF UPDATE trigger for a VIEW
68
+ */
69
+ export const generateUpdateTrigger = (
70
+ viewName: string,
71
+ baseTableName: string,
72
+ baseFields: readonly string[]
73
+ ): readonly string[] => {
74
+ const updateTriggerFunction = `${viewName}_instead_of_update`
75
+ const updateTrigger = `${viewName}_update_trigger`
76
+ const updateSetList = baseFields.map((name) => `${name} = NEW.${name}`).join(', ')
77
+
78
+ return [
79
+ `CREATE OR REPLACE FUNCTION ${updateTriggerFunction}()
80
+ RETURNS TRIGGER AS $$
81
+ BEGIN
82
+ UPDATE ${baseTableName}
83
+ SET ${updateSetList}
84
+ WHERE id = OLD.id;
85
+ RETURN NEW;
86
+ END;
87
+ $$ LANGUAGE plpgsql`,
88
+ `DROP TRIGGER IF EXISTS ${updateTrigger} ON ${viewName}`,
89
+ `CREATE TRIGGER ${updateTrigger}
90
+ INSTEAD OF UPDATE ON ${viewName}
91
+ FOR EACH ROW
92
+ EXECUTE FUNCTION ${updateTriggerFunction}()`,
93
+ ]
94
+ }
95
+
96
+ /**
97
+ * Generate INSTEAD OF DELETE trigger for a VIEW
98
+ */
99
+ export const generateDeleteTrigger = (
100
+ viewName: string,
101
+ baseTableName: string
102
+ ): readonly string[] => {
103
+ const deleteTriggerFunction = `${viewName}_instead_of_delete`
104
+ const deleteTrigger = `${viewName}_delete_trigger`
105
+
106
+ return [
107
+ `CREATE OR REPLACE FUNCTION ${deleteTriggerFunction}()
108
+ RETURNS TRIGGER AS $$
109
+ BEGIN
110
+ DELETE FROM ${baseTableName}
111
+ WHERE id = OLD.id;
112
+ RETURN OLD;
113
+ END;
114
+ $$ LANGUAGE plpgsql`,
115
+ `DROP TRIGGER IF EXISTS ${deleteTrigger} ON ${viewName}`,
116
+ `CREATE TRIGGER ${deleteTrigger}
117
+ INSTEAD OF DELETE ON ${viewName}
118
+ FOR EACH ROW
119
+ EXECUTE FUNCTION ${deleteTriggerFunction}()`,
120
+ ]
121
+ }