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,154 @@
|
|
|
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 { sanitizeTableName } from '../field-utils'
|
|
9
|
+
import type { Table } from '@/domain/models/app/table'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Generate triggers for created-at fields (set on INSERT, prevent UPDATE)
|
|
13
|
+
* Includes intrinsic created_at column if not explicitly defined
|
|
14
|
+
*/
|
|
15
|
+
export const generateCreatedAtTriggers = (table: Table): readonly string[] => {
|
|
16
|
+
const createdAtFields = table.fields.filter((field) => field.type === 'created-at')
|
|
17
|
+
const hasCreatedAtField = table.fields.some((field) => field.name === 'created_at')
|
|
18
|
+
|
|
19
|
+
// Collect all created_at field names
|
|
20
|
+
const fieldNames = [
|
|
21
|
+
...createdAtFields.map((f) => f.name),
|
|
22
|
+
// Include intrinsic created_at column if not explicitly defined
|
|
23
|
+
...(!hasCreatedAtField ? ['created_at'] : []),
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
if (fieldNames.length === 0) return []
|
|
27
|
+
|
|
28
|
+
const sanitized = sanitizeTableName(table.name)
|
|
29
|
+
const setFunctionName = `set_${sanitized}_created_at`
|
|
30
|
+
const setTriggerName = `a_trigger_${sanitized}_set_created_at` // Prefix with 'a_' to ensure it runs before formula triggers
|
|
31
|
+
const preventFunctionName = `prevent_${sanitized}_created_at_update`
|
|
32
|
+
const preventTriggerName = `a_trigger_${sanitized}_created_at_immutable` // Prefix with 'a_' to ensure it runs before formula triggers
|
|
33
|
+
|
|
34
|
+
return [
|
|
35
|
+
// Create trigger function to set created_at on INSERT
|
|
36
|
+
`CREATE OR REPLACE FUNCTION ${setFunctionName}()
|
|
37
|
+
RETURNS TRIGGER AS $$
|
|
38
|
+
BEGIN
|
|
39
|
+
${fieldNames.map((name) => `IF NEW.${name} IS NULL THEN NEW.${name} = CURRENT_TIMESTAMP; END IF;`).join('\n ')}
|
|
40
|
+
RETURN NEW;
|
|
41
|
+
END;
|
|
42
|
+
$$ LANGUAGE plpgsql`,
|
|
43
|
+
// Create INSERT trigger
|
|
44
|
+
`DROP TRIGGER IF EXISTS ${setTriggerName} ON ${sanitized}`,
|
|
45
|
+
`CREATE TRIGGER ${setTriggerName}
|
|
46
|
+
BEFORE INSERT ON ${sanitized}
|
|
47
|
+
FOR EACH ROW
|
|
48
|
+
EXECUTE FUNCTION ${setFunctionName}()`,
|
|
49
|
+
// Create trigger function to prevent updates
|
|
50
|
+
`CREATE OR REPLACE FUNCTION ${preventFunctionName}()
|
|
51
|
+
RETURNS TRIGGER AS $$
|
|
52
|
+
BEGIN
|
|
53
|
+
${fieldNames.map((name) => `NEW.${name} = OLD.${name};`).join('\n ')}
|
|
54
|
+
RETURN NEW;
|
|
55
|
+
END;
|
|
56
|
+
$$ LANGUAGE plpgsql`,
|
|
57
|
+
// Create UPDATE trigger
|
|
58
|
+
`DROP TRIGGER IF EXISTS ${preventTriggerName} ON ${sanitized}`,
|
|
59
|
+
`CREATE TRIGGER ${preventTriggerName}
|
|
60
|
+
BEFORE UPDATE ON ${sanitized}
|
|
61
|
+
FOR EACH ROW
|
|
62
|
+
EXECUTE FUNCTION ${preventFunctionName}()`,
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Generate trigger to prevent updates to autonumber fields (immutability)
|
|
68
|
+
*/
|
|
69
|
+
export const generateAutonumberTriggers = (table: Table): readonly string[] => {
|
|
70
|
+
const autonumberFields = table.fields.filter((field) => field.type === 'autonumber')
|
|
71
|
+
|
|
72
|
+
if (autonumberFields.length === 0) return []
|
|
73
|
+
|
|
74
|
+
const sanitized = sanitizeTableName(table.name)
|
|
75
|
+
const fieldNames = autonumberFields.map((f) => f.name)
|
|
76
|
+
const triggerFunctionName = `prevent_${sanitized}_autonumber_update`
|
|
77
|
+
const triggerName = `trigger_${sanitized}_autonumber_immutable`
|
|
78
|
+
|
|
79
|
+
return [
|
|
80
|
+
// Create trigger function
|
|
81
|
+
`CREATE OR REPLACE FUNCTION ${triggerFunctionName}()
|
|
82
|
+
RETURNS TRIGGER AS $$
|
|
83
|
+
BEGIN
|
|
84
|
+
${fieldNames.map((name) => `NEW.${name} = OLD.${name};`).join('\n ')}
|
|
85
|
+
RETURN NEW;
|
|
86
|
+
END;
|
|
87
|
+
$$ LANGUAGE plpgsql`,
|
|
88
|
+
// Create trigger
|
|
89
|
+
`DROP TRIGGER IF EXISTS ${triggerName} ON ${sanitized}`,
|
|
90
|
+
`CREATE TRIGGER ${triggerName}
|
|
91
|
+
BEFORE UPDATE ON ${sanitized}
|
|
92
|
+
FOR EACH ROW
|
|
93
|
+
EXECUTE FUNCTION ${triggerFunctionName}()`,
|
|
94
|
+
]
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Generate trigger to automatically update updated-by fields on UPDATE
|
|
99
|
+
*/
|
|
100
|
+
export const generateUpdatedByTriggers = (table: Table): readonly string[] => {
|
|
101
|
+
const updatedByFields = table.fields.filter((field) => field.type === 'updated-by')
|
|
102
|
+
|
|
103
|
+
if (updatedByFields.length === 0) return []
|
|
104
|
+
|
|
105
|
+
const sanitized = sanitizeTableName(table.name)
|
|
106
|
+
|
|
107
|
+
return [
|
|
108
|
+
// Create trigger (fires on UPDATE only - updated_by should be NULL on INSERT)
|
|
109
|
+
`DROP TRIGGER IF EXISTS set_updated_by ON ${sanitized}`,
|
|
110
|
+
`CREATE TRIGGER set_updated_by
|
|
111
|
+
BEFORE UPDATE ON ${sanitized}
|
|
112
|
+
FOR EACH ROW
|
|
113
|
+
EXECUTE FUNCTION set_updated_by()`,
|
|
114
|
+
]
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Generate triggers to automatically set/update updated-at fields on INSERT/UPDATE
|
|
119
|
+
* Includes intrinsic updated_at column if not explicitly defined
|
|
120
|
+
*/
|
|
121
|
+
export const generateUpdatedAtTriggers = (table: Table): readonly string[] => {
|
|
122
|
+
const updatedAtFields = table.fields.filter((field) => field.type === 'updated-at')
|
|
123
|
+
const hasUpdatedAtField = table.fields.some((field) => field.name === 'updated_at')
|
|
124
|
+
|
|
125
|
+
// Collect all updated_at field names
|
|
126
|
+
const fieldNames = [
|
|
127
|
+
...updatedAtFields.map((f) => f.name),
|
|
128
|
+
// Include intrinsic updated_at column if not explicitly defined
|
|
129
|
+
...(!hasUpdatedAtField ? ['updated_at'] : []),
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
if (fieldNames.length === 0) return []
|
|
133
|
+
|
|
134
|
+
const sanitized = sanitizeTableName(table.name)
|
|
135
|
+
const triggerFunctionName = `update_${sanitized}_updated_at`
|
|
136
|
+
const triggerName = `a_trigger_${sanitized}_updated_at` // Prefix with 'a_' to ensure it runs before formula triggers
|
|
137
|
+
|
|
138
|
+
return [
|
|
139
|
+
// Create trigger function (handles both INSERT and UPDATE)
|
|
140
|
+
`CREATE OR REPLACE FUNCTION ${triggerFunctionName}()
|
|
141
|
+
RETURNS TRIGGER AS $$
|
|
142
|
+
BEGIN
|
|
143
|
+
${fieldNames.map((name) => `NEW.${name} = CURRENT_TIMESTAMP;`).join('\n ')}
|
|
144
|
+
RETURN NEW;
|
|
145
|
+
END;
|
|
146
|
+
$$ LANGUAGE plpgsql`,
|
|
147
|
+
// Create trigger for both INSERT and UPDATE
|
|
148
|
+
`DROP TRIGGER IF EXISTS ${triggerName} ON ${sanitized}`,
|
|
149
|
+
`CREATE TRIGGER ${triggerName}
|
|
150
|
+
BEFORE INSERT OR UPDATE ON ${sanitized}
|
|
151
|
+
FOR EACH ROW
|
|
152
|
+
EXECUTE FUNCTION ${triggerFunctionName}()`,
|
|
153
|
+
]
|
|
154
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
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
|
+
* Database Infrastructure Module
|
|
10
|
+
*
|
|
11
|
+
* Provides database access and ORM utilities.
|
|
12
|
+
* Currently uses Drizzle ORM with PostgreSQL via Bun SQL.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* import { Database, DatabaseLive } from '@/infrastructure/database'
|
|
17
|
+
* import { users } from '@/infrastructure/database/schema'
|
|
18
|
+
*
|
|
19
|
+
* const program = Effect.gen(function* () {
|
|
20
|
+
* const db = yield* Database
|
|
21
|
+
* const allUsers = yield* Effect.tryPromise(() => db.select().from(users))
|
|
22
|
+
* return allUsers
|
|
23
|
+
* }).pipe(Effect.provide(DatabaseLive))
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
export { db, type DrizzleDB, type DrizzleTransaction } from './drizzle/db'
|
|
28
|
+
export { Database, DatabaseLive } from './drizzle/layer'
|
|
29
|
+
export * from './drizzle/schema'
|
|
30
|
+
export {
|
|
31
|
+
SessionContextError,
|
|
32
|
+
ForbiddenError,
|
|
33
|
+
UniqueConstraintViolationError,
|
|
34
|
+
ValidationError,
|
|
35
|
+
} from './session-context'
|
|
@@ -0,0 +1,356 @@
|
|
|
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
|
+
* Expression generators for lookup, rollup, and count fields
|
|
10
|
+
*
|
|
11
|
+
* These functions generate SQL subquery expressions for computed columns
|
|
12
|
+
* used in lookup VIEWs.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { generateSqlCondition } from '../filter-operators'
|
|
16
|
+
import { toSingular, generateJunctionTableName } from '../sql/sql-generators'
|
|
17
|
+
import type { Fields } from '@/domain/models/app/table/fields'
|
|
18
|
+
import type { ViewFilterCondition } from '@/domain/models/app/table/views/filters'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Build WHERE clause from filter condition
|
|
22
|
+
*/
|
|
23
|
+
const buildWhereClause = (filter: ViewFilterCondition, aliasPrefix: string): string => {
|
|
24
|
+
const { field, operator, value } = filter
|
|
25
|
+
const column = `${aliasPrefix}.${field}`
|
|
26
|
+
// Use legacy string escaping mode for backward compatibility
|
|
27
|
+
return generateSqlCondition(column, operator, value, { useEscapeSqlString: true })
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Options for generating lookup expressions
|
|
32
|
+
*/
|
|
33
|
+
interface LookupExpressionOptions {
|
|
34
|
+
readonly lookupName: string
|
|
35
|
+
readonly relationshipField: string
|
|
36
|
+
readonly relatedField: string
|
|
37
|
+
readonly filters: ViewFilterCondition | undefined
|
|
38
|
+
readonly tableAlias: string
|
|
39
|
+
readonly actualTableName: string
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Generate reverse lookup expression (one-to-many)
|
|
44
|
+
*/
|
|
45
|
+
const generateReverseLookupExpression = (options: LookupExpressionOptions): string => {
|
|
46
|
+
const { lookupName, relationshipField, relatedField, filters, tableAlias, actualTableName } =
|
|
47
|
+
options
|
|
48
|
+
// Infer table name from lookup field name (e.g., "active_tasks" → "tasks")
|
|
49
|
+
const lookupNameParts = lookupName.split('_')
|
|
50
|
+
const relatedTable = lookupNameParts[lookupNameParts.length - 1] ?? actualTableName
|
|
51
|
+
|
|
52
|
+
const alias = `${relatedTable}_for_${lookupName}`
|
|
53
|
+
const baseCondition = `${alias}.${relationshipField} = ${tableAlias}.id`
|
|
54
|
+
const whereConditions = filters
|
|
55
|
+
? [baseCondition, buildWhereClause(filters, alias)]
|
|
56
|
+
: [baseCondition]
|
|
57
|
+
const whereClause = whereConditions.join(' AND ')
|
|
58
|
+
|
|
59
|
+
return `(
|
|
60
|
+
SELECT STRING_AGG(${alias}.${relatedField}::TEXT, ', ' ORDER BY ${alias}.${relatedField})
|
|
61
|
+
FROM ${relatedTable} AS ${alias}
|
|
62
|
+
WHERE ${whereClause}
|
|
63
|
+
) AS ${lookupName}`
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Options for many-to-many lookup expressions
|
|
68
|
+
*/
|
|
69
|
+
interface ManyToManyLookupOptions {
|
|
70
|
+
readonly lookupName: string
|
|
71
|
+
readonly relatedTable: string
|
|
72
|
+
readonly relatedField: string
|
|
73
|
+
readonly filters: ViewFilterCondition | undefined
|
|
74
|
+
readonly tableAlias: string
|
|
75
|
+
readonly actualTableName: string
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Generate many-to-many lookup expression (through junction table)
|
|
80
|
+
*/
|
|
81
|
+
const generateManyToManyLookupExpression = (options: ManyToManyLookupOptions): string => {
|
|
82
|
+
const { lookupName, relatedTable, relatedField, filters, tableAlias, actualTableName } = options
|
|
83
|
+
const alias = `${relatedTable}_for_${lookupName}`
|
|
84
|
+
const junctionTable = generateJunctionTableName(actualTableName, relatedTable)
|
|
85
|
+
const junctionAlias = `junction_${lookupName}`
|
|
86
|
+
const foreignKeyInJunction = `${toSingular(actualTableName)}_id`
|
|
87
|
+
const relatedForeignKeyInJunction = `${toSingular(relatedTable)}_id`
|
|
88
|
+
|
|
89
|
+
const baseCondition = `${junctionAlias}.${foreignKeyInJunction} = ${tableAlias}.id`
|
|
90
|
+
const joinCondition = `${alias}.id = ${junctionAlias}.${relatedForeignKeyInJunction}`
|
|
91
|
+
const whereConditions = filters
|
|
92
|
+
? [baseCondition, buildWhereClause(filters, alias)]
|
|
93
|
+
: [baseCondition]
|
|
94
|
+
const whereClause = whereConditions.join(' AND ')
|
|
95
|
+
|
|
96
|
+
return `(
|
|
97
|
+
SELECT STRING_AGG(${alias}.${relatedField}::TEXT, ', ' ORDER BY ${alias}.${relatedField})
|
|
98
|
+
FROM ${junctionTable} AS ${junctionAlias}
|
|
99
|
+
INNER JOIN ${relatedTable} AS ${alias} ON ${joinCondition}
|
|
100
|
+
WHERE ${whereClause}
|
|
101
|
+
) AS ${lookupName}`
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Options for forward lookup expressions
|
|
106
|
+
*/
|
|
107
|
+
interface ForwardLookupOptions {
|
|
108
|
+
readonly lookupName: string
|
|
109
|
+
readonly relationshipField: string
|
|
110
|
+
readonly relatedTable: string
|
|
111
|
+
readonly relatedField: string
|
|
112
|
+
readonly filters: ViewFilterCondition | undefined
|
|
113
|
+
readonly tableAlias: string
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Generate forward lookup expression (many-to-one)
|
|
118
|
+
*/
|
|
119
|
+
const generateForwardLookupExpression = (options: ForwardLookupOptions): string => {
|
|
120
|
+
const { lookupName, relationshipField, relatedTable, relatedField, filters, tableAlias } = options
|
|
121
|
+
const alias = `${relatedTable}_for_${lookupName}`
|
|
122
|
+
|
|
123
|
+
if (filters) {
|
|
124
|
+
const whereClause = buildWhereClause(filters, alias)
|
|
125
|
+
return `(
|
|
126
|
+
SELECT ${alias}.${relatedField}
|
|
127
|
+
FROM ${relatedTable} AS ${alias}
|
|
128
|
+
WHERE ${alias}.id = ${tableAlias}.${relationshipField} AND ${whereClause}
|
|
129
|
+
) AS ${lookupName}`
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Direct column reference via LEFT JOIN (handled in main VIEW SELECT)
|
|
133
|
+
return `${alias}.${relatedField} AS ${lookupName}`
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Check if field is a many-to-many relationship with related table
|
|
138
|
+
*/
|
|
139
|
+
const isManyToManyWithRelatedTable = (
|
|
140
|
+
field: Fields[number]
|
|
141
|
+
): field is Fields[number] & { relationType: 'many-to-many'; relatedTable: string } =>
|
|
142
|
+
'relationType' in field &&
|
|
143
|
+
field.relationType === 'many-to-many' &&
|
|
144
|
+
'relatedTable' in field &&
|
|
145
|
+
typeof field.relatedTable === 'string'
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Check if field has a related table
|
|
149
|
+
*/
|
|
150
|
+
const hasRelatedTable = (
|
|
151
|
+
field: Fields[number]
|
|
152
|
+
): field is Fields[number] & { relatedTable: string } =>
|
|
153
|
+
'relatedTable' in field && typeof field.relatedTable === 'string'
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Generate lookup column expression
|
|
157
|
+
* Handles forward (many-to-one), reverse (one-to-many), and many-to-many lookups
|
|
158
|
+
*/
|
|
159
|
+
export const generateLookupExpression = (
|
|
160
|
+
lookupField: Fields[number] & {
|
|
161
|
+
readonly type: 'lookup'
|
|
162
|
+
readonly relationshipField: string
|
|
163
|
+
readonly relatedField: string
|
|
164
|
+
readonly filters?: ViewFilterCondition
|
|
165
|
+
},
|
|
166
|
+
tableAlias: string,
|
|
167
|
+
allFields: readonly Fields[number][],
|
|
168
|
+
actualTableName: string
|
|
169
|
+
): string => {
|
|
170
|
+
const { name: lookupName, relationshipField, relatedField, filters } = lookupField
|
|
171
|
+
const relationshipFieldDef = allFields.find((f) => f.name === relationshipField)
|
|
172
|
+
const baseOptions = { lookupName, relationshipField, relatedField, filters, tableAlias }
|
|
173
|
+
|
|
174
|
+
// Reverse lookup (relationship field not in current table)
|
|
175
|
+
if (!relationshipFieldDef || relationshipFieldDef.type !== 'relationship') {
|
|
176
|
+
return generateReverseLookupExpression({ ...baseOptions, actualTableName })
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Many-to-many lookup (via junction table)
|
|
180
|
+
if (isManyToManyWithRelatedTable(relationshipFieldDef)) {
|
|
181
|
+
return generateManyToManyLookupExpression({
|
|
182
|
+
...baseOptions,
|
|
183
|
+
relatedTable: relationshipFieldDef.relatedTable,
|
|
184
|
+
actualTableName,
|
|
185
|
+
})
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Forward lookup (many-to-one)
|
|
189
|
+
if (hasRelatedTable(relationshipFieldDef)) {
|
|
190
|
+
return generateForwardLookupExpression({
|
|
191
|
+
...baseOptions,
|
|
192
|
+
relatedTable: relationshipFieldDef.relatedTable,
|
|
193
|
+
})
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Fallback: NULL if relationship is invalid
|
|
197
|
+
return `NULL AS ${lookupName}`
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Map aggregation function name to PostgreSQL aggregate function
|
|
202
|
+
*/
|
|
203
|
+
const mapAggregationToPostgres = (aggregation: string, relatedField: string): string => {
|
|
204
|
+
const upperAgg = aggregation.toUpperCase()
|
|
205
|
+
|
|
206
|
+
switch (upperAgg) {
|
|
207
|
+
case 'SUM':
|
|
208
|
+
return `SUM(${relatedField})`
|
|
209
|
+
case 'COUNT':
|
|
210
|
+
return `COUNT(${relatedField})`
|
|
211
|
+
case 'AVG':
|
|
212
|
+
return `AVG(${relatedField})`
|
|
213
|
+
case 'MIN':
|
|
214
|
+
return `MIN(${relatedField})`
|
|
215
|
+
case 'MAX':
|
|
216
|
+
return `MAX(${relatedField})`
|
|
217
|
+
case 'COUNTA':
|
|
218
|
+
return `COUNT(CASE WHEN ${relatedField} IS NOT NULL AND ${relatedField} != '' THEN 1 END)`
|
|
219
|
+
case 'COUNTALL':
|
|
220
|
+
return `COUNT(*)`
|
|
221
|
+
case 'ARRAYUNIQUE':
|
|
222
|
+
return `ARRAY_AGG(DISTINCT ${relatedField} ORDER BY ${relatedField})`
|
|
223
|
+
default:
|
|
224
|
+
return `SUM(${relatedField})`
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Generate default value for empty aggregation results
|
|
230
|
+
*/
|
|
231
|
+
const getDefaultValueForAggregation = (aggregation: string): string => {
|
|
232
|
+
const upperAgg = aggregation.toUpperCase()
|
|
233
|
+
|
|
234
|
+
switch (upperAgg) {
|
|
235
|
+
case 'AVG':
|
|
236
|
+
case 'MIN':
|
|
237
|
+
case 'MAX':
|
|
238
|
+
return 'NULL'
|
|
239
|
+
case 'ARRAYUNIQUE':
|
|
240
|
+
return 'ARRAY[]::TEXT[]'
|
|
241
|
+
default:
|
|
242
|
+
return '0'
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Generate rollup column expression with aggregation
|
|
248
|
+
*/
|
|
249
|
+
export const generateRollupExpression = (
|
|
250
|
+
rollupField: Fields[number] & {
|
|
251
|
+
readonly type: 'rollup'
|
|
252
|
+
readonly relationshipField: string
|
|
253
|
+
readonly relatedField: string
|
|
254
|
+
readonly aggregation: string
|
|
255
|
+
readonly filters?: ViewFilterCondition
|
|
256
|
+
},
|
|
257
|
+
tableName: string,
|
|
258
|
+
allFields: readonly Fields[number][]
|
|
259
|
+
): string => {
|
|
260
|
+
const { name: rollupName, relationshipField, relatedField, aggregation, filters } = rollupField
|
|
261
|
+
|
|
262
|
+
const relationshipFieldDef = allFields.find((f) => f.name === relationshipField)
|
|
263
|
+
|
|
264
|
+
if (!relationshipFieldDef || relationshipFieldDef.type !== 'relationship') {
|
|
265
|
+
return `${getDefaultValueForAggregation(aggregation)} AS ${rollupName}`
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (
|
|
269
|
+
!('relatedTable' in relationshipFieldDef) ||
|
|
270
|
+
typeof relationshipFieldDef.relatedTable !== 'string'
|
|
271
|
+
) {
|
|
272
|
+
return `${getDefaultValueForAggregation(aggregation)} AS ${rollupName}`
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const { relatedTable } = relationshipFieldDef
|
|
276
|
+
const alias = `${relatedTable}_for_${rollupName}`
|
|
277
|
+
|
|
278
|
+
const aggregationExpr = mapAggregationToPostgres(aggregation, `${alias}.${relatedField}`)
|
|
279
|
+
const defaultValue = getDefaultValueForAggregation(aggregation)
|
|
280
|
+
|
|
281
|
+
// Determine the foreign key column in the related table
|
|
282
|
+
const foreignKeyColumn =
|
|
283
|
+
'foreignKey' in relationshipFieldDef && typeof relationshipFieldDef.foreignKey === 'string'
|
|
284
|
+
? relationshipFieldDef.foreignKey
|
|
285
|
+
: relationshipField
|
|
286
|
+
|
|
287
|
+
const baseCondition = `${alias}.${foreignKeyColumn} = ${tableName}.id`
|
|
288
|
+
const whereConditions = filters
|
|
289
|
+
? [baseCondition, buildWhereClause(filters, alias)]
|
|
290
|
+
: [baseCondition]
|
|
291
|
+
|
|
292
|
+
const whereClause = whereConditions.join(' AND ')
|
|
293
|
+
|
|
294
|
+
return `COALESCE(
|
|
295
|
+
(SELECT ${aggregationExpr}
|
|
296
|
+
FROM ${relatedTable} AS ${alias}
|
|
297
|
+
WHERE ${whereClause}),
|
|
298
|
+
${defaultValue}
|
|
299
|
+
) AS ${rollupName}`
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Generate count column expression with optional filtering
|
|
304
|
+
* Counts linked records from a relationship field, with optional conditions
|
|
305
|
+
*/
|
|
306
|
+
export const generateCountExpression = (
|
|
307
|
+
countField: Fields[number] & {
|
|
308
|
+
readonly type: 'count'
|
|
309
|
+
readonly relationshipField: string
|
|
310
|
+
readonly conditions?: readonly ViewFilterCondition[]
|
|
311
|
+
},
|
|
312
|
+
tableName: string,
|
|
313
|
+
allFields: readonly Fields[number][]
|
|
314
|
+
): string => {
|
|
315
|
+
const { name: countName, relationshipField, conditions } = countField
|
|
316
|
+
|
|
317
|
+
const relationshipFieldDef = allFields.find((f) => f.name === relationshipField)
|
|
318
|
+
|
|
319
|
+
// Count field must reference a valid relationship field in same table
|
|
320
|
+
if (!relationshipFieldDef || relationshipFieldDef.type !== 'relationship') {
|
|
321
|
+
return `0 AS ${countName}`
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (
|
|
325
|
+
!('relatedTable' in relationshipFieldDef) ||
|
|
326
|
+
typeof relationshipFieldDef.relatedTable !== 'string'
|
|
327
|
+
) {
|
|
328
|
+
return `0 AS ${countName}`
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const { relatedTable } = relationshipFieldDef
|
|
332
|
+
const alias = `${relatedTable}_for_${countName}`
|
|
333
|
+
|
|
334
|
+
// Determine the foreign key column in the related table
|
|
335
|
+
const foreignKeyColumn =
|
|
336
|
+
'foreignKey' in relationshipFieldDef && typeof relationshipFieldDef.foreignKey === 'string'
|
|
337
|
+
? relationshipFieldDef.foreignKey
|
|
338
|
+
: relationshipField
|
|
339
|
+
|
|
340
|
+
// Build WHERE clause with base condition + optional filter conditions
|
|
341
|
+
const baseCondition = `${alias}.${foreignKeyColumn} = ${tableName}.id`
|
|
342
|
+
|
|
343
|
+
// Convert conditions array to WHERE clauses
|
|
344
|
+
const filterConditions = conditions?.map((condition) => buildWhereClause(condition, alias)) ?? []
|
|
345
|
+
|
|
346
|
+
const whereConditions = [baseCondition, ...filterConditions]
|
|
347
|
+
const whereClause = whereConditions.join(' AND ')
|
|
348
|
+
|
|
349
|
+
// Use COALESCE to ensure 0 instead of NULL when no records match
|
|
350
|
+
return `COALESCE(
|
|
351
|
+
(SELECT COUNT(*)
|
|
352
|
+
FROM ${relatedTable} AS ${alias}
|
|
353
|
+
WHERE ${whereClause}),
|
|
354
|
+
0
|
|
355
|
+
) AS ${countName}`
|
|
356
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
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 { toSingular, generateJunctionTableName } from '../sql/sql-generators'
|
|
9
|
+
import { buildWhereClause } from './lookup-view-helpers'
|
|
10
|
+
import type { ViewFilterCondition } from '@/domain/models/app/table/views/filters'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Configuration for lookup expression generation
|
|
14
|
+
*/
|
|
15
|
+
export type LookupExpressionConfig = {
|
|
16
|
+
readonly lookupName: string
|
|
17
|
+
readonly relationshipField: string
|
|
18
|
+
readonly relatedField: string
|
|
19
|
+
readonly filters: ViewFilterCondition | undefined
|
|
20
|
+
readonly tableAlias: string
|
|
21
|
+
readonly actualTableName: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Configuration for many-to-many lookup expression
|
|
26
|
+
*/
|
|
27
|
+
export type ManyToManyLookupConfig = {
|
|
28
|
+
readonly lookupName: string
|
|
29
|
+
readonly relatedTable: string
|
|
30
|
+
readonly relatedField: string
|
|
31
|
+
readonly filters: ViewFilterCondition | undefined
|
|
32
|
+
readonly tableAlias: string
|
|
33
|
+
readonly actualTableName: string
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Configuration for forward lookup expression
|
|
38
|
+
*/
|
|
39
|
+
export type ForwardLookupConfig = {
|
|
40
|
+
readonly lookupName: string
|
|
41
|
+
readonly relationshipField: string
|
|
42
|
+
readonly relatedTable: string
|
|
43
|
+
readonly relatedField: string
|
|
44
|
+
readonly filters: ViewFilterCondition | undefined
|
|
45
|
+
readonly tableAlias: string
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Generate reverse lookup expression (one-to-many)
|
|
50
|
+
*/
|
|
51
|
+
export const generateReverseLookupExpression = (config: LookupExpressionConfig): string => {
|
|
52
|
+
const { lookupName, relationshipField, relatedField, filters, tableAlias, actualTableName } =
|
|
53
|
+
config
|
|
54
|
+
// Infer table name from lookup field name (e.g., "active_tasks" → "tasks")
|
|
55
|
+
const lookupNameParts = lookupName.split('_')
|
|
56
|
+
const relatedTable = lookupNameParts[lookupNameParts.length - 1] ?? actualTableName
|
|
57
|
+
|
|
58
|
+
const alias = `${relatedTable}_for_${lookupName}`
|
|
59
|
+
const baseCondition = `${alias}.${relationshipField} = ${tableAlias}.id`
|
|
60
|
+
const whereConditions = filters
|
|
61
|
+
? [baseCondition, buildWhereClause(filters, alias)]
|
|
62
|
+
: [baseCondition]
|
|
63
|
+
const whereClause = whereConditions.join(' AND ')
|
|
64
|
+
|
|
65
|
+
return `(
|
|
66
|
+
SELECT STRING_AGG(${alias}.${relatedField}::TEXT, ', ' ORDER BY ${alias}.${relatedField})
|
|
67
|
+
FROM ${relatedTable} AS ${alias}
|
|
68
|
+
WHERE ${whereClause}
|
|
69
|
+
) AS ${lookupName}`
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Generate many-to-many lookup expression (through junction table)
|
|
74
|
+
*/
|
|
75
|
+
export const generateManyToManyLookupExpression = (config: ManyToManyLookupConfig): string => {
|
|
76
|
+
const { lookupName, relatedTable, relatedField, filters, tableAlias, actualTableName } = config
|
|
77
|
+
const alias = `${relatedTable}_for_${lookupName}`
|
|
78
|
+
const junctionTable = generateJunctionTableName(actualTableName, relatedTable)
|
|
79
|
+
const junctionAlias = `junction_${lookupName}`
|
|
80
|
+
const foreignKeyInJunction = `${toSingular(actualTableName)}_id`
|
|
81
|
+
const relatedForeignKeyInJunction = `${toSingular(relatedTable)}_id`
|
|
82
|
+
|
|
83
|
+
const baseCondition = `${junctionAlias}.${foreignKeyInJunction} = ${tableAlias}.id`
|
|
84
|
+
const joinCondition = `${alias}.id = ${junctionAlias}.${relatedForeignKeyInJunction}`
|
|
85
|
+
const whereConditions = filters
|
|
86
|
+
? [baseCondition, buildWhereClause(filters, alias)]
|
|
87
|
+
: [baseCondition]
|
|
88
|
+
const whereClause = whereConditions.join(' AND ')
|
|
89
|
+
|
|
90
|
+
return `(
|
|
91
|
+
SELECT STRING_AGG(${alias}.${relatedField}::TEXT, ', ' ORDER BY ${alias}.${relatedField})
|
|
92
|
+
FROM ${junctionTable} AS ${junctionAlias}
|
|
93
|
+
INNER JOIN ${relatedTable} AS ${alias} ON ${joinCondition}
|
|
94
|
+
WHERE ${whereClause}
|
|
95
|
+
) AS ${lookupName}`
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Generate forward lookup expression (many-to-one)
|
|
100
|
+
*/
|
|
101
|
+
export const generateForwardLookupExpression = (config: ForwardLookupConfig): string => {
|
|
102
|
+
const { lookupName, relationshipField, relatedTable, relatedField, filters, tableAlias } = config
|
|
103
|
+
const alias = `${relatedTable}_for_${lookupName}`
|
|
104
|
+
|
|
105
|
+
if (filters) {
|
|
106
|
+
const whereClause = buildWhereClause(filters, alias)
|
|
107
|
+
return `(
|
|
108
|
+
SELECT ${alias}.${relatedField}
|
|
109
|
+
FROM ${relatedTable} AS ${alias}
|
|
110
|
+
WHERE ${alias}.id = ${tableAlias}.${relationshipField} AND ${whereClause}
|
|
111
|
+
) AS ${lookupName}`
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Direct column reference via LEFT JOIN (handled in main VIEW SELECT)
|
|
115
|
+
return `${alias}.${relatedField} AS ${lookupName}`
|
|
116
|
+
}
|