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,695 @@
|
|
|
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 { Schema } from 'effect'
|
|
9
|
+
import { TableSchema } from '@/domain/models/app/table'
|
|
10
|
+
import { detectCycles } from '@/domain/models/app/table/cycle-detection'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Auto-generate table IDs for tables that don't have one
|
|
14
|
+
*
|
|
15
|
+
* Tables without explicit IDs get auto-generated numeric IDs.
|
|
16
|
+
* IDs are assigned sequentially starting from the highest existing ID + 1.
|
|
17
|
+
*/
|
|
18
|
+
const autoGenerateTableIds = (
|
|
19
|
+
tables: ReadonlyArray<Record<string, unknown>>
|
|
20
|
+
): ReadonlyArray<Record<string, unknown>> => {
|
|
21
|
+
// Find the highest existing numeric ID
|
|
22
|
+
const maxId = tables.reduce((max, table) => {
|
|
23
|
+
if (table.id !== undefined && typeof table.id === 'number') {
|
|
24
|
+
return Math.max(max, table.id)
|
|
25
|
+
}
|
|
26
|
+
return max
|
|
27
|
+
}, 0)
|
|
28
|
+
|
|
29
|
+
// Assign IDs to tables without one using reduce (functional pattern)
|
|
30
|
+
const { tablesWithIds } = tables.reduce<{
|
|
31
|
+
tablesWithIds: ReadonlyArray<Record<string, unknown>>
|
|
32
|
+
nextId: number
|
|
33
|
+
}>(
|
|
34
|
+
(acc, table) => {
|
|
35
|
+
if (table.id === undefined) {
|
|
36
|
+
return {
|
|
37
|
+
tablesWithIds: [...acc.tablesWithIds, { ...table, id: acc.nextId }],
|
|
38
|
+
nextId: acc.nextId + 1,
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
...acc,
|
|
43
|
+
tablesWithIds: [...acc.tablesWithIds, table],
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
{ tablesWithIds: [], nextId: maxId + 1 }
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
return tablesWithIds
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Detect circular relationship dependencies between tables.
|
|
54
|
+
* A circular dependency exists when Table A references Table B, and Table B references Table A
|
|
55
|
+
* (directly or through a chain of other tables).
|
|
56
|
+
*
|
|
57
|
+
* Self-referencing relationships (e.g., employees.manager → employees) are NOT considered circular
|
|
58
|
+
* dependencies because they don't prevent table creation order determination.
|
|
59
|
+
*
|
|
60
|
+
* Bidirectional relationships (one-to-many in one direction, many-to-one in the reverse) are also
|
|
61
|
+
* NOT circular dependencies because the foreign key is always on the "many" side, allowing proper
|
|
62
|
+
* table creation order (create "one" side first, then "many" side with FK).
|
|
63
|
+
*
|
|
64
|
+
* Circular dependencies are ALLOWED when at least one side allows NULL (required: false).
|
|
65
|
+
* This enables the INSERT-UPDATE pattern:
|
|
66
|
+
* 1. INSERT with NULL foreign key
|
|
67
|
+
* 2. INSERT second table with reference to first
|
|
68
|
+
* 3. UPDATE first table to complete the circular reference
|
|
69
|
+
* PostgreSQL validates constraints at statement end, making this pattern valid.
|
|
70
|
+
*
|
|
71
|
+
* @param tables - Array of tables to validate
|
|
72
|
+
* @returns Array of table names involved in circular dependencies, or empty array if none found
|
|
73
|
+
*/
|
|
74
|
+
const detectCircularRelationships = (
|
|
75
|
+
tables: ReadonlyArray<{
|
|
76
|
+
readonly name: string
|
|
77
|
+
readonly fields: ReadonlyArray<{
|
|
78
|
+
readonly name: string
|
|
79
|
+
readonly type: string
|
|
80
|
+
readonly relatedTable?: string
|
|
81
|
+
readonly relationType?: string
|
|
82
|
+
readonly required?: boolean
|
|
83
|
+
}>
|
|
84
|
+
}>
|
|
85
|
+
): ReadonlyArray<string> => {
|
|
86
|
+
// Build dependency graph: table name -> tables it references via REQUIRED many-to-one relationships
|
|
87
|
+
// We only track REQUIRED (NOT NULL) relationships because those prevent INSERT-UPDATE pattern
|
|
88
|
+
// Optional (NULL-able) relationships can be created later via UPDATE after both tables exist
|
|
89
|
+
const dependencyGraph: ReadonlyMap<string, ReadonlyArray<string>> = new Map(
|
|
90
|
+
tables.map((table) => {
|
|
91
|
+
const relatedTables = table.fields
|
|
92
|
+
.filter(
|
|
93
|
+
(field) =>
|
|
94
|
+
field.type === 'relationship' &&
|
|
95
|
+
field.relatedTable !== undefined &&
|
|
96
|
+
field.relatedTable !== table.name && // Exclude self-references
|
|
97
|
+
(field.relationType === 'many-to-one' || field.relationType === undefined) && // Only track dependencies from FK side
|
|
98
|
+
field.required !== false // Track required relationships (undefined = required by default, only false = optional)
|
|
99
|
+
)
|
|
100
|
+
.map((field) => field.relatedTable as string)
|
|
101
|
+
return [table.name, relatedTables] as const
|
|
102
|
+
})
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
// Use shared cycle detection utility
|
|
106
|
+
return detectCycles(dependencyGraph)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Detect circular permission inheritance between tables.
|
|
111
|
+
* A circular permission inheritance exists when Table A inherits from Table B,
|
|
112
|
+
* and Table B inherits from Table A (directly or through a chain of other tables).
|
|
113
|
+
*
|
|
114
|
+
* @param tables - Array of tables to validate
|
|
115
|
+
* @returns Array of table names involved in circular permission inheritance, or empty array if none found
|
|
116
|
+
*/
|
|
117
|
+
const detectCircularPermissionInheritance = (
|
|
118
|
+
tables: ReadonlyArray<{
|
|
119
|
+
readonly name: string
|
|
120
|
+
readonly permissions?: {
|
|
121
|
+
readonly inherit?: string
|
|
122
|
+
}
|
|
123
|
+
}>
|
|
124
|
+
): ReadonlyArray<string> => {
|
|
125
|
+
// Build dependency graph: table name -> parent table it inherits permissions from
|
|
126
|
+
const dependencyGraph: ReadonlyMap<string, ReadonlyArray<string>> = new Map(
|
|
127
|
+
tables.map((table) => {
|
|
128
|
+
const inheritFrom = table.permissions?.inherit
|
|
129
|
+
return [table.name, inheritFrom ? [inheritFrom] : []] as const
|
|
130
|
+
})
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
// Use shared cycle detection utility
|
|
134
|
+
return detectCycles(dependencyGraph)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Validate that a relationship field reference is valid.
|
|
139
|
+
*
|
|
140
|
+
* This helper validates that:
|
|
141
|
+
* 1. The relationshipField exists in the current table
|
|
142
|
+
* 2. The relationshipField is a relationship type
|
|
143
|
+
* 3. The relatedField exists in the related table (if applicable)
|
|
144
|
+
*
|
|
145
|
+
* Used by both lookup and rollup field validation.
|
|
146
|
+
*
|
|
147
|
+
* @param params - Validation parameters
|
|
148
|
+
* @returns Error object if validation fails, undefined if valid
|
|
149
|
+
*/
|
|
150
|
+
const validateRelationshipFieldReference = (params: {
|
|
151
|
+
readonly table: {
|
|
152
|
+
readonly name: string
|
|
153
|
+
readonly fields: ReadonlyArray<{
|
|
154
|
+
readonly name: string
|
|
155
|
+
readonly type: string
|
|
156
|
+
}>
|
|
157
|
+
}
|
|
158
|
+
readonly fieldName: string
|
|
159
|
+
readonly relationshipField: string
|
|
160
|
+
readonly relatedField: string | undefined
|
|
161
|
+
readonly tablesByName: ReadonlyMap<
|
|
162
|
+
string,
|
|
163
|
+
{
|
|
164
|
+
readonly name: string
|
|
165
|
+
readonly fields: ReadonlyArray<{
|
|
166
|
+
readonly name: string
|
|
167
|
+
readonly type: string
|
|
168
|
+
}>
|
|
169
|
+
}
|
|
170
|
+
>
|
|
171
|
+
}):
|
|
172
|
+
| {
|
|
173
|
+
readonly table: string
|
|
174
|
+
readonly field: string
|
|
175
|
+
readonly error: string
|
|
176
|
+
}
|
|
177
|
+
| undefined => {
|
|
178
|
+
const { table, fieldName, relationshipField, relatedField, tablesByName } = params
|
|
179
|
+
|
|
180
|
+
// Check if relationshipField exists in the same table
|
|
181
|
+
const fieldInSameTable = table.fields.find((f) => f.name === relationshipField)
|
|
182
|
+
if (!fieldInSameTable) {
|
|
183
|
+
return {
|
|
184
|
+
table: table.name,
|
|
185
|
+
field: fieldName,
|
|
186
|
+
error: `relationshipField "${relationshipField}" not found`,
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Relationship field must be a relationship type
|
|
191
|
+
if (fieldInSameTable.type !== 'relationship') {
|
|
192
|
+
return {
|
|
193
|
+
table: table.name,
|
|
194
|
+
field: fieldName,
|
|
195
|
+
error: `relationshipField "${relationshipField}" must reference a relationship field`,
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Validate relatedField if provided
|
|
200
|
+
if (relatedField !== undefined) {
|
|
201
|
+
const relatedTableName = (fieldInSameTable as { relatedTable?: string }).relatedTable
|
|
202
|
+
if (relatedTableName) {
|
|
203
|
+
const relatedTable = tablesByName.get(relatedTableName)
|
|
204
|
+
if (relatedTable) {
|
|
205
|
+
// Check if relatedField exists in the related table
|
|
206
|
+
// Note: 'id' is always allowed as it's auto-generated (SERIAL primary key)
|
|
207
|
+
const relatedFieldExists =
|
|
208
|
+
relatedField === 'id' || relatedTable.fields.some((f) => f.name === relatedField)
|
|
209
|
+
if (!relatedFieldExists) {
|
|
210
|
+
return {
|
|
211
|
+
table: table.name,
|
|
212
|
+
field: fieldName,
|
|
213
|
+
error: `relatedField "${relatedField}" not found in related table "${relatedTableName}"`,
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return undefined
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Find the related field type for a rollup aggregation.
|
|
225
|
+
*
|
|
226
|
+
* @param params - Lookup parameters
|
|
227
|
+
* @returns The related field type, or undefined if not found
|
|
228
|
+
*/
|
|
229
|
+
const findRelatedFieldType = (params: {
|
|
230
|
+
readonly table: {
|
|
231
|
+
readonly name: string
|
|
232
|
+
readonly fields: ReadonlyArray<{
|
|
233
|
+
readonly name: string
|
|
234
|
+
readonly type: string
|
|
235
|
+
}>
|
|
236
|
+
}
|
|
237
|
+
readonly relationshipField: string
|
|
238
|
+
readonly relatedField: string
|
|
239
|
+
readonly tablesByName: ReadonlyMap<
|
|
240
|
+
string,
|
|
241
|
+
{
|
|
242
|
+
readonly name: string
|
|
243
|
+
readonly fields: ReadonlyArray<{
|
|
244
|
+
readonly name: string
|
|
245
|
+
readonly type: string
|
|
246
|
+
}>
|
|
247
|
+
}
|
|
248
|
+
>
|
|
249
|
+
}): string | undefined => {
|
|
250
|
+
const { table, relationshipField, relatedField, tablesByName } = params
|
|
251
|
+
|
|
252
|
+
// Find the relationship field to get the related table name
|
|
253
|
+
const relationshipFieldObj = table.fields.find((f) => f.name === relationshipField)
|
|
254
|
+
if (!relationshipFieldObj || relationshipFieldObj.type !== 'relationship') {
|
|
255
|
+
return undefined
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const relatedTableName = (relationshipFieldObj as { relatedTable?: string }).relatedTable
|
|
259
|
+
if (!relatedTableName) {
|
|
260
|
+
return undefined
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const relatedTable = tablesByName.get(relatedTableName)
|
|
264
|
+
if (!relatedTable) {
|
|
265
|
+
return undefined
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Find the related field to check its type
|
|
269
|
+
// Note: 'id' is always allowed as it's auto-generated (SERIAL primary key - integer type)
|
|
270
|
+
const relatedFieldObj =
|
|
271
|
+
relatedField === 'id'
|
|
272
|
+
? { name: 'id', type: 'integer' }
|
|
273
|
+
: relatedTable.fields.find((f) => f.name === relatedField)
|
|
274
|
+
|
|
275
|
+
return relatedFieldObj?.type
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Check if field type is numeric.
|
|
280
|
+
*/
|
|
281
|
+
const isNumericFieldType = (fieldType: string): boolean => {
|
|
282
|
+
const numericTypes = ['integer', 'decimal', 'currency', 'percentage', 'duration']
|
|
283
|
+
return numericTypes.includes(fieldType)
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Check if field type is date.
|
|
288
|
+
*/
|
|
289
|
+
const isDateFieldType = (fieldType: string): boolean => {
|
|
290
|
+
return fieldType === 'date'
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Validate sum/avg aggregation requires numeric field.
|
|
295
|
+
*/
|
|
296
|
+
const validateNumericAggregation = (aggregation: string, fieldType: string): string | undefined => {
|
|
297
|
+
if (!isNumericFieldType(fieldType)) {
|
|
298
|
+
return `aggregation function "${aggregation}" is incompatible with field type "${fieldType}" - numeric field required`
|
|
299
|
+
}
|
|
300
|
+
return undefined
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Validate min/max aggregation requires numeric or date field.
|
|
305
|
+
*/
|
|
306
|
+
const validateMinMaxAggregation = (aggregation: string, fieldType: string): string | undefined => {
|
|
307
|
+
if (!isNumericFieldType(fieldType) && !isDateFieldType(fieldType)) {
|
|
308
|
+
return `aggregation function "${aggregation}" is incompatible with field type "${fieldType}" - numeric or date field required`
|
|
309
|
+
}
|
|
310
|
+
return undefined
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Check if aggregation function is compatible with field type.
|
|
315
|
+
*
|
|
316
|
+
* @param aggregation - The aggregation function (sum, avg, min, max, count, counta, countall)
|
|
317
|
+
* @param fieldType - The field type to check compatibility against
|
|
318
|
+
* @returns Error message if incompatible, undefined if valid
|
|
319
|
+
*/
|
|
320
|
+
const checkAggregationCompatibility = (
|
|
321
|
+
aggregation: string,
|
|
322
|
+
fieldType: string
|
|
323
|
+
): string | undefined => {
|
|
324
|
+
const aggregationLower = aggregation.toLowerCase()
|
|
325
|
+
|
|
326
|
+
switch (aggregationLower) {
|
|
327
|
+
case 'sum':
|
|
328
|
+
case 'avg':
|
|
329
|
+
return validateNumericAggregation(aggregation, fieldType)
|
|
330
|
+
case 'min':
|
|
331
|
+
case 'max':
|
|
332
|
+
return validateMinMaxAggregation(aggregation, fieldType)
|
|
333
|
+
case 'count':
|
|
334
|
+
case 'counta':
|
|
335
|
+
case 'countall':
|
|
336
|
+
// Count functions work with any field type
|
|
337
|
+
return undefined
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return undefined
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Validate that a rollup aggregation function is compatible with the related field type.
|
|
345
|
+
*
|
|
346
|
+
* This helper validates that:
|
|
347
|
+
* 1. The aggregation function is supported
|
|
348
|
+
* 2. The aggregation function is compatible with the related field's type
|
|
349
|
+
*
|
|
350
|
+
* Aggregation compatibility:
|
|
351
|
+
* - sum, avg: Only numeric fields (integer, decimal, currency, percentage, duration)
|
|
352
|
+
* - min, max: Numeric and date fields
|
|
353
|
+
* - count, counta, countall: Any field type
|
|
354
|
+
*
|
|
355
|
+
* @param params - Validation parameters
|
|
356
|
+
* @returns Error object if validation fails, undefined if valid
|
|
357
|
+
*/
|
|
358
|
+
const validateRollupAggregation = (params: {
|
|
359
|
+
readonly table: {
|
|
360
|
+
readonly name: string
|
|
361
|
+
readonly fields: ReadonlyArray<{
|
|
362
|
+
readonly name: string
|
|
363
|
+
readonly type: string
|
|
364
|
+
}>
|
|
365
|
+
}
|
|
366
|
+
readonly fieldName: string
|
|
367
|
+
readonly relationshipField: string
|
|
368
|
+
readonly relatedField: string
|
|
369
|
+
readonly aggregation: string
|
|
370
|
+
readonly tablesByName: ReadonlyMap<
|
|
371
|
+
string,
|
|
372
|
+
{
|
|
373
|
+
readonly name: string
|
|
374
|
+
readonly fields: ReadonlyArray<{
|
|
375
|
+
readonly name: string
|
|
376
|
+
readonly type: string
|
|
377
|
+
}>
|
|
378
|
+
}
|
|
379
|
+
>
|
|
380
|
+
}):
|
|
381
|
+
| {
|
|
382
|
+
readonly table: string
|
|
383
|
+
readonly field: string
|
|
384
|
+
readonly error: string
|
|
385
|
+
}
|
|
386
|
+
| undefined => {
|
|
387
|
+
const { table, fieldName, relationshipField, relatedField, aggregation, tablesByName } = params
|
|
388
|
+
|
|
389
|
+
const relatedFieldType = findRelatedFieldType({
|
|
390
|
+
table,
|
|
391
|
+
relationshipField,
|
|
392
|
+
relatedField,
|
|
393
|
+
tablesByName,
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
if (!relatedFieldType) {
|
|
397
|
+
// This should already be caught by validateRelationshipFieldReference
|
|
398
|
+
return undefined
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const error = checkAggregationCompatibility(aggregation, relatedFieldType)
|
|
402
|
+
if (error) {
|
|
403
|
+
return {
|
|
404
|
+
table: table.name,
|
|
405
|
+
field: fieldName,
|
|
406
|
+
error,
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
return undefined
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Validate all rollup fields across all tables.
|
|
415
|
+
*
|
|
416
|
+
* @param tables - Array of tables to validate
|
|
417
|
+
* @param tablesByName - Map of table names to table objects
|
|
418
|
+
* @returns Error object if validation fails, undefined if valid
|
|
419
|
+
*/
|
|
420
|
+
const validateAllRollupFields = (
|
|
421
|
+
tables: ReadonlyArray<{
|
|
422
|
+
readonly name: string
|
|
423
|
+
readonly fields: ReadonlyArray<{
|
|
424
|
+
readonly name: string
|
|
425
|
+
readonly type: string
|
|
426
|
+
}>
|
|
427
|
+
}>,
|
|
428
|
+
tablesByName: ReadonlyMap<
|
|
429
|
+
string,
|
|
430
|
+
{
|
|
431
|
+
readonly name: string
|
|
432
|
+
readonly fields: ReadonlyArray<{
|
|
433
|
+
readonly name: string
|
|
434
|
+
readonly type: string
|
|
435
|
+
}>
|
|
436
|
+
}
|
|
437
|
+
>
|
|
438
|
+
):
|
|
439
|
+
| {
|
|
440
|
+
readonly table: string
|
|
441
|
+
readonly field: string
|
|
442
|
+
readonly error: string
|
|
443
|
+
}
|
|
444
|
+
| undefined => {
|
|
445
|
+
return tables
|
|
446
|
+
.flatMap((table) =>
|
|
447
|
+
table.fields
|
|
448
|
+
.filter((field) => field.type === 'rollup')
|
|
449
|
+
.map((rollupField) => {
|
|
450
|
+
const { relationshipField, relatedField, aggregation } = rollupField as unknown as {
|
|
451
|
+
relationshipField: string
|
|
452
|
+
relatedField: string
|
|
453
|
+
aggregation: string
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// First validate the relationship reference
|
|
457
|
+
const relationshipError = validateRelationshipFieldReference({
|
|
458
|
+
table,
|
|
459
|
+
fieldName: rollupField.name,
|
|
460
|
+
relationshipField,
|
|
461
|
+
relatedField,
|
|
462
|
+
tablesByName,
|
|
463
|
+
})
|
|
464
|
+
|
|
465
|
+
if (relationshipError) {
|
|
466
|
+
return relationshipError
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Then validate aggregation function compatibility with field type
|
|
470
|
+
return validateRollupAggregation({
|
|
471
|
+
table,
|
|
472
|
+
fieldName: rollupField.name,
|
|
473
|
+
relationshipField,
|
|
474
|
+
relatedField,
|
|
475
|
+
aggregation,
|
|
476
|
+
tablesByName,
|
|
477
|
+
})
|
|
478
|
+
})
|
|
479
|
+
.filter((error) => error !== undefined)
|
|
480
|
+
)
|
|
481
|
+
.at(0)
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Data Tables
|
|
486
|
+
*
|
|
487
|
+
* Collection of database tables that define the data structure of your application.
|
|
488
|
+
* Each table represents an entity (e.g., users, products, orders) with fields that
|
|
489
|
+
* define the schema. Tables support relationships, indexes, constraints, and various
|
|
490
|
+
* field types. Tables are the foundation of your application's data model and
|
|
491
|
+
* determine what information can be stored and how it relates.
|
|
492
|
+
*
|
|
493
|
+
* Table IDs can be:
|
|
494
|
+
* - Explicit numeric IDs (e.g., 1, 2, 3)
|
|
495
|
+
* - UUID strings (e.g., '550e8400-e29b-41d4-a716-446655440000')
|
|
496
|
+
* - Simple string identifiers (e.g., 'products', 'users')
|
|
497
|
+
* - Auto-generated (omit the id field and it will be assigned automatically)
|
|
498
|
+
*
|
|
499
|
+
* @example
|
|
500
|
+
* ```typescript
|
|
501
|
+
* const tables = [
|
|
502
|
+
* {
|
|
503
|
+
* id: 1,
|
|
504
|
+
* name: 'users',
|
|
505
|
+
* fields: [
|
|
506
|
+
* { id: 1, name: 'email', type: 'email', required: true },
|
|
507
|
+
* { id: 2, name: 'name', type: 'text', required: true }
|
|
508
|
+
* ]
|
|
509
|
+
* }
|
|
510
|
+
* ]
|
|
511
|
+
* ```
|
|
512
|
+
*
|
|
513
|
+
* @see docs/specifications/roadmap/tables.md for full specification
|
|
514
|
+
*/
|
|
515
|
+
export const TablesSchema = Schema.Array(TableSchema).pipe(
|
|
516
|
+
Schema.transform(
|
|
517
|
+
Schema.Array(TableSchema.pipe(Schema.annotations({ identifier: 'TableWithRequiredId' }))),
|
|
518
|
+
{
|
|
519
|
+
strict: true,
|
|
520
|
+
decode: (tables) =>
|
|
521
|
+
autoGenerateTableIds(tables as ReadonlyArray<Record<string, unknown>>) as ReadonlyArray<
|
|
522
|
+
Schema.Schema.Type<typeof TableSchema>
|
|
523
|
+
>,
|
|
524
|
+
encode: (tables) => tables,
|
|
525
|
+
}
|
|
526
|
+
),
|
|
527
|
+
Schema.filter((tables) => {
|
|
528
|
+
const ids = tables.map((table) => table.id)
|
|
529
|
+
const uniqueIds = new Set(ids)
|
|
530
|
+
return ids.length === uniqueIds.size || 'Table IDs must be unique within the schema'
|
|
531
|
+
}),
|
|
532
|
+
Schema.filter((tables) => {
|
|
533
|
+
const names = tables.map((table) => table.name)
|
|
534
|
+
const uniqueNames = new Set(names)
|
|
535
|
+
return names.length === uniqueNames.size || 'Table names must be unique within the schema'
|
|
536
|
+
}),
|
|
537
|
+
Schema.filter((tables) => {
|
|
538
|
+
const circularTables = detectCircularRelationships(tables)
|
|
539
|
+
if (circularTables.length > 0) {
|
|
540
|
+
return `Circular relationship dependency detected: ${circularTables.join(' -> ')} - cannot resolve table creation order`
|
|
541
|
+
}
|
|
542
|
+
return true
|
|
543
|
+
}),
|
|
544
|
+
Schema.filter((tables) => {
|
|
545
|
+
const circularPermissions = detectCircularPermissionInheritance(tables)
|
|
546
|
+
if (circularPermissions.length > 0) {
|
|
547
|
+
return `Circular permission inheritance detected: ${circularPermissions.join(' -> ')} - inheritance cycle not allowed`
|
|
548
|
+
}
|
|
549
|
+
return true
|
|
550
|
+
}),
|
|
551
|
+
Schema.filter((tables) => {
|
|
552
|
+
// Validate that inherited tables exist
|
|
553
|
+
const tableNames = new Set(tables.map((table) => table.name))
|
|
554
|
+
const invalidInheritance = tables
|
|
555
|
+
.filter((table) => table.permissions?.inherit !== undefined)
|
|
556
|
+
.find((table) => !tableNames.has(table.permissions!.inherit!))
|
|
557
|
+
|
|
558
|
+
if (invalidInheritance) {
|
|
559
|
+
return `Table "${invalidInheritance.name}" inherits from table "${invalidInheritance.permissions!.inherit}" which does not exist`
|
|
560
|
+
}
|
|
561
|
+
return true
|
|
562
|
+
}),
|
|
563
|
+
Schema.filter((tables) => {
|
|
564
|
+
// Create tablesByName map once for all field validations
|
|
565
|
+
const tablesByName = new Map(tables.map((table) => [table.name, table]))
|
|
566
|
+
|
|
567
|
+
// Validate relationship fields reference existing tables
|
|
568
|
+
const invalidRelationship = tables
|
|
569
|
+
.flatMap((table) =>
|
|
570
|
+
table.fields
|
|
571
|
+
.filter((field) => field.type === 'relationship')
|
|
572
|
+
.map((relationshipField) => {
|
|
573
|
+
const { relatedTable } = relationshipField as { relatedTable?: string }
|
|
574
|
+
|
|
575
|
+
if (relatedTable && !tablesByName.has(relatedTable)) {
|
|
576
|
+
return {
|
|
577
|
+
table: table.name,
|
|
578
|
+
field: relationshipField.name,
|
|
579
|
+
relatedTable,
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
return undefined
|
|
584
|
+
})
|
|
585
|
+
.filter((error) => error !== undefined)
|
|
586
|
+
)
|
|
587
|
+
.at(0)
|
|
588
|
+
|
|
589
|
+
if (invalidRelationship) {
|
|
590
|
+
return `Relationship field "${invalidRelationship.table}.${invalidRelationship.field}": relatedTable "${invalidRelationship.relatedTable}" does not exist`
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// Validate lookup fields reference existing relationship fields (either in same table or reverse relationship)
|
|
594
|
+
const invalidLookup = tables
|
|
595
|
+
.flatMap((table) =>
|
|
596
|
+
table.fields
|
|
597
|
+
.filter((field) => field.type === 'lookup')
|
|
598
|
+
.map((lookupField) => {
|
|
599
|
+
const { relationshipField, relatedField } = lookupField as {
|
|
600
|
+
relationshipField: string
|
|
601
|
+
relatedField: string
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// Check if relationshipField exists in the same table (forward lookup)
|
|
605
|
+
const fieldInSameTable = table.fields.find((f) => f.name === relationshipField)
|
|
606
|
+
if (fieldInSameTable) {
|
|
607
|
+
// Use shared validation helper for forward lookup
|
|
608
|
+
return validateRelationshipFieldReference({
|
|
609
|
+
table,
|
|
610
|
+
fieldName: lookupField.name,
|
|
611
|
+
relationshipField,
|
|
612
|
+
relatedField,
|
|
613
|
+
tablesByName,
|
|
614
|
+
})
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// Check if relationshipField exists in other tables (reverse lookup)
|
|
618
|
+
const reverseRelationship = [...tablesByName.values()]
|
|
619
|
+
.flatMap((otherTable) =>
|
|
620
|
+
otherTable.fields
|
|
621
|
+
.filter(
|
|
622
|
+
(field) =>
|
|
623
|
+
field.type === 'relationship' &&
|
|
624
|
+
field.name === relationshipField &&
|
|
625
|
+
(field as { relatedTable?: string }).relatedTable === table.name
|
|
626
|
+
)
|
|
627
|
+
.map(() => ({ found: true, relatedTable: otherTable }))
|
|
628
|
+
)
|
|
629
|
+
.at(0)
|
|
630
|
+
|
|
631
|
+
if (!reverseRelationship) {
|
|
632
|
+
return {
|
|
633
|
+
table: table.name,
|
|
634
|
+
field: lookupField.name,
|
|
635
|
+
error: `relationshipField "${relationshipField}" not found`,
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// For reverse lookup, check if relatedField exists in the table that has the relationship
|
|
640
|
+
if (reverseRelationship.relatedTable) {
|
|
641
|
+
const relatedFieldExists = reverseRelationship.relatedTable.fields.some(
|
|
642
|
+
(f) => f.name === relatedField
|
|
643
|
+
)
|
|
644
|
+
if (!relatedFieldExists) {
|
|
645
|
+
return {
|
|
646
|
+
table: table.name,
|
|
647
|
+
field: lookupField.name,
|
|
648
|
+
error: `relatedField "${relatedField}" not found in related table "${reverseRelationship.relatedTable.name}"`,
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
return undefined
|
|
654
|
+
})
|
|
655
|
+
.filter((error) => error !== undefined)
|
|
656
|
+
)
|
|
657
|
+
.at(0)
|
|
658
|
+
|
|
659
|
+
if (invalidLookup) {
|
|
660
|
+
return `Lookup field "${invalidLookup.table}.${invalidLookup.field}" ${invalidLookup.error}`
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// Validate rollup fields reference existing relationship fields and related fields
|
|
664
|
+
const invalidRollup = validateAllRollupFields(tables, tablesByName)
|
|
665
|
+
|
|
666
|
+
if (invalidRollup) {
|
|
667
|
+
return `Rollup field "${invalidRollup.table}.${invalidRollup.field}" ${invalidRollup.error}`
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
return true
|
|
671
|
+
}),
|
|
672
|
+
Schema.annotations({
|
|
673
|
+
title: 'Data Tables',
|
|
674
|
+
description:
|
|
675
|
+
'Collection of database tables that define the data structure of your application. Each table represents an entity (e.g., users, products, orders) with fields that define the schema. Tables support relationships, indexes, constraints, and various field types. Tables are the foundation of your application data model and determine what information can be stored and how it relates.',
|
|
676
|
+
examples: [
|
|
677
|
+
[
|
|
678
|
+
{
|
|
679
|
+
id: 1,
|
|
680
|
+
name: 'users',
|
|
681
|
+
fields: [
|
|
682
|
+
{ id: 1, name: 'email', type: 'email' as const, required: true },
|
|
683
|
+
{ id: 2, name: 'name', type: 'single-line-text' as const, required: true },
|
|
684
|
+
],
|
|
685
|
+
},
|
|
686
|
+
],
|
|
687
|
+
],
|
|
688
|
+
})
|
|
689
|
+
)
|
|
690
|
+
|
|
691
|
+
export type Tables = Schema.Schema.Type<typeof TablesSchema>
|
|
692
|
+
|
|
693
|
+
// Re-export Table and TableSchema for convenience
|
|
694
|
+
export { TableSchema } from '@/domain/models/app/table'
|
|
695
|
+
export type { Table } from '@/domain/models/app/table'
|