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,96 @@
|
|
|
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 } from 'effect'
|
|
9
|
+
import { sanitizeTableName } from '../field-utils'
|
|
10
|
+
import { createVolatileFormulaTriggers } from '../formula/formula-trigger-generators'
|
|
11
|
+
import { generateIndexStatements } from '../generators/index-generators'
|
|
12
|
+
import {
|
|
13
|
+
generateCreatedAtTriggers,
|
|
14
|
+
generateAutonumberTriggers,
|
|
15
|
+
generateUpdatedByTriggers,
|
|
16
|
+
generateUpdatedAtTriggers,
|
|
17
|
+
} from '../generators/trigger-generators'
|
|
18
|
+
import { shouldUseView, getBaseTableName } from '../lookup/lookup-view-generators'
|
|
19
|
+
import {
|
|
20
|
+
executeSQLStatements,
|
|
21
|
+
executeSQLStatementsParallel,
|
|
22
|
+
type TransactionLike,
|
|
23
|
+
type SQLExecutionError,
|
|
24
|
+
} from '../sql/sql-execution'
|
|
25
|
+
import type { Table } from '@/domain/models/app/table'
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Apply table features (indexes, triggers)
|
|
29
|
+
* Shared by both createNewTable and migrateExistingTable
|
|
30
|
+
* Note: Triggers are applied to the base table, not the VIEW
|
|
31
|
+
*
|
|
32
|
+
* Field-level permissions are enforced at the application layer,
|
|
33
|
+
* not via PostgreSQL column-level GRANTs.
|
|
34
|
+
*/
|
|
35
|
+
export const applyTableFeatures = (
|
|
36
|
+
tx: TransactionLike,
|
|
37
|
+
table: Table
|
|
38
|
+
): Effect.Effect<void, SQLExecutionError> =>
|
|
39
|
+
Effect.gen(function* () {
|
|
40
|
+
// Sanitize table name for PostgreSQL
|
|
41
|
+
const sanitized = sanitizeTableName(table.name)
|
|
42
|
+
// Determine actual table name (base table if using VIEW)
|
|
43
|
+
const physicalTableName = shouldUseView(table) ? getBaseTableName(sanitized) : sanitized
|
|
44
|
+
|
|
45
|
+
// Create table object with physical table name for trigger generation
|
|
46
|
+
const physicalTable = shouldUseView(table) ? { ...table, name: physicalTableName } : table
|
|
47
|
+
|
|
48
|
+
// Indexes and triggers (can run in parallel - all independent)
|
|
49
|
+
// These create IF NOT EXISTS so order doesn't matter
|
|
50
|
+
yield* Effect.all(
|
|
51
|
+
[
|
|
52
|
+
executeSQLStatementsParallel(tx, generateIndexStatements(physicalTable)),
|
|
53
|
+
executeSQLStatements(tx, generateCreatedAtTriggers(physicalTable)),
|
|
54
|
+
executeSQLStatements(tx, generateAutonumberTriggers(physicalTable)),
|
|
55
|
+
executeSQLStatements(tx, generateUpdatedByTriggers(physicalTable)),
|
|
56
|
+
executeSQLStatements(tx, generateUpdatedAtTriggers(physicalTable)),
|
|
57
|
+
Effect.promise(() => createVolatileFormulaTriggers(tx, physicalTableName, table.fields)),
|
|
58
|
+
],
|
|
59
|
+
{ concurrency: 'unbounded' }
|
|
60
|
+
)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Apply table features without indexes (triggers only)
|
|
65
|
+
* Used during migration when indexes are handled separately by syncIndexes
|
|
66
|
+
* Note: Triggers are applied to the base table, not the VIEW
|
|
67
|
+
*
|
|
68
|
+
* Field-level permissions are enforced at the application layer,
|
|
69
|
+
* not via PostgreSQL column-level GRANTs.
|
|
70
|
+
*/
|
|
71
|
+
export const applyTableFeaturesWithoutIndexes = (
|
|
72
|
+
tx: TransactionLike,
|
|
73
|
+
table: Table
|
|
74
|
+
): Effect.Effect<void, SQLExecutionError> =>
|
|
75
|
+
Effect.gen(function* () {
|
|
76
|
+
// Sanitize table name for PostgreSQL
|
|
77
|
+
const sanitized = sanitizeTableName(table.name)
|
|
78
|
+
// Determine actual table name (base table if using VIEW)
|
|
79
|
+
const physicalTableName = shouldUseView(table) ? getBaseTableName(sanitized) : sanitized
|
|
80
|
+
|
|
81
|
+
// Create table object with physical table name for trigger generation
|
|
82
|
+
const physicalTable = shouldUseView(table) ? { ...table, name: physicalTableName } : table
|
|
83
|
+
|
|
84
|
+
// Triggers (can run in parallel - all independent)
|
|
85
|
+
// These create IF NOT EXISTS so order doesn't matter
|
|
86
|
+
yield* Effect.all(
|
|
87
|
+
[
|
|
88
|
+
executeSQLStatements(tx, generateCreatedAtTriggers(physicalTable)),
|
|
89
|
+
executeSQLStatements(tx, generateAutonumberTriggers(physicalTable)),
|
|
90
|
+
executeSQLStatements(tx, generateUpdatedByTriggers(physicalTable)),
|
|
91
|
+
executeSQLStatements(tx, generateUpdatedAtTriggers(physicalTable)),
|
|
92
|
+
Effect.promise(() => createVolatileFormulaTriggers(tx, physicalTableName, table.fields)),
|
|
93
|
+
],
|
|
94
|
+
{ concurrency: 'unbounded' }
|
|
95
|
+
)
|
|
96
|
+
})
|
|
@@ -0,0 +1,58 @@
|
|
|
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
|
+
* Normalize PostgreSQL type names by removing size specifications
|
|
10
|
+
*/
|
|
11
|
+
export const normalizeType = (type: string): string =>
|
|
12
|
+
type
|
|
13
|
+
.replace(/\(\d+\)/, '') // Remove (N) like varchar(255) -> varchar
|
|
14
|
+
.replace(/\(\d+,\s*\d+\)/, '') // Remove (N,M) like numeric(10,2) -> numeric
|
|
15
|
+
.trim()
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Type compatibility groups - types within a group can be safely copied between each other
|
|
19
|
+
*/
|
|
20
|
+
const TYPE_COMPATIBILITY_GROUPS: readonly (readonly string[])[] = [
|
|
21
|
+
// Integer types (can be widened but not narrowed, but for recreation we allow same family)
|
|
22
|
+
['smallint', 'integer', 'bigint', 'int2', 'int4', 'int8'],
|
|
23
|
+
// Text types
|
|
24
|
+
['text', 'varchar', 'character varying', 'char', 'character', 'bpchar'],
|
|
25
|
+
// Boolean
|
|
26
|
+
['boolean', 'bool'],
|
|
27
|
+
// Floating point
|
|
28
|
+
['real', 'double precision', 'float4', 'float8'],
|
|
29
|
+
// Numeric/Decimal
|
|
30
|
+
['numeric', 'decimal'],
|
|
31
|
+
// Date/Time
|
|
32
|
+
['timestamp', 'timestamp without time zone', 'timestamp with time zone', 'timestamptz'],
|
|
33
|
+
['date'],
|
|
34
|
+
['time', 'time without time zone', 'time with time zone', 'timetz'],
|
|
35
|
+
// UUID
|
|
36
|
+
['uuid'],
|
|
37
|
+
// JSON
|
|
38
|
+
['json', 'jsonb'],
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Check if two PostgreSQL data types are compatible for data copying
|
|
43
|
+
* Returns true if data can be safely copied from oldType to newType
|
|
44
|
+
*/
|
|
45
|
+
export const areTypesCompatible = (oldType: string, newType: string): boolean => {
|
|
46
|
+
// Exact match is always compatible
|
|
47
|
+
if (oldType === newType) return true
|
|
48
|
+
|
|
49
|
+
const normalizedOld = normalizeType(oldType)
|
|
50
|
+
const normalizedNew = normalizeType(newType)
|
|
51
|
+
|
|
52
|
+
if (normalizedOld === normalizedNew) return true
|
|
53
|
+
|
|
54
|
+
// Check if both types are in the same compatibility group
|
|
55
|
+
return TYPE_COMPATIBILITY_GROUPS.some(
|
|
56
|
+
(group) => group.includes(normalizedOld) && group.includes(normalizedNew)
|
|
57
|
+
)
|
|
58
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
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
|
+
* Table Operations - Re-export Module
|
|
10
|
+
*
|
|
11
|
+
* This file re-exports all table operation utilities from the split module structure.
|
|
12
|
+
* Maintained for backwards compatibility with existing imports.
|
|
13
|
+
*
|
|
14
|
+
* @see ./table-operations/ for the actual implementations
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
// Type exports
|
|
18
|
+
export type { MigrationConfig } from './table-operations/'
|
|
19
|
+
|
|
20
|
+
// All function exports
|
|
21
|
+
export {
|
|
22
|
+
// Type compatibility utilities
|
|
23
|
+
normalizeType,
|
|
24
|
+
areTypesCompatible,
|
|
25
|
+
// Column generators
|
|
26
|
+
generateIdColumn,
|
|
27
|
+
needsAutomaticIdColumn,
|
|
28
|
+
generateCreatedAtColumn,
|
|
29
|
+
generateUpdatedAtColumn,
|
|
30
|
+
generateDeletedAtColumn,
|
|
31
|
+
generatePrimaryKeyConstraintIfNeeded,
|
|
32
|
+
// CREATE TABLE SQL generation
|
|
33
|
+
generateCreateTableSQL,
|
|
34
|
+
// Table features (indexes, triggers, field permissions)
|
|
35
|
+
applyTableFeatures,
|
|
36
|
+
applyTableFeaturesWithoutIndexes,
|
|
37
|
+
// Migration utilities
|
|
38
|
+
getCompatibleColumns,
|
|
39
|
+
copyDataAndResetSequences,
|
|
40
|
+
recreateTableWithDataEffect,
|
|
41
|
+
// Table effect operations
|
|
42
|
+
migrateExistingTableEffect,
|
|
43
|
+
createNewTableEffect,
|
|
44
|
+
createLookupViewsEffect,
|
|
45
|
+
createTableViewsEffect,
|
|
46
|
+
createOrMigrateTableEffect,
|
|
47
|
+
} from './table-operations/'
|
|
@@ -0,0 +1,80 @@
|
|
|
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 } from 'effect'
|
|
9
|
+
import { db, SessionContextError } from '@/infrastructure/database'
|
|
10
|
+
import { injectCreateAuthorship } from '../mutation-helpers/authorship-helpers'
|
|
11
|
+
import { logActivity } from '../query-helpers/activity-log-helpers'
|
|
12
|
+
import { wrapDatabaseErrorWithValidation } from '../shared/error-handling'
|
|
13
|
+
import { validateTableName } from '../shared/validation'
|
|
14
|
+
import { createSingleRecordInBatch, runEffectInTx } from './batch-helpers'
|
|
15
|
+
import type { Session } from '@/infrastructure/auth/better-auth/schema'
|
|
16
|
+
import type { ValidationError } from '@/infrastructure/database'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Batch create records
|
|
20
|
+
*
|
|
21
|
+
* Creates multiple records in a single transaction.
|
|
22
|
+
* Permissions applied via application layer.
|
|
23
|
+
*
|
|
24
|
+
* @param session - Better Auth session
|
|
25
|
+
* @param tableName - Name of the table
|
|
26
|
+
* @param recordsData - Array of field objects to insert
|
|
27
|
+
* @returns Effect resolving to array of created records
|
|
28
|
+
*/
|
|
29
|
+
export function batchCreateRecords(
|
|
30
|
+
session: Readonly<Session>,
|
|
31
|
+
tableName: string,
|
|
32
|
+
recordsData: readonly Record<string, unknown>[]
|
|
33
|
+
): Effect.Effect<readonly Record<string, unknown>[], SessionContextError | ValidationError> {
|
|
34
|
+
return Effect.gen(function* () {
|
|
35
|
+
const createdRecords = yield* Effect.tryPromise({
|
|
36
|
+
try: () =>
|
|
37
|
+
db.transaction(async (tx) => {
|
|
38
|
+
validateTableName(tableName)
|
|
39
|
+
|
|
40
|
+
if (recordsData.length === 0) {
|
|
41
|
+
// eslint-disable-next-line functional/no-throw-statements -- Required for transaction error handling
|
|
42
|
+
throw new SessionContextError('Cannot create batch with no records', undefined)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Inject authorship metadata for each record
|
|
46
|
+
const recordsWithAuthorship = await Promise.all(
|
|
47
|
+
recordsData.map((fields) =>
|
|
48
|
+
injectCreateAuthorship(fields, session.userId, tx, tableName)
|
|
49
|
+
)
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
// Use Effect.reduce with runEffectInTx to properly propagate ValidationError
|
|
53
|
+
return await runEffectInTx(
|
|
54
|
+
Effect.reduce(
|
|
55
|
+
recordsWithAuthorship,
|
|
56
|
+
[] as readonly Record<string, unknown>[],
|
|
57
|
+
(acc, fields) =>
|
|
58
|
+
createSingleRecordInBatch(tx, tableName, fields).pipe(
|
|
59
|
+
Effect.map((record) => (record ? [...acc, record] : acc))
|
|
60
|
+
)
|
|
61
|
+
)
|
|
62
|
+
)
|
|
63
|
+
}),
|
|
64
|
+
catch: wrapDatabaseErrorWithValidation(`Failed to create batch records in ${tableName}`),
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
// Log activity for each created record
|
|
68
|
+
yield* Effect.forEach(createdRecords, (record) =>
|
|
69
|
+
logActivity({
|
|
70
|
+
session,
|
|
71
|
+
tableName,
|
|
72
|
+
action: 'create',
|
|
73
|
+
recordId: String(record.id),
|
|
74
|
+
changes: { after: record },
|
|
75
|
+
})
|
|
76
|
+
).pipe(Effect.asVoid)
|
|
77
|
+
|
|
78
|
+
return createdRecords
|
|
79
|
+
})
|
|
80
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
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 'drizzle-orm'
|
|
9
|
+
import { Effect } from 'effect'
|
|
10
|
+
import { db, SessionContextError, type DrizzleTransaction } from '@/infrastructure/database'
|
|
11
|
+
import { fetchRecordsByIds } from '../mutation-helpers/record-fetch-helpers'
|
|
12
|
+
import { logActivity } from '../query-helpers/activity-log-helpers'
|
|
13
|
+
import { wrapDatabaseError } from '../shared/error-handling'
|
|
14
|
+
import { validateTableName } from '../shared/validation'
|
|
15
|
+
import { runEffectInTx } from './batch-helpers'
|
|
16
|
+
import type { Session } from '@/infrastructure/auth/better-auth/schema'
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Validate records exist for batch delete
|
|
20
|
+
*/
|
|
21
|
+
async function validateRecordsForDelete(
|
|
22
|
+
tx: Readonly<DrizzleTransaction>,
|
|
23
|
+
tableIdent: Readonly<ReturnType<typeof sql.identifier>>,
|
|
24
|
+
recordIds: readonly string[]
|
|
25
|
+
): Promise<void> {
|
|
26
|
+
const validationResults = await Promise.all(
|
|
27
|
+
recordIds.map(async (recordId) => {
|
|
28
|
+
const checkResult = (await tx.execute(
|
|
29
|
+
sql`SELECT id FROM ${tableIdent} WHERE id = ${recordId} LIMIT 1`
|
|
30
|
+
)) as readonly Record<string, unknown>[]
|
|
31
|
+
|
|
32
|
+
if (checkResult.length === 0) {
|
|
33
|
+
return { recordId, error: 'not found' }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return { recordId, error: undefined }
|
|
37
|
+
})
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
const firstError = validationResults.find((result) => result.error !== undefined)
|
|
41
|
+
if (firstError) {
|
|
42
|
+
// eslint-disable-next-line functional/no-throw-statements -- Required for Effect.tryPromise error handling
|
|
43
|
+
throw new Error(`Record ${firstError.recordId} not found`)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Validate records exist for batch delete with Effect error handling
|
|
49
|
+
*/
|
|
50
|
+
function validateRecordsForDeleteWithEffect(
|
|
51
|
+
tx: Readonly<DrizzleTransaction>,
|
|
52
|
+
tableIdent: Readonly<ReturnType<typeof sql.identifier>>,
|
|
53
|
+
recordIds: readonly string[]
|
|
54
|
+
): Effect.Effect<void, SessionContextError> {
|
|
55
|
+
return Effect.tryPromise({
|
|
56
|
+
try: () => validateRecordsForDelete(tx, tableIdent, recordIds),
|
|
57
|
+
catch: (error) => {
|
|
58
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
|
59
|
+
return new SessionContextError(`Validation failed: ${errorMessage}`, error)
|
|
60
|
+
},
|
|
61
|
+
})
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Check if table supports soft delete (has deleted_at column)
|
|
66
|
+
*/
|
|
67
|
+
function checkSoftDeleteSupport(
|
|
68
|
+
tx: Readonly<DrizzleTransaction>,
|
|
69
|
+
tableName: string
|
|
70
|
+
): Effect.Effect<boolean, SessionContextError> {
|
|
71
|
+
return Effect.tryPromise({
|
|
72
|
+
try: async () => {
|
|
73
|
+
const columnCheck = (await tx.execute(
|
|
74
|
+
sql`SELECT column_name FROM information_schema.columns WHERE table_name = ${tableName} AND column_name = 'deleted_at'`
|
|
75
|
+
)) as readonly Record<string, unknown>[]
|
|
76
|
+
return columnCheck.length > 0
|
|
77
|
+
},
|
|
78
|
+
catch: (error) => new SessionContextError('Failed to check deleted_at column', error),
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Check if table has deleted_by column for authorship tracking
|
|
84
|
+
*/
|
|
85
|
+
function checkDeletedBySupport(
|
|
86
|
+
tx: Readonly<DrizzleTransaction>,
|
|
87
|
+
tableName: string
|
|
88
|
+
): Effect.Effect<boolean, SessionContextError> {
|
|
89
|
+
return Effect.tryPromise({
|
|
90
|
+
try: async () => {
|
|
91
|
+
const columnCheck = (await tx.execute(
|
|
92
|
+
sql`SELECT column_name FROM information_schema.columns WHERE table_name = ${tableName} AND column_name = 'deleted_by'`
|
|
93
|
+
)) as readonly Record<string, unknown>[]
|
|
94
|
+
return columnCheck.length > 0
|
|
95
|
+
},
|
|
96
|
+
catch: (error) => new SessionContextError('Failed to check deleted_by column', error),
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Execute delete query (soft or hard delete based on parameters)
|
|
102
|
+
*/
|
|
103
|
+
function executeDeleteQuery(
|
|
104
|
+
tx: Readonly<DrizzleTransaction>,
|
|
105
|
+
params: {
|
|
106
|
+
readonly tableName: string
|
|
107
|
+
readonly recordIds: readonly string[]
|
|
108
|
+
readonly hasSoftDelete: boolean
|
|
109
|
+
readonly hasDeletedBy: boolean
|
|
110
|
+
readonly permanent: boolean
|
|
111
|
+
readonly userId: string
|
|
112
|
+
}
|
|
113
|
+
): Effect.Effect<number, SessionContextError> {
|
|
114
|
+
return Effect.tryPromise({
|
|
115
|
+
try: async () => {
|
|
116
|
+
const tableIdent = sql.identifier(params.tableName)
|
|
117
|
+
const idParams = sql.join(
|
|
118
|
+
params.recordIds.map((id) => sql`${id}`),
|
|
119
|
+
sql.raw(', ')
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
// Determine query type: permanent delete, soft delete, or hard delete (no soft delete support)
|
|
123
|
+
const query = params.permanent
|
|
124
|
+
? sql`DELETE FROM ${tableIdent} WHERE id IN (${idParams}) RETURNING id`
|
|
125
|
+
: params.hasSoftDelete
|
|
126
|
+
? params.hasDeletedBy
|
|
127
|
+
? sql`UPDATE ${tableIdent} SET deleted_at = NOW(), deleted_by = ${params.userId} WHERE id IN (${idParams}) AND deleted_at IS NULL RETURNING id`
|
|
128
|
+
: sql`UPDATE ${tableIdent} SET deleted_at = NOW() WHERE id IN (${idParams}) AND deleted_at IS NULL RETURNING id`
|
|
129
|
+
: sql`DELETE FROM ${tableIdent} WHERE id IN (${idParams}) RETURNING id`
|
|
130
|
+
|
|
131
|
+
const result = (await tx.execute(query)) as readonly Record<string, unknown>[]
|
|
132
|
+
return result.length
|
|
133
|
+
},
|
|
134
|
+
catch: (error) =>
|
|
135
|
+
new SessionContextError(`Failed to delete records in ${params.tableName}`, error),
|
|
136
|
+
})
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Log delete activities for all deleted records
|
|
141
|
+
*/
|
|
142
|
+
function logDeleteActivities(
|
|
143
|
+
session: Readonly<Session>,
|
|
144
|
+
tableName: string,
|
|
145
|
+
recordsBefore: readonly Record<string, unknown>[]
|
|
146
|
+
): Effect.Effect<void, never> {
|
|
147
|
+
return Effect.forEach(recordsBefore, (record) =>
|
|
148
|
+
logActivity({
|
|
149
|
+
session,
|
|
150
|
+
tableName,
|
|
151
|
+
action: 'delete',
|
|
152
|
+
recordId: String(record.id),
|
|
153
|
+
changes: { before: record },
|
|
154
|
+
})
|
|
155
|
+
).pipe(Effect.asVoid)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Batch delete records
|
|
160
|
+
*
|
|
161
|
+
* Deletes multiple records (soft or hard delete based on parameters).
|
|
162
|
+
* Validates all records exist before deleting any.
|
|
163
|
+
* Rolls back if any record is not found.
|
|
164
|
+
* Permissions applied via application layer.
|
|
165
|
+
*
|
|
166
|
+
* @param session - Better Auth session
|
|
167
|
+
* @param tableName - Name of the table
|
|
168
|
+
* @param recordIds - Array of record IDs to delete
|
|
169
|
+
* @param permanent - If true, performs hard delete; otherwise soft delete (if supported)
|
|
170
|
+
* @returns Effect resolving to number of deleted records
|
|
171
|
+
*/
|
|
172
|
+
export function batchDeleteRecords(
|
|
173
|
+
session: Readonly<Session>,
|
|
174
|
+
tableName: string,
|
|
175
|
+
recordIds: readonly string[],
|
|
176
|
+
permanent = false
|
|
177
|
+
): Effect.Effect<number, SessionContextError> {
|
|
178
|
+
return Effect.gen(function* () {
|
|
179
|
+
const { deletedCount, recordsBefore } = yield* Effect.tryPromise({
|
|
180
|
+
try: () =>
|
|
181
|
+
db.transaction(async (tx) => {
|
|
182
|
+
validateTableName(tableName)
|
|
183
|
+
const tableIdent = sql.identifier(tableName)
|
|
184
|
+
|
|
185
|
+
// eslint-disable-next-line functional/no-expression-statements -- Required for transaction validation
|
|
186
|
+
await runEffectInTx(validateRecordsForDeleteWithEffect(tx, tableIdent, recordIds))
|
|
187
|
+
|
|
188
|
+
const before = await runEffectInTx(fetchRecordsByIds(tx, tableName, recordIds))
|
|
189
|
+
const hasSoftDelete = await runEffectInTx(checkSoftDeleteSupport(tx, tableName))
|
|
190
|
+
const hasDeletedBy = await runEffectInTx(checkDeletedBySupport(tx, tableName))
|
|
191
|
+
|
|
192
|
+
const count = await runEffectInTx(
|
|
193
|
+
executeDeleteQuery(tx, {
|
|
194
|
+
tableName,
|
|
195
|
+
recordIds,
|
|
196
|
+
hasSoftDelete,
|
|
197
|
+
hasDeletedBy,
|
|
198
|
+
permanent,
|
|
199
|
+
userId: session.userId,
|
|
200
|
+
})
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
return { deletedCount: count, recordsBefore: before }
|
|
204
|
+
}),
|
|
205
|
+
catch: wrapDatabaseError(`Failed to delete records in ${tableName}`),
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
yield* logDeleteActivities(session, tableName, recordsBefore)
|
|
209
|
+
|
|
210
|
+
return deletedCount
|
|
211
|
+
})
|
|
212
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
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 'drizzle-orm'
|
|
9
|
+
import { Data, Effect, Exit, Cause } from 'effect'
|
|
10
|
+
import { ValidationError, type DrizzleTransaction } from '@/infrastructure/database'
|
|
11
|
+
import { validateColumnName } from '../shared/validation'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Batch validation error - returned when batch validation fails
|
|
15
|
+
*/
|
|
16
|
+
export class BatchValidationError extends Data.TaggedError('BatchValidationError')<{
|
|
17
|
+
readonly message: string
|
|
18
|
+
readonly details?: readonly string[]
|
|
19
|
+
}> {}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Run an Effect inside a database transaction, properly unwrapping errors.
|
|
23
|
+
*
|
|
24
|
+
* Unlike Effect.runPromise which wraps errors in FiberFailure (breaking instanceof checks
|
|
25
|
+
* in outer catch handlers), this helper extracts the original error via Cause.squash
|
|
26
|
+
* and re-throws it directly. This ensures SessionContextError, ValidationError, etc.
|
|
27
|
+
* are properly detected by instanceof in Effect.tryPromise catch handlers.
|
|
28
|
+
*/
|
|
29
|
+
export async function runEffectInTx<A, E>(effect: Effect.Effect<A, E, never>): Promise<A> {
|
|
30
|
+
const exit = await Effect.runPromiseExit(effect)
|
|
31
|
+
if (Exit.isSuccess(exit)) return exit.value
|
|
32
|
+
// eslint-disable-next-line functional/no-throw-statements -- Required to propagate Effect errors in async transaction context
|
|
33
|
+
throw Cause.squash(exit.cause)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Build INSERT SQL clauses from fields object.
|
|
38
|
+
* Returns undefined if fields is empty.
|
|
39
|
+
*/
|
|
40
|
+
function buildInsertClauses(fields: Readonly<Record<string, unknown>>):
|
|
41
|
+
| {
|
|
42
|
+
readonly columnsClause: ReturnType<typeof sql.join>
|
|
43
|
+
readonly valuesClause: ReturnType<typeof sql.join>
|
|
44
|
+
}
|
|
45
|
+
| undefined {
|
|
46
|
+
const entries = Object.entries(fields)
|
|
47
|
+
if (entries.length === 0) return undefined
|
|
48
|
+
|
|
49
|
+
const columnIdentifiers = entries.map(([key]) => {
|
|
50
|
+
validateColumnName(key)
|
|
51
|
+
return sql.identifier(key)
|
|
52
|
+
})
|
|
53
|
+
const valueParams = entries.map(([, value]) => sql`${value}`)
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
columnsClause: sql.join(columnIdentifiers, sql.raw(', ')),
|
|
57
|
+
valuesClause: sql.join(valueParams, sql.raw(', ')),
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Handle PostgreSQL NOT NULL violation errors, converting to ValidationError.
|
|
63
|
+
*/
|
|
64
|
+
// eslint-disable-next-line functional/prefer-immutable-types -- called from Effect.tryPromise catch
|
|
65
|
+
function handleInsertError(error: unknown): ValidationError {
|
|
66
|
+
const pgError = error as { code?: string; message?: string }
|
|
67
|
+
if (pgError.code === '23502' || pgError.message?.includes('null value in column')) {
|
|
68
|
+
return new ValidationError('Validation failed: Required field is missing', [
|
|
69
|
+
{ record: 0, field: 'unknown', error: 'Required field is missing' },
|
|
70
|
+
])
|
|
71
|
+
}
|
|
72
|
+
const errorMessage: string =
|
|
73
|
+
pgError.message !== undefined ? pgError.message : 'Insert failed due to constraint violation'
|
|
74
|
+
return new ValidationError(errorMessage, [])
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Helper to create a single record within a transaction
|
|
79
|
+
*/
|
|
80
|
+
export async function createSingleRecord(
|
|
81
|
+
tx: Readonly<DrizzleTransaction>,
|
|
82
|
+
tableName: string,
|
|
83
|
+
fields: Readonly<Record<string, unknown>>
|
|
84
|
+
): Promise<Readonly<Record<string, unknown>> | undefined> {
|
|
85
|
+
const clauses = buildInsertClauses(fields)
|
|
86
|
+
if (!clauses) return undefined
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const result = (await tx.execute(
|
|
90
|
+
sql`INSERT INTO ${sql.identifier(tableName)} (${clauses.columnsClause}) VALUES (${clauses.valuesClause}) RETURNING *`
|
|
91
|
+
)) as readonly Record<string, unknown>[]
|
|
92
|
+
|
|
93
|
+
return result[0] ?? undefined
|
|
94
|
+
} catch (error) {
|
|
95
|
+
// eslint-disable-next-line functional/no-throw-statements -- Required for error propagation
|
|
96
|
+
throw handleInsertError(error)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Effect-based helper to create a single record within a batch operation
|
|
102
|
+
*
|
|
103
|
+
* This is the Effect version of createSingleRecord, used by batchCreateRecords
|
|
104
|
+
* to properly propagate ValidationError through Effect.reduce.
|
|
105
|
+
*/
|
|
106
|
+
export function createSingleRecordInBatch(
|
|
107
|
+
tx: Readonly<DrizzleTransaction>,
|
|
108
|
+
tableName: string,
|
|
109
|
+
fields: Readonly<Record<string, unknown>>
|
|
110
|
+
): Effect.Effect<Record<string, unknown> | undefined, ValidationError> {
|
|
111
|
+
return Effect.tryPromise({
|
|
112
|
+
try: async () => {
|
|
113
|
+
const clauses = buildInsertClauses(fields)
|
|
114
|
+
if (!clauses) return undefined
|
|
115
|
+
|
|
116
|
+
const result = (await tx.execute(
|
|
117
|
+
sql`INSERT INTO ${sql.identifier(tableName)} (${clauses.columnsClause}) VALUES (${clauses.valuesClause}) RETURNING *`
|
|
118
|
+
)) as readonly Record<string, unknown>[]
|
|
119
|
+
|
|
120
|
+
return result[0] ?? undefined
|
|
121
|
+
},
|
|
122
|
+
catch: handleInsertError,
|
|
123
|
+
})
|
|
124
|
+
}
|