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,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
|
+
}
|