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,242 @@
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 {
10
+ renderInlineScriptTag,
11
+ renderScriptTag,
12
+ renderWindowConfig,
13
+ } from '@/presentation/scripts/script-renderers'
14
+ import { buildPageMetadataI18n } from './PageMetadataI18n'
15
+ import type { GroupedScripts } from './PageScripts'
16
+ import type { Languages } from '@/domain/models/app/languages'
17
+ import type { Page } from '@/domain/models/app/pages'
18
+ import type { Theme } from '@/domain/models/app/theme'
19
+
20
+ /**
21
+ * Props for PageBodyScripts component
22
+ */
23
+ type PageBodyScriptsProps = {
24
+ readonly page: Page
25
+ readonly theme: Theme | undefined
26
+ readonly languages: Languages | undefined
27
+ readonly direction: 'ltr' | 'rtl'
28
+ readonly scripts: GroupedScripts
29
+ readonly position: 'start' | 'end'
30
+ }
31
+
32
+ /**
33
+ * Renders external and inline scripts for a given position
34
+ */
35
+ function renderScripts(
36
+ externalScripts: GroupedScripts['external']['head'],
37
+ inlineScripts: GroupedScripts['inline']['head'],
38
+ keyPrefix: string
39
+ ): ReactElement {
40
+ return (
41
+ <>
42
+ {externalScripts.map((script, index) =>
43
+ renderScriptTag({
44
+ src: script.src,
45
+ async: script.async,
46
+ defer: script.defer,
47
+ module: script.module,
48
+ integrity: script.integrity,
49
+ crossOrigin: script.crossorigin as 'anonymous' | 'use-credentials' | undefined,
50
+ reactKey: `${keyPrefix}-${index}`,
51
+ })
52
+ )}
53
+ {inlineScripts.map((script, index) =>
54
+ renderInlineScriptTag({
55
+ code: script.code,
56
+ async: script.async,
57
+ reactKey: `inline-${keyPrefix}-${index}`,
58
+ })
59
+ )}
60
+ </>
61
+ )
62
+ }
63
+
64
+ /**
65
+ * Renders language switcher scripts and configuration
66
+ */
67
+ function LanguageSwitcherScripts({
68
+ page,
69
+ languages,
70
+ theme,
71
+ direction,
72
+ }: {
73
+ readonly page: Page
74
+ readonly languages: Languages
75
+ readonly theme: Theme | undefined
76
+ readonly direction: 'ltr' | 'rtl'
77
+ }): ReactElement {
78
+ // Build enriched metadata with i18n translations for all languages
79
+ const enrichedMeta = buildPageMetadataI18n(page, languages)
80
+
81
+ return (
82
+ <>
83
+ {/* Configuration data for external script (CSP-compliant) */}
84
+ <div
85
+ data-language-switcher-config={JSON.stringify(languages)}
86
+ style={{ display: 'none' }}
87
+ />
88
+ {/* Page metadata for client-side updates (title, i18n) */}
89
+ <div
90
+ data-page-meta={JSON.stringify(enrichedMeta)}
91
+ style={{ display: 'none' }}
92
+ />
93
+ {/* Expose languages config to window for testing/debugging - fallback defaults to default language */}
94
+ {renderWindowConfig({
95
+ windowKey: 'APP_LANGUAGES',
96
+ data: {
97
+ ...languages,
98
+ fallback: languages.fallback ?? languages.default,
99
+ },
100
+ reactKey: 'window-app-languages',
101
+ })}
102
+ {/* Expose theme config with RTL-aware direction to window for testing/debugging */}
103
+ {renderWindowConfig({
104
+ windowKey: 'APP_THEME',
105
+ data: {
106
+ ...(theme || {}),
107
+ direction: direction,
108
+ },
109
+ reactKey: 'window-app-theme',
110
+ })}
111
+ {/* External script file loaded only when needed (defer ensures DOM is ready) */}
112
+ <script
113
+ src="/assets/language-switcher.js"
114
+ defer={true}
115
+ />
116
+ </>
117
+ )
118
+ }
119
+
120
+ /**
121
+ * Renders scroll animation script if needed
122
+ */
123
+ function ScrollAnimationScript({
124
+ page,
125
+ theme,
126
+ }: {
127
+ readonly page: Page
128
+ readonly theme: Theme | undefined
129
+ }): ReactElement | undefined {
130
+ const needsAnimation = (page.sections && page.sections.length > 0) || theme?.animations?.scaleUp
131
+ if (!needsAnimation) return undefined
132
+ return (
133
+ <script
134
+ src="/assets/scroll-animation.js"
135
+ defer={true}
136
+ />
137
+ )
138
+ }
139
+
140
+ /**
141
+ * Renders feature flags configuration if configured
142
+ */
143
+ function FeatureFlagsScript({ page }: { readonly page: Page }): ReactElement | undefined {
144
+ if (!page.scripts?.features) return undefined
145
+ return renderWindowConfig({
146
+ windowKey: 'FEATURES',
147
+ data: page.scripts.features,
148
+ reactKey: 'window-features',
149
+ })
150
+ }
151
+
152
+ /**
153
+ * Renders conditional script tags (banner, animation, features)
154
+ */
155
+ function renderConditionalScripts(config: {
156
+ readonly page: Page
157
+ readonly theme: Theme | undefined
158
+ readonly languages: Languages | undefined
159
+ readonly direction: 'ltr' | 'rtl'
160
+ }): ReactElement {
161
+ const { page, theme, languages, direction } = config
162
+ return (
163
+ <>
164
+ <ScrollAnimationScript
165
+ page={page}
166
+ theme={theme}
167
+ />
168
+ {languages && (
169
+ <LanguageSwitcherScripts
170
+ page={page}
171
+ languages={languages}
172
+ theme={theme}
173
+ direction={direction}
174
+ />
175
+ )}
176
+ <FeatureFlagsScript page={page} />
177
+ </>
178
+ )
179
+ }
180
+
181
+ /**
182
+ * Click interaction handler (SECURITY: Safe - static code, no user input)
183
+ */
184
+ const clickScript = `!function(){document.addEventListener("click",function(t){const e=t.target.closest("[data-click-animation], [data-click-navigate], [data-click-open-url], [data-click-scroll-to], [data-click-toggle-element], [data-click-submit-form]");if(!e)return;const n=e.getAttribute("data-click-animation"),a=e.getAttribute("data-click-navigate"),c=e.getAttribute("data-click-open-url"),i=e.getAttribute("data-click-open-in-new-tab")==="true",o=e.getAttribute("data-click-scroll-to"),l=e.getAttribute("data-click-toggle-element"),r=e.getAttribute("data-click-submit-form"),s=c||a,d=!!c;if(r){const t=document.querySelector(r);t&&"FORM"===t.tagName&&t.requestSubmit()}else if(l){const t=document.querySelector(l);if(t){const e="none"===window.getComputedStyle(t).display;t.style.display=e?"":"none"}}else if(o){const t=document.querySelector(o);t&&t.scrollIntoView({behavior:"smooth",block:"start"})}else if(n&&"none"!==n){const t="animate-"+n;if(e.classList.add(t),s){let n=!1;const a=function(){n||(n=!0,e.classList.remove(t),d&&i?window.open(s,"_blank"):window.location.href=s)};e.addEventListener("animationend",a,{once:!0}),setTimeout(a,300)}else{const n=function(){e.classList.remove(t)};e.addEventListener("animationend",n,{once:!0}),setTimeout(n,300)}}else s&&(d&&i?window.open(s,"_blank"):window.location.href=s)})}();`
185
+
186
+ /**
187
+ * Renders scripts for body end position
188
+ */
189
+ function renderBodyEndScripts(config: {
190
+ readonly page: Page
191
+ readonly theme: Theme | undefined
192
+ readonly languages: Languages | undefined
193
+ readonly direction: 'ltr' | 'rtl'
194
+ readonly scripts: GroupedScripts
195
+ }): ReactElement {
196
+ const { page, theme, languages, direction, scripts } = config
197
+ return (
198
+ <>
199
+ {renderScripts(scripts.external.bodyEnd, scripts.inline.bodyEnd, 'body-end')}
200
+ {renderConditionalScripts({ page, theme, languages, direction })}
201
+ {/* Render APP_CONFIG after inline scripts to merge with any existing values */}
202
+ {page.scripts?.config &&
203
+ renderWindowConfig({
204
+ windowKey: 'APP_CONFIG',
205
+ data: page.scripts.config,
206
+ reactKey: 'window-app-config-body',
207
+ })}
208
+ <script dangerouslySetInnerHTML={{ __html: clickScript }} />
209
+ </>
210
+ )
211
+ }
212
+
213
+ /**
214
+ * Renders scripts for body start or end position
215
+ *
216
+ * For 'start' position:
217
+ * - External and inline scripts positioned at body-start
218
+ *
219
+ * For 'end' position:
220
+ * - External and inline scripts positioned at body-end
221
+ * - Banner dismiss script (if banner is dismissible)
222
+ * - Scroll animation script (if theme has scaleUp animation)
223
+ * - Language switcher script (if languages configured)
224
+ * - Feature flags script (if features configured)
225
+ *
226
+ * @param props - Component props
227
+ * @returns Script elements for the specified position
228
+ */
229
+ export function PageBodyScripts({
230
+ page,
231
+ theme,
232
+ languages,
233
+ direction,
234
+ scripts,
235
+ position,
236
+ }: PageBodyScriptsProps): Readonly<ReactElement> {
237
+ if (position === 'start') {
238
+ return renderScripts(scripts.external.bodyStart, scripts.inline.bodyStart, 'body-start')
239
+ }
240
+
241
+ return renderBodyEndScripts({ page, theme, languages, direction, scripts })
242
+ }
@@ -0,0 +1,52 @@
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 { Theme } from '@/domain/models/app/theme'
9
+
10
+ /**
11
+ * Body style configuration for theme fonts
12
+ */
13
+ export type BodyStyle = {
14
+ readonly fontFamily?: string
15
+ readonly fontSize?: string
16
+ readonly lineHeight?: string
17
+ readonly fontStyle?: 'normal' | 'italic' | 'oblique'
18
+ readonly letterSpacing?: string
19
+ readonly textTransform?: 'none' | 'uppercase' | 'lowercase' | 'capitalize'
20
+ }
21
+
22
+ /**
23
+ * Builds font family string with optional fallback
24
+ */
25
+ function buildFontFamily(family: string, fallback?: string): string {
26
+ return fallback ? `${family}, ${fallback}` : family
27
+ }
28
+
29
+ /**
30
+ * Generates inline body styles from theme fonts configuration
31
+ *
32
+ * Inline styles have highest specificity and override Tailwind base styles.
33
+ *
34
+ * @param theme - Theme configuration
35
+ * @returns Body style object or undefined if no body font configured
36
+ */
37
+ export function generateBodyStyle(theme: Theme | undefined): BodyStyle | undefined {
38
+ if (!theme?.fonts?.body) return undefined
39
+
40
+ const bodyFont = theme.fonts.body
41
+
42
+ return {
43
+ ...(bodyFont.family && { fontFamily: buildFontFamily(bodyFont.family, bodyFont.fallback) }),
44
+ ...(bodyFont.size && { fontSize: bodyFont.size }),
45
+ ...(bodyFont.lineHeight && { lineHeight: bodyFont.lineHeight }),
46
+ ...(bodyFont.style && { fontStyle: bodyFont.style as 'normal' | 'italic' | 'oblique' }),
47
+ ...(bodyFont.letterSpacing && { letterSpacing: bodyFont.letterSpacing }),
48
+ ...(bodyFont.transform && {
49
+ textTransform: bodyFont.transform as 'none' | 'uppercase' | 'lowercase' | 'capitalize',
50
+ }),
51
+ }
52
+ }
@@ -0,0 +1,380 @@
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 { normalizeFavicons } from '@/application/metadata/favicon-transformer'
10
+ import { renderInlineScriptTag, renderScriptTag } from '@/presentation/scripts/script-renderers'
11
+ import { resolveTranslationPattern } from '@/presentation/translations/translation-resolver'
12
+ import {
13
+ AnalyticsHead,
14
+ CustomElementsHead,
15
+ DnsPrefetchLinks,
16
+ FaviconLink,
17
+ FaviconSetLinks,
18
+ OpenGraphMeta,
19
+ PreloadLinks,
20
+ StructuredDataScript,
21
+ TwitterCardMeta,
22
+ } from '@/presentation/ui/metadata'
23
+ import type { GroupedScripts } from './PageScripts'
24
+ import type { Languages } from '@/domain/models/app/languages'
25
+ import type { CustomElement } from '@/domain/models/app/page/meta/custom-elements'
26
+ import type { OpenGraph } from '@/domain/models/app/page/meta/open-graph'
27
+ import type { Page } from '@/domain/models/app/pages'
28
+ import type { Theme } from '@/domain/models/app/theme'
29
+
30
+ /**
31
+ * Props for PageHead component
32
+ */
33
+ type PageHeadProps = {
34
+ readonly page: Page
35
+ readonly theme: Theme | undefined
36
+ readonly directionStyles: string
37
+ readonly title: string
38
+ readonly description: string
39
+ readonly keywords?: string
40
+ readonly canonical?: string
41
+ readonly lang: string
42
+ readonly languages?: Languages
43
+ readonly scripts: GroupedScripts
44
+ }
45
+
46
+ /**
47
+ * Checks if custom elements include a viewport meta tag
48
+ */
49
+ function hasCustomViewportMeta(customElements: readonly CustomElement[] | undefined): boolean {
50
+ if (!customElements) return false
51
+ return customElements.some(
52
+ (element) => element.type === 'meta' && element.attrs?.name === 'viewport'
53
+ )
54
+ }
55
+
56
+ /**
57
+ * Extracts OpenGraph properties from meta object
58
+ * Handles both standard openGraph object and og:* prefixed direct properties
59
+ */
60
+ function extractOpenGraphData(
61
+ page: Page,
62
+ lang: string,
63
+ languages: Languages | undefined
64
+ ): OpenGraph | undefined {
65
+ if (!page.meta) return undefined
66
+
67
+ // Start with existing openGraph object if present
68
+ const openGraph = page.meta.openGraph || {}
69
+
70
+ // Extract og:* prefixed properties from meta (type assertion needed for dynamic access)
71
+ const metaRecord = page.meta as Record<string, unknown>
72
+ const ogSiteName = metaRecord['og:site_name']
73
+
74
+ // Resolve translation patterns in extracted properties
75
+ const resolvedSiteName =
76
+ typeof ogSiteName === 'string'
77
+ ? resolveTranslationPattern(ogSiteName, lang, languages)
78
+ : undefined
79
+
80
+ // Merge og:* properties into openGraph structure
81
+ const merged = {
82
+ ...openGraph,
83
+ ...(resolvedSiteName && { siteName: resolvedSiteName }),
84
+ }
85
+
86
+ // Only return if there's at least one property
87
+ return Object.keys(merged).length > 0 ? merged : undefined
88
+ }
89
+
90
+ /**
91
+ * Renders basic meta tags (charset, viewport, title, description, keywords, canonical)
92
+ */
93
+ function BasicMetaTags({
94
+ title,
95
+ description,
96
+ keywords,
97
+ canonical,
98
+ hasCustomViewport,
99
+ }: {
100
+ readonly title: string
101
+ readonly description: string
102
+ readonly keywords?: string
103
+ readonly canonical?: string
104
+ readonly hasCustomViewport: boolean
105
+ }): ReactElement {
106
+ return (
107
+ <>
108
+ <meta charSet="UTF-8" />
109
+ {!hasCustomViewport && (
110
+ <meta
111
+ name="viewport"
112
+ content="width=device-width, initial-scale=1.0"
113
+ />
114
+ )}
115
+ <title>{title}</title>
116
+ {description && (
117
+ <meta
118
+ name="description"
119
+ content={description}
120
+ />
121
+ )}
122
+ {keywords && (
123
+ <meta
124
+ name="keywords"
125
+ content={keywords}
126
+ />
127
+ )}
128
+ {canonical && (
129
+ <link
130
+ rel="canonical"
131
+ href={canonical}
132
+ />
133
+ )}
134
+ </>
135
+ )
136
+ }
137
+
138
+ /**
139
+ * Renders font stylesheets from theme configuration
140
+ */
141
+ function ThemeFonts({ theme }: { readonly theme: Theme | undefined }): ReactElement | undefined {
142
+ if (!theme?.fonts) return undefined
143
+
144
+ return (
145
+ <>
146
+ {Object.values(theme.fonts).map((font, index) =>
147
+ font.url ? (
148
+ <link
149
+ key={`font-${index}`}
150
+ rel="stylesheet"
151
+ href={font.url}
152
+ />
153
+ ) : undefined
154
+ )}
155
+ </>
156
+ )
157
+ }
158
+
159
+ /**
160
+ * Renders custom stylesheet link from meta.stylesheet
161
+ */
162
+ function CustomStylesheet({
163
+ stylesheet,
164
+ }: {
165
+ readonly stylesheet: string | undefined
166
+ }): ReactElement | undefined {
167
+ if (!stylesheet) return undefined
168
+
169
+ return (
170
+ <link
171
+ rel="stylesheet"
172
+ href={stylesheet}
173
+ />
174
+ )
175
+ }
176
+
177
+ /**
178
+ * Renders Google Fonts with performance optimizations
179
+ * Includes preconnect hints for fonts.googleapis.com and fonts.gstatic.com
180
+ */
181
+ function GoogleFonts({
182
+ googleFonts,
183
+ }: {
184
+ readonly googleFonts: string | undefined
185
+ }): ReactElement | undefined {
186
+ if (!googleFonts) return undefined
187
+
188
+ return (
189
+ <>
190
+ <link
191
+ rel="preconnect"
192
+ href="https://fonts.googleapis.com"
193
+ />
194
+ <link
195
+ rel="preconnect"
196
+ href="https://fonts.gstatic.com"
197
+ crossOrigin="anonymous"
198
+ />
199
+ <link
200
+ rel="stylesheet"
201
+ href={googleFonts}
202
+ />
203
+ </>
204
+ )
205
+ }
206
+
207
+ /**
208
+ * Renders global CSS and direction styles
209
+ * Theme CSS is compiled globally at /assets/output.css
210
+ *
211
+ * SECURITY: Safe use of dangerouslySetInnerHTML
212
+ * - Content: RTL direction styles (build-time generated CSS)
213
+ * - Source: directionStyles prop computed from language direction
214
+ * - Risk: None - contains only CSS direction properties
215
+ * - Validation: Generated by language direction logic (ltr/rtl)
216
+ * - Purpose: Apply RTL-aware styling for Arabic/Hebrew languages
217
+ * - XSS Protection: CSS syntax prevents script execution
218
+ * - Content: Fixed format like "[dir='rtl'] { direction: rtl; }"
219
+ */
220
+ function GlobalStyles({ directionStyles }: { readonly directionStyles: string }): ReactElement {
221
+ return (
222
+ <>
223
+ <link
224
+ rel="stylesheet"
225
+ href="/assets/output.css"
226
+ />
227
+ <style dangerouslySetInnerHTML={{ __html: directionStyles }} />
228
+ </>
229
+ )
230
+ }
231
+
232
+ /**
233
+ * Renders external and inline scripts for head section
234
+ * Note: APP_CONFIG is rendered in body-end to merge with inline scripts
235
+ */
236
+ function HeadScripts({ scripts }: { readonly scripts: GroupedScripts }): ReactElement {
237
+ return (
238
+ <>
239
+ {scripts.external.head.map((script, index) =>
240
+ renderScriptTag({
241
+ src: script.src,
242
+ async: script.async,
243
+ defer: script.defer,
244
+ module: script.module,
245
+ integrity: script.integrity,
246
+ crossOrigin: script.crossorigin,
247
+ reactKey: `head-${index}`,
248
+ })
249
+ )}
250
+ {scripts.inline.head.map((script, index) =>
251
+ renderInlineScriptTag({
252
+ code: script.code,
253
+ async: script.async,
254
+ reactKey: `inline-head-${index}`,
255
+ })
256
+ )}
257
+ </>
258
+ )
259
+ }
260
+
261
+ /**
262
+ * Renders hreflang alternate links for multi-language SEO
263
+ * Generates <link rel="alternate" hreflang="..."> tags for each supported language
264
+ *
265
+ * Uses dual-pattern approach:
266
+ * - hreflang attribute: Full locale (e.g., 'en-US', 'fr-FR') for SEO standards
267
+ * - href attribute: Short code URL path (e.g., '/en/', '/fr/') for routing
268
+ *
269
+ * Includes x-default link pointing to the default language for undefined locales
270
+ */
271
+ function HreflangLinks({
272
+ page,
273
+ languages,
274
+ }: {
275
+ readonly page: Page
276
+ readonly languages: Languages | undefined
277
+ }): ReactElement | undefined {
278
+ if (!languages || languages.supported.length <= 1) {
279
+ return undefined
280
+ }
281
+
282
+ const basePath = page.path === '/' ? '' : page.path
283
+
284
+ return (
285
+ <>
286
+ {languages.supported.map((lang) => {
287
+ // Use full locale for hreflang attribute (e.g., 'en-US', 'fr-FR')
288
+ // Use short code for URL path (e.g., '/en/', '/fr/')
289
+ const hreflang = lang.locale || lang.code
290
+ return (
291
+ <link
292
+ key={lang.code}
293
+ rel="alternate"
294
+ hrefLang={hreflang}
295
+ href={`/${lang.code}${basePath}/`}
296
+ />
297
+ )
298
+ })}
299
+ <link
300
+ key="x-default"
301
+ rel="alternate"
302
+ hrefLang="x-default"
303
+ href={`/${languages.default}${basePath}/`}
304
+ />
305
+ </>
306
+ )
307
+ }
308
+
309
+ /**
310
+ * Renders the complete <head> section of a dynamic page
311
+ *
312
+ * Includes:
313
+ * - Basic meta tags (charset, viewport, title, description)
314
+ * - OpenGraph and Twitter Card metadata
315
+ * - Structured data (JSON-LD)
316
+ * - Preload hints for critical resources
317
+ * - DNS prefetch hints
318
+ * - Analytics scripts
319
+ * - Custom elements
320
+ * - Favicon links
321
+ * - Custom stylesheet from meta.stylesheet
322
+ * - Google Fonts with preconnect hints from meta.googleFonts
323
+ * - Font stylesheets from theme
324
+ * - Global CSS with compiled theme tokens
325
+ * - Direction styles for RTL support
326
+ * - External and inline scripts positioned in head
327
+ *
328
+ * @param props - Component props
329
+ * @returns Head section elements
330
+ */
331
+ export function PageHead({
332
+ page,
333
+ theme,
334
+ directionStyles,
335
+ title,
336
+ description,
337
+ keywords,
338
+ canonical,
339
+ lang,
340
+ languages,
341
+ scripts,
342
+ }: PageHeadProps): Readonly<ReactElement> {
343
+ const hasCustomViewport = hasCustomViewportMeta(page.meta?.customElements)
344
+ const openGraphData = extractOpenGraphData(page, lang, languages)
345
+ const normalizedFavicons = normalizeFavicons(page.meta?.favicons)
346
+
347
+ return (
348
+ <>
349
+ <BasicMetaTags
350
+ title={title}
351
+ description={description}
352
+ keywords={keywords}
353
+ canonical={canonical}
354
+ hasCustomViewport={hasCustomViewport}
355
+ />
356
+ <OpenGraphMeta
357
+ openGraph={openGraphData}
358
+ lang={lang}
359
+ languages={languages}
360
+ />
361
+ <TwitterCardMeta page={page} />
362
+ <StructuredDataScript page={page} />
363
+ <PreloadLinks preload={page.meta?.preload} />
364
+ <DnsPrefetchLinks dnsPrefetch={page.meta?.dnsPrefetch} />
365
+ <HreflangLinks
366
+ page={page}
367
+ languages={languages}
368
+ />
369
+ <AnalyticsHead analytics={page.meta?.analytics} />
370
+ <CustomElementsHead customElements={page.meta?.customElements} />
371
+ <FaviconLink favicon={page.meta?.favicon} />
372
+ <FaviconSetLinks favicons={normalizedFavicons} />
373
+ <CustomStylesheet stylesheet={page.meta?.stylesheet} />
374
+ <GoogleFonts googleFonts={page.meta?.googleFonts} />
375
+ <ThemeFonts theme={theme} />
376
+ <GlobalStyles directionStyles={directionStyles} />
377
+ <HeadScripts scripts={scripts} />
378
+ </>
379
+ )
380
+ }