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,598 @@
|
|
|
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 { SQL } from 'bun'
|
|
9
|
+
import { Config, Effect, Console, Data, Runtime, type ConfigError } from 'effect'
|
|
10
|
+
import { AuthConfigRequiredForUserFields } from '@/infrastructure/errors/auth-config-required-error'
|
|
11
|
+
import { SchemaInitializationError } from '@/infrastructure/errors/schema-initialization-error'
|
|
12
|
+
import { logInfo } from '@/infrastructure/logging/logger'
|
|
13
|
+
import {
|
|
14
|
+
needsUsersTable,
|
|
15
|
+
needsUpdatedByTrigger,
|
|
16
|
+
ensureBetterAuthUsersTable,
|
|
17
|
+
ensureUpdatedByTriggerFunction,
|
|
18
|
+
type BetterAuthUsersTableRequired,
|
|
19
|
+
} from '../auth/auth-validation'
|
|
20
|
+
import { sanitizeTableName, isManyToManyRelationship } from '../field-utils'
|
|
21
|
+
import * as lookupViewGenerators from '../lookup/lookup-view-generators'
|
|
22
|
+
import {
|
|
23
|
+
getPreviousSchema,
|
|
24
|
+
logRollbackOperation,
|
|
25
|
+
recordMigration,
|
|
26
|
+
storeSchemaChecksum,
|
|
27
|
+
generateSchemaChecksum,
|
|
28
|
+
validateStoredChecksum,
|
|
29
|
+
} from '../migration-audit-trail'
|
|
30
|
+
import {
|
|
31
|
+
dropObsoleteTables,
|
|
32
|
+
renameTablesIfNeeded,
|
|
33
|
+
syncForeignKeyConstraints,
|
|
34
|
+
} from '../schema-migration-helpers'
|
|
35
|
+
import {
|
|
36
|
+
tableExists,
|
|
37
|
+
executeSQL,
|
|
38
|
+
type SQLExecutionError,
|
|
39
|
+
type TransactionLike,
|
|
40
|
+
} from '../sql/sql-execution'
|
|
41
|
+
import { generateJunctionTableDDL, generateJunctionTableName } from '../sql/sql-generators'
|
|
42
|
+
import {
|
|
43
|
+
createOrMigrateTableEffect,
|
|
44
|
+
createLookupViewsEffect,
|
|
45
|
+
createTableViewsEffect,
|
|
46
|
+
} from '../table-operations'
|
|
47
|
+
import * as viewGenerators from '../views/view-generators'
|
|
48
|
+
import {
|
|
49
|
+
detectCircularDependenciesWithOptionalFK,
|
|
50
|
+
sortTablesByDependencies,
|
|
51
|
+
} from './schema-dependency-sorting'
|
|
52
|
+
import type { App } from '@/domain/models/app'
|
|
53
|
+
import type { Table } from '@/domain/models/app/table'
|
|
54
|
+
|
|
55
|
+
// Re-export error types for convenience
|
|
56
|
+
export { AuthConfigRequiredForUserFields } from '@/infrastructure/errors/auth-config-required-error'
|
|
57
|
+
export { SchemaInitializationError } from '@/infrastructure/errors/schema-initialization-error'
|
|
58
|
+
export { BetterAuthUsersTableRequired } from '../auth/auth-validation'
|
|
59
|
+
|
|
60
|
+
export class NoDatabaseUrlError extends Data.TaggedError('NoDatabaseUrlError')<{
|
|
61
|
+
readonly message: string
|
|
62
|
+
}> {}
|
|
63
|
+
|
|
64
|
+
/** Ensure Better Auth prerequisites exist (users table + updated-by trigger) */
|
|
65
|
+
const ensureAuthPrerequisites = (
|
|
66
|
+
tx: TransactionLike,
|
|
67
|
+
tables: readonly Table[],
|
|
68
|
+
hasAuthConfig: boolean
|
|
69
|
+
): Effect.Effect<void, never, never> =>
|
|
70
|
+
Effect.gen(function* () {
|
|
71
|
+
logInfo('[executeSchemaInit] Checking if Better Auth users table is needed...')
|
|
72
|
+
const needs = needsUsersTable(tables)
|
|
73
|
+
logInfo(`[executeSchemaInit] needsUsersTable: ${needs}`)
|
|
74
|
+
logInfo(`[executeSchemaInit] hasAuthConfig: ${hasAuthConfig}`)
|
|
75
|
+
|
|
76
|
+
// Only enforce users table existence if auth is configured
|
|
77
|
+
// If auth is NOT configured, authorship fields will be NULL
|
|
78
|
+
if (needs && hasAuthConfig) {
|
|
79
|
+
logInfo('[executeSchemaInit] Better Auth users table is needed, verifying it exists...')
|
|
80
|
+
yield* Effect.promise(() => ensureBetterAuthUsersTable(tx))
|
|
81
|
+
} else if (needs && !hasAuthConfig) {
|
|
82
|
+
logInfo(
|
|
83
|
+
'[executeSchemaInit] User fields present but auth not configured - fields will be NULL'
|
|
84
|
+
)
|
|
85
|
+
} else {
|
|
86
|
+
logInfo('[executeSchemaInit] Better Auth users table not needed')
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (needsUpdatedByTrigger(tables)) {
|
|
90
|
+
yield* Effect.promise(() => ensureUpdatedByTriggerFunction(tx))
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
/** Build map of which tables use VIEWs (have lookup fields) */
|
|
95
|
+
const buildTableUsesViewMap = (
|
|
96
|
+
tables: readonly Table[],
|
|
97
|
+
lookupViewModule: typeof lookupViewGenerators
|
|
98
|
+
): ReadonlyMap<string, boolean> =>
|
|
99
|
+
new Map(tables.map((table) => [table.name, lookupViewModule.shouldUseView(table)]))
|
|
100
|
+
|
|
101
|
+
// Configuration for createMigrateTables
|
|
102
|
+
type CreateMigrateTablesConfig = {
|
|
103
|
+
readonly tx: TransactionLike
|
|
104
|
+
readonly sortedTables: readonly Table[]
|
|
105
|
+
readonly tableUsesView: ReadonlyMap<string, boolean>
|
|
106
|
+
readonly circularTables: ReadonlySet<string>
|
|
107
|
+
readonly previousSchema: { readonly tables: readonly object[] } | undefined
|
|
108
|
+
readonly lookupViewModule: typeof lookupViewGenerators
|
|
109
|
+
readonly hasAuthConfig: boolean
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Create or migrate each table in sorted order */
|
|
113
|
+
const createMigrateTables = (
|
|
114
|
+
config: CreateMigrateTablesConfig
|
|
115
|
+
): Effect.Effect<void, SQLExecutionError, never> =>
|
|
116
|
+
Effect.gen(function* () {
|
|
117
|
+
const {
|
|
118
|
+
tx,
|
|
119
|
+
sortedTables,
|
|
120
|
+
tableUsesView,
|
|
121
|
+
circularTables,
|
|
122
|
+
previousSchema,
|
|
123
|
+
lookupViewModule,
|
|
124
|
+
hasAuthConfig,
|
|
125
|
+
} = config
|
|
126
|
+
/* eslint-disable functional/no-loop-statements */
|
|
127
|
+
for (const table of sortedTables) {
|
|
128
|
+
const sanitized = sanitizeTableName(table.name)
|
|
129
|
+
const physicalTableName = lookupViewModule.shouldUseView(table)
|
|
130
|
+
? lookupViewModule.getBaseTableName(sanitized)
|
|
131
|
+
: sanitized
|
|
132
|
+
const exists = yield* tableExists(tx, physicalTableName)
|
|
133
|
+
logInfo(`[Creating/migrating table] ${table.name} (exists: ${exists})`)
|
|
134
|
+
yield* createOrMigrateTableEffect({
|
|
135
|
+
tx,
|
|
136
|
+
table,
|
|
137
|
+
exists,
|
|
138
|
+
tableUsesView,
|
|
139
|
+
previousSchema,
|
|
140
|
+
skipForeignKeys: circularTables.has(table.name),
|
|
141
|
+
hasAuthConfig,
|
|
142
|
+
})
|
|
143
|
+
logInfo(`[Created/migrated table] ${table.name}`)
|
|
144
|
+
}
|
|
145
|
+
/* eslint-enable functional/no-loop-statements */
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
/** Add foreign key constraints for tables with circular dependencies */
|
|
149
|
+
const addCircularFKConstraints = (
|
|
150
|
+
tx: TransactionLike,
|
|
151
|
+
sortedTables: readonly Table[],
|
|
152
|
+
circularTables: ReadonlySet<string>,
|
|
153
|
+
tableUsesView: ReadonlyMap<string, boolean>
|
|
154
|
+
): Effect.Effect<void, SQLExecutionError, never> =>
|
|
155
|
+
Effect.gen(function* () {
|
|
156
|
+
if (circularTables.size === 0) return
|
|
157
|
+
logInfo(`[Adding FK constraints for circular dependencies]`)
|
|
158
|
+
/* eslint-disable functional/no-loop-statements */
|
|
159
|
+
for (const table of sortedTables.filter((t) => circularTables.has(t.name))) {
|
|
160
|
+
yield* syncForeignKeyConstraints(tx, table, tableUsesView)
|
|
161
|
+
logInfo(`[Added FK constraints] ${table.name}`)
|
|
162
|
+
}
|
|
163
|
+
/* eslint-enable functional/no-loop-statements */
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
/** Collect junction table specs for many-to-many relationships (functional construction with deduplication) */
|
|
167
|
+
const collectJunctionTableSpecs = (
|
|
168
|
+
sortedTables: readonly Table[],
|
|
169
|
+
tableUsesView: ReadonlyMap<string, boolean>
|
|
170
|
+
): ReadonlyMap<string, { readonly name: string; readonly ddl: string }> => {
|
|
171
|
+
const junctionSpecs = sortedTables.flatMap((table) => {
|
|
172
|
+
const manyToManyFields = table.fields.filter(isManyToManyRelationship)
|
|
173
|
+
return manyToManyFields.map((field) => {
|
|
174
|
+
const junctionTableName = generateJunctionTableName(table.name, field.relatedTable)
|
|
175
|
+
const ddl = generateJunctionTableDDL(table.name, field.relatedTable, tableUsesView)
|
|
176
|
+
return [junctionTableName, { name: junctionTableName, ddl }] as const
|
|
177
|
+
})
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
// Deduplicate by junction table name (keep first occurrence)
|
|
181
|
+
return new Map(junctionSpecs)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Create junction tables for many-to-many relationships */
|
|
185
|
+
const createJunctionTables = (
|
|
186
|
+
tx: TransactionLike,
|
|
187
|
+
junctionTableSpecs: ReadonlyMap<string, { readonly name: string; readonly ddl: string }>
|
|
188
|
+
): Effect.Effect<void, SQLExecutionError, never> =>
|
|
189
|
+
Effect.gen(function* () {
|
|
190
|
+
if (junctionTableSpecs.size === 0) return
|
|
191
|
+
logInfo(`[Creating junction tables] ${Array.from(junctionTableSpecs.keys()).join(', ')}`)
|
|
192
|
+
yield* Effect.all(
|
|
193
|
+
Array.from(junctionTableSpecs.values()).map((spec) =>
|
|
194
|
+
executeSQL(tx, spec.ddl).pipe(
|
|
195
|
+
Effect.tap(() => logInfo(`[Created junction table] ${spec.name}`))
|
|
196
|
+
)
|
|
197
|
+
),
|
|
198
|
+
{ concurrency: 'unbounded' }
|
|
199
|
+
)
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
/** Drop obsolete views and create all views (lookup + user-defined) */
|
|
203
|
+
const createAllViews = (
|
|
204
|
+
tx: TransactionLike,
|
|
205
|
+
sortedTables: readonly Table[],
|
|
206
|
+
viewGeneratorsModule: typeof viewGenerators
|
|
207
|
+
): Effect.Effect<void, SQLExecutionError, never> =>
|
|
208
|
+
Effect.gen(function* () {
|
|
209
|
+
yield* Effect.promise(() => viewGeneratorsModule.dropAllObsoleteViews(tx, sortedTables))
|
|
210
|
+
yield* Effect.all(
|
|
211
|
+
sortedTables.map((table) => createLookupViewsEffect(tx, table)),
|
|
212
|
+
{ concurrency: 'unbounded' }
|
|
213
|
+
)
|
|
214
|
+
yield* Effect.all(
|
|
215
|
+
sortedTables.map((table) => createTableViewsEffect(tx, table)),
|
|
216
|
+
{ concurrency: 'unbounded' }
|
|
217
|
+
)
|
|
218
|
+
})
|
|
219
|
+
|
|
220
|
+
/** Execute all migration steps within a transaction */
|
|
221
|
+
const executeMigrationSteps = (
|
|
222
|
+
tx: TransactionLike,
|
|
223
|
+
tables: readonly Table[],
|
|
224
|
+
app: App
|
|
225
|
+
): Effect.Effect<void, SQLExecutionError, never> =>
|
|
226
|
+
Effect.gen(function* () {
|
|
227
|
+
// Step 0: Validate stored checksum to detect tampering
|
|
228
|
+
yield* validateStoredChecksum(tx)
|
|
229
|
+
|
|
230
|
+
// Steps 1-2: Ensure Better Auth prerequisites
|
|
231
|
+
yield* ensureAuthPrerequisites(tx, tables, !!app.auth)
|
|
232
|
+
|
|
233
|
+
// Step 3: Load previous schema for field rename detection
|
|
234
|
+
const previousSchema = yield* getPreviousSchema(tx)
|
|
235
|
+
|
|
236
|
+
// Step 3.5: Rename tables that have changed names
|
|
237
|
+
yield* renameTablesIfNeeded(tx, tables, previousSchema)
|
|
238
|
+
|
|
239
|
+
// Step 4: Drop tables that exist in database but not in schema
|
|
240
|
+
yield* dropObsoleteTables(tx, tables)
|
|
241
|
+
|
|
242
|
+
// Step 5: Build view map and detect circular dependencies
|
|
243
|
+
const tableUsesView = buildTableUsesViewMap(tables, lookupViewGenerators)
|
|
244
|
+
const circularTables = detectCircularDependenciesWithOptionalFK(tables)
|
|
245
|
+
if (circularTables.size > 0) {
|
|
246
|
+
logInfo(`[Circular dependencies detected] ${Array.from(circularTables).join(', ')}`)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Sort and log table creation order
|
|
250
|
+
const sortedTables = sortTablesByDependencies(tables)
|
|
251
|
+
logInfo(`[Table creation order] ${sortedTables.map((t) => t.name).join(' → ')}`)
|
|
252
|
+
|
|
253
|
+
// Step 6: Create or migrate tables
|
|
254
|
+
yield* createMigrateTables({
|
|
255
|
+
tx,
|
|
256
|
+
sortedTables,
|
|
257
|
+
tableUsesView,
|
|
258
|
+
circularTables,
|
|
259
|
+
previousSchema,
|
|
260
|
+
lookupViewModule: lookupViewGenerators,
|
|
261
|
+
hasAuthConfig: !!app.auth,
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
// Step 7: Add FK constraints for circular dependencies
|
|
265
|
+
yield* addCircularFKConstraints(tx, sortedTables, circularTables, tableUsesView)
|
|
266
|
+
|
|
267
|
+
// Step 8: Create junction tables for many-to-many relationships
|
|
268
|
+
const junctionTableSpecs = collectJunctionTableSpecs(sortedTables, tableUsesView)
|
|
269
|
+
yield* createJunctionTables(tx, junctionTableSpecs)
|
|
270
|
+
|
|
271
|
+
// Steps 9-11: Create all views
|
|
272
|
+
yield* createAllViews(tx, sortedTables, viewGenerators)
|
|
273
|
+
|
|
274
|
+
// Steps 12-13: Record migration and store checksum
|
|
275
|
+
yield* recordMigration(tx, app)
|
|
276
|
+
yield* storeSchemaChecksum(tx, app)
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
/** Log rollback operation in a separate transaction */
|
|
280
|
+
const logRollbackError = (
|
|
281
|
+
databaseUrl: string,
|
|
282
|
+
errorMessage: string,
|
|
283
|
+
runtime: Runtime.Runtime<never>
|
|
284
|
+
): Effect.Effect<void, never, never> =>
|
|
285
|
+
Effect.gen(function* () {
|
|
286
|
+
logInfo(`[executeSchemaInit] CATCH HANDLER - Error caught: ${errorMessage}`)
|
|
287
|
+
const logDb = new SQL(databaseUrl)
|
|
288
|
+
|
|
289
|
+
yield* Effect.tryPromise({
|
|
290
|
+
try: async () => {
|
|
291
|
+
/* eslint-disable-next-line functional/no-expression-statements */
|
|
292
|
+
await logDb.begin(async (logTx) => {
|
|
293
|
+
/* eslint-disable-next-line functional/no-expression-statements */
|
|
294
|
+
await Runtime.runPromise(runtime)(
|
|
295
|
+
logRollbackOperation(logTx, errorMessage).pipe(
|
|
296
|
+
Effect.catchAll((logError) => {
|
|
297
|
+
logInfo(`[executeSchemaInit] Failed to log rollback: ${logError.message}`)
|
|
298
|
+
return Effect.void
|
|
299
|
+
})
|
|
300
|
+
)
|
|
301
|
+
)
|
|
302
|
+
})
|
|
303
|
+
logInfo('[executeSchemaInit] CATCH HANDLER - Rollback logged and committed')
|
|
304
|
+
},
|
|
305
|
+
catch: () => undefined, // Non-fatal
|
|
306
|
+
}).pipe(
|
|
307
|
+
Effect.ensuring(
|
|
308
|
+
Effect.gen(function* () {
|
|
309
|
+
yield* Effect.promise(() => logDb.close())
|
|
310
|
+
logInfo('[executeSchemaInit] CATCH HANDLER - Log DB connection closed')
|
|
311
|
+
})
|
|
312
|
+
),
|
|
313
|
+
Effect.ignore
|
|
314
|
+
)
|
|
315
|
+
})
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Execute schema initialization using bun:sql with transaction support
|
|
319
|
+
* Uses Bun's native SQL driver for optimal performance
|
|
320
|
+
*
|
|
321
|
+
* Now supports incremental schema migrations:
|
|
322
|
+
* - For new tables: CREATE TABLE
|
|
323
|
+
* - For existing tables: ALTER TABLE ADD COLUMN for new fields
|
|
324
|
+
*
|
|
325
|
+
* SECURITY NOTE: tx.unsafe() is intentionally used here for DDL execution.
|
|
326
|
+
*
|
|
327
|
+
* This is SAFE because:
|
|
328
|
+
* 1. SQL is generated from validated Effect Schema objects, not user input
|
|
329
|
+
* - Table names come from schema definitions validated at startup
|
|
330
|
+
* - Field names/types are constrained by the domain model (Fields type)
|
|
331
|
+
* 2. DDL statements (CREATE TABLE, CREATE INDEX) cannot use parameterized queries
|
|
332
|
+
* - PostgreSQL does not support $1 placeholders in DDL statements
|
|
333
|
+
* - Table and column names must be interpolated directly
|
|
334
|
+
* 3. All identifiers come from validated schema definitions
|
|
335
|
+
* - The App schema is validated via Effect Schema before reaching this code
|
|
336
|
+
* - Invalid identifiers would fail schema validation, not reach SQL execution
|
|
337
|
+
* 4. Transaction boundary provides atomicity
|
|
338
|
+
* - If any statement fails, the entire transaction rolls back
|
|
339
|
+
* - No partial schema state is possible
|
|
340
|
+
*
|
|
341
|
+
* This pattern is standard for schema migration tools (Drizzle, Prisma, etc.)
|
|
342
|
+
* which all generate and execute DDL strings directly.
|
|
343
|
+
*/
|
|
344
|
+
const executeSchemaInit = (
|
|
345
|
+
databaseUrl: string,
|
|
346
|
+
tables: readonly Table[],
|
|
347
|
+
app: App
|
|
348
|
+
): Effect.Effect<void, SchemaInitializationError, never> =>
|
|
349
|
+
Effect.gen(function* () {
|
|
350
|
+
const db = new SQL({ url: databaseUrl, max: 1 })
|
|
351
|
+
const runtime = yield* Effect.runtime<never>()
|
|
352
|
+
|
|
353
|
+
try {
|
|
354
|
+
yield* Effect.tryPromise({
|
|
355
|
+
try: async () => {
|
|
356
|
+
/* eslint-disable-next-line functional/no-expression-statements */
|
|
357
|
+
await db.begin(async (tx) => {
|
|
358
|
+
/* eslint-disable-next-line functional/no-expression-statements */
|
|
359
|
+
await Runtime.runPromise(runtime)(executeMigrationSteps(tx, tables, app))
|
|
360
|
+
logInfo('[executeSchemaInit] Transaction completed successfully (auto-commit)')
|
|
361
|
+
})
|
|
362
|
+
},
|
|
363
|
+
catch: (error) =>
|
|
364
|
+
new SchemaInitializationError({
|
|
365
|
+
message: `Schema initialization failed: ${String(error)}`,
|
|
366
|
+
cause: error,
|
|
367
|
+
}),
|
|
368
|
+
}).pipe(
|
|
369
|
+
Effect.catchAll((error) =>
|
|
370
|
+
Effect.gen(function* () {
|
|
371
|
+
yield* logRollbackError(databaseUrl, error.message, runtime)
|
|
372
|
+
return yield* error
|
|
373
|
+
})
|
|
374
|
+
)
|
|
375
|
+
)
|
|
376
|
+
} finally {
|
|
377
|
+
yield* Effect.promise(() => db.close())
|
|
378
|
+
}
|
|
379
|
+
})
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Check if schema checksum matches saved checksum (fast path optimization)
|
|
383
|
+
* Returns true if migration should be skipped, false otherwise
|
|
384
|
+
*
|
|
385
|
+
* IMPORTANT: Also verifies that expected tables actually exist.
|
|
386
|
+
* This prevents skipping migration when template databases have checksum but no tables.
|
|
387
|
+
*/
|
|
388
|
+
const checkShouldSkipMigration = (
|
|
389
|
+
databaseUrl: string,
|
|
390
|
+
currentChecksum: string,
|
|
391
|
+
tables: readonly Table[]
|
|
392
|
+
): Effect.Effect<boolean, SchemaInitializationError> =>
|
|
393
|
+
Effect.tryPromise({
|
|
394
|
+
try: async () => {
|
|
395
|
+
const quickDb = new SQL(databaseUrl)
|
|
396
|
+
try {
|
|
397
|
+
// Quick read-only query to check checksum (no transaction needed)
|
|
398
|
+
const result = (await quickDb.unsafe(
|
|
399
|
+
`SELECT checksum FROM system.schema_checksum WHERE id = 'singleton'`
|
|
400
|
+
)) as readonly { checksum: string }[]
|
|
401
|
+
|
|
402
|
+
// Early return if checksum doesn't match
|
|
403
|
+
if (result.length === 0 || result[0]?.checksum !== currentChecksum) {
|
|
404
|
+
logInfo(
|
|
405
|
+
'[checkShouldSkipMigration] Schema checksum differs or missing - running full migration'
|
|
406
|
+
)
|
|
407
|
+
return false
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// Checksum matches, but verify tables actually exist
|
|
411
|
+
// This prevents skipping migration when template DBs have checksum but no tables
|
|
412
|
+
if (tables.length === 0) {
|
|
413
|
+
logInfo(
|
|
414
|
+
'[checkShouldSkipMigration] Schema checksum matches and no tables expected - skipping migration (fast path)'
|
|
415
|
+
)
|
|
416
|
+
return true
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
// Check if the first table exists (as a sanity check)
|
|
420
|
+
// Note: Table names come from validated schema, not user input (see SECURITY NOTE above)
|
|
421
|
+
const firstTableName = tables[0]?.name
|
|
422
|
+
if (!firstTableName) {
|
|
423
|
+
logInfo(
|
|
424
|
+
'[checkShouldSkipMigration] Schema checksum matches and tables verified - skipping migration (fast path)'
|
|
425
|
+
)
|
|
426
|
+
return true
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Sanitize table name for PostgreSQL check
|
|
430
|
+
const sanitizedTableName = sanitizeTableName(firstTableName)
|
|
431
|
+
|
|
432
|
+
const tableCheck = (await quickDb.unsafe(`
|
|
433
|
+
SELECT EXISTS (
|
|
434
|
+
SELECT FROM information_schema.tables
|
|
435
|
+
WHERE table_schema = 'public'
|
|
436
|
+
AND table_name = '${sanitizedTableName}'
|
|
437
|
+
)
|
|
438
|
+
`)) as readonly { exists: boolean }[]
|
|
439
|
+
|
|
440
|
+
if (!tableCheck[0]?.exists) {
|
|
441
|
+
logInfo(
|
|
442
|
+
`[checkShouldSkipMigration] Checksum matches but table '${sanitizedTableName}' does not exist - running full migration (template DB detected)`
|
|
443
|
+
)
|
|
444
|
+
return false
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
logInfo(
|
|
448
|
+
'[checkShouldSkipMigration] Schema checksum matches and tables verified - skipping migration (fast path)'
|
|
449
|
+
)
|
|
450
|
+
return true
|
|
451
|
+
} catch {
|
|
452
|
+
// Table might not exist yet (first run) - proceed with full migration
|
|
453
|
+
logInfo('[checkShouldSkipMigration] Checksum table not found - running full migration')
|
|
454
|
+
return false
|
|
455
|
+
} finally {
|
|
456
|
+
/* eslint-disable-next-line functional/no-expression-statements */
|
|
457
|
+
await quickDb.close()
|
|
458
|
+
}
|
|
459
|
+
},
|
|
460
|
+
catch: () =>
|
|
461
|
+
new SchemaInitializationError({
|
|
462
|
+
message: 'Failed to check schema checksum',
|
|
463
|
+
cause: undefined,
|
|
464
|
+
}),
|
|
465
|
+
}).pipe(Effect.catchAll(() => Effect.succeed(false))) // Non-fatal - if check fails, proceed with full migration
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Error type union for schema initialization
|
|
469
|
+
*/
|
|
470
|
+
export type SchemaError =
|
|
471
|
+
| SchemaInitializationError
|
|
472
|
+
| NoDatabaseUrlError
|
|
473
|
+
| BetterAuthUsersTableRequired
|
|
474
|
+
| AuthConfigRequiredForUserFields
|
|
475
|
+
|
|
476
|
+
/**
|
|
477
|
+
* Initialize database schema from app configuration (internal with error handling)
|
|
478
|
+
*
|
|
479
|
+
* Uses Bun's native SQL driver (bun:sql) for:
|
|
480
|
+
* - Zero-dependency PostgreSQL access
|
|
481
|
+
* - Optimal performance on Bun runtime
|
|
482
|
+
* - Built-in connection pooling
|
|
483
|
+
* - Transaction support with automatic rollback
|
|
484
|
+
*
|
|
485
|
+
* Errors are logged and handled internally - returns Effect<void, never>
|
|
486
|
+
* for simpler composition in application layer.
|
|
487
|
+
*
|
|
488
|
+
* @see docs/infrastructure/database/runtime-sql-migrations/04-migration-executor.md
|
|
489
|
+
*/
|
|
490
|
+
const initializeSchemaInternal = (
|
|
491
|
+
app: App
|
|
492
|
+
): Effect.Effect<void, SchemaError | ConfigError.ConfigError> =>
|
|
493
|
+
Effect.gen(function* () {
|
|
494
|
+
logInfo('[initializeSchemaInternal] Starting schema initialization...')
|
|
495
|
+
logInfo(`[initializeSchemaInternal] App tables count: ${app.tables?.length || 0}`)
|
|
496
|
+
|
|
497
|
+
// Normalize tables to empty array if undefined
|
|
498
|
+
const tables = app.tables ?? []
|
|
499
|
+
|
|
500
|
+
// Check if tables require user fields and auth configuration status
|
|
501
|
+
// Note: Authorship fields (created-by, updated-by, deleted-by) are allowed without auth config
|
|
502
|
+
// When auth is not configured, these fields will be NULL
|
|
503
|
+
const tablesNeedUsersTable = needsUsersTable(tables)
|
|
504
|
+
const hasAuthConfig = !!app.auth
|
|
505
|
+
logInfo(`[initializeSchemaInternal] Tables need users table: ${tablesNeedUsersTable}`)
|
|
506
|
+
logInfo(`[initializeSchemaInternal] Auth config present: ${hasAuthConfig}`)
|
|
507
|
+
|
|
508
|
+
// No validation error - authorship fields are allowed without auth (they'll be NULL)
|
|
509
|
+
|
|
510
|
+
// Get database URL from Effect Config (reads from environment)
|
|
511
|
+
const databaseUrlConfig = yield* Config.string('DATABASE_URL').pipe(Config.withDefault(''))
|
|
512
|
+
logInfo(`[initializeSchemaInternal] DATABASE_URL: ${databaseUrlConfig ? 'present' : 'missing'}`)
|
|
513
|
+
|
|
514
|
+
// Skip if no DATABASE_URL configured
|
|
515
|
+
if (!databaseUrlConfig) {
|
|
516
|
+
yield* Console.log('No DATABASE_URL found, skipping schema initialization')
|
|
517
|
+
return
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
yield* Console.log('Initializing database schema...')
|
|
521
|
+
|
|
522
|
+
// Fast path: Check if schema checksum matches (before opening transaction)
|
|
523
|
+
const currentChecksum = generateSchemaChecksum(app)
|
|
524
|
+
const shouldSkipMigration = yield* checkShouldSkipMigration(
|
|
525
|
+
databaseUrlConfig,
|
|
526
|
+
currentChecksum,
|
|
527
|
+
tables
|
|
528
|
+
)
|
|
529
|
+
|
|
530
|
+
// Even if migration is skipped, we need to clean up obsolete views
|
|
531
|
+
// Views might be created manually via SQL and need cleanup
|
|
532
|
+
if (shouldSkipMigration) {
|
|
533
|
+
yield* Console.log('✓ Schema unchanged, cleaning up obsolete views...')
|
|
534
|
+
// Quick cleanup of views not in schema (separate transaction)
|
|
535
|
+
const db = new SQL({ url: databaseUrlConfig, max: 1 })
|
|
536
|
+
try {
|
|
537
|
+
yield* Effect.tryPromise({
|
|
538
|
+
try: async () => {
|
|
539
|
+
// Side effect: Drop obsolete views in database transaction
|
|
540
|
+
/* eslint-disable functional/no-expression-statements */
|
|
541
|
+
await db.begin(async (tx) => {
|
|
542
|
+
await viewGenerators.dropAllObsoleteViews(tx, tables)
|
|
543
|
+
})
|
|
544
|
+
/* eslint-enable functional/no-expression-statements */
|
|
545
|
+
},
|
|
546
|
+
catch: (error) =>
|
|
547
|
+
new SchemaInitializationError({
|
|
548
|
+
message: `View cleanup failed: ${String(error)}`,
|
|
549
|
+
cause: error,
|
|
550
|
+
}),
|
|
551
|
+
})
|
|
552
|
+
} finally {
|
|
553
|
+
yield* Effect.promise(() => db.close())
|
|
554
|
+
}
|
|
555
|
+
yield* Console.log('✓ Schema unchanged, view cleanup complete')
|
|
556
|
+
return
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
// Execute schema initialization with bun:sql (even if tables is empty - to drop obsolete tables)
|
|
560
|
+
yield* executeSchemaInit(databaseUrlConfig, tables, app)
|
|
561
|
+
|
|
562
|
+
yield* Console.log('✓ Database schema initialized successfully')
|
|
563
|
+
})
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Initialize database schema from app configuration
|
|
567
|
+
*
|
|
568
|
+
* Public API that handles errors internally to maintain backward compatibility.
|
|
569
|
+
* Configuration errors are propagated, other errors are logged.
|
|
570
|
+
*
|
|
571
|
+
* Propagated errors:
|
|
572
|
+
* - AuthConfigRequiredForUserFields: auth not configured but user fields used
|
|
573
|
+
* - SchemaInitializationError: schema creation failed (database likely required)
|
|
574
|
+
*
|
|
575
|
+
* @param app - Application configuration with tables
|
|
576
|
+
* @returns Effect that propagates configuration errors but logs optional failures
|
|
577
|
+
*/
|
|
578
|
+
export const initializeSchema = (
|
|
579
|
+
app: App
|
|
580
|
+
): Effect.Effect<void, AuthConfigRequiredForUserFields | SchemaInitializationError> =>
|
|
581
|
+
initializeSchemaInternal(app).pipe(
|
|
582
|
+
Effect.catchAll(
|
|
583
|
+
(error): Effect.Effect<void, AuthConfigRequiredForUserFields | SchemaInitializationError> => {
|
|
584
|
+
// Re-throw auth config errors - these are fatal configuration issues
|
|
585
|
+
if (error instanceof AuthConfigRequiredForUserFields) {
|
|
586
|
+
return Effect.fail(error)
|
|
587
|
+
}
|
|
588
|
+
// Re-throw schema initialization errors - database is required when tables are defined
|
|
589
|
+
if (error instanceof SchemaInitializationError) {
|
|
590
|
+
return Effect.fail(error)
|
|
591
|
+
}
|
|
592
|
+
// Log other errors but don't fail
|
|
593
|
+
return Console.error(`Error initializing database schema: ${error._tag}`).pipe(
|
|
594
|
+
Effect.flatMap(() => Effect.void)
|
|
595
|
+
)
|
|
596
|
+
}
|
|
597
|
+
)
|
|
598
|
+
)
|