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,252 @@
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 { escapeSqlString } from './sql-utils'
9
+ import type { Fields } from '@/domain/models/app/table/fields'
10
+
11
+ /**
12
+ * Generate CHECK constraints for array fields with maxItems
13
+ */
14
+ export const generateArrayConstraints = (fields: readonly Fields[number][]): readonly string[] =>
15
+ fields
16
+ .filter(
17
+ (field): field is Fields[number] & { type: 'array'; maxItems: number } =>
18
+ field.type === 'array' && 'maxItems' in field && typeof field.maxItems === 'number'
19
+ )
20
+ .map(
21
+ (field) =>
22
+ `CONSTRAINT check_${field.name}_max_items CHECK (array_length(${field.name}, 1) IS NULL OR array_length(${field.name}, 1) <= ${field.maxItems})`
23
+ )
24
+
25
+ /**
26
+ * Generate CHECK constraints for multiple-attachments fields with maxFiles
27
+ */
28
+ export const generateMultipleAttachmentsConstraints = (
29
+ fields: readonly Fields[number][]
30
+ ): readonly string[] =>
31
+ fields
32
+ .filter(
33
+ (field): field is Fields[number] & { type: 'multiple-attachments'; maxFiles: number } =>
34
+ field.type === 'multiple-attachments' &&
35
+ 'maxFiles' in field &&
36
+ typeof field.maxFiles === 'number'
37
+ )
38
+ .map(
39
+ (field) =>
40
+ `CONSTRAINT check_${field.name}_max_files CHECK (jsonb_array_length(${field.name}) IS NULL OR jsonb_array_length(${field.name}) <= ${field.maxFiles})`
41
+ )
42
+
43
+ /**
44
+ * Generate CHECK constraints for numeric fields with min/max values
45
+ * Supports: integer, decimal, currency, percentage, rating
46
+ */
47
+ export const generateNumericConstraints = (fields: readonly Fields[number][]): readonly string[] =>
48
+ fields
49
+ .filter(
50
+ (
51
+ field
52
+ ): field is Fields[number] & {
53
+ type: 'integer' | 'decimal' | 'currency' | 'percentage' | 'rating'
54
+ } =>
55
+ (field.type === 'integer' ||
56
+ field.type === 'decimal' ||
57
+ field.type === 'currency' ||
58
+ field.type === 'percentage' ||
59
+ field.type === 'rating') &&
60
+ (('min' in field && typeof field.min === 'number') ||
61
+ ('max' in field && typeof field.max === 'number'))
62
+ )
63
+ .map((field) => {
64
+ const hasMin = 'min' in field && typeof field.min === 'number'
65
+ const hasMax = 'max' in field && typeof field.max === 'number'
66
+
67
+ // Rating fields always have a minimum of 1 (ratings start from 1, not 0)
68
+ const effectiveMin = field.type === 'rating' && !hasMin ? 1 : hasMin ? field.min : undefined
69
+
70
+ const conditions = [
71
+ ...(effectiveMin !== undefined ? [`${field.name} >= ${effectiveMin}`] : []),
72
+ ...(hasMax ? [`${field.name} <= ${field.max}`] : []),
73
+ ]
74
+
75
+ const constraintName = `check_${field.name}_range`
76
+ const constraintCondition = conditions.join(' AND ')
77
+ return `CONSTRAINT ${constraintName} CHECK (${constraintCondition})`
78
+ })
79
+
80
+ /**
81
+ * Generate CHECK constraints for progress fields (automatic 0-100 range)
82
+ */
83
+ export const generateProgressConstraints = (fields: readonly Fields[number][]): readonly string[] =>
84
+ fields
85
+ .filter((field): field is Fields[number] & { type: 'progress' } => field.type === 'progress')
86
+ .map((field) => {
87
+ const constraintName = `check_${field.name}_range`
88
+ return `CONSTRAINT ${constraintName} CHECK (${field.name} >= 0 AND ${field.name} <= 100)`
89
+ })
90
+
91
+ /**
92
+ * Extract string values from field options
93
+ * Handles both simple string arrays (single-select) and object arrays with value property (status)
94
+ */
95
+ const extractOptionValues = (
96
+ field: Fields[number]
97
+ ): readonly string[] | readonly { value: string }[] => {
98
+ if ('options' in field && Array.isArray(field.options)) {
99
+ return field.options as readonly string[] | readonly { value: string }[]
100
+ }
101
+ return []
102
+ }
103
+
104
+ /**
105
+ * Generate CHECK constraint for enum-based fields (single-select, status)
106
+ *
107
+ * SECURITY NOTE: Options come from validated Effect Schema (SingleSelectFieldSchema, StatusFieldSchema).
108
+ * We escape single quotes to prevent SQL injection following defense-in-depth security principles.
109
+ *
110
+ * DRY PRINCIPLE: This function consolidates enum constraint generation for both single-select
111
+ * (simple string options) and status (object options with value property) field types.
112
+ */
113
+ const generateEnumCheckConstraint = (
114
+ field: Fields[number] & { readonly options: readonly unknown[] }
115
+ ): string => {
116
+ const options = extractOptionValues(field)
117
+ const values = options
118
+ .map((opt) => {
119
+ const value = typeof opt === 'string' ? opt : (opt as { value: string }).value
120
+ return `'${escapeSqlString(value)}'`
121
+ })
122
+ .join(', ')
123
+ const constraintName = `check_${field.name}_enum`
124
+ return `CONSTRAINT ${constraintName} CHECK (${field.name} IN (${values}))`
125
+ }
126
+
127
+ /**
128
+ * Generate CHECK constraints for single-select fields with enum options
129
+ */
130
+ export const generateEnumConstraints = (fields: readonly Fields[number][]): readonly string[] =>
131
+ fields
132
+ .filter(
133
+ (field): field is Fields[number] & { type: 'single-select'; options: readonly string[] } =>
134
+ field.type === 'single-select' && 'options' in field && Array.isArray(field.options)
135
+ )
136
+ .map(generateEnumCheckConstraint)
137
+
138
+ /**
139
+ * Generate CHECK constraints for status fields with status options
140
+ */
141
+ export const generateStatusConstraints = (fields: readonly Fields[number][]): readonly string[] =>
142
+ fields
143
+ .filter(
144
+ (
145
+ field
146
+ ): field is Fields[number] & {
147
+ type: 'status'
148
+ options: readonly { value: string; color?: string }[]
149
+ } => field.type === 'status' && 'options' in field && Array.isArray(field.options)
150
+ )
151
+ .map(generateEnumCheckConstraint)
152
+
153
+ /**
154
+ * Generate CHECK constraints for rich-text fields with maxLength
155
+ */
156
+ export const generateRichTextConstraints = (fields: readonly Fields[number][]): readonly string[] =>
157
+ fields
158
+ .filter(
159
+ (field): field is Fields[number] & { type: 'rich-text'; maxLength: number } =>
160
+ field.type === 'rich-text' && 'maxLength' in field && typeof field.maxLength === 'number'
161
+ )
162
+ .map((field) => {
163
+ const constraintName = `check_${field.name}_max_length`
164
+ return `CONSTRAINT ${constraintName} CHECK (LENGTH(${field.name}) <= ${field.maxLength})`
165
+ })
166
+
167
+ /**
168
+ * Barcode format validation patterns
169
+ * Uses PostgreSQL regex patterns to validate barcode formats
170
+ */
171
+ const barcodeFormatPatterns: Record<string, string> = {
172
+ 'EAN-13': '^[0-9]{13}$',
173
+ 'EAN-8': '^[0-9]{8}$',
174
+ 'UPC-A': '^[0-9]{12}$',
175
+ 'UPC-E': '^[0-9]{6,8}$',
176
+ 'CODE-128': '^[\\x00-\\x7F]+$',
177
+ 'CODE-39': '^[A-Z0-9\\-\\.\\$\\/\\+\\%\\ ]+$',
178
+ }
179
+
180
+ /**
181
+ * Generate CHECK constraints for barcode fields with format validation
182
+ */
183
+ export const generateBarcodeConstraints = (fields: readonly Fields[number][]): readonly string[] =>
184
+ fields
185
+ .filter(
186
+ (field): field is Fields[number] & { type: 'barcode'; format: string } =>
187
+ field.type === 'barcode' && 'format' in field && typeof field.format === 'string'
188
+ )
189
+ .map((field) => {
190
+ const pattern = barcodeFormatPatterns[field.format]
191
+ if (!pattern) {
192
+ return ''
193
+ }
194
+ const constraintName = `check_${field.name}_format`
195
+ return `CONSTRAINT ${constraintName} CHECK (${field.name} ~ '${pattern}')`
196
+ })
197
+ .filter((constraint) => constraint !== '')
198
+
199
+ /**
200
+ * Generate CHECK constraints for color fields with hex color format validation
201
+ */
202
+ export const generateColorConstraints = (fields: readonly Fields[number][]): readonly string[] =>
203
+ fields
204
+ .filter((field): field is Fields[number] & { type: 'color' } => field.type === 'color')
205
+ .map((field) => {
206
+ const constraintName = `check_${field.name}_format`
207
+ return `CONSTRAINT ${constraintName} CHECK (${field.name} ~ '^#[0-9a-fA-F]{6}$')`
208
+ })
209
+
210
+ /**
211
+ * Generate CHECK constraints for multi-select fields with options
212
+ * Validates that all selected values are from the predefined options list
213
+ * Uses PostgreSQL <@ (contained by) operator for array validation
214
+ *
215
+ * @example
216
+ * Field: { type: 'multi-select', options: ['work', 'personal'] }
217
+ * Constraint: CHECK (tags <@ ARRAY['work', 'personal']::text[])
218
+ */
219
+ export const generateMultiSelectConstraints = (
220
+ fields: readonly Fields[number][]
221
+ ): readonly string[] =>
222
+ fields
223
+ .filter(
224
+ (field): field is Fields[number] & { type: 'multi-select'; options: readonly string[] } =>
225
+ field.type === 'multi-select' && 'options' in field && Array.isArray(field.options)
226
+ )
227
+ .map((field) => {
228
+ const escapedOptions = field.options.map((opt) => `'${escapeSqlString(opt)}'`).join(', ')
229
+ const constraintName = `check_${field.name}_options`
230
+ return `CONSTRAINT ${constraintName} CHECK (${field.name} <@ ARRAY[${escapedOptions}]::text[])`
231
+ })
232
+
233
+ /**
234
+ * Generate custom CHECK constraints defined at table level
235
+ *
236
+ * Used for complex business rules that involve multiple fields or
237
+ * conditional validation beyond field-level constraints.
238
+ *
239
+ * @example
240
+ * ```typescript
241
+ * const constraints = [{
242
+ * name: 'chk_active_members_have_email',
243
+ * check: '(is_active = false) OR (email IS NOT NULL)'
244
+ * }]
245
+ * ```
246
+ */
247
+ export const generateCustomCheckConstraints = (
248
+ constraints?: readonly { readonly name: string; readonly check: string }[]
249
+ ): readonly string[] =>
250
+ constraints
251
+ ? constraints.map((constraint) => `CONSTRAINT ${constraint.name} CHECK (${constraint.check})`)
252
+ : []
@@ -0,0 +1,174 @@
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 {
9
+ isFormulaVolatile,
10
+ isFormulaReturningArray,
11
+ translateFormulaToPostgres,
12
+ } from '../formula/formula-utils'
13
+ import { isAutoTimestampField, isFieldNotNull, shouldUseSerial } from './sql-field-predicates'
14
+ import { mapFieldTypeToPostgres, mapFormulaResultTypeToPostgres } from './sql-type-mappings'
15
+ import { escapeSqlString } from './sql-utils'
16
+ import type { Fields } from '@/domain/models/app/table/fields'
17
+
18
+ /**
19
+ * Format default value for SQL
20
+ * Numbers and booleans are unquoted, strings are quoted and escaped
21
+ */
22
+ const formatDefaultValue = (defaultValue: unknown): string => {
23
+ if (typeof defaultValue === 'boolean') {
24
+ return String(defaultValue)
25
+ }
26
+ if (typeof defaultValue === 'number') {
27
+ return String(defaultValue)
28
+ }
29
+ return `'${escapeSqlString(String(defaultValue))}'`
30
+ }
31
+
32
+ /**
33
+ * Generate SERIAL column definition for auto-increment fields
34
+ * When isPrimaryKey is true and it's a single-field PK, add PRIMARY KEY inline for better PostgreSQL constraint recognition
35
+ */
36
+ const generateSerialColumn = (fieldName: string, isPrimaryKey: boolean = false): string =>
37
+ isPrimaryKey ? `${fieldName} SERIAL PRIMARY KEY` : `${fieldName} SERIAL NOT NULL`
38
+
39
+ /**
40
+ * Generate NOT NULL constraint
41
+ */
42
+ const generateNotNullConstraint = (
43
+ field: Fields[number],
44
+ isPrimaryKey: boolean,
45
+ hasAuthConfig: boolean = true
46
+ ): string => {
47
+ return isFieldNotNull(field, isPrimaryKey, hasAuthConfig) ? ' NOT NULL' : ''
48
+ }
49
+
50
+ /**
51
+ * Format array default value as PostgreSQL ARRAY literal
52
+ */
53
+ const formatArrayDefault = (defaultValue: readonly unknown[]): string => {
54
+ const arrayValues = defaultValue.map((val) => `'${escapeSqlString(String(val))}'`).join(', ')
55
+ return ` DEFAULT ARRAY[${arrayValues}]`
56
+ }
57
+
58
+ /**
59
+ * Format special default values (PostgreSQL functions, INTERVAL, etc.)
60
+ */
61
+ const formatSpecialDefault = (field: Fields[number], defaultValue: unknown): string | undefined => {
62
+ // PostgreSQL functions like CURRENT_DATE, NOW() should not be quoted
63
+ if (typeof defaultValue === 'string' && defaultValue.toUpperCase() === 'CURRENT_DATE') {
64
+ return ' DEFAULT CURRENT_DATE'
65
+ }
66
+ if (typeof defaultValue === 'string' && defaultValue.toUpperCase() === 'NOW()') {
67
+ return ' DEFAULT NOW()'
68
+ }
69
+ // Duration fields: convert seconds to INTERVAL
70
+ if (field.type === 'duration' && typeof defaultValue === 'number') {
71
+ return ` DEFAULT INTERVAL '${defaultValue} seconds'`
72
+ }
73
+ // Array fields (multi-select): convert to PostgreSQL array literal
74
+ if (Array.isArray(defaultValue)) {
75
+ return formatArrayDefault(defaultValue)
76
+ }
77
+ return undefined
78
+ }
79
+
80
+ /**
81
+ * Generate DEFAULT clause
82
+ */
83
+ const generateDefaultClause = (field: Fields[number]): string => {
84
+ // Auto-timestamp fields get CURRENT_TIMESTAMP default (PostgreSQL function for current timestamp)
85
+ if (isAutoTimestampField(field)) {
86
+ return ' DEFAULT CURRENT_TIMESTAMP'
87
+ }
88
+
89
+ // Progress fields with required=true get DEFAULT 0 automatically
90
+ if (field.type === 'progress' && field.required === true && !('default' in field)) {
91
+ return ' DEFAULT 0'
92
+ }
93
+
94
+ // Explicit default values
95
+ if ('default' in field && field.default !== undefined) {
96
+ const defaultValue = field.default
97
+ const specialDefault = formatSpecialDefault(field, defaultValue)
98
+ if (specialDefault) {
99
+ return specialDefault
100
+ }
101
+ return ` DEFAULT ${formatDefaultValue(defaultValue)}`
102
+ }
103
+
104
+ return ''
105
+ }
106
+
107
+ /**
108
+ * Generate formula column definition (GENERATED ALWAYS AS or trigger-based)
109
+ *
110
+ * NOTE: Formula fields with volatile functions (CURRENT_DATE, NOW(), etc.) cannot use
111
+ * GENERATED ALWAYS AS because PostgreSQL requires generated columns to be immutable.
112
+ * For volatile formulas, we create regular columns and handle computation via triggers.
113
+ */
114
+ const generateFormulaColumn = (
115
+ field: Fields[number] & { readonly type: 'formula'; readonly formula: string },
116
+ allFields?: readonly Fields[number][]
117
+ ): string => {
118
+ const baseResultType =
119
+ 'resultType' in field && field.resultType
120
+ ? mapFormulaResultTypeToPostgres(field.resultType)
121
+ : 'TEXT'
122
+
123
+ // Auto-detect array return type for functions like STRING_TO_ARRAY
124
+ // If formula returns an array but resultType doesn't specify array, append []
125
+ const resultType =
126
+ isFormulaReturningArray(field.formula) && !baseResultType.endsWith('[]')
127
+ ? `${baseResultType}[]`
128
+ : baseResultType
129
+
130
+ // Translate formula to PostgreSQL syntax with field type context
131
+ // Note: translateFormulaToPostgres handles ROUND with double precision by casting to NUMERIC
132
+ // and converts date::TEXT to TO_CHAR(date, 'format') which is STABLE (not IMMUTABLE)
133
+ const translatedFormula = translateFormulaToPostgres(field.formula, allFields)
134
+
135
+ // Volatile formulas (contain CURRENT_DATE, NOW(), etc.) need trigger-based computation
136
+ // because PostgreSQL GENERATED columns must be immutable
137
+ // Check volatility on TRANSLATED formula since date::TEXT becomes TO_CHAR (STABLE)
138
+ if (isFormulaVolatile(translatedFormula)) {
139
+ // Create regular column - trigger will populate it
140
+ return `${field.name} ${resultType}`
141
+ }
142
+
143
+ // Immutable formulas can use GENERATED ALWAYS AS
144
+ return `${field.name} ${resultType} GENERATED ALWAYS AS (${translatedFormula}) STORED`
145
+ }
146
+
147
+ /**
148
+ * Generate column definition with constraints
149
+ *
150
+ * NOTE: UNIQUE constraints are NOT generated inline. Named UNIQUE constraints
151
+ * are generated at the table level via generateUniqueConstraints() to ensure
152
+ * they appear in information_schema.table_constraints with queryable constraint names.
153
+ */
154
+ export const generateColumnDefinition = (
155
+ field: Fields[number],
156
+ isPrimaryKey: boolean,
157
+ allFields?: readonly Fields[number][],
158
+ hasAuthConfig: boolean = true
159
+ ): string => {
160
+ // SERIAL columns for auto-increment fields
161
+ if (shouldUseSerial(field, isPrimaryKey)) {
162
+ return generateSerialColumn(field.name, isPrimaryKey)
163
+ }
164
+
165
+ // Formula fields: check if formula is volatile
166
+ if (field.type === 'formula' && 'formula' in field && field.formula) {
167
+ return generateFormulaColumn(field, allFields)
168
+ }
169
+
170
+ const columnType = mapFieldTypeToPostgres(field)
171
+ const notNull = generateNotNullConstraint(field, isPrimaryKey, hasAuthConfig)
172
+ const defaultValue = generateDefaultClause(field)
173
+ return `${field.name} ${columnType}${notNull}${defaultValue}`
174
+ }
@@ -0,0 +1,245 @@
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 { Data, Effect } from 'effect'
9
+
10
+ /**
11
+ * Type definition for a database transaction that can execute raw SQL
12
+ */
13
+ export interface TransactionLike {
14
+ readonly unsafe: (sql: string) => Promise<readonly unknown[]>
15
+ }
16
+
17
+ /**
18
+ * Error type for SQL execution failures
19
+ */
20
+ export class SQLExecutionError extends Data.TaggedError('SQLExecutionError')<{
21
+ readonly message: string
22
+ readonly sql?: string
23
+ readonly cause?: unknown
24
+ }> {}
25
+
26
+ /**
27
+ * Type definition for information_schema.columns row
28
+ */
29
+ export interface ColumnInfo {
30
+ readonly column_name: string
31
+ readonly data_type: string
32
+ readonly is_nullable: string
33
+ readonly column_default: string | null
34
+ }
35
+
36
+ /**
37
+ * Type definition for table existence query result
38
+ */
39
+ interface TableExistsResult {
40
+ readonly exists: boolean
41
+ }
42
+
43
+ /**
44
+ * Type definition for table name query result
45
+ */
46
+ interface TableNameResult {
47
+ readonly tablename: string
48
+ }
49
+
50
+ /**
51
+ * Type definition for view name query result
52
+ */
53
+ interface ViewNameResult {
54
+ readonly viewname: string
55
+ }
56
+
57
+ /**
58
+ * Type definition for materialized view name query result
59
+ */
60
+ interface MatViewNameResult {
61
+ readonly matviewname: string
62
+ }
63
+
64
+ // ============================================================================
65
+ // SQL Statement Execution Helpers
66
+ // ============================================================================
67
+
68
+ /**
69
+ * Execute a single SQL statement within an Effect context
70
+ *
71
+ * SECURITY NOTE: This function uses tx.unsafe() which is intentional for DDL execution.
72
+ * See schema-initializer.ts for detailed security rationale.
73
+ */
74
+ export const executeSQL = (
75
+ tx: TransactionLike,
76
+ sql: string
77
+ ): Effect.Effect<readonly unknown[], SQLExecutionError> =>
78
+ Effect.tryPromise({
79
+ try: () => tx.unsafe(sql),
80
+ catch: (error) =>
81
+ new SQLExecutionError({
82
+ message: `SQL execution failed: ${String(error)}`,
83
+ sql,
84
+ cause: error,
85
+ }),
86
+ })
87
+
88
+ /**
89
+ * Execute multiple SQL statements sequentially
90
+ * Use this when statements must be executed in order (e.g., DDL that depends on previous statements)
91
+ */
92
+ /* eslint-disable functional/no-loop-statements */
93
+ export const executeSQLStatements = (
94
+ tx: TransactionLike,
95
+ statements: readonly string[]
96
+ ): Effect.Effect<void, SQLExecutionError> =>
97
+ statements.length === 0
98
+ ? Effect.void
99
+ : Effect.gen(function* () {
100
+ for (const sql of statements) {
101
+ yield* executeSQL(tx, sql)
102
+ }
103
+ })
104
+ /* eslint-enable functional/no-loop-statements */
105
+
106
+ /**
107
+ * Execute multiple SQL statements in parallel
108
+ * Use this when statements are independent (e.g., DROP VIEW statements, index creation)
109
+ *
110
+ * Note: PostgreSQL allows concurrent DDL operations within a transaction,
111
+ * but some operations may still serialize at the database level.
112
+ */
113
+ export const executeSQLStatementsParallel = (
114
+ tx: TransactionLike,
115
+ statements: readonly string[]
116
+ ): Effect.Effect<void, SQLExecutionError> =>
117
+ statements.length === 0
118
+ ? Effect.void
119
+ : Effect.all(
120
+ statements.map((sql) => executeSQL(tx, sql)),
121
+ { concurrency: 'unbounded' }
122
+ ).pipe(Effect.asVoid)
123
+
124
+ // ============================================================================
125
+ // Information Schema Query Helpers
126
+ // ============================================================================
127
+
128
+ /**
129
+ * Check if a table exists in the database
130
+ *
131
+ * SECURITY NOTE: String interpolation is used for tableName.
132
+ * This is SAFE because:
133
+ * 1. tableName comes from validated Effect Schema (Table.name field)
134
+ * 2. Table names are defined in schema configuration, not user input
135
+ * 3. The App schema is validated before reaching this code
136
+ * 4. information_schema queries are read-only (no data modification risk)
137
+ */
138
+ export const tableExists = (
139
+ tx: TransactionLike,
140
+ tableName: string
141
+ ): Effect.Effect<boolean, SQLExecutionError> =>
142
+ executeSQL(
143
+ tx,
144
+ `
145
+ SELECT EXISTS (
146
+ SELECT 1
147
+ FROM information_schema.tables
148
+ WHERE table_name = '${tableName}'
149
+ AND table_schema = 'public'
150
+ ) as exists
151
+ `
152
+ ).pipe(Effect.map((result) => (result as readonly TableExistsResult[])[0]?.exists ?? false))
153
+
154
+ /**
155
+ * Get existing columns from a table
156
+ *
157
+ * SECURITY NOTE: String interpolation is used for tableName.
158
+ * This is SAFE because tableName comes from validated schema configuration.
159
+ */
160
+ export const getExistingColumns = (
161
+ tx: TransactionLike,
162
+ tableName: string
163
+ ): Effect.Effect<
164
+ ReadonlyMap<string, { dataType: string; isNullable: string; columnDefault: string | null }>,
165
+ SQLExecutionError
166
+ > =>
167
+ executeSQL(
168
+ tx,
169
+ `
170
+ SELECT column_name, data_type, is_nullable, column_default
171
+ FROM information_schema.columns
172
+ WHERE table_name = '${tableName}'
173
+ AND table_schema = 'public'
174
+ `
175
+ ).pipe(
176
+ Effect.map((result) => {
177
+ const rows = result as readonly ColumnInfo[]
178
+ return new Map(
179
+ rows.map((row) => [
180
+ row.column_name,
181
+ {
182
+ dataType: row.data_type,
183
+ isNullable: row.is_nullable,
184
+ columnDefault: row.column_default,
185
+ },
186
+ ])
187
+ )
188
+ })
189
+ )
190
+
191
+ /**
192
+ * Get all existing table names in the public schema
193
+ *
194
+ * SECURITY NOTE: This query is read-only and uses pg_tables system catalog.
195
+ * No user input is involved.
196
+ */
197
+ export const getExistingTableNames = (
198
+ tx: TransactionLike
199
+ ): Effect.Effect<readonly string[], SQLExecutionError> =>
200
+ executeSQL(
201
+ tx,
202
+ `
203
+ SELECT tablename
204
+ FROM pg_tables
205
+ WHERE schemaname = 'public'
206
+ `
207
+ ).pipe(Effect.map((result) => (result as readonly TableNameResult[]).map((row) => row.tablename)))
208
+
209
+ /**
210
+ * Get all existing view names in the public schema
211
+ *
212
+ * SECURITY NOTE: This query is read-only and uses pg_views system catalog.
213
+ * No user input is involved.
214
+ */
215
+ export const getExistingViews = (
216
+ tx: TransactionLike
217
+ ): Effect.Effect<readonly string[], SQLExecutionError> =>
218
+ executeSQL(
219
+ tx,
220
+ `
221
+ SELECT viewname
222
+ FROM pg_views
223
+ WHERE schemaname = 'public'
224
+ `
225
+ ).pipe(Effect.map((result) => (result as readonly ViewNameResult[]).map((row) => row.viewname)))
226
+
227
+ /**
228
+ * Get all existing materialized view names in the public schema
229
+ *
230
+ * SECURITY NOTE: This query is read-only and uses pg_matviews system catalog.
231
+ * No user input is involved.
232
+ */
233
+ export const getExistingMaterializedViews = (
234
+ tx: TransactionLike
235
+ ): Effect.Effect<readonly string[], SQLExecutionError> =>
236
+ executeSQL(
237
+ tx,
238
+ `
239
+ SELECT matviewname
240
+ FROM pg_matviews
241
+ WHERE schemaname = 'public'
242
+ `
243
+ ).pipe(
244
+ Effect.map((result) => (result as readonly MatViewNameResult[]).map((row) => row.matviewname))
245
+ )