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,58 @@
1
+ /**
2
+ * Copyright (c) 2025 ESSENTIAL SERVICES
3
+ *
4
+ * This source code is licensed under the Business Source License 1.1
5
+ * found in the LICENSE.md file in the root directory of this source tree.
6
+ */
7
+
8
+ import type { Languages } from '@/domain/models/app/languages'
9
+ import type { Page } from '@/domain/models/app/pages'
10
+
11
+ /**
12
+ * Page language configuration
13
+ */
14
+ export type PageLangConfig = {
15
+ readonly lang: string
16
+ readonly direction: 'ltr' | 'rtl'
17
+ readonly directionStyles: string
18
+ }
19
+
20
+ /**
21
+ * Resolves language configuration for a page
22
+ *
23
+ * Priority: page.meta.lang > detectedLanguage > languages.default > 'en-US'
24
+ *
25
+ * @param page - Page configuration
26
+ * @param languages - Languages configuration
27
+ * @param detectedLanguage - Detected language from browser/URL
28
+ * @returns Language configuration with direction and styles
29
+ */
30
+ export function resolvePageLanguage(
31
+ page: Page,
32
+ languages: Languages | undefined,
33
+ detectedLanguage: string | undefined
34
+ ): PageLangConfig {
35
+ // Priority: page.meta.lang (EXPLICIT) > detectedLanguage > languages.default > fallback
36
+ // NOTE: Using explicit check to ensure page.meta.lang is always preferred when present
37
+ const lang =
38
+ page.meta && page.meta.lang
39
+ ? page.meta.lang
40
+ : detectedLanguage
41
+ ? detectedLanguage
42
+ : languages?.default
43
+ ? languages.default
44
+ : 'en-US'
45
+
46
+ // Determine text direction from language configuration
47
+ // Match by code (en) or locale (en-US)
48
+ const langConfig = languages?.supported.find((l) => l.code === lang || l.locale === lang)
49
+ const direction = langConfig?.direction || 'ltr'
50
+
51
+ // Generate CSS for body direction to ensure RTL/LTR is applied as CSS property
52
+ const directionStyles = `
53
+ html[lang="${lang}"] { direction: ${direction}; }
54
+ html[lang="${lang}"] body { direction: ${direction}; }
55
+ `
56
+
57
+ return { lang, direction, directionStyles }
58
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Copyright (c) 2025 ESSENTIAL SERVICES
3
+ *
4
+ * This source code is licensed under the Business Source License 1.1
5
+ * found in the LICENSE.md file in the root directory of this source tree.
6
+ */
7
+
8
+ import { type ReactElement } from 'react'
9
+ import { SectionRenderer } from '@/presentation/ui/pages/SectionRenderer'
10
+ import { toSlug } from '@/presentation/utils/string-utils'
11
+ import type {
12
+ ComponentReference,
13
+ SimpleComponentReference,
14
+ } from '@/domain/models/app/component/common/component-reference'
15
+ import type { Components } from '@/domain/models/app/components'
16
+ import type { Languages } from '@/domain/models/app/languages'
17
+ import type { Component } from '@/domain/models/app/page/sections'
18
+ import type { Page } from '@/domain/models/app/pages'
19
+ import type { Theme } from '@/domain/models/app/theme'
20
+
21
+ type PageMainProps = {
22
+ readonly page: Page
23
+ readonly sections: ReadonlyArray<Component | SimpleComponentReference | ComponentReference>
24
+ readonly theme?: Theme
25
+ readonly components?: Components
26
+ readonly languages?: Languages
27
+ readonly currentLang: string
28
+ }
29
+
30
+ /**
31
+ * Renders the main content area with page sections
32
+ */
33
+ export function PageMain({
34
+ page,
35
+ sections,
36
+ theme,
37
+ components,
38
+ languages,
39
+ currentLang,
40
+ }: PageMainProps): Readonly<ReactElement> {
41
+ return (
42
+ <main
43
+ data-testid={`page-${toSlug(page.name ?? page.path)}`}
44
+ data-page-name={page.name}
45
+ data-page-id={page.id}
46
+ style={{ minHeight: '1px' }}
47
+ >
48
+ <SectionRenderer
49
+ sections={sections}
50
+ pageVars={page.vars}
51
+ theme={theme}
52
+ components={components}
53
+ languages={languages}
54
+ currentLang={currentLang}
55
+ />
56
+ </main>
57
+ )
58
+ }
@@ -0,0 +1,168 @@
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 { resolveTranslationPattern } from '@/presentation/translations/translation-resolver'
9
+ import type { Languages } from '@/domain/models/app/languages'
10
+ import type { Page } from '@/domain/models/app/pages'
11
+ import type { Theme } from '@/domain/models/app/theme'
12
+
13
+ /**
14
+ * Metadata derived from page configuration
15
+ */
16
+ export type PageMetadata = {
17
+ readonly lang: string
18
+ readonly direction: 'ltr' | 'rtl'
19
+ readonly title: string
20
+ readonly description: string
21
+ readonly keywords?: string
22
+ readonly canonical?: string
23
+ readonly bodyStyle:
24
+ | {
25
+ readonly fontFamily?: string
26
+ readonly fontSize?: string
27
+ readonly lineHeight?: string
28
+ readonly fontStyle?: 'normal' | 'italic' | 'oblique'
29
+ readonly letterSpacing?: string
30
+ readonly textTransform?: 'none' | 'uppercase' | 'lowercase' | 'capitalize'
31
+ }
32
+ | undefined
33
+ }
34
+
35
+ /**
36
+ * Build font-family string with fallback
37
+ */
38
+ function buildFontFamily(family?: string, fallback?: string): string | undefined {
39
+ if (!family) {
40
+ return undefined
41
+ }
42
+ return fallback ? `${family}, ${fallback}` : family
43
+ }
44
+
45
+ /**
46
+ * Build body style object from theme fonts configuration
47
+ */
48
+ function buildBodyStyle(theme: Theme | undefined): PageMetadata['bodyStyle'] {
49
+ if (!theme?.fonts?.body) {
50
+ return undefined
51
+ }
52
+
53
+ const { body } = theme.fonts
54
+ const fontFamily = buildFontFamily(body.family, body.fallback)
55
+
56
+ return {
57
+ ...(fontFamily && { fontFamily }),
58
+ ...(body.size && { fontSize: body.size }),
59
+ ...(body.lineHeight && { lineHeight: body.lineHeight }),
60
+ ...(body.style && { fontStyle: body.style as 'normal' | 'italic' | 'oblique' }),
61
+ ...(body.letterSpacing && { letterSpacing: body.letterSpacing }),
62
+ ...(body.transform && {
63
+ textTransform: body.transform as 'none' | 'uppercase' | 'lowercase' | 'capitalize',
64
+ }),
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Determine language code (priority: page.meta.lang > detectedLanguage > default)
70
+ */
71
+ function determineLanguage(
72
+ page: Page,
73
+ languages: Languages | undefined,
74
+ detectedLanguage: string | undefined
75
+ ): string {
76
+ return page.meta?.lang || detectedLanguage || languages?.default || 'en-US'
77
+ }
78
+
79
+ /**
80
+ * Determine text direction from language configuration
81
+ */
82
+ function determineDirection(languages: Languages | undefined, lang: string): 'ltr' | 'rtl' {
83
+ const langConfig = languages?.supported.find((l) => l.code === lang)
84
+ return langConfig?.direction || 'ltr'
85
+ }
86
+
87
+ /**
88
+ * Determine page title with translation resolution
89
+ * Priority: meta.i18n[lang].title > meta.title (with $t: pattern) > page.name > page.path
90
+ */
91
+ function determineTitle(page: Page, lang: string, languages: Languages | undefined): string {
92
+ // Check if page has i18n translations for this language
93
+ if (page.meta?.i18n?.[lang]?.title) {
94
+ return page.meta.i18n[lang].title
95
+ }
96
+
97
+ // Fall back to base title with translation pattern resolution
98
+ const rawTitle = page.meta?.title || page.name || page.path
99
+ return resolveTranslationPattern(rawTitle, lang, languages)
100
+ }
101
+
102
+ /**
103
+ * Determine page description with translation resolution
104
+ * Priority: meta.i18n[lang].description > meta.description (with $t: pattern)
105
+ */
106
+ function determineDescription(page: Page, lang: string, languages: Languages | undefined): string {
107
+ // Check if page has i18n translations for this language
108
+ if (page.meta?.i18n?.[lang]?.description) {
109
+ return page.meta.i18n[lang].description
110
+ }
111
+
112
+ // Fall back to base description with translation pattern resolution
113
+ const rawDescription = page.meta?.description || ''
114
+ return resolveTranslationPattern(rawDescription, lang, languages)
115
+ }
116
+
117
+ /**
118
+ * Determine page keywords with translation resolution
119
+ * Resolves $t: patterns in keywords
120
+ */
121
+ function determineKeywords(
122
+ page: Page,
123
+ lang: string,
124
+ languages: Languages | undefined
125
+ ): string | undefined {
126
+ if (!page.meta?.keywords) {
127
+ return undefined
128
+ }
129
+
130
+ return resolveTranslationPattern(page.meta.keywords, lang, languages)
131
+ }
132
+
133
+ /**
134
+ * Extracts and computes metadata from page configuration
135
+ *
136
+ * Determines language (page.meta.lang > detectedLanguage > default),
137
+ * text direction from language config, title/description, and body styles from theme.
138
+ *
139
+ * @param page - Page configuration
140
+ * @param theme - Optional theme configuration
141
+ * @param languages - Optional languages configuration
142
+ * @param detectedLanguage - Optional detected language from browser or URL
143
+ * @returns Computed page metadata
144
+ */
145
+ export function extractPageMetadata(
146
+ page: Page,
147
+ theme: Theme | undefined,
148
+ languages: Languages | undefined,
149
+ detectedLanguage: string | undefined
150
+ ): Readonly<PageMetadata> {
151
+ const lang = determineLanguage(page, languages, detectedLanguage)
152
+ const direction = determineDirection(languages, lang)
153
+ const title = determineTitle(page, lang, languages)
154
+ const description = determineDescription(page, lang, languages)
155
+ const keywords = determineKeywords(page, lang, languages)
156
+ const canonical = page.meta?.canonical
157
+ const bodyStyle = buildBodyStyle(theme)
158
+
159
+ return {
160
+ lang,
161
+ direction,
162
+ title,
163
+ description,
164
+ keywords,
165
+ canonical,
166
+ bodyStyle,
167
+ }
168
+ }
@@ -0,0 +1,169 @@
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 { resolveTranslationPattern } from '@/presentation/translations/translation-resolver'
9
+ import type { Languages } from '@/domain/models/app/languages'
10
+ import type { Meta } from '@/domain/models/app/page/meta'
11
+ import type { Page } from '@/domain/models/app/pages'
12
+
13
+ /**
14
+ * Resolves i18n metadata for a single language
15
+ */
16
+ function resolveMetaI18n(
17
+ meta: Meta,
18
+ langCode: string,
19
+ languages: Languages
20
+ ): { title?: string; description?: string; keywords?: string; 'og:site_name'?: string } {
21
+ const metaRecord = meta as Record<string, unknown>
22
+ const ogSiteName = metaRecord['og:site_name']
23
+
24
+ return {
25
+ ...(meta.title && {
26
+ title: resolveTranslationPattern(meta.title, langCode, languages),
27
+ }),
28
+ ...(meta.description && {
29
+ description: resolveTranslationPattern(meta.description, langCode, languages),
30
+ }),
31
+ ...(meta.keywords && {
32
+ keywords: resolveTranslationPattern(meta.keywords, langCode, languages),
33
+ }),
34
+ ...(typeof ogSiteName === 'string' && {
35
+ 'og:site_name': resolveTranslationPattern(ogSiteName, langCode, languages),
36
+ }),
37
+ ...(meta.openGraph?.siteName &&
38
+ typeof ogSiteName !== 'string' && {
39
+ 'og:site_name': resolveTranslationPattern(meta.openGraph.siteName, langCode, languages),
40
+ }),
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Merges existing i18n with generated i18n
46
+ */
47
+ function mergeI18n(
48
+ generatedI18n: Record<
49
+ string,
50
+ { title?: string; description?: string; keywords?: string; 'og:site_name'?: string }
51
+ >,
52
+ existingI18n:
53
+ | Record<
54
+ string,
55
+ { title?: string; description?: string; keywords?: string; 'og:site_name'?: string }
56
+ >
57
+ | undefined
58
+ ): typeof generatedI18n {
59
+ if (!existingI18n) {
60
+ return generatedI18n
61
+ }
62
+
63
+ return Object.keys(generatedI18n).reduce(
64
+ (acc, langCode) => ({
65
+ ...acc,
66
+ [langCode]: {
67
+ ...generatedI18n[langCode],
68
+ ...(existingI18n[langCode] || {}),
69
+ },
70
+ }),
71
+ {} as typeof generatedI18n
72
+ )
73
+ }
74
+
75
+ /**
76
+ * Resolves base meta fields with translation patterns
77
+ */
78
+ function resolveBaseMeta(meta: Meta, currentLang: string, languages: Languages): Meta {
79
+ const metaRecord = meta as Record<string, unknown>
80
+ const ogSiteName = metaRecord['og:site_name']
81
+
82
+ const resolvedMeta = {
83
+ ...meta,
84
+ ...(meta.title && { title: resolveTranslationPattern(meta.title, currentLang, languages) }),
85
+ ...(meta.description && {
86
+ description: resolveTranslationPattern(meta.description, currentLang, languages),
87
+ }),
88
+ ...(meta.keywords && {
89
+ keywords: resolveTranslationPattern(meta.keywords, currentLang, languages),
90
+ }),
91
+ }
92
+
93
+ // Resolve og:site_name if present (either as direct property or in openGraph object)
94
+ if (typeof ogSiteName === 'string') {
95
+ return {
96
+ ...resolvedMeta,
97
+ 'og:site_name': resolveTranslationPattern(ogSiteName, currentLang, languages),
98
+ } as Meta
99
+ }
100
+
101
+ if (meta.openGraph?.siteName) {
102
+ return {
103
+ ...resolvedMeta,
104
+ openGraph: {
105
+ ...meta.openGraph,
106
+ siteName: resolveTranslationPattern(meta.openGraph.siteName, currentLang, languages),
107
+ },
108
+ }
109
+ }
110
+
111
+ return resolvedMeta
112
+ }
113
+
114
+ /**
115
+ * Builds i18n metadata structure for client-side language switching
116
+ *
117
+ * Resolves translation patterns ($t:...) for all supported languages
118
+ * and constructs a complete i18n structure that the client can use
119
+ * to update meta tags when switching languages.
120
+ *
121
+ * CRITICAL: All $t: tokens in base meta fields are resolved to ensure
122
+ * no translation tokens appear in the serialized HTML output.
123
+ *
124
+ * @param page - Page configuration
125
+ * @param languages - Languages configuration
126
+ * @returns Page meta with i18n structure populated and all $t: tokens resolved
127
+ */
128
+ export function buildPageMetadataI18n(
129
+ page: Page,
130
+ languages: Languages | undefined
131
+ ): Meta | Record<string, never> {
132
+ if (!page.meta || !languages) {
133
+ return (page.meta || {}) as Meta | Record<string, never>
134
+ }
135
+
136
+ const { meta } = page
137
+ const currentLang = meta.lang || languages.default
138
+
139
+ // Build i18n structure for all supported languages
140
+ const generatedI18n = languages.supported.reduce(
141
+ (acc, lang) => ({
142
+ ...acc,
143
+ [lang.code]: resolveMetaI18n(meta, lang.code, languages),
144
+ }),
145
+ {} as Record<
146
+ string,
147
+ { title?: string; description?: string; keywords?: string; 'og:site_name'?: string }
148
+ >
149
+ )
150
+
151
+ // Merge existing meta.i18n with generated i18n (existing takes precedence)
152
+ const existingI18n = (meta as Record<string, unknown>).i18n as
153
+ | Record<
154
+ string,
155
+ { title?: string; description?: string; keywords?: string; 'og:site_name'?: string }
156
+ >
157
+ | undefined
158
+
159
+ const i18n = mergeI18n(generatedI18n, existingI18n)
160
+
161
+ // Resolve all $t: tokens in base meta fields for current language
162
+ const resolvedMeta = resolveBaseMeta(meta, currentLang, languages)
163
+
164
+ // Return resolved meta with i18n structure
165
+ return {
166
+ ...resolvedMeta,
167
+ i18n,
168
+ }
169
+ }
@@ -0,0 +1,78 @@
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 { Page } from '@/domain/models/app/pages'
9
+
10
+ /**
11
+ * External script configuration
12
+ */
13
+ export type ExternalScript = {
14
+ readonly src: string
15
+ readonly position?: 'head' | 'body-start' | 'body-end'
16
+ readonly async?: boolean
17
+ readonly defer?: boolean
18
+ readonly module?: boolean
19
+ readonly integrity?: string
20
+ readonly crossorigin?: 'anonymous' | 'use-credentials'
21
+ }
22
+
23
+ /**
24
+ * Inline script configuration
25
+ */
26
+ export type InlineScript = {
27
+ readonly code: string
28
+ readonly position?: 'head' | 'body-start' | 'body-end'
29
+ readonly async?: boolean
30
+ }
31
+
32
+ /**
33
+ * Grouped scripts by position
34
+ */
35
+ export type GroupedScripts = {
36
+ readonly external: {
37
+ readonly head: ReadonlyArray<ExternalScript>
38
+ readonly bodyStart: ReadonlyArray<ExternalScript>
39
+ readonly bodyEnd: ReadonlyArray<ExternalScript>
40
+ }
41
+ readonly inline: {
42
+ readonly head: ReadonlyArray<InlineScript>
43
+ readonly bodyStart: ReadonlyArray<InlineScript>
44
+ readonly bodyEnd: ReadonlyArray<InlineScript>
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Groups external and inline scripts by their position in the document
50
+ *
51
+ * Scripts can be positioned in 'head', 'body-start', or 'body-end'.
52
+ * Scripts without a position default to 'body-end'.
53
+ *
54
+ * @param page - Page configuration containing scripts
55
+ * @returns Scripts grouped by position
56
+ */
57
+ export function groupScriptsByPosition(page: Page): Readonly<GroupedScripts> {
58
+ // Extract external scripts (support both test shorthand and canonical property)
59
+ const externalScripts = page.scripts?.externalScripts || page.scripts?.external || []
60
+
61
+ // Extract inline scripts
62
+ const inlineScripts = page.scripts?.inlineScripts || []
63
+
64
+ return {
65
+ external: {
66
+ head: externalScripts.filter((script) => script.position === 'head'),
67
+ bodyStart: externalScripts.filter((script) => script.position === 'body-start'),
68
+ bodyEnd: externalScripts.filter(
69
+ (script) => !script.position || script.position === 'body-end'
70
+ ),
71
+ },
72
+ inline: {
73
+ head: inlineScripts.filter((script) => script.position === 'head'),
74
+ bodyStart: inlineScripts.filter((script) => script.position === 'body-start'),
75
+ bodyEnd: inlineScripts.filter((script) => !script.position || script.position === 'body-end'),
76
+ },
77
+ }
78
+ }
@@ -0,0 +1,67 @@
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 ReactElement } from 'react'
9
+ import { getComponentInfo } from '@/presentation/translations/component-utils'
10
+ import { renderSectionWithSpacing } from '@/presentation/ui/pages/SectionSpacing'
11
+ import { ComponentRenderer } from '@/presentation/ui/sections/component-renderer'
12
+ import type {
13
+ ComponentReference,
14
+ SimpleComponentReference,
15
+ } from '@/domain/models/app/component/common/component-reference'
16
+ import type { Components } from '@/domain/models/app/components'
17
+ import type { Languages } from '@/domain/models/app/languages'
18
+ import type { Component } from '@/domain/models/app/page/sections'
19
+ import type { Theme } from '@/domain/models/app/theme'
20
+
21
+ /**
22
+ * Props for the SectionRenderer component
23
+ */
24
+ export interface SectionRendererProps {
25
+ readonly sections: ReadonlyArray<Component | SimpleComponentReference | ComponentReference>
26
+ readonly pageVars?: Record<string, string | number | boolean>
27
+ readonly theme?: Theme
28
+ readonly components?: Components
29
+ readonly languages?: Languages
30
+ readonly currentLang: string
31
+ }
32
+
33
+ /**
34
+ * Render sections with theme spacing
35
+ * Handles section wrapping, container spacing, and component resolution
36
+ *
37
+ * @param props - Component props
38
+ * @returns React element with sections
39
+ */
40
+ export function SectionRenderer({
41
+ sections,
42
+ pageVars,
43
+ theme,
44
+ components,
45
+ languages,
46
+ currentLang,
47
+ }: SectionRendererProps): Readonly<ReactElement> {
48
+ const renderedSections = sections.map((section, index) => {
49
+ const componentInfo = getComponentInfo(section, index, sections)
50
+
51
+ return (
52
+ <ComponentRenderer
53
+ key={index}
54
+ component={section}
55
+ pageVars={pageVars}
56
+ componentName={componentInfo?.name}
57
+ componentInstanceIndex={componentInfo?.instanceIndex}
58
+ components={components}
59
+ theme={theme}
60
+ languages={languages}
61
+ currentLang={currentLang}
62
+ />
63
+ )
64
+ })
65
+
66
+ return renderSectionWithSpacing(theme, sections, renderedSections)
67
+ }