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,53 @@
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
+ * Default theme color palette
10
+ *
11
+ * Provides a minimal set of predefined theme colors for quick prototyping.
12
+ * These colors are used when a component references a color by name (e.g., color="orange")
13
+ * but no theme configuration is provided in the schema.
14
+ *
15
+ * NOTE: This is a simplified implementation. In a full theme system, colors should
16
+ * come from the app's theme configuration (src/domain/models/app/theme/colors.ts).
17
+ * Future enhancement: Integrate with theme context to support custom colors.
18
+ *
19
+ * @see src/domain/models/app/theme/colors.ts - For proper theme color configuration
20
+ * @see src/presentation/ui/sections/utils/theme-tokens.ts - For theme token substitution
21
+ */
22
+ export const DEFAULT_THEME_COLORS = {
23
+ orange: '#F97316',
24
+ blue: '#3B82F6',
25
+ green: '#10B981',
26
+ red: '#EF4444',
27
+ } as const
28
+
29
+ /**
30
+ * Supported theme color names
31
+ *
32
+ * Type-safe color names that can be used in components.
33
+ */
34
+ export type ThemeColorName = keyof typeof DEFAULT_THEME_COLORS
35
+
36
+ /**
37
+ * Resolves a color name to its hex value
38
+ *
39
+ * @param color - Color name from DEFAULT_THEME_COLORS
40
+ * @returns Hex color value, or undefined if color not found
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * resolveThemeColor('orange') // '#F97316'
45
+ * resolveThemeColor('invalid') // undefined
46
+ * ```
47
+ */
48
+ export function resolveThemeColor(color: string | undefined): string | undefined {
49
+ if (!color) {
50
+ return undefined
51
+ }
52
+ return DEFAULT_THEME_COLORS[color as ThemeColorName]
53
+ }
@@ -0,0 +1,54 @@
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 {
9
+ ComponentReference,
10
+ SimpleComponentReference,
11
+ } from '@/domain/models/app/component/common/component-reference'
12
+ import type { Component } from '@/domain/models/app/page/sections'
13
+
14
+ /**
15
+ * Get component information for a section
16
+ *
17
+ * Determines if a section is a component reference and calculates its instance index
18
+ * when multiple instances of the same component exist.
19
+ *
20
+ * @param section - Section to analyze (Component, SimpleComponentReference, or ComponentReference)
21
+ * @param index - Position of this section in the sections array
22
+ * @param sections - Complete array of sections for counting occurrences
23
+ * @returns Component info with name and optional instanceIndex, or undefined if not a component reference
24
+ */
25
+ export function getComponentInfo(
26
+ section: Component | SimpleComponentReference | ComponentReference,
27
+ index: number,
28
+ sections: ReadonlyArray<Component | SimpleComponentReference | ComponentReference>
29
+ ): { name: string; instanceIndex?: number } | undefined {
30
+ if (!('component' in section || '$ref' in section)) {
31
+ return undefined
32
+ }
33
+
34
+ const componentName = 'component' in section ? section.component : section.$ref
35
+
36
+ // Count total occurrences of this component name in all sections
37
+ const totalOccurrences = sections.filter((s) => {
38
+ const sName = 'component' in s ? s.component : '$ref' in s ? s.$ref : undefined
39
+ return sName === componentName
40
+ }).length
41
+
42
+ // Only set instanceIndex if there are multiple instances
43
+ if (totalOccurrences <= 1) {
44
+ return { name: componentName }
45
+ }
46
+
47
+ // Count previous occurrences of the same component name
48
+ const previousOccurrences = sections.slice(0, index).filter((s) => {
49
+ const sName = 'component' in s ? s.component : '$ref' in s ? s.$ref : undefined
50
+ return sName === componentName
51
+ })
52
+
53
+ return { name: componentName, instanceIndex: previousOccurrences.length }
54
+ }
@@ -0,0 +1,16 @@
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
+ // Component utilities
9
+ export { getComponentInfo } from './component-utils'
10
+
11
+ // Translation resolution
12
+ export {
13
+ resolveTranslation,
14
+ resolveTranslationPattern,
15
+ collectTranslationsForKey,
16
+ } from './translation-resolver'
@@ -0,0 +1,22 @@
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
+ * Re-export translation resolver functions from domain layer
10
+ *
11
+ * These pure functions were moved to the domain layer to respect layer boundaries.
12
+ * This file re-exports them for backward compatibility with existing presentation
13
+ * layer code. Application layer code should import directly from domain.
14
+ *
15
+ * @see src/domain/utils/translation-resolver.ts for implementation
16
+ */
17
+ export {
18
+ normalizeLanguageCode,
19
+ resolveTranslation,
20
+ resolveTranslationPattern,
21
+ collectTranslationsForKey,
22
+ } from '@/domain/utils/translation-resolver'
@@ -0,0 +1,119 @@
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 { Languages } from '@/domain/models/app/languages'
10
+
11
+ /**
12
+ * Helper to check if flag is an image path (starts with /)
13
+ */
14
+ const isImageFlag = (flag: string | undefined): boolean => Boolean(flag?.startsWith('/'))
15
+
16
+ /**
17
+ * Helper to determine if flag should be shown (emoji flags only, not image paths)
18
+ */
19
+ const shouldShowFlag = (flag: string | undefined): boolean => Boolean(flag && !isImageFlag(flag))
20
+
21
+ /**
22
+ * Language switcher button component
23
+ */
24
+ function LanguageSwitcherButton({
25
+ defaultLanguage,
26
+ defaultCode,
27
+ }: {
28
+ readonly defaultLanguage: Languages['supported'][number] | undefined
29
+ readonly defaultCode: string
30
+ }): ReactElement {
31
+ return (
32
+ <button
33
+ data-testid="language-switcher-button"
34
+ type="button"
35
+ >
36
+ {shouldShowFlag(defaultLanguage?.flag) && (
37
+ <span data-testid="language-flag">{defaultLanguage!.flag} </span>
38
+ )}
39
+ <span
40
+ data-testid="language-code"
41
+ aria-hidden="true"
42
+ style={{ display: 'none' }}
43
+ />
44
+ <span
45
+ data-testid="current-language"
46
+ data-code={defaultCode}
47
+ >
48
+ {defaultLanguage?.label || defaultCode}
49
+ </span>
50
+ </button>
51
+ )
52
+ }
53
+
54
+ /**
55
+ * LanguageSwitcher component - Server-side rendered language switcher
56
+ *
57
+ * This component renders the static HTML structure for the language switcher.
58
+ * All client-side interactivity (click handlers, language detection, localStorage)
59
+ * is handled by the vanilla JavaScript file: language-switcher.js
60
+ *
61
+ * Architecture:
62
+ * - React component = SSR only (renders HTML structure)
63
+ * - Vanilla JS = Client-side progressive enhancement (handles interactivity)
64
+ *
65
+ * This separation ensures:
66
+ * - No duplicate logic between React and vanilla JS
67
+ * - Works without React hydration (pure progressive enhancement)
68
+ * - Clear separation of concerns (SSR vs client-side)
69
+ *
70
+ * @param props - Component props
71
+ * @param props.languages - Languages configuration from AppSchema
72
+ * @param props.variant - Display variant (dropdown, inline, tabs) - defaults to dropdown
73
+ * @param props.showFlags - Whether to show flag emojis - defaults to false
74
+ * @returns React element with language switcher HTML structure
75
+ */
76
+ export function LanguageSwitcher({
77
+ languages,
78
+ variant = 'dropdown',
79
+ showFlags = false,
80
+ }: {
81
+ readonly languages: Languages
82
+ readonly variant?: string
83
+ readonly showFlags?: boolean
84
+ }): Readonly<ReactElement> {
85
+ const defaultLanguage = languages.supported.find((lang) => lang.code === languages.default)
86
+
87
+ return (
88
+ <div
89
+ data-testid="language-switcher"
90
+ className="relative"
91
+ data-variant={variant}
92
+ >
93
+ <LanguageSwitcherButton
94
+ defaultLanguage={defaultLanguage}
95
+ defaultCode={languages.default}
96
+ />
97
+
98
+ {/* Dropdown menu - vanilla JS will handle show/hide */}
99
+ <div
100
+ data-language-dropdown
101
+ className="absolute top-full left-0 z-10"
102
+ aria-hidden="true"
103
+ style={{ display: 'none' }}
104
+ data-supported-languages={JSON.stringify(languages.supported)}
105
+ data-show-flags={showFlags}
106
+ />
107
+
108
+ {/* Fallback indicator - shows when fallback is configured */}
109
+ {languages.fallback && (
110
+ <div
111
+ data-testid="fallback-handled"
112
+ aria-label={`Fallback language: ${languages.fallback}`}
113
+ >
114
+ Fallback: {languages.fallback}
115
+ </div>
116
+ )}
117
+ </div>
118
+ )
119
+ }
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Copyright (c) 2025 ESSENTIAL SERVICES
3
+ *
4
+ * This source code is licensed under the Business Source License 1.1
5
+ * found in the LICENSE.md file in the root directory of this source tree.
6
+ */
7
+
8
+ import { type ReactElement } from 'react'
9
+ import { renderScriptTag } from '@/presentation/scripts/script-renderers'
10
+ import type { Analytics } from '@/domain/models/app/page/meta/analytics'
11
+
12
+ /**
13
+ * Build DNS prefetch link for analytics provider
14
+ */
15
+ export function buildDnsPrefetchLink(
16
+ provider: Analytics['providers'][number],
17
+ providerIndex: number,
18
+ hidden: boolean
19
+ ): ReactElement | undefined {
20
+ if (!provider.dnsPrefetch) return undefined
21
+
22
+ const styleAttr = hidden ? { style: { display: 'none' } } : {}
23
+
24
+ return (
25
+ <link
26
+ key={`dns-${providerIndex}`}
27
+ rel="dns-prefetch"
28
+ href={provider.dnsPrefetch}
29
+ {...styleAttr}
30
+ />
31
+ )
32
+ }
33
+
34
+ /**
35
+ * Build external scripts for analytics provider
36
+ */
37
+ export function buildExternalScripts(
38
+ provider: Analytics['providers'][number],
39
+ providerIndex: number,
40
+ hidden: boolean
41
+ ): readonly ReactElement[] {
42
+ if (!provider.scripts || provider.scripts.length === 0) return []
43
+
44
+ return provider.scripts.map((script, scriptIndex) =>
45
+ renderScriptTag({
46
+ src: script.src,
47
+ async: script.async,
48
+ defer: script.defer,
49
+ dataTestId: `analytics-${provider.name}`,
50
+ reactKey: `script-${providerIndex}-${scriptIndex}`,
51
+ hidden,
52
+ })
53
+ )
54
+ }
55
+
56
+ /**
57
+ * Build initialization script for analytics provider
58
+ *
59
+ * SECURITY: Safe use of dangerouslySetInnerHTML
60
+ * - Content: Analytics provider initialization code from configuration
61
+ * - Source: Validated Analytics schema (page.meta.analytics.providers[].initScript)
62
+ * - Risk: Low - content is from server configuration, not user input
63
+ * - Validation: Schema validation ensures string type
64
+ * - Purpose: Execute analytics provider setup (e.g., Google Analytics, Plausible)
65
+ * - CSP: Inline script - consider using nonce for stricter CSP
66
+ * - Best Practice: Use external scripts with SRI when possible
67
+ */
68
+ export function buildInitScript(
69
+ provider: Analytics['providers'][number],
70
+ providerIndex: number,
71
+ hidden: boolean
72
+ ): ReactElement | undefined {
73
+ if (!provider.initScript) return undefined
74
+
75
+ const styleAttr = hidden ? { style: { display: 'none' } } : {}
76
+
77
+ return (
78
+ <script
79
+ key={`init-${providerIndex}`}
80
+ data-testid={`analytics-${provider.name}`}
81
+ dangerouslySetInnerHTML={{
82
+ __html: provider.initScript,
83
+ }}
84
+ {...styleAttr}
85
+ />
86
+ )
87
+ }
88
+
89
+ /**
90
+ * Build marker script for analytics provider (when no scripts exist)
91
+ *
92
+ * SECURITY: Safe use of dangerouslySetInnerHTML
93
+ * - Content: Static comment marker with provider name
94
+ * - Source: provider.name from validated Analytics schema
95
+ * - Risk: None - contains only static comment text
96
+ * - Validation: provider.name is validated as string by schema
97
+ * - Purpose: Testing/debugging marker when provider has no actual scripts
98
+ * - XSS Protection: Comment syntax prevents code execution
99
+ */
100
+ export function buildMarkerScript(
101
+ provider: Analytics['providers'][number],
102
+ providerIndex: number,
103
+ hidden: boolean
104
+ ): ReactElement | undefined {
105
+ const hasNoScripts = (!provider.scripts || provider.scripts.length === 0) && !provider.initScript
106
+
107
+ if (!hasNoScripts) return undefined
108
+
109
+ const styleAttr = hidden ? { style: { display: 'none' } } : {}
110
+
111
+ return (
112
+ <script
113
+ key={`marker-${providerIndex}`}
114
+ data-testid={`analytics-${provider.name}`}
115
+ dangerouslySetInnerHTML={{
116
+ __html: `/* ${provider.name} analytics marker */`,
117
+ }}
118
+ {...styleAttr}
119
+ />
120
+ )
121
+ }
122
+
123
+ /**
124
+ * Build config data script for analytics provider
125
+ *
126
+ * SECURITY: Safe use of dangerouslySetInnerHTML
127
+ * - Content: JSON configuration data (JSON.stringify)
128
+ * - Source: provider.config from validated Analytics schema
129
+ * - Risk: None - JSON data cannot execute as code
130
+ * - Validation: Schema validation ensures object type
131
+ * - Purpose: Store analytics configuration as JSON for client-side access
132
+ * - XSS Protection: type="application/json" prevents script execution
133
+ * - Format: Safe serialization via JSON.stringify
134
+ */
135
+ export function buildConfigScript(
136
+ provider: Analytics['providers'][number],
137
+ providerIndex: number,
138
+ hidden: boolean
139
+ ): ReactElement | undefined {
140
+ if (!provider.config) return undefined
141
+
142
+ const styleAttr = hidden ? { style: { display: 'none' } } : {}
143
+
144
+ return (
145
+ <script
146
+ key={`config-${providerIndex}`}
147
+ data-testid={`analytics-${provider.name}-config`}
148
+ type="application/json"
149
+ dangerouslySetInnerHTML={{
150
+ __html: JSON.stringify(provider.config),
151
+ }}
152
+ {...styleAttr}
153
+ />
154
+ )
155
+ }
156
+
157
+ /**
158
+ * Build all elements for a single analytics provider
159
+ */
160
+ export function buildProviderElements(
161
+ provider: Analytics['providers'][number],
162
+ providerIndex: number
163
+ ): readonly ReactElement[] {
164
+ const isEnabled = provider.enabled !== false
165
+ const hidden = !isEnabled
166
+
167
+ return [
168
+ buildDnsPrefetchLink(provider, providerIndex, hidden),
169
+ ...buildExternalScripts(provider, providerIndex, hidden),
170
+ buildInitScript(provider, providerIndex, hidden),
171
+ buildMarkerScript(provider, providerIndex, hidden),
172
+ buildConfigScript(provider, providerIndex, hidden),
173
+ ].filter((el): el is ReactElement => el !== undefined)
174
+ }
@@ -0,0 +1,39 @@
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 { buildProviderElements } from './analytics-builders'
10
+ import type { Analytics } from '@/domain/models/app/page/meta/analytics'
11
+
12
+ /**
13
+ * Render analytics provider scripts and configuration in HEAD section
14
+ * Generates DNS prefetch, external scripts with data-testid, and initialization scripts
15
+ *
16
+ * @param analytics - Analytics configuration from page.meta (union type)
17
+ * @returns React fragment with head elements
18
+ */
19
+ export function AnalyticsHead({
20
+ analytics,
21
+ }: {
22
+ readonly analytics?: Analytics | { readonly [x: string]: unknown }
23
+ }): Readonly<ReactElement | undefined> {
24
+ // Type guard: ensure analytics has providers array (not generic record)
25
+ if (
26
+ !analytics ||
27
+ !('providers' in analytics) ||
28
+ !Array.isArray(analytics.providers) ||
29
+ analytics.providers.length === 0
30
+ ) {
31
+ return undefined
32
+ }
33
+
34
+ // Type assertion: after type guard, we know analytics has providers array
35
+ const analyticsConfig = analytics as Analytics
36
+
37
+ // Render all providers using builder pattern
38
+ return <>{analyticsConfig.providers.flatMap(buildProviderElements)}</>
39
+ }
@@ -0,0 +1,157 @@
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 { CustomElements } from '@/domain/models/app/page/meta/custom-elements'
10
+
11
+ /**
12
+ * Build meta element
13
+ */
14
+ export function buildMetaElement(element: CustomElements[number], key: string): ReactElement {
15
+ return (
16
+ <meta
17
+ key={key}
18
+ {...element.attrs}
19
+ />
20
+ )
21
+ }
22
+
23
+ /**
24
+ * Build link element
25
+ */
26
+ export function buildLinkElement(element: CustomElements[number], key: string): ReactElement {
27
+ return (
28
+ <link
29
+ key={key}
30
+ {...element.attrs}
31
+ />
32
+ )
33
+ }
34
+
35
+ /**
36
+ * Process boolean HTML attributes for script elements
37
+ * Converts string 'true'/'false' to boolean/undefined
38
+ */
39
+ function processBooleanAttributes(
40
+ attrs: Record<string, unknown> | undefined
41
+ ): Record<string, unknown> {
42
+ if (!attrs) return {}
43
+
44
+ const booleanAttrs = ['async', 'defer', 'noModule'] as const
45
+
46
+ // First, copy all non-boolean attributes
47
+ const result = Object.entries(attrs).reduce<Record<string, unknown>>((acc, [key, value]) => {
48
+ if (!booleanAttrs.includes(key as (typeof booleanAttrs)[number])) {
49
+ return { ...acc, [key]: value }
50
+ }
51
+ return acc
52
+ }, {})
53
+
54
+ // Then process boolean attributes
55
+ return booleanAttrs.reduce<Record<string, unknown>>((acc, attr) => {
56
+ if (!(attr in attrs)) return acc
57
+
58
+ const value = attrs[attr]
59
+ if (value === 'true') {
60
+ return { ...acc, [attr]: true }
61
+ }
62
+ // Remove false/empty values (omit from result)
63
+ return acc
64
+ }, result)
65
+ }
66
+
67
+ /**
68
+ * Build script element
69
+ * Handles boolean attributes (async, defer) - converts string 'true'/'false' to boolean
70
+ *
71
+ * SECURITY: Safe use of dangerouslySetInnerHTML
72
+ * - Content: Custom script code from page configuration
73
+ * - Source: Validated CustomElements schema (page.meta.customElements[].content)
74
+ * - Risk: Low - content is from server configuration, not user input
75
+ * - Validation: Schema validation ensures string type
76
+ * - Purpose: Render custom inline scripts for analytics, tracking, etc.
77
+ * - CSP: Inline script - consider using nonce for stricter CSP
78
+ * - Best Practice: Prefer external scripts with SRI when possible
79
+ */
80
+ export function buildScriptElement(element: CustomElements[number], key: string): ReactElement {
81
+ const processedAttrs = processBooleanAttributes(element.attrs)
82
+
83
+ if (element.content) {
84
+ return (
85
+ <script
86
+ key={key}
87
+ {...processedAttrs}
88
+ dangerouslySetInnerHTML={{ __html: element.content }}
89
+ />
90
+ )
91
+ }
92
+ return (
93
+ <script
94
+ key={key}
95
+ {...processedAttrs}
96
+ />
97
+ )
98
+ }
99
+
100
+ /**
101
+ * Build style element
102
+ *
103
+ * SECURITY: Safe use of dangerouslySetInnerHTML
104
+ * - Content: Custom CSS code from page configuration
105
+ * - Source: Validated CustomElements schema (page.meta.customElements[].content)
106
+ * - Risk: Low - CSS cannot execute JavaScript
107
+ * - Validation: Schema validation ensures string type
108
+ * - Purpose: Render custom inline styles for page-specific styling
109
+ * - XSS Protection: CSS syntax prevents script execution
110
+ * - CSP: style-src 'unsafe-inline' required (consider nonce for stricter CSP)
111
+ */
112
+ export function buildStyleElement(element: CustomElements[number], key: string): ReactElement {
113
+ return (
114
+ <style
115
+ key={key}
116
+ {...element.attrs}
117
+ dangerouslySetInnerHTML={{ __html: element.content || '' }}
118
+ />
119
+ )
120
+ }
121
+
122
+ /**
123
+ * Build base element
124
+ */
125
+ export function buildBaseElement(element: CustomElements[number], key: string): ReactElement {
126
+ return (
127
+ <base
128
+ key={key}
129
+ {...element.attrs}
130
+ />
131
+ )
132
+ }
133
+
134
+ /**
135
+ * Build custom element based on type
136
+ */
137
+ export function buildCustomElement(
138
+ element: CustomElements[number],
139
+ index: number
140
+ ): ReactElement | undefined {
141
+ const key = `custom-${element.type}-${index}`
142
+
143
+ switch (element.type) {
144
+ case 'meta':
145
+ return buildMetaElement(element, key)
146
+ case 'link':
147
+ return buildLinkElement(element, key)
148
+ case 'script':
149
+ return buildScriptElement(element, key)
150
+ case 'style':
151
+ return buildStyleElement(element, key)
152
+ case 'base':
153
+ return buildBaseElement(element, key)
154
+ default:
155
+ return undefined
156
+ }
157
+ }