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,162 @@
|
|
|
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 { sendEmail } from '../../email/email-service'
|
|
9
|
+
import { passwordResetEmail, emailVerificationEmail } from '../../email/templates'
|
|
10
|
+
import { logError } from '../../logging'
|
|
11
|
+
import type { Auth, AuthEmailTemplate } from '@/domain/models/app/auth'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Substitute variables in a template string
|
|
15
|
+
*
|
|
16
|
+
* Replaces $variable patterns with actual values from the context.
|
|
17
|
+
* Supported variables: $name, $url, $email, $organizationName, $inviterName
|
|
18
|
+
*/
|
|
19
|
+
const substituteVariables = (
|
|
20
|
+
template: string,
|
|
21
|
+
context: Readonly<{
|
|
22
|
+
name?: string
|
|
23
|
+
url: string
|
|
24
|
+
email: string
|
|
25
|
+
organizationName?: string
|
|
26
|
+
inviterName?: string
|
|
27
|
+
}>
|
|
28
|
+
): string => {
|
|
29
|
+
return template
|
|
30
|
+
.replace(/\$name/g, context.name ?? 'there')
|
|
31
|
+
.replace(/\$url/g, context.url)
|
|
32
|
+
.replace(/\$email/g, context.email)
|
|
33
|
+
.replace(/\$organizationName/g, context.organizationName ?? 'the organization')
|
|
34
|
+
.replace(/\$inviterName/g, context.inviterName ?? 'Someone')
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Email handler configuration for the factory
|
|
39
|
+
*/
|
|
40
|
+
type EmailHandlerConfig = Readonly<{
|
|
41
|
+
/** Email type for logging (e.g., 'password reset', 'verification') */
|
|
42
|
+
emailType: string
|
|
43
|
+
/** Function to build the action URL from base URL and token */
|
|
44
|
+
buildUrl: (url: string, token: string) => string
|
|
45
|
+
/** Function to generate the default template when no custom template is provided */
|
|
46
|
+
getDefaultTemplate: (params: Readonly<{ userName?: string; actionUrl: string }>) => Readonly<{
|
|
47
|
+
subject: string
|
|
48
|
+
html: string
|
|
49
|
+
text: string
|
|
50
|
+
}>
|
|
51
|
+
}>
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Generic email handler factory - eliminates duplication between email types
|
|
55
|
+
*
|
|
56
|
+
* Creates a Better Auth email callback that:
|
|
57
|
+
* 1. Builds the action URL using the provided strategy
|
|
58
|
+
* 2. Sends custom template if provided (with variable substitution)
|
|
59
|
+
* 3. Falls back to default template otherwise
|
|
60
|
+
* 4. Handles errors silently to prevent user enumeration
|
|
61
|
+
*/
|
|
62
|
+
const createEmailHandler = (config: EmailHandlerConfig, customTemplate?: AuthEmailTemplate) => {
|
|
63
|
+
return async ({
|
|
64
|
+
user,
|
|
65
|
+
url,
|
|
66
|
+
token,
|
|
67
|
+
}: Readonly<{
|
|
68
|
+
user: Readonly<{ email: string; name?: string }>
|
|
69
|
+
url: string
|
|
70
|
+
token: string
|
|
71
|
+
}>) => {
|
|
72
|
+
const actionUrl = config.buildUrl(url, token)
|
|
73
|
+
const context = { name: user.name, url: actionUrl, email: user.email }
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
// Custom template takes precedence - use it entirely (don't mix with defaults)
|
|
77
|
+
if (customTemplate?.subject) {
|
|
78
|
+
// eslint-disable-next-line functional/no-expression-statements -- Better Auth email callback requires side effect
|
|
79
|
+
await sendEmail({
|
|
80
|
+
to: user.email,
|
|
81
|
+
subject: substituteVariables(customTemplate.subject, context),
|
|
82
|
+
html: customTemplate.html ? substituteVariables(customTemplate.html, context) : undefined,
|
|
83
|
+
text: customTemplate.text ? substituteVariables(customTemplate.text, context) : undefined,
|
|
84
|
+
})
|
|
85
|
+
} else {
|
|
86
|
+
// Use default template
|
|
87
|
+
const defaultTemplate = config.getDefaultTemplate({
|
|
88
|
+
userName: user.name,
|
|
89
|
+
actionUrl,
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
// eslint-disable-next-line functional/no-expression-statements -- Better Auth email callback requires side effect
|
|
93
|
+
await sendEmail({
|
|
94
|
+
to: user.email,
|
|
95
|
+
subject: defaultTemplate.subject,
|
|
96
|
+
html: defaultTemplate.html,
|
|
97
|
+
text: defaultTemplate.text,
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
} catch (error) {
|
|
101
|
+
// Don't throw - silent failure prevents user enumeration attacks
|
|
102
|
+
logError(`[EMAIL] Failed to send ${config.emailType} email to ${user.email}`, error)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Create password reset email handler with optional custom templates
|
|
109
|
+
*/
|
|
110
|
+
const createPasswordResetEmailHandler = (customTemplate?: AuthEmailTemplate) =>
|
|
111
|
+
createEmailHandler(
|
|
112
|
+
{
|
|
113
|
+
emailType: 'password reset',
|
|
114
|
+
buildUrl: (url, token) => `${url}?token=${token}`,
|
|
115
|
+
getDefaultTemplate: ({ userName, actionUrl }) =>
|
|
116
|
+
passwordResetEmail({ userName, resetUrl: actionUrl, expiresIn: '1 hour' }),
|
|
117
|
+
},
|
|
118
|
+
customTemplate
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Create email verification handler with optional custom templates
|
|
123
|
+
*/
|
|
124
|
+
const createVerificationEmailHandler = (customTemplate?: AuthEmailTemplate) =>
|
|
125
|
+
createEmailHandler(
|
|
126
|
+
{
|
|
127
|
+
emailType: 'verification',
|
|
128
|
+
// Better Auth sometimes includes token in URL already
|
|
129
|
+
buildUrl: (url, token) => (url.includes('token=') ? url : `${url}?token=${token}`),
|
|
130
|
+
getDefaultTemplate: ({ userName, actionUrl }) =>
|
|
131
|
+
emailVerificationEmail({ userName, verifyUrl: actionUrl, expiresIn: '24 hours' }),
|
|
132
|
+
},
|
|
133
|
+
customTemplate
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Create magic link email handler with optional custom templates
|
|
138
|
+
*/
|
|
139
|
+
const createMagicLinkEmailHandler = (customTemplate?: AuthEmailTemplate) =>
|
|
140
|
+
createEmailHandler(
|
|
141
|
+
{
|
|
142
|
+
emailType: 'magic link',
|
|
143
|
+
buildUrl: (url, token) => `${url}?token=${token}`,
|
|
144
|
+
getDefaultTemplate: ({ userName, actionUrl }) => ({
|
|
145
|
+
subject: 'Sign in to your account',
|
|
146
|
+
html: `<p>Hi ${userName ?? 'there'},</p><p>Click here to sign in: <a href="${actionUrl}">Sign In</a></p><p>This link will expire in 10 minutes.</p>`,
|
|
147
|
+
text: `Hi ${userName ?? 'there'},\n\nClick here to sign in: ${actionUrl}\n\nThis link will expire in 10 minutes.`,
|
|
148
|
+
}),
|
|
149
|
+
},
|
|
150
|
+
customTemplate
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Create email handlers from auth configuration
|
|
155
|
+
*/
|
|
156
|
+
export const createEmailHandlers = (authConfig?: Auth) => {
|
|
157
|
+
return {
|
|
158
|
+
passwordReset: createPasswordResetEmailHandler(authConfig?.emailTemplates?.resetPassword),
|
|
159
|
+
verification: createVerificationEmailHandler(authConfig?.emailTemplates?.verification),
|
|
160
|
+
magicLink: createMagicLinkEmailHandler(authConfig?.emailTemplates?.magicLink),
|
|
161
|
+
}
|
|
162
|
+
}
|
|
@@ -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
|
+
/**
|
|
9
|
+
* Better Auth Module
|
|
10
|
+
*
|
|
11
|
+
* Provides authentication functionality using Better Auth library.
|
|
12
|
+
* Re-exports all auth-related services and types.
|
|
13
|
+
*/
|
|
14
|
+
export { auth } from './auth'
|
|
15
|
+
export { Auth, AuthLive, createAuthLayer } from './layer'
|
|
16
|
+
export { AuthError } from '../../errors/auth-error'
|
|
@@ -0,0 +1,97 @@
|
|
|
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 { Context, Effect, Layer } from 'effect'
|
|
9
|
+
import { AuthError } from '../../errors/auth-error'
|
|
10
|
+
import { createAuthInstance } from './auth'
|
|
11
|
+
import type { auth } from './auth'
|
|
12
|
+
import type { Auth as AuthConfig } from '@/domain/models/app/auth'
|
|
13
|
+
|
|
14
|
+
// Re-export AuthError for convenience
|
|
15
|
+
export { AuthError }
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Auth Effect Context
|
|
19
|
+
*
|
|
20
|
+
* Provides authentication service for dependency injection in Effect programs.
|
|
21
|
+
* Use this in Application layer to access authentication without direct imports.
|
|
22
|
+
*
|
|
23
|
+
* Implementation uses Better Auth library internally.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* const protectedProgram = Effect.gen(function* () {
|
|
28
|
+
* const authService = yield* Auth
|
|
29
|
+
* const session = yield* authService.requireSession(headers)
|
|
30
|
+
* return { userId: session.user.id, email: session.user.email }
|
|
31
|
+
* })
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export class Auth extends Context.Tag('Auth')<
|
|
35
|
+
Auth,
|
|
36
|
+
{
|
|
37
|
+
readonly api: ReturnType<typeof createAuthInstance>['api']
|
|
38
|
+
readonly handler: ReturnType<typeof createAuthInstance>['handler']
|
|
39
|
+
readonly getSession: (
|
|
40
|
+
headers: Headers
|
|
41
|
+
) => Effect.Effect<Awaited<ReturnType<typeof auth.api.getSession>>, AuthError>
|
|
42
|
+
readonly requireSession: (
|
|
43
|
+
headers: Headers
|
|
44
|
+
) => Effect.Effect<NonNullable<Awaited<ReturnType<typeof auth.api.getSession>>>, AuthError>
|
|
45
|
+
}
|
|
46
|
+
>() {}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Create an Auth Layer with a specific auth configuration
|
|
50
|
+
*
|
|
51
|
+
* This allows us to create an Auth layer with app-specific configuration
|
|
52
|
+
* (e.g., with admin plugin enabled) instead of using the default instance.
|
|
53
|
+
*
|
|
54
|
+
* @param authConfig - Optional auth configuration from app schema
|
|
55
|
+
* @returns Layer providing Auth service with the specified configuration
|
|
56
|
+
*/
|
|
57
|
+
export const createAuthLayer = (authConfig?: AuthConfig): Layer.Layer<Auth> => {
|
|
58
|
+
const authInstance = createAuthInstance(authConfig)
|
|
59
|
+
|
|
60
|
+
return Layer.succeed(
|
|
61
|
+
Auth,
|
|
62
|
+
Auth.of({
|
|
63
|
+
api: authInstance.api,
|
|
64
|
+
handler: authInstance.handler,
|
|
65
|
+
|
|
66
|
+
getSession: (headers) =>
|
|
67
|
+
Effect.tryPromise({
|
|
68
|
+
try: () => authInstance.api.getSession({ headers }),
|
|
69
|
+
catch: (error) => new AuthError(error),
|
|
70
|
+
}),
|
|
71
|
+
|
|
72
|
+
requireSession: (headers) =>
|
|
73
|
+
Effect.gen(function* () {
|
|
74
|
+
const session = yield* Effect.tryPromise({
|
|
75
|
+
try: () => authInstance.api.getSession({ headers }),
|
|
76
|
+
catch: (error) => new AuthError(error),
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
if (!session) {
|
|
80
|
+
return yield* Effect.fail(new AuthError('Unauthorized'))
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return session
|
|
84
|
+
}),
|
|
85
|
+
})
|
|
86
|
+
)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Live Auth Layer
|
|
91
|
+
*
|
|
92
|
+
* Provides the production authentication service with Effect-wrapped methods.
|
|
93
|
+
* Implementation uses Better Auth library internally with default configuration.
|
|
94
|
+
*
|
|
95
|
+
* @deprecated Use createAuthLayer(authConfig) instead for app-specific configuration
|
|
96
|
+
*/
|
|
97
|
+
export const AuthLive = createAuthLayer()
|
|
@@ -0,0 +1,56 @@
|
|
|
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 { admin } from 'better-auth/plugins'
|
|
9
|
+
import type { Auth } from '@/domain/models/app/auth'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Admin plugin configuration extracted from auth config
|
|
13
|
+
*/
|
|
14
|
+
export interface AdminPluginConfig {
|
|
15
|
+
readonly defaultRole: string
|
|
16
|
+
readonly firstUserAdmin: boolean
|
|
17
|
+
readonly impersonation: boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Parse admin plugin configuration from auth config
|
|
22
|
+
*
|
|
23
|
+
* Admin features are always enabled when auth is configured.
|
|
24
|
+
* Uses `defaultRole` from auth config (defaults to 'member').
|
|
25
|
+
*/
|
|
26
|
+
export const parseAdminConfig = (authConfig?: Auth): AdminPluginConfig | undefined => {
|
|
27
|
+
if (!authConfig) return undefined
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
defaultRole: authConfig.defaultRole ?? 'member',
|
|
31
|
+
firstUserAdmin: true,
|
|
32
|
+
impersonation: false,
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Build admin plugin if auth is configured
|
|
38
|
+
*
|
|
39
|
+
* The admin plugin provides:
|
|
40
|
+
* - User management (list, ban, unban, impersonate)
|
|
41
|
+
* - Role-based access control (admin, member, viewer roles)
|
|
42
|
+
*
|
|
43
|
+
* Admin features are always enabled when auth is configured — no separate toggle.
|
|
44
|
+
*/
|
|
45
|
+
export const buildAdminPlugin = (authConfig?: Auth) => {
|
|
46
|
+
const config = parseAdminConfig(authConfig)
|
|
47
|
+
if (!config) return []
|
|
48
|
+
|
|
49
|
+
return [
|
|
50
|
+
admin({
|
|
51
|
+
defaultRole: config.defaultRole,
|
|
52
|
+
adminRoles: ['admin'],
|
|
53
|
+
impersonationSessionDuration: config.impersonation ? 60 * 60 : undefined,
|
|
54
|
+
}),
|
|
55
|
+
]
|
|
56
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
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 { magicLink } from 'better-auth/plugins'
|
|
9
|
+
import { hasStrategy } from '@/domain/models/app/auth'
|
|
10
|
+
import type { Auth } from '@/domain/models/app/auth'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Build magic link plugin if enabled in auth configuration
|
|
14
|
+
*/
|
|
15
|
+
export const buildMagicLinkPlugin = (
|
|
16
|
+
sendMagicLink: (data: {
|
|
17
|
+
readonly user: { readonly email: string }
|
|
18
|
+
readonly url: string
|
|
19
|
+
readonly token: string
|
|
20
|
+
}) => Promise<void>,
|
|
21
|
+
authConfig?: Auth
|
|
22
|
+
) => {
|
|
23
|
+
return hasStrategy(authConfig, 'magicLink')
|
|
24
|
+
? [
|
|
25
|
+
magicLink({
|
|
26
|
+
sendMagicLink: async ({ email, token, url }) =>
|
|
27
|
+
sendMagicLink({ user: { email }, url, token }),
|
|
28
|
+
}),
|
|
29
|
+
]
|
|
30
|
+
: []
|
|
31
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
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 { twoFactor } from 'better-auth/plugins'
|
|
9
|
+
import type { Auth } from '@/domain/models/app/auth'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Build two-factor plugin if enabled in auth configuration
|
|
13
|
+
*
|
|
14
|
+
* NOTE: modelName option removed - drizzleSchema in auth.ts uses standard model names
|
|
15
|
+
* and Drizzle pgTable() definitions specify actual database table names
|
|
16
|
+
*/
|
|
17
|
+
export const buildTwoFactorPlugin = (authConfig?: Auth) => {
|
|
18
|
+
return authConfig?.twoFactor ? [twoFactor()] : []
|
|
19
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
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 { relations } from 'drizzle-orm'
|
|
9
|
+
import { text, boolean, timestamp, pgSchema, index } from 'drizzle-orm/pg-core'
|
|
10
|
+
|
|
11
|
+
// Better Auth schema - isolated from main app schema
|
|
12
|
+
// All auth tables are created in the "auth" PostgreSQL schema
|
|
13
|
+
export const authSchema = pgSchema('auth')
|
|
14
|
+
|
|
15
|
+
// Better Auth Tables (using native table names in dedicated auth schema)
|
|
16
|
+
// Schema isolation prevents conflicts when users create their own tables
|
|
17
|
+
export const users = authSchema.table('user', {
|
|
18
|
+
id: text('id').primaryKey(),
|
|
19
|
+
name: text('name').notNull(),
|
|
20
|
+
email: text('email').notNull().unique(),
|
|
21
|
+
emailVerified: boolean('email_verified').notNull().default(false),
|
|
22
|
+
image: text('image'),
|
|
23
|
+
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
|
24
|
+
updatedAt: timestamp('updated_at', { withTimezone: true })
|
|
25
|
+
.notNull()
|
|
26
|
+
.defaultNow()
|
|
27
|
+
.$onUpdate(() => new Date()),
|
|
28
|
+
// Admin plugin fields
|
|
29
|
+
role: text('role'),
|
|
30
|
+
banned: boolean('banned').default(false),
|
|
31
|
+
banReason: text('ban_reason'),
|
|
32
|
+
banExpires: timestamp('ban_expires', { withTimezone: true }),
|
|
33
|
+
// Two-factor plugin fields
|
|
34
|
+
twoFactorEnabled: boolean('two_factor_enabled').default(false),
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
export const sessions = authSchema.table(
|
|
38
|
+
'session',
|
|
39
|
+
{
|
|
40
|
+
id: text('id').primaryKey(),
|
|
41
|
+
expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),
|
|
42
|
+
token: text('token').notNull().unique(),
|
|
43
|
+
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
|
44
|
+
updatedAt: timestamp('updated_at', { withTimezone: true })
|
|
45
|
+
.notNull()
|
|
46
|
+
.defaultNow()
|
|
47
|
+
.$onUpdate(() => new Date()),
|
|
48
|
+
ipAddress: text('ip_address'),
|
|
49
|
+
userAgent: text('user_agent'),
|
|
50
|
+
userId: text('user_id')
|
|
51
|
+
.notNull()
|
|
52
|
+
.references(() => users.id, { onDelete: 'cascade' }),
|
|
53
|
+
// Admin plugin fields
|
|
54
|
+
impersonatedBy: text('impersonated_by'),
|
|
55
|
+
},
|
|
56
|
+
(table) => [index('session_userId_idx').on(table.userId)]
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
export const accounts = authSchema.table(
|
|
60
|
+
'account',
|
|
61
|
+
{
|
|
62
|
+
id: text('id').primaryKey(),
|
|
63
|
+
accountId: text('account_id').notNull(),
|
|
64
|
+
providerId: text('provider_id').notNull(),
|
|
65
|
+
userId: text('user_id')
|
|
66
|
+
.notNull()
|
|
67
|
+
.references(() => users.id, { onDelete: 'cascade' }),
|
|
68
|
+
accessToken: text('access_token'),
|
|
69
|
+
refreshToken: text('refresh_token'),
|
|
70
|
+
idToken: text('id_token'),
|
|
71
|
+
accessTokenExpiresAt: timestamp('access_token_expires_at', { withTimezone: true }),
|
|
72
|
+
refreshTokenExpiresAt: timestamp('refresh_token_expires_at', { withTimezone: true }),
|
|
73
|
+
scope: text('scope'),
|
|
74
|
+
password: text('password'),
|
|
75
|
+
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
|
|
76
|
+
updatedAt: timestamp('updated_at', { withTimezone: true })
|
|
77
|
+
.notNull()
|
|
78
|
+
.defaultNow()
|
|
79
|
+
.$onUpdate(() => new Date()),
|
|
80
|
+
},
|
|
81
|
+
(table) => [index('account_userId_idx').on(table.userId)]
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
export const verifications = authSchema.table(
|
|
85
|
+
'verification',
|
|
86
|
+
{
|
|
87
|
+
id: text('id').primaryKey(),
|
|
88
|
+
identifier: text('identifier').notNull(),
|
|
89
|
+
value: text('value').notNull(),
|
|
90
|
+
expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),
|
|
91
|
+
createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(),
|
|
92
|
+
updatedAt: timestamp('updated_at', { withTimezone: true })
|
|
93
|
+
.defaultNow()
|
|
94
|
+
.notNull()
|
|
95
|
+
.$onUpdate(() => new Date()),
|
|
96
|
+
},
|
|
97
|
+
(table) => [index('verification_identifier_idx').on(table.identifier)]
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
// Two-factor plugin table
|
|
101
|
+
export const twoFactors = authSchema.table(
|
|
102
|
+
'two_factor',
|
|
103
|
+
{
|
|
104
|
+
id: text('id').primaryKey(),
|
|
105
|
+
userId: text('user_id')
|
|
106
|
+
.notNull()
|
|
107
|
+
.references(() => users.id, { onDelete: 'cascade' }),
|
|
108
|
+
secret: text('secret').notNull(),
|
|
109
|
+
backupCodes: text('backup_codes').notNull(),
|
|
110
|
+
},
|
|
111
|
+
(table) => [
|
|
112
|
+
index('twoFactor_secret_idx').on(table.secret),
|
|
113
|
+
index('twoFactor_userId_idx').on(table.userId),
|
|
114
|
+
]
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
// Drizzle Relations (for type-safe joins)
|
|
118
|
+
export const usersRelations = relations(users, ({ many }) => ({
|
|
119
|
+
sessions: many(sessions),
|
|
120
|
+
accounts: many(accounts),
|
|
121
|
+
twoFactors: many(twoFactors),
|
|
122
|
+
}))
|
|
123
|
+
|
|
124
|
+
export const sessionsRelations = relations(sessions, ({ one }) => ({
|
|
125
|
+
user: one(users, {
|
|
126
|
+
fields: [sessions.userId],
|
|
127
|
+
references: [users.id],
|
|
128
|
+
}),
|
|
129
|
+
}))
|
|
130
|
+
|
|
131
|
+
export const accountsRelations = relations(accounts, ({ one }) => ({
|
|
132
|
+
user: one(users, {
|
|
133
|
+
fields: [accounts.userId],
|
|
134
|
+
references: [users.id],
|
|
135
|
+
}),
|
|
136
|
+
}))
|
|
137
|
+
|
|
138
|
+
export const twoFactorsRelations = relations(twoFactors, ({ one }) => ({
|
|
139
|
+
user: one(users, {
|
|
140
|
+
fields: [twoFactors.userId],
|
|
141
|
+
references: [users.id],
|
|
142
|
+
}),
|
|
143
|
+
}))
|
|
144
|
+
|
|
145
|
+
// Type inference
|
|
146
|
+
export type User = typeof users.$inferSelect
|
|
147
|
+
export type NewUser = typeof users.$inferInsert
|
|
148
|
+
export type Session = typeof sessions.$inferSelect
|
|
149
|
+
export type Account = typeof accounts.$inferSelect
|
|
150
|
+
export type Verification = typeof verifications.$inferSelect
|
|
151
|
+
export type TwoFactor = typeof twoFactors.$inferSelect
|
|
152
|
+
export type NewTwoFactor = typeof twoFactors.$inferInsert
|
|
@@ -0,0 +1,27 @@
|
|
|
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
|
+
* Authentication Infrastructure Module
|
|
10
|
+
*
|
|
11
|
+
* Provides authentication services and utilities.
|
|
12
|
+
* Currently uses Better Auth for authentication.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* import { Auth, AuthLive } from '@/infrastructure/auth'
|
|
17
|
+
*
|
|
18
|
+
* const program = Effect.gen(function* () {
|
|
19
|
+
* const auth = yield* Auth
|
|
20
|
+
* const session = yield* auth.requireSession(headers)
|
|
21
|
+
* return session.user
|
|
22
|
+
* }).pipe(Effect.provide(AuthLive))
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
export { auth } from './better-auth/auth'
|
|
27
|
+
export { Auth, AuthLive, AuthError } from './better-auth/layer'
|
|
@@ -0,0 +1,130 @@
|
|
|
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 { Effect, Ref, pipe } from 'effect'
|
|
9
|
+
import type { Theme } from '@/domain/models/app/theme'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Compiled CSS result with metadata
|
|
13
|
+
*/
|
|
14
|
+
export interface CompiledCSS {
|
|
15
|
+
readonly css: string
|
|
16
|
+
readonly timestamp: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* In-memory cache for compiled CSS using Effect.Ref
|
|
21
|
+
* Stores multiple themes keyed by normalized theme hash
|
|
22
|
+
* Avoids recompiling on every request for better performance
|
|
23
|
+
* Uses functional state management to avoid mutations
|
|
24
|
+
*/
|
|
25
|
+
const cssCache = Ref.unsafeMake<Map<string, CompiledCSS>>(new Map())
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Recursively sort object keys for consistent JSON serialization
|
|
29
|
+
* This ensures the same theme always produces the same cache key
|
|
30
|
+
* regardless of property insertion order
|
|
31
|
+
*/
|
|
32
|
+
const sortObjectKeys = (obj: unknown): unknown => {
|
|
33
|
+
if (obj === null || typeof obj !== 'object') {
|
|
34
|
+
return obj
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (Array.isArray(obj)) {
|
|
38
|
+
return obj.map(sortObjectKeys)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const record = obj as Record<string, unknown>
|
|
42
|
+
const sortedKeys = Object.keys(record).toSorted()
|
|
43
|
+
|
|
44
|
+
return sortedKeys.reduce<Record<string, unknown>>(
|
|
45
|
+
(acc, key) => ({ ...acc, [key]: sortObjectKeys(record[key]) }),
|
|
46
|
+
{}
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Normalize theme for consistent cache key generation
|
|
52
|
+
* Sorts object keys recursively to ensure property order independence
|
|
53
|
+
*
|
|
54
|
+
* @param theme - Optional theme configuration
|
|
55
|
+
* @returns Normalized theme (or undefined if no theme)
|
|
56
|
+
*/
|
|
57
|
+
export const normalizeTheme = (theme?: Theme): Theme | undefined => {
|
|
58
|
+
if (!theme) return undefined
|
|
59
|
+
return sortObjectKeys(theme) as Theme
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Create theme cache key from app theme
|
|
64
|
+
* Returns consistent hash for same theme content regardless of property order
|
|
65
|
+
*
|
|
66
|
+
* @param theme - Optional theme configuration
|
|
67
|
+
* @returns Cache key string (JSON stringified normalized theme)
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* // These produce the same cache key:
|
|
71
|
+
* getThemeCacheKey({ colors: { primary: '#ff5733' }, fonts: { sans: 'Inter' } })
|
|
72
|
+
* getThemeCacheKey({ fonts: { sans: 'Inter' }, colors: { primary: '#ff5733' } })
|
|
73
|
+
*/
|
|
74
|
+
export const getThemeCacheKey = (theme?: Theme): string => {
|
|
75
|
+
const normalized = normalizeTheme(theme)
|
|
76
|
+
return JSON.stringify(normalized ?? {})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Get cached CSS if available
|
|
81
|
+
*
|
|
82
|
+
* @param cacheKey - Cache key for the theme
|
|
83
|
+
* @returns Effect that yields cached CSS or undefined
|
|
84
|
+
*/
|
|
85
|
+
export const getCachedCSS = (cacheKey: string): Effect.Effect<CompiledCSS | undefined, never> =>
|
|
86
|
+
pipe(
|
|
87
|
+
Ref.get(cssCache),
|
|
88
|
+
Effect.map((cache) => cache.get(cacheKey))
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Store compiled CSS in cache
|
|
93
|
+
*
|
|
94
|
+
* @param cacheKey - Cache key for the theme
|
|
95
|
+
* @param compiled - Compiled CSS result
|
|
96
|
+
* @returns Effect that updates the cache
|
|
97
|
+
*/
|
|
98
|
+
export const setCachedCSS = (cacheKey: string, compiled: CompiledCSS): Effect.Effect<void, never> =>
|
|
99
|
+
Ref.update(cssCache, (currentCache) => new Map([...currentCache, [cacheKey, compiled]]))
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Clear all cached CSS (useful for testing or hot reload)
|
|
103
|
+
*
|
|
104
|
+
* @returns Effect that clears the cache
|
|
105
|
+
*/
|
|
106
|
+
export const clearCSSCache = (): Effect.Effect<void, never> => Ref.set(cssCache, new Map())
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get or compute cached CSS
|
|
110
|
+
* This is a convenience function that combines getCachedCSS and setCachedCSS
|
|
111
|
+
* with a computation function for cleaner usage
|
|
112
|
+
*
|
|
113
|
+
* @param cacheKey - Cache key for the theme
|
|
114
|
+
* @param compute - Effect that computes the CSS if not cached
|
|
115
|
+
* @returns Effect that yields cached or newly computed CSS
|
|
116
|
+
*/
|
|
117
|
+
export const getOrComputeCachedCSS = <E>(
|
|
118
|
+
cacheKey: string,
|
|
119
|
+
compute: Effect.Effect<CompiledCSS, E>
|
|
120
|
+
): Effect.Effect<CompiledCSS, E> =>
|
|
121
|
+
Effect.gen(function* () {
|
|
122
|
+
const cached = yield* getCachedCSS(cacheKey)
|
|
123
|
+
if (cached !== undefined) {
|
|
124
|
+
return cached
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const compiled = yield* compute
|
|
128
|
+
yield* setCachedCSS(cacheKey, compiled)
|
|
129
|
+
return compiled
|
|
130
|
+
})
|