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.
- package/CHANGELOG.md +3497 -0
- package/LICENSE.md +147 -0
- package/LICENSE_EE.md +297 -0
- package/README.md +321 -0
- package/drizzle/0000_melted_kabuki.sql +163 -0
- package/drizzle/meta/0000_snapshot.json +1216 -0
- package/drizzle/meta/_journal.json +13 -0
- package/package.json +167 -0
- package/schemas/0.0.1/app.openapi.json +70 -0
- package/schemas/0.0.1/app.schema.json +7961 -0
- package/schemas/0.0.2/app.openapi.json +80 -0
- package/schemas/0.0.2/app.schema.json +8829 -0
- package/schemas/development/app.openapi.json +70 -0
- package/schemas/development/app.schema.json +7456 -0
- package/src/application/errors/app-validation-error.ts +14 -0
- package/src/application/errors/static-generation-error.ts +16 -0
- package/src/application/metadata/favicon-transformer.ts +127 -0
- package/src/application/models/server.ts +27 -0
- package/src/application/ports/models/user-metadata.ts +36 -0
- package/src/application/ports/models/user-session.ts +34 -0
- package/src/application/ports/repositories/activity-log-repository.ts +68 -0
- package/src/application/ports/repositories/activity-repository.ts +49 -0
- package/src/application/ports/repositories/analytics-repository.ts +164 -0
- package/src/application/ports/repositories/auth-repository.ts +33 -0
- package/src/application/ports/repositories/batch-repository.ts +86 -0
- package/src/application/ports/repositories/comment-repository.ts +150 -0
- package/src/application/ports/repositories/index.ts +41 -0
- package/src/application/ports/repositories/table-repository.ts +139 -0
- package/src/application/ports/services/css-compiler.ts +55 -0
- package/src/application/ports/services/index.ts +16 -0
- package/src/application/ports/services/page-renderer.ts +79 -0
- package/src/application/ports/services/server-factory.ts +80 -0
- package/src/application/ports/services/static-site-generator.ts +82 -0
- package/src/application/use-cases/activity/programs.ts +66 -0
- package/src/application/use-cases/analytics/collect-page-view.ts +114 -0
- package/src/application/use-cases/analytics/purge-old-data.ts +40 -0
- package/src/application/use-cases/analytics/query-campaigns.ts +43 -0
- package/src/application/use-cases/analytics/query-devices.ts +36 -0
- package/src/application/use-cases/analytics/query-overview.ts +50 -0
- package/src/application/use-cases/analytics/query-pages.ts +40 -0
- package/src/application/use-cases/analytics/query-referrers.ts +43 -0
- package/src/application/use-cases/analytics/ua-parser.ts +89 -0
- package/src/application/use-cases/analytics/visitor-hash.ts +77 -0
- package/src/application/use-cases/auth/bootstrap-admin.ts +270 -0
- package/src/application/use-cases/list-activity-logs.ts +123 -0
- package/src/application/use-cases/server/generate-static-helpers.ts +374 -0
- package/src/application/use-cases/server/generate-static.ts +287 -0
- package/src/application/use-cases/server/start-server.ts +118 -0
- package/src/application/use-cases/server/startup-error-handler.ts +69 -0
- package/src/application/use-cases/server/static-content-generators.ts +182 -0
- package/src/application/use-cases/server/static-language-generators.ts +181 -0
- package/src/application/use-cases/server/static-url-rewriter.ts +237 -0
- package/src/application/use-cases/server/translation-replacer.ts +164 -0
- package/src/application/use-cases/tables/activity-programs.ts +93 -0
- package/src/application/use-cases/tables/batch-operations.ts +156 -0
- package/src/application/use-cases/tables/comment-programs.ts +436 -0
- package/src/application/use-cases/tables/permissions/permissions.ts +25 -0
- package/src/application/use-cases/tables/programs.ts +435 -0
- package/src/application/use-cases/tables/table-operations.ts +412 -0
- package/src/application/use-cases/tables/user-role.ts +52 -0
- package/src/application/use-cases/tables/utils/display-formatter.ts +471 -0
- package/src/application/use-cases/tables/utils/field-read-filter.ts +189 -0
- package/src/application/use-cases/tables/utils/list-helpers.ts +122 -0
- package/src/application/use-cases/tables/utils/record-transformer.ts +319 -0
- package/src/cli.ts +370 -0
- package/src/domain/errors/create-tagged-error.ts +36 -0
- package/src/domain/errors/index.ts +78 -0
- package/src/domain/models/api/analytics.ts +179 -0
- package/src/domain/models/api/auth.ts +231 -0
- package/src/domain/models/api/common.ts +60 -0
- package/src/domain/models/api/error.ts +89 -0
- package/src/domain/models/api/health.ts +38 -0
- package/src/domain/models/api/index.ts +42 -0
- package/src/domain/models/api/request.ts +132 -0
- package/src/domain/models/api/tables.ts +444 -0
- package/src/domain/models/app/analytics/index.ts +129 -0
- package/src/domain/models/app/auth/config.ts +116 -0
- package/src/domain/models/app/auth/index.ts +230 -0
- package/src/domain/models/app/auth/methods/email-and-password.ts +67 -0
- package/src/domain/models/app/auth/methods/index.ts +11 -0
- package/src/domain/models/app/auth/methods/magic-link.ts +54 -0
- package/src/domain/models/app/auth/oauth/index.ts +8 -0
- package/src/domain/models/app/auth/oauth/providers.ts +105 -0
- package/src/domain/models/app/auth/plugins/admin.ts +130 -0
- package/src/domain/models/app/auth/plugins/index.ts +74 -0
- package/src/domain/models/app/auth/plugins/two-factor.ts +63 -0
- package/src/domain/models/app/auth/roles.ts +179 -0
- package/src/domain/models/app/auth/strategies.ts +191 -0
- package/src/domain/models/app/auth/validation.ts +127 -0
- package/src/domain/models/app/common/branded-ids.ts +200 -0
- package/src/domain/models/app/common/definitions.ts +187 -0
- package/src/domain/models/app/component/common/component-children.ts +119 -0
- package/src/domain/models/app/component/common/component-props.ts +89 -0
- package/src/domain/models/app/component/common/component-reference.ts +170 -0
- package/src/domain/models/app/component/component.ts +81 -0
- package/src/domain/models/app/components.ts +65 -0
- package/src/domain/models/app/description.ts +83 -0
- package/src/domain/models/app/index.ts +258 -0
- package/src/domain/models/app/language/language-config.ts +200 -0
- package/src/domain/models/app/languages.ts +205 -0
- package/src/domain/models/app/name.ts +66 -0
- package/src/domain/models/app/page/common/interactions/click-interaction.ts +116 -0
- package/src/domain/models/app/page/common/interactions/entrance-animation.ts +84 -0
- package/src/domain/models/app/page/common/interactions/hover-interaction.ts +144 -0
- package/src/domain/models/app/page/common/interactions/interactions.ts +64 -0
- package/src/domain/models/app/page/common/interactions/scroll-interaction.ts +93 -0
- package/src/domain/models/app/page/common/responsive.ts +114 -0
- package/src/domain/models/app/page/common/url.ts +35 -0
- package/src/domain/models/app/page/common/variable-reference.ts +53 -0
- package/src/domain/models/app/page/id.ts +44 -0
- package/src/domain/models/app/page/index.ts +270 -0
- package/src/domain/models/app/page/meta/analytics.ts +248 -0
- package/src/domain/models/app/page/meta/custom-elements.ts +180 -0
- package/src/domain/models/app/page/meta/dns-prefetch.ts +77 -0
- package/src/domain/models/app/page/meta/favicon-set.ts +203 -0
- package/src/domain/models/app/page/meta/favicon.ts +50 -0
- package/src/domain/models/app/page/meta/favicons-config.ts +73 -0
- package/src/domain/models/app/page/meta/index.ts +278 -0
- package/src/domain/models/app/page/meta/open-graph.ts +166 -0
- package/src/domain/models/app/page/meta/preload.ts +190 -0
- package/src/domain/models/app/page/meta/structured-data/article.ts +211 -0
- package/src/domain/models/app/page/meta/structured-data/breadcrumb.ts +115 -0
- package/src/domain/models/app/page/meta/structured-data/common-fields.ts +201 -0
- package/src/domain/models/app/page/meta/structured-data/education-event.ts +256 -0
- package/src/domain/models/app/page/meta/structured-data/faq-page.ts +127 -0
- package/src/domain/models/app/page/meta/structured-data/index.ts +95 -0
- package/src/domain/models/app/page/meta/structured-data/local-business.ts +247 -0
- package/src/domain/models/app/page/meta/structured-data/organization.ts +171 -0
- package/src/domain/models/app/page/meta/structured-data/person.ts +138 -0
- package/src/domain/models/app/page/meta/structured-data/postal-address.ts +106 -0
- package/src/domain/models/app/page/meta/structured-data/product.ts +214 -0
- package/src/domain/models/app/page/meta/twitter-card.ts +217 -0
- package/src/domain/models/app/page/name.ts +38 -0
- package/src/domain/models/app/page/path.ts +21 -0
- package/src/domain/models/app/page/scripts/external-scripts.ts +163 -0
- package/src/domain/models/app/page/scripts/features.ts +135 -0
- package/src/domain/models/app/page/scripts/inline-scripts.ts +114 -0
- package/src/domain/models/app/page/scripts/scripts.ts +102 -0
- package/src/domain/models/app/page/sections.ts +298 -0
- package/src/domain/models/app/pages.ts +61 -0
- package/src/domain/models/app/permissions/index.ts +61 -0
- package/src/domain/models/app/permissions/resource-action.ts +114 -0
- package/src/domain/models/app/permissions/roles.ts +120 -0
- package/src/domain/models/app/table/check-constraints.ts +105 -0
- package/src/domain/models/app/table/cycle-detection.ts +124 -0
- package/src/domain/models/app/table/database-identifier.ts +153 -0
- package/src/domain/models/app/table/field-name.ts +36 -0
- package/src/domain/models/app/table/field-types/advanced/array-field.ts +33 -0
- package/src/domain/models/app/table/field-types/advanced/autonumber-field.ts +54 -0
- package/src/domain/models/app/table/field-types/advanced/button-field.ts +56 -0
- package/src/domain/models/app/table/field-types/advanced/color-field.ts +57 -0
- package/src/domain/models/app/table/field-types/advanced/count-field.ts +54 -0
- package/src/domain/models/app/table/field-types/advanced/formula-field.ts +58 -0
- package/src/domain/models/app/table/field-types/advanced/geolocation-field.ts +49 -0
- package/src/domain/models/app/table/field-types/advanced/index.ts +16 -0
- package/src/domain/models/app/table/field-types/advanced/json-field.ts +25 -0
- package/src/domain/models/app/table/field-types/advanced/unknown-field.ts +85 -0
- package/src/domain/models/app/table/field-types/base-field.ts +42 -0
- package/src/domain/models/app/table/field-types/date-time/created-at-field.ts +49 -0
- package/src/domain/models/app/table/field-types/date-time/date-field.ts +95 -0
- package/src/domain/models/app/table/field-types/date-time/deleted-at-field.ts +56 -0
- package/src/domain/models/app/table/field-types/date-time/duration-field.ts +73 -0
- package/src/domain/models/app/table/field-types/date-time/index.ts +12 -0
- package/src/domain/models/app/table/field-types/date-time/updated-at-field.ts +50 -0
- package/src/domain/models/app/table/field-types/index.ts +19 -0
- package/src/domain/models/app/table/field-types/media/barcode-field.ts +58 -0
- package/src/domain/models/app/table/field-types/media/index.ts +10 -0
- package/src/domain/models/app/table/field-types/media/multiple-attachments-field.ts +80 -0
- package/src/domain/models/app/table/field-types/media/single-attachment-field.ts +81 -0
- package/src/domain/models/app/table/field-types/numeric/currency-field.ts +144 -0
- package/src/domain/models/app/table/field-types/numeric/decimal-field.ts +113 -0
- package/src/domain/models/app/table/field-types/numeric/index.ts +13 -0
- package/src/domain/models/app/table/field-types/numeric/integer-field.ts +98 -0
- package/src/domain/models/app/table/field-types/numeric/percentage-field.ts +115 -0
- package/src/domain/models/app/table/field-types/numeric/progress-field.ts +71 -0
- package/src/domain/models/app/table/field-types/numeric/rating-field.ts +74 -0
- package/src/domain/models/app/table/field-types/relational/index.ts +10 -0
- package/src/domain/models/app/table/field-types/relational/lookup-field.ts +46 -0
- package/src/domain/models/app/table/field-types/relational/relationship-field.ts +112 -0
- package/src/domain/models/app/table/field-types/relational/rollup-field.ts +58 -0
- package/src/domain/models/app/table/field-types/selection/checkbox-field.ts +51 -0
- package/src/domain/models/app/table/field-types/selection/index.ts +11 -0
- package/src/domain/models/app/table/field-types/selection/multi-select-field.ts +68 -0
- package/src/domain/models/app/table/field-types/selection/single-select-field.ts +54 -0
- package/src/domain/models/app/table/field-types/selection/status-field.ts +37 -0
- package/src/domain/models/app/table/field-types/text/email-field.ts +80 -0
- package/src/domain/models/app/table/field-types/text/index.ts +13 -0
- package/src/domain/models/app/table/field-types/text/long-text-field.ts +77 -0
- package/src/domain/models/app/table/field-types/text/phone-number-field.ts +82 -0
- package/src/domain/models/app/table/field-types/text/rich-text-field.ts +66 -0
- package/src/domain/models/app/table/field-types/text/single-line-text-field.ts +79 -0
- package/src/domain/models/app/table/field-types/text/url-field.ts +81 -0
- package/src/domain/models/app/table/field-types/user/created-by-field.ts +50 -0
- package/src/domain/models/app/table/field-types/user/deleted-by-field.ts +57 -0
- package/src/domain/models/app/table/field-types/user/index.ts +11 -0
- package/src/domain/models/app/table/field-types/user/updated-by-field.ts +51 -0
- package/src/domain/models/app/table/field-types/user/user-field.ts +52 -0
- package/src/domain/models/app/table/field-types/validation-utils.ts +166 -0
- package/src/domain/models/app/table/fields.ts +216 -0
- package/src/domain/models/app/table/foreign-keys.ts +111 -0
- package/src/domain/models/app/table/formula-keywords.ts +326 -0
- package/src/domain/models/app/table/id.ts +31 -0
- package/src/domain/models/app/table/index.ts +290 -0
- package/src/domain/models/app/table/indexes.ts +80 -0
- package/src/domain/models/app/table/name.ts +37 -0
- package/src/domain/models/app/table/permissions/field-permission.ts +83 -0
- package/src/domain/models/app/table/permissions/index.ts +167 -0
- package/src/domain/models/app/table/permissions/permission-evaluator.ts +372 -0
- package/src/domain/models/app/table/permissions/permission.ts +49 -0
- package/src/domain/models/app/table/primary-key.ts +62 -0
- package/src/domain/models/app/table/table-formula-validation.ts +168 -0
- package/src/domain/models/app/table/table-indexes-validation.ts +38 -0
- package/src/domain/models/app/table/table-permissions-validation.ts +77 -0
- package/src/domain/models/app/table/table-primary-key-validation.ts +49 -0
- package/src/domain/models/app/table/table-views-validation.ts +408 -0
- package/src/domain/models/app/table/unique-constraints.ts +79 -0
- package/src/domain/models/app/table/views/fields.ts +28 -0
- package/src/domain/models/app/table/views/filters.ts +162 -0
- package/src/domain/models/app/table/views/group-by.ts +32 -0
- package/src/domain/models/app/table/views/id.ts +50 -0
- package/src/domain/models/app/table/views/index.ts +177 -0
- package/src/domain/models/app/table/views/name.ts +32 -0
- package/src/domain/models/app/table/views/permissions.ts +98 -0
- package/src/domain/models/app/table/views/sorts.ts +31 -0
- package/src/domain/models/app/tables.ts +695 -0
- package/src/domain/models/app/theme/animations.ts +208 -0
- package/src/domain/models/app/theme/border-radius.ts +58 -0
- package/src/domain/models/app/theme/breakpoints.ts +62 -0
- package/src/domain/models/app/theme/colors.ts +110 -0
- package/src/domain/models/app/theme/fonts.ts +164 -0
- package/src/domain/models/app/theme/shadows.ts +61 -0
- package/src/domain/models/app/theme/spacing.ts +115 -0
- package/src/domain/models/app/theme.ts +66 -0
- package/src/domain/models/app/version.ts +87 -0
- package/src/domain/models/record-comment.ts +91 -0
- package/src/domain/utils/content-parsing.ts +49 -0
- package/src/domain/utils/format-detection.ts +69 -0
- package/src/domain/utils/index.ts +9 -0
- package/src/domain/utils/route-matcher.ts +184 -0
- package/src/domain/utils/translation-resolver.ts +170 -0
- package/src/index.ts +208 -0
- package/src/infrastructure/analytics/tracking-script.ts +48 -0
- package/src/infrastructure/auth/better-auth/auth.ts +216 -0
- package/src/infrastructure/auth/better-auth/email-handlers.ts +162 -0
- package/src/infrastructure/auth/better-auth/index.ts +16 -0
- package/src/infrastructure/auth/better-auth/layer.ts +97 -0
- package/src/infrastructure/auth/better-auth/plugins/admin.ts +56 -0
- package/src/infrastructure/auth/better-auth/plugins/magic-link.ts +31 -0
- package/src/infrastructure/auth/better-auth/plugins/two-factor.ts +19 -0
- package/src/infrastructure/auth/better-auth/schema.ts +152 -0
- package/src/infrastructure/auth/index.ts +27 -0
- package/src/infrastructure/css/cache/css-cache-service.ts +130 -0
- package/src/infrastructure/css/compiler.ts +210 -0
- package/src/infrastructure/css/css-compiler-live.ts +20 -0
- package/src/infrastructure/css/index.ts +25 -0
- package/src/infrastructure/css/styles/animation-styles-generator.ts +177 -0
- package/src/infrastructure/css/styles/click-animations.ts +147 -0
- package/src/infrastructure/css/styles/component-layer-generators.ts +147 -0
- package/src/infrastructure/css/theme/theme-generators.ts +130 -0
- package/src/infrastructure/css/theme/theme-layer-generators.ts +219 -0
- package/src/infrastructure/css/theme/theme-token-resolver.ts +76 -0
- package/src/infrastructure/database/activity-queries.ts +111 -0
- package/src/infrastructure/database/auth/auth-validation.ts +101 -0
- package/src/infrastructure/database/drizzle/db-bun.ts +17 -0
- package/src/infrastructure/database/drizzle/db.ts +17 -0
- package/src/infrastructure/database/drizzle/index.ts +16 -0
- package/src/infrastructure/database/drizzle/layer.ts +34 -0
- package/src/infrastructure/database/drizzle/migrate.ts +77 -0
- package/src/infrastructure/database/drizzle/schema/activity-log.ts +111 -0
- package/src/infrastructure/database/drizzle/schema/analytics-page-views.ts +116 -0
- package/src/infrastructure/database/drizzle/schema/migration-audit.ts +68 -0
- package/src/infrastructure/database/drizzle/schema/record-comments.ts +79 -0
- package/src/infrastructure/database/drizzle/schema.ts +12 -0
- package/src/infrastructure/database/field-utils.ts +87 -0
- package/src/infrastructure/database/filter-operators.ts +136 -0
- package/src/infrastructure/database/formula/formula-trigger-generators.ts +114 -0
- package/src/infrastructure/database/formula/formula-utils.ts +440 -0
- package/src/infrastructure/database/generators/index-generators.ts +152 -0
- package/src/infrastructure/database/generators/trigger-generators.ts +154 -0
- package/src/infrastructure/database/index.ts +35 -0
- package/src/infrastructure/database/lookup/lookup-expression-generators.ts +356 -0
- package/src/infrastructure/database/lookup/lookup-expressions.ts +116 -0
- package/src/infrastructure/database/lookup/lookup-view-generators.ts +403 -0
- package/src/infrastructure/database/lookup/lookup-view-helpers.ts +65 -0
- package/src/infrastructure/database/lookup/lookup-view-triggers.ts +121 -0
- package/src/infrastructure/database/migration-audit-trail.ts +375 -0
- package/src/infrastructure/database/repositories/activity-log-repository-live.ts +99 -0
- package/src/infrastructure/database/repositories/activity-repository-live.ts +21 -0
- package/src/infrastructure/database/repositories/analytics-repository-live.ts +316 -0
- package/src/infrastructure/database/repositories/auth-repository-live.ts +42 -0
- package/src/infrastructure/database/repositories/batch-repository-live.ts +29 -0
- package/src/infrastructure/database/repositories/comment-repository-live.ts +39 -0
- package/src/infrastructure/database/repositories/table-repository-live.ts +38 -0
- package/src/infrastructure/database/schema/schema-dependency-sorting.ts +142 -0
- package/src/infrastructure/database/schema/schema-initializer.ts +598 -0
- package/src/infrastructure/database/schema-migration/column-detection.ts +286 -0
- package/src/infrastructure/database/schema-migration/constants.ts +31 -0
- package/src/infrastructure/database/schema-migration/constraint-sync.ts +288 -0
- package/src/infrastructure/database/schema-migration/index-sync.ts +108 -0
- package/src/infrastructure/database/schema-migration/index.ts +66 -0
- package/src/infrastructure/database/schema-migration/migration-statements.ts +106 -0
- package/src/infrastructure/database/schema-migration/rename-detection.ts +87 -0
- package/src/infrastructure/database/schema-migration/table-operations.ts +65 -0
- package/src/infrastructure/database/schema-migration/type-utils.ts +98 -0
- package/src/infrastructure/database/schema-migration/types.ts +14 -0
- package/src/infrastructure/database/schema-migration-helpers.ts +53 -0
- package/src/infrastructure/database/session-context.ts +20 -0
- package/src/infrastructure/database/sql/sql-check-constraints.ts +252 -0
- package/src/infrastructure/database/sql/sql-column-generators.ts +174 -0
- package/src/infrastructure/database/sql/sql-execution.ts +245 -0
- package/src/infrastructure/database/sql/sql-field-predicates.ts +81 -0
- package/src/infrastructure/database/sql/sql-generators.ts +91 -0
- package/src/infrastructure/database/sql/sql-junction-tables.ts +79 -0
- package/src/infrastructure/database/sql/sql-key-constraints.ts +210 -0
- package/src/infrastructure/database/sql/sql-type-mappings.ts +106 -0
- package/src/infrastructure/database/sql/sql-utils.ts +53 -0
- package/src/infrastructure/database/table-live-layers.ts +30 -0
- package/src/infrastructure/database/table-operations/column-generators.ts +82 -0
- package/src/infrastructure/database/table-operations/create-table-sql.ts +81 -0
- package/src/infrastructure/database/table-operations/index.ts +55 -0
- package/src/infrastructure/database/table-operations/migration-utils.ts +157 -0
- package/src/infrastructure/database/table-operations/table-effects.ts +234 -0
- package/src/infrastructure/database/table-operations/table-features.ts +96 -0
- package/src/infrastructure/database/table-operations/type-compatibility.ts +58 -0
- package/src/infrastructure/database/table-operations.ts +47 -0
- package/src/infrastructure/database/table-queries/batch/batch-create.ts +80 -0
- package/src/infrastructure/database/table-queries/batch/batch-delete.ts +212 -0
- package/src/infrastructure/database/table-queries/batch/batch-helpers.ts +124 -0
- package/src/infrastructure/database/table-queries/batch/batch-restore.ts +161 -0
- package/src/infrastructure/database/table-queries/batch/batch-update.ts +146 -0
- package/src/infrastructure/database/table-queries/batch/batch-upsert.ts +357 -0
- package/src/infrastructure/database/table-queries/batch/batch.ts +14 -0
- package/src/infrastructure/database/table-queries/crud/crud-read.ts +351 -0
- package/src/infrastructure/database/table-queries/crud/crud-write.ts +399 -0
- package/src/infrastructure/database/table-queries/crud/crud.ts +16 -0
- package/src/infrastructure/database/table-queries/index.ts +11 -0
- package/src/infrastructure/database/table-queries/mutation-helpers/authorship-helpers.ts +152 -0
- package/src/infrastructure/database/table-queries/mutation-helpers/create-record-helpers.ts +90 -0
- package/src/infrastructure/database/table-queries/mutation-helpers/delete-helpers.ts +163 -0
- package/src/infrastructure/database/table-queries/mutation-helpers/record-fetch-helpers.ts +79 -0
- package/src/infrastructure/database/table-queries/mutation-helpers/update-helpers.ts +74 -0
- package/src/infrastructure/database/table-queries/query-helpers/activity-log-helpers.ts +53 -0
- package/src/infrastructure/database/table-queries/query-helpers/activity-queries.ts +106 -0
- package/src/infrastructure/database/table-queries/query-helpers/aggregation-helpers.ts +314 -0
- package/src/infrastructure/database/table-queries/query-helpers/comment-queries.ts +414 -0
- package/src/infrastructure/database/table-queries/query-helpers/record-validation-queries.ts +126 -0
- package/src/infrastructure/database/table-queries/query-helpers/trash-helpers.ts +58 -0
- package/src/infrastructure/database/table-queries/shared/error-handling.ts +47 -0
- package/src/infrastructure/database/table-queries/shared/typed-execute.ts +27 -0
- package/src/infrastructure/database/table-queries/shared/user-join-helpers.ts +38 -0
- package/src/infrastructure/database/table-queries/shared/validation.ts +39 -0
- package/src/infrastructure/database/views/view-generators.ts +258 -0
- package/src/infrastructure/devtools/devtools-layer.ts +43 -0
- package/src/infrastructure/devtools/index.ts +8 -0
- package/src/infrastructure/email/email-config.ts +103 -0
- package/src/infrastructure/email/email-service.ts +152 -0
- package/src/infrastructure/email/index.ts +107 -0
- package/src/infrastructure/email/nodemailer.ts +125 -0
- package/src/infrastructure/email/templates.ts +244 -0
- package/src/infrastructure/errors/auth-config-required-error.ts +21 -0
- package/src/infrastructure/errors/auth-error.ts +16 -0
- package/src/infrastructure/errors/css-compilation-error.ts +14 -0
- package/src/infrastructure/errors/index.ts +26 -0
- package/src/infrastructure/errors/schema-initialization-error.ts +19 -0
- package/src/infrastructure/errors/server-creation-error.ts +14 -0
- package/src/infrastructure/filesystem/copy-directory.ts +136 -0
- package/src/infrastructure/layers/app-layer.ts +61 -0
- package/src/infrastructure/layers/page-renderer-layer.ts +41 -0
- package/src/infrastructure/logging/index.ts +8 -0
- package/src/infrastructure/logging/logger.ts +204 -0
- package/src/infrastructure/schema/file-loader.ts +53 -0
- package/src/infrastructure/schema/index.ts +15 -0
- package/src/infrastructure/schema/remote-loader.ts +48 -0
- package/src/infrastructure/server/index.ts +26 -0
- package/src/infrastructure/server/language-detection.ts +87 -0
- package/src/infrastructure/server/lifecycle.ts +67 -0
- package/src/infrastructure/server/route-setup/api-routes.ts +310 -0
- package/src/infrastructure/server/route-setup/auth-route-utils.ts +399 -0
- package/src/infrastructure/server/route-setup/auth-routes.ts +245 -0
- package/src/infrastructure/server/route-setup/openapi-routes.ts +45 -0
- package/src/infrastructure/server/route-setup/openapi-schema.ts +120 -0
- package/src/infrastructure/server/route-setup/page-routes.ts +219 -0
- package/src/infrastructure/server/route-setup/static-assets.ts +191 -0
- package/src/infrastructure/server/server-factory-live.ts +45 -0
- package/src/infrastructure/server/server.ts +275 -0
- package/src/infrastructure/server/ssg-adapter.ts +196 -0
- package/src/infrastructure/server/static-site-generator-live.ts +20 -0
- package/src/infrastructure/utils/accept-language-parser.ts +106 -0
- package/src/infrastructure/utils/glob-matcher.ts +50 -0
- package/src/presentation/api/client.ts +114 -0
- package/src/presentation/api/middleware/auth.ts +233 -0
- package/src/presentation/api/middleware/table.ts +155 -0
- package/src/presentation/api/middleware/validation.ts +88 -0
- package/src/presentation/api/routes/activity/get-activity-by-id-handler.ts +77 -0
- package/src/presentation/api/routes/activity/index.ts +28 -0
- package/src/presentation/api/routes/activity.ts +339 -0
- package/src/presentation/api/routes/analytics.ts +328 -0
- package/src/presentation/api/routes/auth.ts +169 -0
- package/src/presentation/api/routes/index.ts +11 -0
- package/src/presentation/api/routes/tables/activity-handlers.ts +57 -0
- package/src/presentation/api/routes/tables/batch-permission-helpers.ts +163 -0
- package/src/presentation/api/routes/tables/batch-routes.ts +355 -0
- package/src/presentation/api/routes/tables/comment-handlers.ts +377 -0
- package/src/presentation/api/routes/tables/create-record-helpers.ts +179 -0
- package/src/presentation/api/routes/tables/effect-runner.ts +58 -0
- package/src/presentation/api/routes/tables/error-handlers.ts +53 -0
- package/src/presentation/api/routes/tables/field-permission-validation.ts +167 -0
- package/src/presentation/api/routes/tables/filter-parser.ts +75 -0
- package/src/presentation/api/routes/tables/formula-parser.ts +118 -0
- package/src/presentation/api/routes/tables/index.ts +113 -0
- package/src/presentation/api/routes/tables/list-records-filter.ts +54 -0
- package/src/presentation/api/routes/tables/param-parsers.ts +59 -0
- package/src/presentation/api/routes/tables/record-handlers.ts +484 -0
- package/src/presentation/api/routes/tables/record-routes.ts +53 -0
- package/src/presentation/api/routes/tables/record-update-handler.ts +200 -0
- package/src/presentation/api/routes/tables/sort-validation.ts +85 -0
- package/src/presentation/api/routes/tables/table-routes.ts +76 -0
- package/src/presentation/api/routes/tables/timezone-validation.ts +41 -0
- package/src/presentation/api/routes/tables/upsert-helpers.ts +471 -0
- package/src/presentation/api/routes/tables/utils.ts +159 -0
- package/src/presentation/api/routes/tables/view-routes.ts +51 -0
- package/src/presentation/api/routes/tables.ts +9 -0
- package/src/presentation/api/utils/context-helpers.ts +43 -0
- package/src/presentation/api/utils/error-sanitizer.ts +235 -0
- package/src/presentation/api/utils/field-permission-validator.ts +53 -0
- package/src/presentation/api/utils/filter-field-validator.ts +90 -0
- package/src/presentation/api/utils/index.ts +13 -0
- package/src/presentation/api/utils/run-effect.ts +94 -0
- package/src/presentation/api/utils/validate-request.ts +89 -0
- package/src/presentation/api/validation/index.ts +29 -0
- package/src/presentation/api/validation/rules/field-rules.ts +158 -0
- package/src/presentation/api/validation/rules/record-rules.ts +73 -0
- package/src/presentation/cli/index.ts +19 -0
- package/src/presentation/cli/schema-loader.ts +172 -0
- package/src/presentation/hooks/use-breakpoint.ts +155 -0
- package/src/presentation/rendering/render-error-pages.tsx +60 -0
- package/src/presentation/rendering/render-homepage.tsx +137 -0
- package/src/presentation/scripts/script-renderers.ts +112 -0
- package/src/presentation/styling/animation-composer.ts +117 -0
- package/src/presentation/styling/index.ts +13 -0
- package/src/presentation/styling/parse-style.ts +243 -0
- package/src/presentation/styling/style-utils.ts +50 -0
- package/src/presentation/styling/theme-colors.ts +53 -0
- package/src/presentation/translations/component-utils.ts +54 -0
- package/src/presentation/translations/index.ts +16 -0
- package/src/presentation/translations/translation-resolver.ts +22 -0
- package/src/presentation/ui/languages/language-switcher.tsx +119 -0
- package/src/presentation/ui/metadata/analytics-builders.tsx +174 -0
- package/src/presentation/ui/metadata/analytics-head.tsx +39 -0
- package/src/presentation/ui/metadata/custom-elements-builders.tsx +157 -0
- package/src/presentation/ui/metadata/extract-component-meta.ts +108 -0
- package/src/presentation/ui/metadata/head-elements.tsx +164 -0
- package/src/presentation/ui/metadata/index.tsx +35 -0
- package/src/presentation/ui/metadata/meta-utils.tsx +42 -0
- package/src/presentation/ui/metadata/open-graph-meta.tsx +57 -0
- package/src/presentation/ui/metadata/structured-data-from-component.tsx +134 -0
- package/src/presentation/ui/metadata/structured-data.tsx +88 -0
- package/src/presentation/ui/metadata/twitter-card-meta.tsx +80 -0
- package/src/presentation/ui/pages/DefaultHomePage.tsx +43 -0
- package/src/presentation/ui/pages/DefaultPageConfigs.ts +220 -0
- package/src/presentation/ui/pages/DynamicPage.tsx +307 -0
- package/src/presentation/ui/pages/ErrorPage.tsx +25 -0
- package/src/presentation/ui/pages/NotFoundPage.tsx +25 -0
- package/src/presentation/ui/pages/PageBodyScripts.tsx +242 -0
- package/src/presentation/ui/pages/PageBodyStyles.ts +52 -0
- package/src/presentation/ui/pages/PageHead.tsx +380 -0
- package/src/presentation/ui/pages/PageLangResolver.ts +58 -0
- package/src/presentation/ui/pages/PageMain.tsx +58 -0
- package/src/presentation/ui/pages/PageMetadata.ts +168 -0
- package/src/presentation/ui/pages/PageMetadataI18n.ts +169 -0
- package/src/presentation/ui/pages/PageScripts.ts +78 -0
- package/src/presentation/ui/pages/SectionRenderer.tsx +67 -0
- package/src/presentation/ui/pages/SectionSpacing.tsx +131 -0
- package/src/presentation/ui/sections/component-renderer.tsx +426 -0
- package/src/presentation/ui/sections/component-renderer.types.ts +33 -0
- package/src/presentation/ui/sections/components/component-reference-handler.tsx +74 -0
- package/src/presentation/ui/sections/components/component-resolution.ts +65 -0
- package/src/presentation/ui/sections/components/index.ts +9 -0
- package/src/presentation/ui/sections/hero.tsx +394 -0
- package/src/presentation/ui/sections/props/component-builder.ts +183 -0
- package/src/presentation/ui/sections/props/element-props.ts +179 -0
- package/src/presentation/ui/sections/props/index.ts +9 -0
- package/src/presentation/ui/sections/props/prop-conversion.ts +171 -0
- package/src/presentation/ui/sections/props/props-builder-config.ts +42 -0
- package/src/presentation/ui/sections/props/props-builder.ts +296 -0
- package/src/presentation/ui/sections/renderers/element-renderers/html-element-renderer.tsx +124 -0
- package/src/presentation/ui/sections/renderers/element-renderers/index.ts +59 -0
- package/src/presentation/ui/sections/renderers/element-renderers/interactive-renderers.tsx +231 -0
- package/src/presentation/ui/sections/renderers/element-renderers/media-renderers.tsx +102 -0
- package/src/presentation/ui/sections/renderers/element-renderers/text-content-renderers.tsx +42 -0
- package/src/presentation/ui/sections/renderers/element-renderers.ts +53 -0
- package/src/presentation/ui/sections/renderers/html-element-helpers.ts +100 -0
- package/src/presentation/ui/sections/renderers/specialized-renderers.tsx +212 -0
- package/src/presentation/ui/sections/rendering/component-dispatch-config.ts +31 -0
- package/src/presentation/ui/sections/rendering/component-registry/index.ts +39 -0
- package/src/presentation/ui/sections/rendering/component-registry/interactive-components.ts +54 -0
- package/src/presentation/ui/sections/rendering/component-registry/media-components.ts +36 -0
- package/src/presentation/ui/sections/rendering/component-registry/special-components.tsx +153 -0
- package/src/presentation/ui/sections/rendering/component-registry/structural-components.ts +215 -0
- package/src/presentation/ui/sections/rendering/component-registry/text-components.ts +57 -0
- package/src/presentation/ui/sections/rendering/component-registry-helpers.tsx +29 -0
- package/src/presentation/ui/sections/rendering/component-registry.tsx +21 -0
- package/src/presentation/ui/sections/rendering/component-type-dispatcher.tsx +33 -0
- package/src/presentation/ui/sections/rendering/index.ts +9 -0
- package/src/presentation/ui/sections/responsive/responsive-children-builder.tsx +96 -0
- package/src/presentation/ui/sections/responsive/responsive-content-builder.tsx +95 -0
- package/src/presentation/ui/sections/responsive/responsive-props-merger.ts +195 -0
- package/src/presentation/ui/sections/responsive/responsive-resolver.ts +213 -0
- package/src/presentation/ui/sections/styling/animation-composer-wrapper.ts +65 -0
- package/src/presentation/ui/sections/styling/class-builders.ts +45 -0
- package/src/presentation/ui/sections/styling/color-resolver.ts +43 -0
- package/src/presentation/ui/sections/styling/hover-interaction-handler.ts +107 -0
- package/src/presentation/ui/sections/styling/index.ts +9 -0
- package/src/presentation/ui/sections/styling/interaction-props-builder.ts +55 -0
- package/src/presentation/ui/sections/styling/shadow-resolver.ts +83 -0
- package/src/presentation/ui/sections/styling/spacing-resolver.ts +104 -0
- package/src/presentation/ui/sections/styling/style-processor.ts +170 -0
- package/src/presentation/ui/sections/styling/theme-tokens.ts +184 -0
- package/src/presentation/ui/sections/translations/i18n-content-resolver.ts +198 -0
- package/src/presentation/ui/sections/translations/index.ts +9 -0
- package/src/presentation/ui/sections/translations/translation-handler.ts +143 -0
- package/src/presentation/ui/sections/translations/variable-substitution.ts +225 -0
- package/src/presentation/ui/sections/utils/time-parser.ts +82 -0
- package/src/presentation/utils/link-attributes.ts +50 -0
- package/src/presentation/utils/string-utils.ts +58 -0
- package/src/presentation/utils/styles.ts +50 -0
- package/tsconfig.json +46 -0
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 ESSENTIAL SERVICES
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the Business Source License 1.1
|
|
5
|
+
* found in the LICENSE.md file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { type ReactElement } from 'react'
|
|
9
|
+
import {
|
|
10
|
+
renderInlineScriptTag,
|
|
11
|
+
renderScriptTag,
|
|
12
|
+
renderWindowConfig,
|
|
13
|
+
} from '@/presentation/scripts/script-renderers'
|
|
14
|
+
import { buildPageMetadataI18n } from './PageMetadataI18n'
|
|
15
|
+
import type { GroupedScripts } from './PageScripts'
|
|
16
|
+
import type { Languages } from '@/domain/models/app/languages'
|
|
17
|
+
import type { Page } from '@/domain/models/app/pages'
|
|
18
|
+
import type { Theme } from '@/domain/models/app/theme'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Props for PageBodyScripts component
|
|
22
|
+
*/
|
|
23
|
+
type PageBodyScriptsProps = {
|
|
24
|
+
readonly page: Page
|
|
25
|
+
readonly theme: Theme | undefined
|
|
26
|
+
readonly languages: Languages | undefined
|
|
27
|
+
readonly direction: 'ltr' | 'rtl'
|
|
28
|
+
readonly scripts: GroupedScripts
|
|
29
|
+
readonly position: 'start' | 'end'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Renders external and inline scripts for a given position
|
|
34
|
+
*/
|
|
35
|
+
function renderScripts(
|
|
36
|
+
externalScripts: GroupedScripts['external']['head'],
|
|
37
|
+
inlineScripts: GroupedScripts['inline']['head'],
|
|
38
|
+
keyPrefix: string
|
|
39
|
+
): ReactElement {
|
|
40
|
+
return (
|
|
41
|
+
<>
|
|
42
|
+
{externalScripts.map((script, index) =>
|
|
43
|
+
renderScriptTag({
|
|
44
|
+
src: script.src,
|
|
45
|
+
async: script.async,
|
|
46
|
+
defer: script.defer,
|
|
47
|
+
module: script.module,
|
|
48
|
+
integrity: script.integrity,
|
|
49
|
+
crossOrigin: script.crossorigin as 'anonymous' | 'use-credentials' | undefined,
|
|
50
|
+
reactKey: `${keyPrefix}-${index}`,
|
|
51
|
+
})
|
|
52
|
+
)}
|
|
53
|
+
{inlineScripts.map((script, index) =>
|
|
54
|
+
renderInlineScriptTag({
|
|
55
|
+
code: script.code,
|
|
56
|
+
async: script.async,
|
|
57
|
+
reactKey: `inline-${keyPrefix}-${index}`,
|
|
58
|
+
})
|
|
59
|
+
)}
|
|
60
|
+
</>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Renders language switcher scripts and configuration
|
|
66
|
+
*/
|
|
67
|
+
function LanguageSwitcherScripts({
|
|
68
|
+
page,
|
|
69
|
+
languages,
|
|
70
|
+
theme,
|
|
71
|
+
direction,
|
|
72
|
+
}: {
|
|
73
|
+
readonly page: Page
|
|
74
|
+
readonly languages: Languages
|
|
75
|
+
readonly theme: Theme | undefined
|
|
76
|
+
readonly direction: 'ltr' | 'rtl'
|
|
77
|
+
}): ReactElement {
|
|
78
|
+
// Build enriched metadata with i18n translations for all languages
|
|
79
|
+
const enrichedMeta = buildPageMetadataI18n(page, languages)
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<>
|
|
83
|
+
{/* Configuration data for external script (CSP-compliant) */}
|
|
84
|
+
<div
|
|
85
|
+
data-language-switcher-config={JSON.stringify(languages)}
|
|
86
|
+
style={{ display: 'none' }}
|
|
87
|
+
/>
|
|
88
|
+
{/* Page metadata for client-side updates (title, i18n) */}
|
|
89
|
+
<div
|
|
90
|
+
data-page-meta={JSON.stringify(enrichedMeta)}
|
|
91
|
+
style={{ display: 'none' }}
|
|
92
|
+
/>
|
|
93
|
+
{/* Expose languages config to window for testing/debugging - fallback defaults to default language */}
|
|
94
|
+
{renderWindowConfig({
|
|
95
|
+
windowKey: 'APP_LANGUAGES',
|
|
96
|
+
data: {
|
|
97
|
+
...languages,
|
|
98
|
+
fallback: languages.fallback ?? languages.default,
|
|
99
|
+
},
|
|
100
|
+
reactKey: 'window-app-languages',
|
|
101
|
+
})}
|
|
102
|
+
{/* Expose theme config with RTL-aware direction to window for testing/debugging */}
|
|
103
|
+
{renderWindowConfig({
|
|
104
|
+
windowKey: 'APP_THEME',
|
|
105
|
+
data: {
|
|
106
|
+
...(theme || {}),
|
|
107
|
+
direction: direction,
|
|
108
|
+
},
|
|
109
|
+
reactKey: 'window-app-theme',
|
|
110
|
+
})}
|
|
111
|
+
{/* External script file loaded only when needed (defer ensures DOM is ready) */}
|
|
112
|
+
<script
|
|
113
|
+
src="/assets/language-switcher.js"
|
|
114
|
+
defer={true}
|
|
115
|
+
/>
|
|
116
|
+
</>
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Renders scroll animation script if needed
|
|
122
|
+
*/
|
|
123
|
+
function ScrollAnimationScript({
|
|
124
|
+
page,
|
|
125
|
+
theme,
|
|
126
|
+
}: {
|
|
127
|
+
readonly page: Page
|
|
128
|
+
readonly theme: Theme | undefined
|
|
129
|
+
}): ReactElement | undefined {
|
|
130
|
+
const needsAnimation = (page.sections && page.sections.length > 0) || theme?.animations?.scaleUp
|
|
131
|
+
if (!needsAnimation) return undefined
|
|
132
|
+
return (
|
|
133
|
+
<script
|
|
134
|
+
src="/assets/scroll-animation.js"
|
|
135
|
+
defer={true}
|
|
136
|
+
/>
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Renders feature flags configuration if configured
|
|
142
|
+
*/
|
|
143
|
+
function FeatureFlagsScript({ page }: { readonly page: Page }): ReactElement | undefined {
|
|
144
|
+
if (!page.scripts?.features) return undefined
|
|
145
|
+
return renderWindowConfig({
|
|
146
|
+
windowKey: 'FEATURES',
|
|
147
|
+
data: page.scripts.features,
|
|
148
|
+
reactKey: 'window-features',
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Renders conditional script tags (banner, animation, features)
|
|
154
|
+
*/
|
|
155
|
+
function renderConditionalScripts(config: {
|
|
156
|
+
readonly page: Page
|
|
157
|
+
readonly theme: Theme | undefined
|
|
158
|
+
readonly languages: Languages | undefined
|
|
159
|
+
readonly direction: 'ltr' | 'rtl'
|
|
160
|
+
}): ReactElement {
|
|
161
|
+
const { page, theme, languages, direction } = config
|
|
162
|
+
return (
|
|
163
|
+
<>
|
|
164
|
+
<ScrollAnimationScript
|
|
165
|
+
page={page}
|
|
166
|
+
theme={theme}
|
|
167
|
+
/>
|
|
168
|
+
{languages && (
|
|
169
|
+
<LanguageSwitcherScripts
|
|
170
|
+
page={page}
|
|
171
|
+
languages={languages}
|
|
172
|
+
theme={theme}
|
|
173
|
+
direction={direction}
|
|
174
|
+
/>
|
|
175
|
+
)}
|
|
176
|
+
<FeatureFlagsScript page={page} />
|
|
177
|
+
</>
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Click interaction handler (SECURITY: Safe - static code, no user input)
|
|
183
|
+
*/
|
|
184
|
+
const clickScript = `!function(){document.addEventListener("click",function(t){const e=t.target.closest("[data-click-animation], [data-click-navigate], [data-click-open-url], [data-click-scroll-to], [data-click-toggle-element], [data-click-submit-form]");if(!e)return;const n=e.getAttribute("data-click-animation"),a=e.getAttribute("data-click-navigate"),c=e.getAttribute("data-click-open-url"),i=e.getAttribute("data-click-open-in-new-tab")==="true",o=e.getAttribute("data-click-scroll-to"),l=e.getAttribute("data-click-toggle-element"),r=e.getAttribute("data-click-submit-form"),s=c||a,d=!!c;if(r){const t=document.querySelector(r);t&&"FORM"===t.tagName&&t.requestSubmit()}else if(l){const t=document.querySelector(l);if(t){const e="none"===window.getComputedStyle(t).display;t.style.display=e?"":"none"}}else if(o){const t=document.querySelector(o);t&&t.scrollIntoView({behavior:"smooth",block:"start"})}else if(n&&"none"!==n){const t="animate-"+n;if(e.classList.add(t),s){let n=!1;const a=function(){n||(n=!0,e.classList.remove(t),d&&i?window.open(s,"_blank"):window.location.href=s)};e.addEventListener("animationend",a,{once:!0}),setTimeout(a,300)}else{const n=function(){e.classList.remove(t)};e.addEventListener("animationend",n,{once:!0}),setTimeout(n,300)}}else s&&(d&&i?window.open(s,"_blank"):window.location.href=s)})}();`
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Renders scripts for body end position
|
|
188
|
+
*/
|
|
189
|
+
function renderBodyEndScripts(config: {
|
|
190
|
+
readonly page: Page
|
|
191
|
+
readonly theme: Theme | undefined
|
|
192
|
+
readonly languages: Languages | undefined
|
|
193
|
+
readonly direction: 'ltr' | 'rtl'
|
|
194
|
+
readonly scripts: GroupedScripts
|
|
195
|
+
}): ReactElement {
|
|
196
|
+
const { page, theme, languages, direction, scripts } = config
|
|
197
|
+
return (
|
|
198
|
+
<>
|
|
199
|
+
{renderScripts(scripts.external.bodyEnd, scripts.inline.bodyEnd, 'body-end')}
|
|
200
|
+
{renderConditionalScripts({ page, theme, languages, direction })}
|
|
201
|
+
{/* Render APP_CONFIG after inline scripts to merge with any existing values */}
|
|
202
|
+
{page.scripts?.config &&
|
|
203
|
+
renderWindowConfig({
|
|
204
|
+
windowKey: 'APP_CONFIG',
|
|
205
|
+
data: page.scripts.config,
|
|
206
|
+
reactKey: 'window-app-config-body',
|
|
207
|
+
})}
|
|
208
|
+
<script dangerouslySetInnerHTML={{ __html: clickScript }} />
|
|
209
|
+
</>
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Renders scripts for body start or end position
|
|
215
|
+
*
|
|
216
|
+
* For 'start' position:
|
|
217
|
+
* - External and inline scripts positioned at body-start
|
|
218
|
+
*
|
|
219
|
+
* For 'end' position:
|
|
220
|
+
* - External and inline scripts positioned at body-end
|
|
221
|
+
* - Banner dismiss script (if banner is dismissible)
|
|
222
|
+
* - Scroll animation script (if theme has scaleUp animation)
|
|
223
|
+
* - Language switcher script (if languages configured)
|
|
224
|
+
* - Feature flags script (if features configured)
|
|
225
|
+
*
|
|
226
|
+
* @param props - Component props
|
|
227
|
+
* @returns Script elements for the specified position
|
|
228
|
+
*/
|
|
229
|
+
export function PageBodyScripts({
|
|
230
|
+
page,
|
|
231
|
+
theme,
|
|
232
|
+
languages,
|
|
233
|
+
direction,
|
|
234
|
+
scripts,
|
|
235
|
+
position,
|
|
236
|
+
}: PageBodyScriptsProps): Readonly<ReactElement> {
|
|
237
|
+
if (position === 'start') {
|
|
238
|
+
return renderScripts(scripts.external.bodyStart, scripts.inline.bodyStart, 'body-start')
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return renderBodyEndScripts({ page, theme, languages, direction, scripts })
|
|
242
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 ESSENTIAL SERVICES
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the Business Source License 1.1
|
|
5
|
+
* found in the LICENSE.md file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Theme } from '@/domain/models/app/theme'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Body style configuration for theme fonts
|
|
12
|
+
*/
|
|
13
|
+
export type BodyStyle = {
|
|
14
|
+
readonly fontFamily?: string
|
|
15
|
+
readonly fontSize?: string
|
|
16
|
+
readonly lineHeight?: string
|
|
17
|
+
readonly fontStyle?: 'normal' | 'italic' | 'oblique'
|
|
18
|
+
readonly letterSpacing?: string
|
|
19
|
+
readonly textTransform?: 'none' | 'uppercase' | 'lowercase' | 'capitalize'
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Builds font family string with optional fallback
|
|
24
|
+
*/
|
|
25
|
+
function buildFontFamily(family: string, fallback?: string): string {
|
|
26
|
+
return fallback ? `${family}, ${fallback}` : family
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Generates inline body styles from theme fonts configuration
|
|
31
|
+
*
|
|
32
|
+
* Inline styles have highest specificity and override Tailwind base styles.
|
|
33
|
+
*
|
|
34
|
+
* @param theme - Theme configuration
|
|
35
|
+
* @returns Body style object or undefined if no body font configured
|
|
36
|
+
*/
|
|
37
|
+
export function generateBodyStyle(theme: Theme | undefined): BodyStyle | undefined {
|
|
38
|
+
if (!theme?.fonts?.body) return undefined
|
|
39
|
+
|
|
40
|
+
const bodyFont = theme.fonts.body
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
...(bodyFont.family && { fontFamily: buildFontFamily(bodyFont.family, bodyFont.fallback) }),
|
|
44
|
+
...(bodyFont.size && { fontSize: bodyFont.size }),
|
|
45
|
+
...(bodyFont.lineHeight && { lineHeight: bodyFont.lineHeight }),
|
|
46
|
+
...(bodyFont.style && { fontStyle: bodyFont.style as 'normal' | 'italic' | 'oblique' }),
|
|
47
|
+
...(bodyFont.letterSpacing && { letterSpacing: bodyFont.letterSpacing }),
|
|
48
|
+
...(bodyFont.transform && {
|
|
49
|
+
textTransform: bodyFont.transform as 'none' | 'uppercase' | 'lowercase' | 'capitalize',
|
|
50
|
+
}),
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 ESSENTIAL SERVICES
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the Business Source License 1.1
|
|
5
|
+
* found in the LICENSE.md file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { type ReactElement } from 'react'
|
|
9
|
+
import { normalizeFavicons } from '@/application/metadata/favicon-transformer'
|
|
10
|
+
import { renderInlineScriptTag, renderScriptTag } from '@/presentation/scripts/script-renderers'
|
|
11
|
+
import { resolveTranslationPattern } from '@/presentation/translations/translation-resolver'
|
|
12
|
+
import {
|
|
13
|
+
AnalyticsHead,
|
|
14
|
+
CustomElementsHead,
|
|
15
|
+
DnsPrefetchLinks,
|
|
16
|
+
FaviconLink,
|
|
17
|
+
FaviconSetLinks,
|
|
18
|
+
OpenGraphMeta,
|
|
19
|
+
PreloadLinks,
|
|
20
|
+
StructuredDataScript,
|
|
21
|
+
TwitterCardMeta,
|
|
22
|
+
} from '@/presentation/ui/metadata'
|
|
23
|
+
import type { GroupedScripts } from './PageScripts'
|
|
24
|
+
import type { Languages } from '@/domain/models/app/languages'
|
|
25
|
+
import type { CustomElement } from '@/domain/models/app/page/meta/custom-elements'
|
|
26
|
+
import type { OpenGraph } from '@/domain/models/app/page/meta/open-graph'
|
|
27
|
+
import type { Page } from '@/domain/models/app/pages'
|
|
28
|
+
import type { Theme } from '@/domain/models/app/theme'
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Props for PageHead component
|
|
32
|
+
*/
|
|
33
|
+
type PageHeadProps = {
|
|
34
|
+
readonly page: Page
|
|
35
|
+
readonly theme: Theme | undefined
|
|
36
|
+
readonly directionStyles: string
|
|
37
|
+
readonly title: string
|
|
38
|
+
readonly description: string
|
|
39
|
+
readonly keywords?: string
|
|
40
|
+
readonly canonical?: string
|
|
41
|
+
readonly lang: string
|
|
42
|
+
readonly languages?: Languages
|
|
43
|
+
readonly scripts: GroupedScripts
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Checks if custom elements include a viewport meta tag
|
|
48
|
+
*/
|
|
49
|
+
function hasCustomViewportMeta(customElements: readonly CustomElement[] | undefined): boolean {
|
|
50
|
+
if (!customElements) return false
|
|
51
|
+
return customElements.some(
|
|
52
|
+
(element) => element.type === 'meta' && element.attrs?.name === 'viewport'
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Extracts OpenGraph properties from meta object
|
|
58
|
+
* Handles both standard openGraph object and og:* prefixed direct properties
|
|
59
|
+
*/
|
|
60
|
+
function extractOpenGraphData(
|
|
61
|
+
page: Page,
|
|
62
|
+
lang: string,
|
|
63
|
+
languages: Languages | undefined
|
|
64
|
+
): OpenGraph | undefined {
|
|
65
|
+
if (!page.meta) return undefined
|
|
66
|
+
|
|
67
|
+
// Start with existing openGraph object if present
|
|
68
|
+
const openGraph = page.meta.openGraph || {}
|
|
69
|
+
|
|
70
|
+
// Extract og:* prefixed properties from meta (type assertion needed for dynamic access)
|
|
71
|
+
const metaRecord = page.meta as Record<string, unknown>
|
|
72
|
+
const ogSiteName = metaRecord['og:site_name']
|
|
73
|
+
|
|
74
|
+
// Resolve translation patterns in extracted properties
|
|
75
|
+
const resolvedSiteName =
|
|
76
|
+
typeof ogSiteName === 'string'
|
|
77
|
+
? resolveTranslationPattern(ogSiteName, lang, languages)
|
|
78
|
+
: undefined
|
|
79
|
+
|
|
80
|
+
// Merge og:* properties into openGraph structure
|
|
81
|
+
const merged = {
|
|
82
|
+
...openGraph,
|
|
83
|
+
...(resolvedSiteName && { siteName: resolvedSiteName }),
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Only return if there's at least one property
|
|
87
|
+
return Object.keys(merged).length > 0 ? merged : undefined
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Renders basic meta tags (charset, viewport, title, description, keywords, canonical)
|
|
92
|
+
*/
|
|
93
|
+
function BasicMetaTags({
|
|
94
|
+
title,
|
|
95
|
+
description,
|
|
96
|
+
keywords,
|
|
97
|
+
canonical,
|
|
98
|
+
hasCustomViewport,
|
|
99
|
+
}: {
|
|
100
|
+
readonly title: string
|
|
101
|
+
readonly description: string
|
|
102
|
+
readonly keywords?: string
|
|
103
|
+
readonly canonical?: string
|
|
104
|
+
readonly hasCustomViewport: boolean
|
|
105
|
+
}): ReactElement {
|
|
106
|
+
return (
|
|
107
|
+
<>
|
|
108
|
+
<meta charSet="UTF-8" />
|
|
109
|
+
{!hasCustomViewport && (
|
|
110
|
+
<meta
|
|
111
|
+
name="viewport"
|
|
112
|
+
content="width=device-width, initial-scale=1.0"
|
|
113
|
+
/>
|
|
114
|
+
)}
|
|
115
|
+
<title>{title}</title>
|
|
116
|
+
{description && (
|
|
117
|
+
<meta
|
|
118
|
+
name="description"
|
|
119
|
+
content={description}
|
|
120
|
+
/>
|
|
121
|
+
)}
|
|
122
|
+
{keywords && (
|
|
123
|
+
<meta
|
|
124
|
+
name="keywords"
|
|
125
|
+
content={keywords}
|
|
126
|
+
/>
|
|
127
|
+
)}
|
|
128
|
+
{canonical && (
|
|
129
|
+
<link
|
|
130
|
+
rel="canonical"
|
|
131
|
+
href={canonical}
|
|
132
|
+
/>
|
|
133
|
+
)}
|
|
134
|
+
</>
|
|
135
|
+
)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Renders font stylesheets from theme configuration
|
|
140
|
+
*/
|
|
141
|
+
function ThemeFonts({ theme }: { readonly theme: Theme | undefined }): ReactElement | undefined {
|
|
142
|
+
if (!theme?.fonts) return undefined
|
|
143
|
+
|
|
144
|
+
return (
|
|
145
|
+
<>
|
|
146
|
+
{Object.values(theme.fonts).map((font, index) =>
|
|
147
|
+
font.url ? (
|
|
148
|
+
<link
|
|
149
|
+
key={`font-${index}`}
|
|
150
|
+
rel="stylesheet"
|
|
151
|
+
href={font.url}
|
|
152
|
+
/>
|
|
153
|
+
) : undefined
|
|
154
|
+
)}
|
|
155
|
+
</>
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Renders custom stylesheet link from meta.stylesheet
|
|
161
|
+
*/
|
|
162
|
+
function CustomStylesheet({
|
|
163
|
+
stylesheet,
|
|
164
|
+
}: {
|
|
165
|
+
readonly stylesheet: string | undefined
|
|
166
|
+
}): ReactElement | undefined {
|
|
167
|
+
if (!stylesheet) return undefined
|
|
168
|
+
|
|
169
|
+
return (
|
|
170
|
+
<link
|
|
171
|
+
rel="stylesheet"
|
|
172
|
+
href={stylesheet}
|
|
173
|
+
/>
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Renders Google Fonts with performance optimizations
|
|
179
|
+
* Includes preconnect hints for fonts.googleapis.com and fonts.gstatic.com
|
|
180
|
+
*/
|
|
181
|
+
function GoogleFonts({
|
|
182
|
+
googleFonts,
|
|
183
|
+
}: {
|
|
184
|
+
readonly googleFonts: string | undefined
|
|
185
|
+
}): ReactElement | undefined {
|
|
186
|
+
if (!googleFonts) return undefined
|
|
187
|
+
|
|
188
|
+
return (
|
|
189
|
+
<>
|
|
190
|
+
<link
|
|
191
|
+
rel="preconnect"
|
|
192
|
+
href="https://fonts.googleapis.com"
|
|
193
|
+
/>
|
|
194
|
+
<link
|
|
195
|
+
rel="preconnect"
|
|
196
|
+
href="https://fonts.gstatic.com"
|
|
197
|
+
crossOrigin="anonymous"
|
|
198
|
+
/>
|
|
199
|
+
<link
|
|
200
|
+
rel="stylesheet"
|
|
201
|
+
href={googleFonts}
|
|
202
|
+
/>
|
|
203
|
+
</>
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Renders global CSS and direction styles
|
|
209
|
+
* Theme CSS is compiled globally at /assets/output.css
|
|
210
|
+
*
|
|
211
|
+
* SECURITY: Safe use of dangerouslySetInnerHTML
|
|
212
|
+
* - Content: RTL direction styles (build-time generated CSS)
|
|
213
|
+
* - Source: directionStyles prop computed from language direction
|
|
214
|
+
* - Risk: None - contains only CSS direction properties
|
|
215
|
+
* - Validation: Generated by language direction logic (ltr/rtl)
|
|
216
|
+
* - Purpose: Apply RTL-aware styling for Arabic/Hebrew languages
|
|
217
|
+
* - XSS Protection: CSS syntax prevents script execution
|
|
218
|
+
* - Content: Fixed format like "[dir='rtl'] { direction: rtl; }"
|
|
219
|
+
*/
|
|
220
|
+
function GlobalStyles({ directionStyles }: { readonly directionStyles: string }): ReactElement {
|
|
221
|
+
return (
|
|
222
|
+
<>
|
|
223
|
+
<link
|
|
224
|
+
rel="stylesheet"
|
|
225
|
+
href="/assets/output.css"
|
|
226
|
+
/>
|
|
227
|
+
<style dangerouslySetInnerHTML={{ __html: directionStyles }} />
|
|
228
|
+
</>
|
|
229
|
+
)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Renders external and inline scripts for head section
|
|
234
|
+
* Note: APP_CONFIG is rendered in body-end to merge with inline scripts
|
|
235
|
+
*/
|
|
236
|
+
function HeadScripts({ scripts }: { readonly scripts: GroupedScripts }): ReactElement {
|
|
237
|
+
return (
|
|
238
|
+
<>
|
|
239
|
+
{scripts.external.head.map((script, index) =>
|
|
240
|
+
renderScriptTag({
|
|
241
|
+
src: script.src,
|
|
242
|
+
async: script.async,
|
|
243
|
+
defer: script.defer,
|
|
244
|
+
module: script.module,
|
|
245
|
+
integrity: script.integrity,
|
|
246
|
+
crossOrigin: script.crossorigin,
|
|
247
|
+
reactKey: `head-${index}`,
|
|
248
|
+
})
|
|
249
|
+
)}
|
|
250
|
+
{scripts.inline.head.map((script, index) =>
|
|
251
|
+
renderInlineScriptTag({
|
|
252
|
+
code: script.code,
|
|
253
|
+
async: script.async,
|
|
254
|
+
reactKey: `inline-head-${index}`,
|
|
255
|
+
})
|
|
256
|
+
)}
|
|
257
|
+
</>
|
|
258
|
+
)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Renders hreflang alternate links for multi-language SEO
|
|
263
|
+
* Generates <link rel="alternate" hreflang="..."> tags for each supported language
|
|
264
|
+
*
|
|
265
|
+
* Uses dual-pattern approach:
|
|
266
|
+
* - hreflang attribute: Full locale (e.g., 'en-US', 'fr-FR') for SEO standards
|
|
267
|
+
* - href attribute: Short code URL path (e.g., '/en/', '/fr/') for routing
|
|
268
|
+
*
|
|
269
|
+
* Includes x-default link pointing to the default language for undefined locales
|
|
270
|
+
*/
|
|
271
|
+
function HreflangLinks({
|
|
272
|
+
page,
|
|
273
|
+
languages,
|
|
274
|
+
}: {
|
|
275
|
+
readonly page: Page
|
|
276
|
+
readonly languages: Languages | undefined
|
|
277
|
+
}): ReactElement | undefined {
|
|
278
|
+
if (!languages || languages.supported.length <= 1) {
|
|
279
|
+
return undefined
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const basePath = page.path === '/' ? '' : page.path
|
|
283
|
+
|
|
284
|
+
return (
|
|
285
|
+
<>
|
|
286
|
+
{languages.supported.map((lang) => {
|
|
287
|
+
// Use full locale for hreflang attribute (e.g., 'en-US', 'fr-FR')
|
|
288
|
+
// Use short code for URL path (e.g., '/en/', '/fr/')
|
|
289
|
+
const hreflang = lang.locale || lang.code
|
|
290
|
+
return (
|
|
291
|
+
<link
|
|
292
|
+
key={lang.code}
|
|
293
|
+
rel="alternate"
|
|
294
|
+
hrefLang={hreflang}
|
|
295
|
+
href={`/${lang.code}${basePath}/`}
|
|
296
|
+
/>
|
|
297
|
+
)
|
|
298
|
+
})}
|
|
299
|
+
<link
|
|
300
|
+
key="x-default"
|
|
301
|
+
rel="alternate"
|
|
302
|
+
hrefLang="x-default"
|
|
303
|
+
href={`/${languages.default}${basePath}/`}
|
|
304
|
+
/>
|
|
305
|
+
</>
|
|
306
|
+
)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Renders the complete <head> section of a dynamic page
|
|
311
|
+
*
|
|
312
|
+
* Includes:
|
|
313
|
+
* - Basic meta tags (charset, viewport, title, description)
|
|
314
|
+
* - OpenGraph and Twitter Card metadata
|
|
315
|
+
* - Structured data (JSON-LD)
|
|
316
|
+
* - Preload hints for critical resources
|
|
317
|
+
* - DNS prefetch hints
|
|
318
|
+
* - Analytics scripts
|
|
319
|
+
* - Custom elements
|
|
320
|
+
* - Favicon links
|
|
321
|
+
* - Custom stylesheet from meta.stylesheet
|
|
322
|
+
* - Google Fonts with preconnect hints from meta.googleFonts
|
|
323
|
+
* - Font stylesheets from theme
|
|
324
|
+
* - Global CSS with compiled theme tokens
|
|
325
|
+
* - Direction styles for RTL support
|
|
326
|
+
* - External and inline scripts positioned in head
|
|
327
|
+
*
|
|
328
|
+
* @param props - Component props
|
|
329
|
+
* @returns Head section elements
|
|
330
|
+
*/
|
|
331
|
+
export function PageHead({
|
|
332
|
+
page,
|
|
333
|
+
theme,
|
|
334
|
+
directionStyles,
|
|
335
|
+
title,
|
|
336
|
+
description,
|
|
337
|
+
keywords,
|
|
338
|
+
canonical,
|
|
339
|
+
lang,
|
|
340
|
+
languages,
|
|
341
|
+
scripts,
|
|
342
|
+
}: PageHeadProps): Readonly<ReactElement> {
|
|
343
|
+
const hasCustomViewport = hasCustomViewportMeta(page.meta?.customElements)
|
|
344
|
+
const openGraphData = extractOpenGraphData(page, lang, languages)
|
|
345
|
+
const normalizedFavicons = normalizeFavicons(page.meta?.favicons)
|
|
346
|
+
|
|
347
|
+
return (
|
|
348
|
+
<>
|
|
349
|
+
<BasicMetaTags
|
|
350
|
+
title={title}
|
|
351
|
+
description={description}
|
|
352
|
+
keywords={keywords}
|
|
353
|
+
canonical={canonical}
|
|
354
|
+
hasCustomViewport={hasCustomViewport}
|
|
355
|
+
/>
|
|
356
|
+
<OpenGraphMeta
|
|
357
|
+
openGraph={openGraphData}
|
|
358
|
+
lang={lang}
|
|
359
|
+
languages={languages}
|
|
360
|
+
/>
|
|
361
|
+
<TwitterCardMeta page={page} />
|
|
362
|
+
<StructuredDataScript page={page} />
|
|
363
|
+
<PreloadLinks preload={page.meta?.preload} />
|
|
364
|
+
<DnsPrefetchLinks dnsPrefetch={page.meta?.dnsPrefetch} />
|
|
365
|
+
<HreflangLinks
|
|
366
|
+
page={page}
|
|
367
|
+
languages={languages}
|
|
368
|
+
/>
|
|
369
|
+
<AnalyticsHead analytics={page.meta?.analytics} />
|
|
370
|
+
<CustomElementsHead customElements={page.meta?.customElements} />
|
|
371
|
+
<FaviconLink favicon={page.meta?.favicon} />
|
|
372
|
+
<FaviconSetLinks favicons={normalizedFavicons} />
|
|
373
|
+
<CustomStylesheet stylesheet={page.meta?.stylesheet} />
|
|
374
|
+
<GoogleFonts googleFonts={page.meta?.googleFonts} />
|
|
375
|
+
<ThemeFonts theme={theme} />
|
|
376
|
+
<GlobalStyles directionStyles={directionStyles} />
|
|
377
|
+
<HeadScripts scripts={scripts} />
|
|
378
|
+
</>
|
|
379
|
+
)
|
|
380
|
+
}
|