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,108 @@
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 { ComponentMeta } from './structured-data-from-component'
9
+ import type {
10
+ ComponentReference,
11
+ SimpleComponentReference,
12
+ } from '@/domain/models/app/component/common/component-reference'
13
+ import type { Components } from '@/domain/models/app/components'
14
+ import type { OpenGraph } from '@/domain/models/app/page/meta/open-graph'
15
+ import type { Component } from '@/domain/models/app/page/sections'
16
+
17
+ /**
18
+ * Substitutes variables in a string value
19
+ *
20
+ * @param value - String value with potential $variable placeholders
21
+ * @param vars - Variables map
22
+ * @returns String with variables substituted
23
+ */
24
+ function substituteMetaVariables(
25
+ value: string | undefined,
26
+ vars: Record<string, string | number | boolean> | undefined
27
+ ): string | undefined {
28
+ if (!value || !vars) return value
29
+
30
+ // Use reduce for functional approach instead of loop with mutation
31
+ return Object.entries(vars).reduce(
32
+ (result, [key, varValue]) => result.replace(new RegExp(`\\$${key}`, 'g'), String(varValue)),
33
+ value
34
+ )
35
+ }
36
+
37
+ /**
38
+ * Extracts Open Graph meta from a component's meta configuration
39
+ *
40
+ * @param meta - Component meta configuration
41
+ * @param vars - Variables for substitution
42
+ * @returns Partial Open Graph configuration
43
+ */
44
+ function extractOpenGraphFromComponentMeta(
45
+ meta: ComponentMeta | undefined,
46
+ vars: Record<string, string | number | boolean> | undefined
47
+ ): Partial<OpenGraph> | undefined {
48
+ if (!meta) return undefined
49
+
50
+ // Build immutably without mutation
51
+ const withImage = meta.image ? { image: substituteMetaVariables(meta.image, vars) } : {}
52
+
53
+ const withTitle = meta.title ? { title: substituteMetaVariables(meta.title, vars) } : {}
54
+
55
+ const withDescription = meta.description
56
+ ? { description: substituteMetaVariables(meta.description, vars) }
57
+ : {}
58
+
59
+ const openGraph = {
60
+ ...withImage,
61
+ ...withTitle,
62
+ ...withDescription,
63
+ }
64
+
65
+ return Object.keys(openGraph).length > 0 ? openGraph : undefined
66
+ }
67
+
68
+ /**
69
+ * Extracts component meta from page sections
70
+ *
71
+ * Processes all sections to find component references, resolves them, and extracts
72
+ * meta information that should be included in the page's Open Graph meta tags.
73
+ *
74
+ * @param sections - Page sections
75
+ * @param components - Available component templates
76
+ * @returns Merged Open Graph configuration from all components
77
+ */
78
+ export function extractComponentMetaFromSections(
79
+ sections: ReadonlyArray<Component | SimpleComponentReference | ComponentReference>,
80
+ components?: Components
81
+ ): Partial<OpenGraph> | undefined {
82
+ if (!sections || !components) return undefined
83
+
84
+ // Use functional map/filter instead of loop with mutation
85
+ const openGraphParts = sections
86
+ .filter(
87
+ (section): section is SimpleComponentReference | ComponentReference =>
88
+ 'component' in section || '$ref' in section
89
+ )
90
+ .map((section) => {
91
+ const componentName = 'component' in section ? section.component : section.$ref
92
+ const vars = 'vars' in section ? section.vars : undefined
93
+
94
+ // Find the component template definition
95
+ const template = components.find((b) => b.name === componentName)
96
+ if (!template?.props?.meta) return undefined
97
+
98
+ // Extract Open Graph meta from component meta
99
+ const meta = template.props.meta as ComponentMeta | undefined
100
+ return extractOpenGraphFromComponentMeta(meta, vars)
101
+ })
102
+ .filter((og): og is Partial<OpenGraph> => og !== undefined)
103
+
104
+ if (openGraphParts.length === 0) return undefined
105
+
106
+ // Merge all Open Graph parts (last one wins for duplicate keys)
107
+ return openGraphParts.reduce((acc, part) => ({ ...acc, ...part }), {})
108
+ }
@@ -0,0 +1,164 @@
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 { buildCustomElement } from './custom-elements-builders'
10
+ import type { CustomElements } from '@/domain/models/app/page/meta/custom-elements'
11
+ import type { FaviconSet } from '@/domain/models/app/page/meta/favicon-set'
12
+ import type { Preload } from '@/domain/models/app/page/meta/preload'
13
+
14
+ /**
15
+ * Render DNS prefetch link tags
16
+ * Generates <link rel="dns-prefetch" href="..."> tags for external domains
17
+ *
18
+ * @param dnsPrefetch - DNS prefetch configuration from page.meta
19
+ * @returns React fragment with DNS prefetch link tags
20
+ */
21
+ export function DnsPrefetchLinks({
22
+ dnsPrefetch,
23
+ }: {
24
+ readonly dnsPrefetch?: ReadonlyArray<string>
25
+ }): Readonly<ReactElement | undefined> {
26
+ if (!dnsPrefetch || dnsPrefetch.length === 0) {
27
+ return undefined
28
+ }
29
+
30
+ return (
31
+ <>
32
+ {dnsPrefetch.map((domain) => (
33
+ <link
34
+ key={domain}
35
+ rel="dns-prefetch"
36
+ href={domain}
37
+ />
38
+ ))}
39
+ </>
40
+ )
41
+ }
42
+
43
+ /**
44
+ * Render custom head elements
45
+ * Generates arbitrary HTML elements (meta, link, script, style, base) in <head>
46
+ *
47
+ * @param customElements - Custom elements configuration from page.meta
48
+ * @returns React fragment with custom head elements
49
+ */
50
+ export function CustomElementsHead({
51
+ customElements,
52
+ }: {
53
+ readonly customElements?: CustomElements
54
+ }): Readonly<ReactElement | undefined> {
55
+ if (!customElements || customElements.length === 0) {
56
+ return undefined
57
+ }
58
+
59
+ return <>{customElements.map(buildCustomElement)}</>
60
+ }
61
+
62
+ /**
63
+ * Render single favicon link tag
64
+ * Generates simple <link rel="icon" href="..."> tag for default favicon
65
+ *
66
+ * @param favicon - Favicon path from page.meta.favicon
67
+ * @returns React element with favicon link tag or undefined
68
+ */
69
+ export function FaviconLink({
70
+ favicon,
71
+ }: {
72
+ readonly favicon?: string
73
+ }): Readonly<ReactElement | undefined> {
74
+ if (!favicon) {
75
+ return undefined
76
+ }
77
+
78
+ return (
79
+ <link
80
+ rel="icon"
81
+ href={favicon}
82
+ />
83
+ )
84
+ }
85
+
86
+ /**
87
+ * Render favicon set link tags
88
+ * Generates <link rel="..."> tags for multi-device favicon support
89
+ *
90
+ * Supports:
91
+ * - icon: Standard browser favicon (16x16, 32x32)
92
+ * - apple-touch-icon: iOS home screen icon (180x180)
93
+ * - manifest: PWA manifest file reference
94
+ * - mask-icon: Safari pinned tab icon (monochrome SVG with color)
95
+ *
96
+ * @param favicons - Favicon set configuration from page.meta
97
+ * @returns React fragment with favicon link tags
98
+ */
99
+ export function FaviconSetLinks({
100
+ favicons,
101
+ }: {
102
+ readonly favicons?: FaviconSet
103
+ }): Readonly<ReactElement | undefined> {
104
+ if (!favicons || favicons.length === 0) {
105
+ return undefined
106
+ }
107
+
108
+ return (
109
+ <>
110
+ {favicons.map((favicon, index) => {
111
+ // Convert relative path (./favicon.png) to absolute path (/favicon.png)
112
+ // Remove the leading ./ to make it an absolute path from the root
113
+ const href = favicon.href.replace(/^\.\//, '/')
114
+
115
+ return (
116
+ <link
117
+ key={index}
118
+ rel={favicon.rel}
119
+ href={href}
120
+ {...(favicon.type && { type: favicon.type })}
121
+ {...(favicon.sizes && { sizes: favicon.sizes })}
122
+ {...(favicon.color && { color: favicon.color })}
123
+ />
124
+ )
125
+ })}
126
+ </>
127
+ )
128
+ }
129
+
130
+ /**
131
+ * Render preload link tags
132
+ * Generates <link rel="preload" ...> tags for critical resources
133
+ *
134
+ * @param preload - Preload configuration from page.meta
135
+ * @returns React fragment with preload link tags
136
+ */
137
+ export function PreloadLinks({
138
+ preload,
139
+ }: {
140
+ readonly preload?: Preload
141
+ }): Readonly<ReactElement | undefined> {
142
+ if (!preload || preload.length === 0) {
143
+ return undefined
144
+ }
145
+
146
+ return (
147
+ <>
148
+ {preload.map((item, index) => (
149
+ <link
150
+ key={index}
151
+ rel="preload"
152
+ href={item.href}
153
+ as={item.as}
154
+ {...(item.type && { type: item.type })}
155
+ {...(item.crossorigin !== undefined &&
156
+ (typeof item.crossorigin === 'boolean'
157
+ ? item.crossorigin && { crossOrigin: '' }
158
+ : { crossOrigin: item.crossorigin }))}
159
+ {...(item.media && { media: item.media })}
160
+ />
161
+ ))}
162
+ </>
163
+ )
164
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Copyright (c) 2025 ESSENTIAL SERVICES
3
+ *
4
+ * This source code is licensed under the Business Source License 1.1
5
+ * found in the LICENSE.md file in the root directory of this source tree.
6
+ */
7
+
8
+ /**
9
+ * Metadata components barrel export
10
+ *
11
+ * This module exports all metadata rendering components:
12
+ * - OpenGraphMeta: Facebook/LinkedIn Open Graph tags
13
+ * - TwitterCardMeta: Twitter/X card tags
14
+ * - StructuredDataScript: Schema.org JSON-LD
15
+ * - AnalyticsHead: Analytics provider scripts
16
+ * - DnsPrefetchLinks: DNS prefetch link tags
17
+ * - CustomElementsHead: Custom head elements
18
+ * - FaviconLink: Simple favicon link
19
+ * - FaviconSetLinks: Multi-device favicon set
20
+ * - PreloadLinks: Resource preload hints
21
+ *
22
+ * @see Individual component files for detailed documentation
23
+ */
24
+
25
+ export { OpenGraphMeta } from './open-graph-meta'
26
+ export { TwitterCardMeta } from './twitter-card-meta'
27
+ export { StructuredDataScript } from './structured-data'
28
+ export { AnalyticsHead } from './analytics-head'
29
+ export {
30
+ DnsPrefetchLinks,
31
+ CustomElementsHead,
32
+ FaviconLink,
33
+ FaviconSetLinks,
34
+ PreloadLinks,
35
+ } from './head-elements'
@@ -0,0 +1,42 @@
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
+
10
+ /**
11
+ * Shared helper to render meta tags with configurable attribute type
12
+ * Eliminates duplication between OpenGraph and Twitter Card rendering
13
+ *
14
+ * @param fields - Array of key-value pairs to render as meta tags
15
+ * @param prefix - Prefix for meta tag name/property (e.g., 'og', 'twitter')
16
+ * @param attributeType - HTML attribute to use ('property' for OG, 'name' for Twitter)
17
+ * @returns React fragment with meta tags
18
+ */
19
+ export function renderMetaTags({
20
+ fields,
21
+ prefix,
22
+ attributeType,
23
+ }: {
24
+ readonly fields: ReadonlyArray<{ readonly key: string; readonly value?: string | number }>
25
+ readonly prefix: string
26
+ readonly attributeType: 'property' | 'name'
27
+ }): ReactElement {
28
+ return (
29
+ <>
30
+ {fields.map(
31
+ ({ key, value }) =>
32
+ value !== undefined && (
33
+ <meta
34
+ key={key}
35
+ {...{ [attributeType]: `${prefix}:${key}` }}
36
+ content={String(value)}
37
+ />
38
+ )
39
+ )}
40
+ </>
41
+ )
42
+ }
@@ -0,0 +1,57 @@
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 { resolveTranslationPattern } from '@/presentation/translations/translation-resolver'
10
+ import { renderMetaTags } from './meta-utils'
11
+ import type { Languages } from '@/domain/models/app/languages'
12
+ import type { OpenGraph } from '@/domain/models/app/page/meta/open-graph'
13
+
14
+ /**
15
+ * Render Open Graph metadata tags
16
+ * Generates <meta property="og:*"> tags for Facebook/LinkedIn sharing
17
+ *
18
+ * @param openGraph - Open Graph configuration from page.meta
19
+ * @param lang - Current language code for translation resolution
20
+ * @param languages - Languages configuration for translation resolution
21
+ * @returns React fragment with OG meta tags
22
+ */
23
+ export function OpenGraphMeta({
24
+ openGraph,
25
+ lang,
26
+ languages,
27
+ }: {
28
+ readonly openGraph?: OpenGraph
29
+ readonly lang?: string
30
+ readonly languages?: Languages
31
+ }): Readonly<ReactElement | undefined> {
32
+ if (!openGraph) {
33
+ return undefined
34
+ }
35
+
36
+ // Resolve translation patterns in OpenGraph fields
37
+ const resolveValue = (value: string | undefined): string | undefined => {
38
+ if (!value || !lang) return value
39
+ return resolveTranslationPattern(value, lang, languages)
40
+ }
41
+
42
+ const fields: ReadonlyArray<{ readonly key: string; readonly value?: string }> = [
43
+ { key: 'title', value: resolveValue(openGraph.title) },
44
+ { key: 'description', value: resolveValue(openGraph.description) },
45
+ { key: 'image', value: openGraph.image },
46
+ { key: 'image:alt', value: resolveValue(openGraph.imageAlt) },
47
+ { key: 'url', value: openGraph.url },
48
+ { key: 'type', value: openGraph.type },
49
+ { key: 'site_name', value: resolveValue(openGraph.siteName) },
50
+ { key: 'locale', value: openGraph.locale },
51
+ { key: 'determiner', value: openGraph.determiner },
52
+ { key: 'video', value: openGraph.video },
53
+ { key: 'audio', value: openGraph.audio },
54
+ ]
55
+
56
+ return renderMetaTags({ fields, prefix: 'og', attributeType: 'property' })
57
+ }
@@ -0,0 +1,134 @@
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
+
10
+ /**
11
+ * Component meta property type for structured data
12
+ */
13
+ export type ComponentMeta = {
14
+ readonly title?: string
15
+ readonly description?: string
16
+ readonly image?: string
17
+ readonly price?: string
18
+ readonly currency?: string
19
+ readonly structuredData?: {
20
+ readonly type: string
21
+ readonly fields: readonly string[]
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Creates an offers object for Schema.org
27
+ *
28
+ * @param price - Product price
29
+ * @param currency - Currency code
30
+ * @returns Offers object or empty object
31
+ */
32
+ function createOffer(price?: string, currency?: string): Record<string, unknown> {
33
+ return price && currency
34
+ ? {
35
+ offers: {
36
+ '@type': 'Offer',
37
+ price,
38
+ priceCurrency: currency,
39
+ },
40
+ }
41
+ : {}
42
+ }
43
+
44
+ /**
45
+ * Maps component meta field to Schema.org property
46
+ *
47
+ * @param fieldName - Schema.org field name
48
+ * @param meta - Component meta configuration
49
+ * @param fields - Enabled fields list
50
+ * @returns Field object or empty object
51
+ */
52
+ function mapMetaField(
53
+ fieldName: string,
54
+ meta: ComponentMeta,
55
+ fields: readonly string[]
56
+ ): Record<string, unknown> {
57
+ if (!fields.includes(fieldName)) return {}
58
+
59
+ const { title, description, image, price, currency } = meta
60
+
61
+ if (fieldName === 'name' && title) return { name: title }
62
+ if (fieldName === 'description' && description) return { description }
63
+ if (fieldName === 'image' && image) return { image }
64
+ if (fieldName === 'offers') return createOffer(price, currency)
65
+
66
+ return {}
67
+ }
68
+
69
+ /**
70
+ * Generates Schema.org structured data JSON-LD from component meta
71
+ *
72
+ * @param meta - Component meta configuration
73
+ * @returns JSON-LD object following Schema.org format
74
+ */
75
+ function generateStructuredData(meta: ComponentMeta): Record<string, unknown> {
76
+ const { structuredData } = meta
77
+
78
+ if (!structuredData) {
79
+ return {}
80
+ }
81
+
82
+ // Build JSON-LD immutably using functional composition
83
+ const baseJsonLd = {
84
+ '@context': 'https://schema.org',
85
+ '@type': structuredData.type,
86
+ }
87
+
88
+ const mappedFields = ['name', 'description', 'image', 'offers'].map((field) =>
89
+ mapMetaField(field, meta, structuredData.fields)
90
+ )
91
+
92
+ return mappedFields.reduce((acc, field) => ({ ...acc, ...field }), baseJsonLd)
93
+ }
94
+
95
+ /**
96
+ * Renders structured data script from component meta
97
+ *
98
+ * SECURITY: Safe use of dangerouslySetInnerHTML
99
+ * - Content: Schema.org JSON-LD from component metadata (JSON.stringify)
100
+ * - Source: Validated component meta configuration (component.meta.structuredData)
101
+ * - Risk: None - JSON data cannot execute as code
102
+ * - Validation: Schema validation ensures correct structure
103
+ * - Purpose: Generate rich search results from component content (SEO)
104
+ * - XSS Protection: type="application/ld+json" prevents script execution
105
+ * - Format: Safe serialization via JSON.stringify with formatting
106
+ *
107
+ * @param props - Component props
108
+ * @param props.meta - Component meta configuration
109
+ * @returns Script element with JSON-LD structured data, or null if no structured data
110
+ */
111
+ export function StructuredDataFromComponent({
112
+ meta,
113
+ }: {
114
+ readonly meta: ComponentMeta | undefined
115
+ }): ReactElement | undefined {
116
+ if (!meta?.structuredData) {
117
+ return undefined
118
+ }
119
+
120
+ const jsonLd = generateStructuredData(meta)
121
+
122
+ if (Object.keys(jsonLd).length === 0) {
123
+ return undefined
124
+ }
125
+
126
+ return (
127
+ <script
128
+ type="application/ld+json"
129
+ dangerouslySetInnerHTML={{
130
+ __html: JSON.stringify(jsonLd, undefined, 2),
131
+ }}
132
+ />
133
+ )
134
+ }
@@ -0,0 +1,88 @@
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 type { Page } from '@/domain/models/app/pages'
10
+
11
+ /**
12
+ * Render structured data as JSON-LD script tags
13
+ * Generates Schema.org structured data for rich search results
14
+ * Supports both 'schema' (canonical) and 'structuredData' (test alias)
15
+ *
16
+ * Handles two formats:
17
+ * 1. Direct Schema.org object: { "@context": "...", "@type": "...", ... }
18
+ * 2. Orchestrator schema: { organization: {...}, faqPage: {...}, ... }
19
+ *
20
+ * Each structured data type is rendered as a separate <script type="application/ld+json">
21
+ * tag for proper Schema.org validation
22
+ *
23
+ * SECURITY: Safe use of dangerouslySetInnerHTML
24
+ * - Content: Schema.org structured data (JSON.stringify)
25
+ * - Source: Validated Page schema (page.meta.schema or page.meta.structuredData)
26
+ * - Risk: None - JSON data cannot execute as code
27
+ * - Validation: Schema validation ensures correct structure
28
+ * - Purpose: Generate rich search results (SEO)
29
+ * - XSS Protection: type="application/ld+json" prevents script execution
30
+ * - Format: Safe serialization via JSON.stringify
31
+ *
32
+ * @param page - Page configuration
33
+ * @returns React fragment with script tags or undefined
34
+ */
35
+ export function StructuredDataScript({
36
+ page,
37
+ }: {
38
+ readonly page: Page
39
+ }): Readonly<ReactElement | undefined> {
40
+ // Support both 'schema' (canonical) and 'structuredData' (test alias)
41
+ const structuredData = page.meta?.schema ?? page.meta?.structuredData
42
+ if (!structuredData) {
43
+ return undefined
44
+ }
45
+
46
+ // Type guard: ensure structuredData is an object
47
+ if (typeof structuredData !== 'object' || structuredData === null) {
48
+ return undefined
49
+ }
50
+
51
+ // Check if this is a direct Schema.org object (has @context and @type)
52
+ const isDirectSchemaObject = '@context' in structuredData && '@type' in structuredData
53
+
54
+ // Handle direct Schema.org object format
55
+ if (isDirectSchemaObject) {
56
+ return (
57
+ <script
58
+ type="application/ld+json"
59
+ dangerouslySetInnerHTML={{
60
+ __html: JSON.stringify(structuredData),
61
+ }}
62
+ />
63
+ )
64
+ }
65
+
66
+ // Handle orchestrator schema format (organization, faqPage, etc.)
67
+ const structuredDataTypes = Object.entries(structuredData).filter(
68
+ ([, value]) => value !== undefined && value !== null
69
+ )
70
+
71
+ if (structuredDataTypes.length === 0) {
72
+ return undefined
73
+ }
74
+
75
+ return (
76
+ <>
77
+ {structuredDataTypes.map(([key, value]) => (
78
+ <script
79
+ key={key}
80
+ type="application/ld+json"
81
+ dangerouslySetInnerHTML={{
82
+ __html: JSON.stringify(value),
83
+ }}
84
+ />
85
+ ))}
86
+ </>
87
+ )
88
+ }