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,136 @@
|
|
|
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 { readdir, mkdir } from 'node:fs/promises'
|
|
9
|
+
import { join, relative } from 'node:path'
|
|
10
|
+
import { Effect, Data } from 'effect'
|
|
11
|
+
import type { Dirent } from 'node:fs'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* File copy error - infrastructure layer error
|
|
15
|
+
*/
|
|
16
|
+
export class FileCopyError extends Data.TaggedError('FileCopyError')<{
|
|
17
|
+
readonly message: string
|
|
18
|
+
readonly cause?: unknown
|
|
19
|
+
}> {}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Copy a single file from source to destination using Bun's native file API
|
|
23
|
+
*/
|
|
24
|
+
const copyFile = (
|
|
25
|
+
sourcePath: string,
|
|
26
|
+
destPath: string
|
|
27
|
+
): Effect.Effect<string, FileCopyError, never> =>
|
|
28
|
+
Effect.gen(function* () {
|
|
29
|
+
// Read file using Bun's native file API (faster and more idiomatic)
|
|
30
|
+
// arrayBuffer() preserves binary content exactly
|
|
31
|
+
const content = yield* Effect.tryPromise({
|
|
32
|
+
try: () => Bun.file(sourcePath).arrayBuffer(),
|
|
33
|
+
catch: (error) =>
|
|
34
|
+
new FileCopyError({
|
|
35
|
+
message: `Failed to read file ${sourcePath}`,
|
|
36
|
+
cause: error,
|
|
37
|
+
}),
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
// Write file using Bun.write (faster than Node.js fs/promises)
|
|
41
|
+
// Bun.write handles ArrayBuffer, Buffer, string, etc.
|
|
42
|
+
yield* Effect.tryPromise({
|
|
43
|
+
try: () => Bun.write(destPath, content),
|
|
44
|
+
catch: (error) =>
|
|
45
|
+
new FileCopyError({
|
|
46
|
+
message: `Failed to write file ${destPath}`,
|
|
47
|
+
cause: error,
|
|
48
|
+
}),
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
return destPath
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Create a directory at the destination
|
|
56
|
+
*/
|
|
57
|
+
const createDirectory = (destPath: string): Effect.Effect<void, FileCopyError, never> =>
|
|
58
|
+
Effect.tryPromise({
|
|
59
|
+
try: () => mkdir(destPath, { recursive: true }),
|
|
60
|
+
catch: (error) =>
|
|
61
|
+
new FileCopyError({
|
|
62
|
+
message: `Failed to create directory ${destPath}`,
|
|
63
|
+
cause: error,
|
|
64
|
+
}),
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Read directory entries
|
|
69
|
+
*/
|
|
70
|
+
const readDirectoryEntries = (
|
|
71
|
+
sourcePath: string
|
|
72
|
+
): Effect.Effect<readonly Dirent[], FileCopyError, never> =>
|
|
73
|
+
Effect.tryPromise({
|
|
74
|
+
try: () => readdir(sourcePath, { withFileTypes: true }),
|
|
75
|
+
catch: (error) =>
|
|
76
|
+
new FileCopyError({
|
|
77
|
+
message: `Failed to read directory ${sourcePath}`,
|
|
78
|
+
cause: error,
|
|
79
|
+
}),
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Recursively copy directory contents from source to destination
|
|
84
|
+
*
|
|
85
|
+
* This function:
|
|
86
|
+
* 1. Preserves directory structure
|
|
87
|
+
* 2. Handles binary files correctly (no corruption)
|
|
88
|
+
* 3. Skips the assets/ directory in destination (reserved for CSS)
|
|
89
|
+
* 4. Returns list of copied file paths (relative to destination)
|
|
90
|
+
*
|
|
91
|
+
* @param source - Source directory path
|
|
92
|
+
* @param destination - Destination directory path
|
|
93
|
+
* @returns Effect with list of copied file paths (relative to destination)
|
|
94
|
+
*/
|
|
95
|
+
export const copyDirectory = (
|
|
96
|
+
source: string,
|
|
97
|
+
destination: string
|
|
98
|
+
): Effect.Effect<readonly string[], FileCopyError, never> => {
|
|
99
|
+
// Recursive copy function that returns list of copied files
|
|
100
|
+
const copyRecursive = (
|
|
101
|
+
sourcePath: string,
|
|
102
|
+
destPath: string
|
|
103
|
+
): Effect.Effect<readonly string[], FileCopyError, never> =>
|
|
104
|
+
Effect.gen(function* () {
|
|
105
|
+
const entries = yield* readDirectoryEntries(sourcePath)
|
|
106
|
+
|
|
107
|
+
// Process entries and collect copied files immutably
|
|
108
|
+
const copiedFiles = yield* Effect.forEach(
|
|
109
|
+
entries,
|
|
110
|
+
(entry) =>
|
|
111
|
+
Effect.gen(function* () {
|
|
112
|
+
const sourceEntryPath = join(sourcePath, entry.name)
|
|
113
|
+
const destEntryPath = join(destPath, entry.name)
|
|
114
|
+
|
|
115
|
+
if (entry.isDirectory()) {
|
|
116
|
+
yield* createDirectory(destEntryPath)
|
|
117
|
+
return yield* copyRecursive(sourceEntryPath, destEntryPath)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (entry.isFile()) {
|
|
121
|
+
yield* copyFile(sourceEntryPath, destEntryPath)
|
|
122
|
+
const relativePath = relative(destination, destEntryPath)
|
|
123
|
+
return [relativePath] as readonly string[]
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Not a directory or file (e.g., symlink) - skip
|
|
127
|
+
return [] as readonly string[]
|
|
128
|
+
}),
|
|
129
|
+
{ concurrency: 'unbounded' }
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
return copiedFiles.flat() as readonly string[]
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
return copyRecursive(source, destination)
|
|
136
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
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 { Layer } from 'effect'
|
|
9
|
+
import { createAuthLayer } from '@/infrastructure/auth/better-auth'
|
|
10
|
+
import { CSSCompilerLive } from '@/infrastructure/css/css-compiler-live'
|
|
11
|
+
import { DatabaseLive } from '@/infrastructure/database/drizzle/layer'
|
|
12
|
+
import { AuthRepositoryLive } from '@/infrastructure/database/repositories/auth-repository-live'
|
|
13
|
+
import { DevToolsLayerOptional } from '@/infrastructure/devtools'
|
|
14
|
+
import { PageRendererLive } from '@/infrastructure/layers/page-renderer-layer'
|
|
15
|
+
import { LoggerLive } from '@/infrastructure/logging/logger'
|
|
16
|
+
import { ServerFactoryLive } from '@/infrastructure/server/server-factory-live'
|
|
17
|
+
import { StaticSiteGeneratorLive } from '@/infrastructure/server/static-site-generator-live'
|
|
18
|
+
import type { Auth as AuthConfig } from '@/domain/models/app/auth'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Application layer composition
|
|
22
|
+
*
|
|
23
|
+
* Combines all live service implementations into a single Layer
|
|
24
|
+
* that can be provided to Application use cases.
|
|
25
|
+
*
|
|
26
|
+
* This is the production dependency wiring point - swap out
|
|
27
|
+
* individual layers here for testing or different environments.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* // In src/index.ts (production)
|
|
32
|
+
* const program = startServer(appConfig).pipe(
|
|
33
|
+
* Effect.provide(createAppLayer(appConfig.auth))
|
|
34
|
+
* )
|
|
35
|
+
*
|
|
36
|
+
* // In tests (with mocks)
|
|
37
|
+
* const TestLayer = Layer.mergeAll(MockServerFactory, MockPageRenderer)
|
|
38
|
+
* const program = startServer(appConfig).pipe(
|
|
39
|
+
* Effect.provide(TestLayer)
|
|
40
|
+
* )
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export const createAppLayer = (authConfig?: AuthConfig) =>
|
|
44
|
+
Layer.mergeAll(
|
|
45
|
+
createAuthLayer(authConfig),
|
|
46
|
+
DatabaseLive,
|
|
47
|
+
ServerFactoryLive,
|
|
48
|
+
PageRendererLive,
|
|
49
|
+
CSSCompilerLive,
|
|
50
|
+
StaticSiteGeneratorLive,
|
|
51
|
+
DevToolsLayerOptional,
|
|
52
|
+
LoggerLive,
|
|
53
|
+
AuthRepositoryLive
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Default application layer with default auth configuration
|
|
58
|
+
*
|
|
59
|
+
* @deprecated Use createAppLayer(authConfig) instead for app-specific configuration
|
|
60
|
+
*/
|
|
61
|
+
export const AppLayer = createAppLayer()
|
|
@@ -0,0 +1,41 @@
|
|
|
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 { Layer } from 'effect'
|
|
9
|
+
import { PageRenderer } from '@/application/ports/services/page-renderer'
|
|
10
|
+
import { renderErrorPage, renderNotFoundPage } from '@/presentation/rendering/render-error-pages'
|
|
11
|
+
import { renderHomePage, renderPage } from '@/presentation/rendering/render-homepage'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Live implementation of PageRenderer using React SSR
|
|
15
|
+
*
|
|
16
|
+
* This Layer provides production page rendering logic,
|
|
17
|
+
* wrapping the presentation layer rendering functions in an
|
|
18
|
+
* Effect Context service.
|
|
19
|
+
*
|
|
20
|
+
* Located in Infrastructure layer because Effect Layer "Live"
|
|
21
|
+
* implementations are adapters (ports/adapters pattern).
|
|
22
|
+
* Infrastructure adapters CAN depend on presentation utilities
|
|
23
|
+
* for rendering logic.
|
|
24
|
+
*
|
|
25
|
+
* The implementation uses Layer.succeed because all rendering
|
|
26
|
+
* functions are pure and synchronous (no async operations).
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* // Provide PageRendererLive to use cases
|
|
31
|
+
* const program = startServer(appConfig).pipe(
|
|
32
|
+
* Effect.provide(PageRendererLive)
|
|
33
|
+
* )
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export const PageRendererLive = Layer.succeed(PageRenderer, {
|
|
37
|
+
renderHome: renderHomePage,
|
|
38
|
+
renderPage,
|
|
39
|
+
renderNotFound: renderNotFoundPage,
|
|
40
|
+
renderError: renderErrorPage,
|
|
41
|
+
})
|
|
@@ -0,0 +1,8 @@
|
|
|
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
|
+
export { logError, logWarning, logInfo, logDebug, createModuleLogger } from './logger'
|
|
@@ -0,0 +1,204 @@
|
|
|
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 { Console, Context, Effect, Layer } from 'effect'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Logger service for application-wide logging
|
|
12
|
+
*
|
|
13
|
+
* Provides structured logging with log levels:
|
|
14
|
+
* - debug: Detailed diagnostic information
|
|
15
|
+
* - info: General informational messages
|
|
16
|
+
* - warn: Warning messages for potential issues
|
|
17
|
+
* - error: Error messages for failures
|
|
18
|
+
*/
|
|
19
|
+
export class Logger extends Context.Tag('Logger')<
|
|
20
|
+
Logger,
|
|
21
|
+
{
|
|
22
|
+
readonly debug: (message: string, ...args: unknown[]) => Effect.Effect<void>
|
|
23
|
+
readonly info: (message: string, ...args: unknown[]) => Effect.Effect<void>
|
|
24
|
+
readonly warn: (message: string, ...args: unknown[]) => Effect.Effect<void>
|
|
25
|
+
readonly error: (message: string, error?: unknown) => Effect.Effect<void>
|
|
26
|
+
}
|
|
27
|
+
>() {}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Format log message with timestamp
|
|
31
|
+
*/
|
|
32
|
+
const formatLogMessage = (level: string, message: string): string => {
|
|
33
|
+
const timestamp = new Date().toISOString()
|
|
34
|
+
return `[${timestamp}] [${level}] ${message}`
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Console-based logger implementation
|
|
39
|
+
*
|
|
40
|
+
* Uses Effect Console for output with structured formatting
|
|
41
|
+
* Format: [2025-01-15T10:30:00.000Z] [LEVEL] message
|
|
42
|
+
*
|
|
43
|
+
* Benefits of Effect Console over console.*:
|
|
44
|
+
* - Already Effect-based (no need to wrap in Effect.sync)
|
|
45
|
+
* - Testable via Console.setConsole for mocking
|
|
46
|
+
* - Integrates with Effect DevTools and tracing
|
|
47
|
+
* - Type-safe with better TypeScript inference
|
|
48
|
+
*/
|
|
49
|
+
export const LoggerLive = Layer.succeed(Logger, {
|
|
50
|
+
debug: (message, ...args) =>
|
|
51
|
+
Effect.gen(function* () {
|
|
52
|
+
if (process.env.LOG_LEVEL === 'debug' || process.env.NODE_ENV === 'development') {
|
|
53
|
+
yield* Console.debug(formatLogMessage('DEBUG', message), ...args)
|
|
54
|
+
}
|
|
55
|
+
}),
|
|
56
|
+
|
|
57
|
+
info: (message, ...args) => Console.log(formatLogMessage('INFO', message), ...args),
|
|
58
|
+
|
|
59
|
+
warn: (message, ...args) => Console.warn(formatLogMessage('WARN', message), ...args),
|
|
60
|
+
|
|
61
|
+
error: (message, error) =>
|
|
62
|
+
error
|
|
63
|
+
? Console.error(formatLogMessage('ERROR', message), error)
|
|
64
|
+
: Console.error(formatLogMessage('ERROR', message)),
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Silent logger for testing
|
|
69
|
+
*
|
|
70
|
+
* Discards all log messages
|
|
71
|
+
*/
|
|
72
|
+
export const LoggerSilent = Layer.succeed(Logger, {
|
|
73
|
+
debug: () => Effect.void,
|
|
74
|
+
info: () => Effect.void,
|
|
75
|
+
warn: () => Effect.void,
|
|
76
|
+
error: () => Effect.void,
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
// ============================================================================
|
|
80
|
+
// Convenience Functions for Non-Effect Contexts
|
|
81
|
+
// ============================================================================
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Convenience logging functions for non-Effect contexts
|
|
85
|
+
*
|
|
86
|
+
* These functions provide a bridge to use the Logger service from code that
|
|
87
|
+
* doesn't use Effect.gen (async callbacks, module initialization, etc.).
|
|
88
|
+
*
|
|
89
|
+
* Implementation: Thin wrapper around Logger service that automatically
|
|
90
|
+
* provides LoggerLive layer and runs the Effect synchronously.
|
|
91
|
+
*
|
|
92
|
+
* Why use this instead of console.*:
|
|
93
|
+
* 1. Consistent formatting with Logger service (timestamps, levels)
|
|
94
|
+
* 2. Single source of truth for logging implementation
|
|
95
|
+
* 3. Log levels for filtering (error, warning, info, debug)
|
|
96
|
+
* 4. Future-proof: easy to add log aggregation, tracing, etc.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```typescript
|
|
100
|
+
* import { logError, logWarning, logInfo } from '@/infrastructure/logging/logger'
|
|
101
|
+
*
|
|
102
|
+
* // In a non-Effect callback (e.g., Better Auth email handler)
|
|
103
|
+
* try {
|
|
104
|
+
* await sendEmail(...)
|
|
105
|
+
* } catch (error) {
|
|
106
|
+
* logError('[EMAIL] Failed to send email', error)
|
|
107
|
+
* }
|
|
108
|
+
* ```
|
|
109
|
+
*/
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Run a Logger service Effect synchronously with LoggerLive layer
|
|
113
|
+
*
|
|
114
|
+
* This is safe for logging effects as they don't require async operations
|
|
115
|
+
* and complete synchronously.
|
|
116
|
+
*/
|
|
117
|
+
const runLogEffect = <A>(effect: Effect.Effect<A, never, Logger>): void => {
|
|
118
|
+
// eslint-disable-next-line functional/no-expression-statements -- Side effect for logging
|
|
119
|
+
Effect.runSync(effect.pipe(Effect.provide(LoggerLive)))
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Log an error message with optional cause
|
|
124
|
+
*
|
|
125
|
+
* Use for unexpected errors, exceptions, and failure conditions.
|
|
126
|
+
*
|
|
127
|
+
* @param message - Error message (include context like [EMAIL], [AUTH])
|
|
128
|
+
* @param cause - Optional error cause for stack trace
|
|
129
|
+
*/
|
|
130
|
+
export const logError = (message: string, cause?: unknown): void => {
|
|
131
|
+
runLogEffect(
|
|
132
|
+
Effect.gen(function* () {
|
|
133
|
+
const logger = yield* Logger
|
|
134
|
+
yield* logger.error(message, cause)
|
|
135
|
+
})
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Log a warning message
|
|
141
|
+
*
|
|
142
|
+
* Use for non-critical issues that should be addressed but don't
|
|
143
|
+
* prevent operation (missing optional config, deprecated usage, etc.).
|
|
144
|
+
*
|
|
145
|
+
* @param message - Warning message
|
|
146
|
+
*/
|
|
147
|
+
export const logWarning = (message: string): void => {
|
|
148
|
+
runLogEffect(
|
|
149
|
+
Effect.gen(function* () {
|
|
150
|
+
const logger = yield* Logger
|
|
151
|
+
yield* logger.warn(message)
|
|
152
|
+
})
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Log an info message
|
|
158
|
+
*
|
|
159
|
+
* Use for notable events during normal operation (startup, config loaded, etc.).
|
|
160
|
+
*
|
|
161
|
+
* @param message - Info message
|
|
162
|
+
*/
|
|
163
|
+
export const logInfo = (message: string): void => {
|
|
164
|
+
runLogEffect(
|
|
165
|
+
Effect.gen(function* () {
|
|
166
|
+
const logger = yield* Logger
|
|
167
|
+
yield* logger.info(message)
|
|
168
|
+
})
|
|
169
|
+
)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Log a debug message
|
|
174
|
+
*
|
|
175
|
+
* Use for detailed debugging information (variable values, flow tracing).
|
|
176
|
+
* These are typically filtered out in production.
|
|
177
|
+
*
|
|
178
|
+
* @param message - Debug message
|
|
179
|
+
*/
|
|
180
|
+
export const logDebug = (message: string): void => {
|
|
181
|
+
runLogEffect(
|
|
182
|
+
Effect.gen(function* () {
|
|
183
|
+
const logger = yield* Logger
|
|
184
|
+
yield* logger.debug(message)
|
|
185
|
+
})
|
|
186
|
+
)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Create a logger with a fixed prefix for a specific module
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* ```typescript
|
|
194
|
+
* const emailLogger = createModuleLogger('EMAIL')
|
|
195
|
+
* emailLogger.error('Failed to send') // Logs: [EMAIL] Failed to send
|
|
196
|
+
* emailLogger.warn('SMTP not configured')
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
199
|
+
export const createModuleLogger = (module: string) => ({
|
|
200
|
+
error: (message: string, cause?: unknown) => logError(`[${module}] ${message}`, cause),
|
|
201
|
+
warning: (message: string) => logWarning(`[${module}] ${message}`),
|
|
202
|
+
info: (message: string) => logInfo(`[${module}] ${message}`),
|
|
203
|
+
debug: (message: string) => logDebug(`[${module}] ${message}`),
|
|
204
|
+
})
|
|
@@ -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
|
+
* File Loader - Infrastructure Layer
|
|
10
|
+
*
|
|
11
|
+
* File system I/O operations for loading schema files.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { detectFormat, getFileExtension, parseJsonContent, parseYamlContent } from '@/domain/utils'
|
|
15
|
+
import type { AppEncoded } from '@/domain/models/app'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Load and parse schema from a file (throws on error, no process.exit)
|
|
19
|
+
*
|
|
20
|
+
* @throws Error if file doesn't exist, format is unsupported, or parsing fails
|
|
21
|
+
*/
|
|
22
|
+
export const loadSchemaFromFile = async (filePath: string): Promise<AppEncoded> => {
|
|
23
|
+
const file = Bun.file(filePath)
|
|
24
|
+
const exists = await file.exists()
|
|
25
|
+
|
|
26
|
+
if (!exists) {
|
|
27
|
+
// eslint-disable-next-line functional/no-throw-statements
|
|
28
|
+
throw new Error(`File not found: ${filePath}`)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const format = detectFormat(filePath)
|
|
32
|
+
|
|
33
|
+
if (format === 'unsupported') {
|
|
34
|
+
const extension = getFileExtension(filePath)
|
|
35
|
+
// eslint-disable-next-line functional/no-throw-statements
|
|
36
|
+
throw new Error(`Unsupported file format: .${extension}. Supported: .json, .yaml, .yml`)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const content = await file.text()
|
|
40
|
+
|
|
41
|
+
return format === 'json' ? parseJsonContent(content) : parseYamlContent(content)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Check if a file exists
|
|
46
|
+
*/
|
|
47
|
+
export const fileExists = async (filePath: string): Promise<boolean> => Bun.file(filePath).exists()
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Read file content as text
|
|
51
|
+
*/
|
|
52
|
+
export const readFileContent = async (filePath: string): Promise<string> =>
|
|
53
|
+
Bun.file(filePath).text()
|
|
@@ -0,0 +1,15 @@
|
|
|
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
|
+
* Schema Infrastructure Module
|
|
10
|
+
*
|
|
11
|
+
* I/O operations for loading schemas from files and remote URLs.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export { loadSchemaFromFile, fileExists, readFileContent } from './file-loader'
|
|
15
|
+
export { fetchRemoteSchema } from './remote-loader'
|
|
@@ -0,0 +1,48 @@
|
|
|
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
|
+
* Remote Loader - Infrastructure Layer
|
|
10
|
+
*
|
|
11
|
+
* Network I/O operations for fetching schemas from remote URLs.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
detectFormatFromContentType,
|
|
16
|
+
detectFormatFromUrl,
|
|
17
|
+
parseSchemaContent,
|
|
18
|
+
} from '@/domain/utils'
|
|
19
|
+
import type { AppEncoded } from '@/domain/models/app'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Fetch and parse schema from a remote URL
|
|
23
|
+
*
|
|
24
|
+
* @throws Error if fetch fails or content cannot be parsed
|
|
25
|
+
*/
|
|
26
|
+
export const fetchRemoteSchema = async (url: string): Promise<AppEncoded> => {
|
|
27
|
+
try {
|
|
28
|
+
const response = await fetch(url)
|
|
29
|
+
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
// eslint-disable-next-line functional/no-throw-statements
|
|
32
|
+
throw new Error(`Failed to fetch schema from ${url}: HTTP ${response.status}`)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const contentType = response.headers.get('content-type') || ''
|
|
36
|
+
const content = await response.text()
|
|
37
|
+
|
|
38
|
+
// Try to detect format from Content-Type header, then URL extension
|
|
39
|
+
const format = detectFormatFromContentType(contentType) || detectFormatFromUrl(url)
|
|
40
|
+
|
|
41
|
+
return parseSchemaContent(content, format)
|
|
42
|
+
} catch (error) {
|
|
43
|
+
// eslint-disable-next-line functional/no-throw-statements
|
|
44
|
+
throw new Error(
|
|
45
|
+
`Failed to fetch or parse schema from ${url}: ${error instanceof Error ? error.message : String(error)}`
|
|
46
|
+
)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
* Server Infrastructure Module
|
|
10
|
+
*
|
|
11
|
+
* Provides HTTP server creation, lifecycle management, and factory implementations.
|
|
12
|
+
* Uses Hono web framework with graceful shutdown support.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* import { ServerFactoryLive } from '@/infrastructure/server'
|
|
17
|
+
*
|
|
18
|
+
* const program = startServer(config).pipe(
|
|
19
|
+
* Effect.provide(ServerFactoryLive)
|
|
20
|
+
* )
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
export { createServer } from './server'
|
|
25
|
+
export { withGracefulShutdown } from './lifecycle'
|
|
26
|
+
export { ServerFactoryLive } from './server-factory-live'
|
|
@@ -0,0 +1,87 @@
|
|
|
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 { detectLanguageFromHeader } from '@/infrastructure/utils/accept-language-parser'
|
|
9
|
+
import type { App } from '@/domain/models/app'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get array of supported language codes from app configuration
|
|
13
|
+
*
|
|
14
|
+
* @param app - Application configuration
|
|
15
|
+
* @returns Array of short language codes or empty array if languages not configured
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* getSupportedLanguageCodes(app) // => ['en', 'fr', 'es']
|
|
19
|
+
*/
|
|
20
|
+
export function getSupportedLanguageCodes(app: App): ReadonlyArray<string> {
|
|
21
|
+
return app.languages?.supported.map((l) => l.code) || []
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Extract and validate language code from URL path
|
|
26
|
+
*
|
|
27
|
+
* @param path - URL path (e.g., '/fr/', '/en/about')
|
|
28
|
+
* @param supportedLanguages - Array of supported short language codes
|
|
29
|
+
* @returns Short language code if valid, undefined otherwise
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* extractLanguageFromPath('/fr/', ['en', 'fr']) // => 'fr'
|
|
33
|
+
* extractLanguageFromPath('/fr/about', ['en', 'fr']) // => 'fr'
|
|
34
|
+
* extractLanguageFromPath('/invalid/', ['en', 'fr']) // => undefined
|
|
35
|
+
* extractLanguageFromPath('/', ['en', 'fr']) // => undefined
|
|
36
|
+
*/
|
|
37
|
+
export function extractLanguageFromPath(
|
|
38
|
+
path: string,
|
|
39
|
+
supportedLanguages: ReadonlyArray<string>
|
|
40
|
+
): string | undefined {
|
|
41
|
+
// Extract first path segment (e.g., '/fr-FR/about' => 'fr-FR')
|
|
42
|
+
const segments = path.split('/').filter(Boolean)
|
|
43
|
+
if (segments.length === 0) {
|
|
44
|
+
return undefined
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const potentialLang = segments[0]
|
|
48
|
+
if (!potentialLang) {
|
|
49
|
+
return undefined
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Validate against supported languages
|
|
53
|
+
return supportedLanguages.includes(potentialLang) ? potentialLang : undefined
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Detect language from Accept-Language header if browser detection is enabled
|
|
58
|
+
*
|
|
59
|
+
* @param app - Application configuration
|
|
60
|
+
* @param header - Accept-Language HTTP header value
|
|
61
|
+
* @returns Detected short language code or undefined
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* detectLanguageIfEnabled(app, 'fr-FR,fr;q=0.9,en;q=0.8') // => 'fr'
|
|
65
|
+
* detectLanguageIfEnabled(appWithDetectionDisabled, 'fr-FR') // => undefined
|
|
66
|
+
*/
|
|
67
|
+
export function detectLanguageIfEnabled(app: App, header: string | undefined): string | undefined {
|
|
68
|
+
if (app.languages?.detectBrowser === false) {
|
|
69
|
+
return undefined
|
|
70
|
+
}
|
|
71
|
+
return detectLanguageFromHeader(header, getSupportedLanguageCodes(app))
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Validate and extract language code from URL subdirectory path
|
|
76
|
+
*
|
|
77
|
+
* @param app - Application configuration
|
|
78
|
+
* @param path - URL path (e.g., '/fr/', '/en/about')
|
|
79
|
+
* @returns Short language code if valid subdirectory, undefined otherwise
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* validateLanguageSubdirectory(app, '/fr/') // => 'fr'
|
|
83
|
+
* validateLanguageSubdirectory(app, '/products/pricing') // => undefined
|
|
84
|
+
*/
|
|
85
|
+
export function validateLanguageSubdirectory(app: App, path: string): string | undefined {
|
|
86
|
+
return extractLanguageFromPath(path, getSupportedLanguageCodes(app))
|
|
87
|
+
}
|