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,286 @@
|
|
|
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 { shouldCreateDatabaseColumn } from '../field-utils'
|
|
9
|
+
import { generateColumnDefinition, isFieldNotNull } from '../sql/sql-generators'
|
|
10
|
+
import {
|
|
11
|
+
normalizeDataType,
|
|
12
|
+
doesColumnTypeMatch,
|
|
13
|
+
generateAlterColumnTypeStatement,
|
|
14
|
+
} from './type-utils'
|
|
15
|
+
import type { Table } from '@/domain/models/app/table'
|
|
16
|
+
import type { Fields } from '@/domain/models/app/table/fields'
|
|
17
|
+
|
|
18
|
+
/** Column info from database */
|
|
19
|
+
export type ExistingColumnInfo = {
|
|
20
|
+
dataType: string
|
|
21
|
+
isNullable: string
|
|
22
|
+
columnDefault: string | null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Check if id column needs to be recreated due to type mismatch
|
|
27
|
+
*/
|
|
28
|
+
export const needsIdColumnRecreation = (
|
|
29
|
+
existingColumns: ReadonlyMap<string, ExistingColumnInfo>,
|
|
30
|
+
shouldProtectIdColumn: boolean
|
|
31
|
+
): boolean => {
|
|
32
|
+
if (!shouldProtectIdColumn) return false
|
|
33
|
+
if (!existingColumns.has('id')) return false
|
|
34
|
+
|
|
35
|
+
const idType = normalizeDataType(existingColumns.get('id')!.dataType)
|
|
36
|
+
return idType !== 'integer' && idType !== 'serial'
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Find columns that should be added to the table
|
|
41
|
+
* Excludes columns that exist but have type mismatches (those will be altered, not dropped/added)
|
|
42
|
+
*/
|
|
43
|
+
export const findColumnsToAdd = (
|
|
44
|
+
table: Table,
|
|
45
|
+
existingColumns: ReadonlyMap<string, ExistingColumnInfo>,
|
|
46
|
+
renamedNewNames: ReadonlySet<string>
|
|
47
|
+
): readonly Fields[number][] =>
|
|
48
|
+
table.fields.filter((field) => {
|
|
49
|
+
if (!shouldCreateDatabaseColumn(field)) return false // Skip UI-only fields
|
|
50
|
+
if (renamedNewNames.has(field.name)) return false // Skip renamed fields
|
|
51
|
+
if (!existingColumns.has(field.name)) return true // New column (needs ADD COLUMN)
|
|
52
|
+
// Existing columns with type mismatches will be handled by ALTER COLUMN TYPE
|
|
53
|
+
return false
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Find columns that should be dropped from the table
|
|
58
|
+
* Excludes columns with type mismatches (those will be altered, not dropped)
|
|
59
|
+
*/
|
|
60
|
+
export const findColumnsToDrop = (
|
|
61
|
+
existingColumns: ReadonlyMap<string, ExistingColumnInfo>,
|
|
62
|
+
schemaFieldsByName: ReadonlyMap<string, Fields[number]>,
|
|
63
|
+
shouldProtectIdColumn: boolean,
|
|
64
|
+
renamedOldNames: ReadonlySet<string>
|
|
65
|
+
): readonly string[] =>
|
|
66
|
+
Array.from(existingColumns.keys()).filter((columnName) => {
|
|
67
|
+
// Never drop protected id column (it's already the correct type at this point)
|
|
68
|
+
if (shouldProtectIdColumn && columnName === 'id') return false
|
|
69
|
+
|
|
70
|
+
// Never drop intrinsic special fields (APP-TABLES-SPECIAL-FIELDS-007)
|
|
71
|
+
// These are automatic timestamp columns managed by triggers
|
|
72
|
+
if (columnName === 'created_at') return false
|
|
73
|
+
if (columnName === 'updated_at') return false
|
|
74
|
+
if (columnName === 'deleted_at') return false
|
|
75
|
+
|
|
76
|
+
// Don't drop if it's being renamed
|
|
77
|
+
if (renamedOldNames.has(columnName)) return false
|
|
78
|
+
|
|
79
|
+
// Drop if not in schema (but only if it's not a UI-only field)
|
|
80
|
+
if (!schemaFieldsByName.has(columnName)) return true
|
|
81
|
+
|
|
82
|
+
const field = schemaFieldsByName.get(columnName)!
|
|
83
|
+
|
|
84
|
+
// If this is a UI-only field that shouldn't have a column, drop it
|
|
85
|
+
if (!shouldCreateDatabaseColumn(field)) return true
|
|
86
|
+
|
|
87
|
+
// Existing columns with type mismatches will be handled by ALTER COLUMN TYPE (not dropped)
|
|
88
|
+
return false
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Filter fields that should be checked for schema modifications
|
|
93
|
+
* Excludes UI-only fields, renamed fields, and fields not in the database
|
|
94
|
+
*/
|
|
95
|
+
export const filterModifiableFields = (
|
|
96
|
+
fields: readonly Fields[number][],
|
|
97
|
+
existingColumns: ReadonlyMap<string, ExistingColumnInfo>,
|
|
98
|
+
renamedNewNames: ReadonlySet<string>
|
|
99
|
+
): readonly Fields[number][] =>
|
|
100
|
+
fields.filter((field) => {
|
|
101
|
+
// Skip UI-only fields, renamed fields, and fields not in database
|
|
102
|
+
if (!shouldCreateDatabaseColumn(field)) return false
|
|
103
|
+
if (renamedNewNames.has(field.name)) return false
|
|
104
|
+
if (!existingColumns.has(field.name)) return false
|
|
105
|
+
return true
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Find columns that need type changes
|
|
110
|
+
* Returns ALTER COLUMN TYPE statements for type mismatches
|
|
111
|
+
*/
|
|
112
|
+
export const findTypeChanges = (
|
|
113
|
+
table: Table,
|
|
114
|
+
existingColumns: ReadonlyMap<string, ExistingColumnInfo>,
|
|
115
|
+
renamedNewNames: ReadonlySet<string>
|
|
116
|
+
): readonly string[] =>
|
|
117
|
+
filterModifiableFields(table.fields, existingColumns, renamedNewNames).flatMap((field) => {
|
|
118
|
+
const existing = existingColumns.get(field.name)!
|
|
119
|
+
if (doesColumnTypeMatch(field, existing.dataType)) return []
|
|
120
|
+
|
|
121
|
+
// Type mismatch detected - generate ALTER COLUMN TYPE statement
|
|
122
|
+
return [generateAlterColumnTypeStatement(table.name, field, existing.dataType)]
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Generate validation query to check if existing data contains NULL values
|
|
127
|
+
* Returns validation query that will throw error if NULL values exist
|
|
128
|
+
*/
|
|
129
|
+
export const generateNotNullValidationQuery = (tableName: string, fieldName: string): string => `
|
|
130
|
+
DO $$
|
|
131
|
+
DECLARE
|
|
132
|
+
null_count INTEGER;
|
|
133
|
+
BEGIN
|
|
134
|
+
SELECT COUNT(*) INTO null_count
|
|
135
|
+
FROM ${tableName}
|
|
136
|
+
WHERE ${fieldName} IS NULL;
|
|
137
|
+
|
|
138
|
+
IF null_count > 0 THEN
|
|
139
|
+
RAISE EXCEPTION 'Migration failed: cannot add NOT NULL constraint to column "${fieldName}" in table "${tableName}" because % existing row(s) contain null values. Either provide a default value or update existing rows first.', null_count;
|
|
140
|
+
END IF;
|
|
141
|
+
END$$;
|
|
142
|
+
`
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Generate backfill query to update NULL values with the default
|
|
146
|
+
* Returns UPDATE statement that sets NULL values to the default value
|
|
147
|
+
*/
|
|
148
|
+
export const generateBackfillQuery = (table: Table, field: Fields[number]): string => {
|
|
149
|
+
const columnDef = generateColumnDefinition(field, false, table.fields)
|
|
150
|
+
// Extract DEFAULT clause from column definition
|
|
151
|
+
// Handle quoted strings (DEFAULT 'value'), unquoted values (DEFAULT 42), and functions (DEFAULT NOW())
|
|
152
|
+
const defaultMatch = columnDef.match(/DEFAULT\s+('(?:[^']|'')*'|[^\s]+)/)
|
|
153
|
+
if (!defaultMatch) {
|
|
154
|
+
// This shouldn't happen if hasDefault is true, but handle gracefully
|
|
155
|
+
return ''
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const defaultClause = defaultMatch[1]
|
|
159
|
+
return `UPDATE ${table.name} SET ${field.name} = ${defaultClause} WHERE ${field.name} IS NULL`
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Find columns that need nullability changes
|
|
164
|
+
* Returns ALTER COLUMN statements for SET NOT NULL / DROP NOT NULL
|
|
165
|
+
* CRITICAL: Validates existing data before applying NOT NULL constraint
|
|
166
|
+
* Migration will FAIL if any existing data contains NULL values without a default
|
|
167
|
+
*
|
|
168
|
+
* When a default value is provided:
|
|
169
|
+
* 1. Set the default value on the column (handled by findDefaultValueChanges)
|
|
170
|
+
* 2. Backfill existing NULL values with the default
|
|
171
|
+
* 3. Then set NOT NULL
|
|
172
|
+
*/
|
|
173
|
+
export const findNullabilityChanges = (
|
|
174
|
+
table: Table,
|
|
175
|
+
existingColumns: ReadonlyMap<string, ExistingColumnInfo>,
|
|
176
|
+
renamedNewNames: ReadonlySet<string>,
|
|
177
|
+
primaryKeyFields: readonly string[]
|
|
178
|
+
): readonly string[] =>
|
|
179
|
+
filterModifiableFields(table.fields, existingColumns, renamedNewNames).flatMap((field) => {
|
|
180
|
+
const existing = existingColumns.get(field.name)!
|
|
181
|
+
const isPrimaryKey = primaryKeyFields.includes(field.name)
|
|
182
|
+
const shouldBeNotNull = isFieldNotNull(field, isPrimaryKey)
|
|
183
|
+
const currentlyNotNull = existing.isNullable === 'NO'
|
|
184
|
+
|
|
185
|
+
// If nullability differs, generate ALTER COLUMN statement
|
|
186
|
+
if (shouldBeNotNull && !currentlyNotNull) {
|
|
187
|
+
// Check if field has a default value
|
|
188
|
+
const hasDefault = 'default' in field && field.default !== undefined
|
|
189
|
+
|
|
190
|
+
if (hasDefault) {
|
|
191
|
+
// If has default: backfill NULL values, then set NOT NULL
|
|
192
|
+
const backfillQuery = generateBackfillQuery(table, field)
|
|
193
|
+
return [
|
|
194
|
+
...(backfillQuery ? [backfillQuery] : []),
|
|
195
|
+
`ALTER TABLE ${table.name} ALTER COLUMN ${field.name} SET NOT NULL`,
|
|
196
|
+
]
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// If no default value, validate that existing data doesn't contain NULL
|
|
200
|
+
// This prevents migration from failing with PostgreSQL's "column contains null values" error
|
|
201
|
+
return [
|
|
202
|
+
generateNotNullValidationQuery(table.name, field.name),
|
|
203
|
+
`ALTER TABLE ${table.name} ALTER COLUMN ${field.name} SET NOT NULL`,
|
|
204
|
+
]
|
|
205
|
+
}
|
|
206
|
+
if (!shouldBeNotNull && currentlyNotNull && !isPrimaryKey) {
|
|
207
|
+
// Only DROP NOT NULL if it's not a primary key or auto-managed field
|
|
208
|
+
return [`ALTER TABLE ${table.name} ALTER COLUMN ${field.name} DROP NOT NULL`]
|
|
209
|
+
}
|
|
210
|
+
return []
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Find columns that need default value changes
|
|
215
|
+
* Returns ALTER COLUMN statements for SET DEFAULT or DROP DEFAULT
|
|
216
|
+
*/
|
|
217
|
+
export const findDefaultValueChanges = (
|
|
218
|
+
table: Table,
|
|
219
|
+
existingColumns: ReadonlyMap<string, ExistingColumnInfo>,
|
|
220
|
+
renamedNewNames: ReadonlySet<string>,
|
|
221
|
+
previousSchema?: { readonly tables: readonly object[] }
|
|
222
|
+
): readonly string[] => {
|
|
223
|
+
// Find previous table definition to compare default values
|
|
224
|
+
const previousTable = previousSchema?.tables.find(
|
|
225
|
+
(t: object) => 'name' in t && t.name === table.name
|
|
226
|
+
) as
|
|
227
|
+
| {
|
|
228
|
+
name: string
|
|
229
|
+
fields?: readonly { id?: number; name?: string; default?: unknown }[]
|
|
230
|
+
}
|
|
231
|
+
| undefined
|
|
232
|
+
|
|
233
|
+
const previousFieldsByName = new Map(
|
|
234
|
+
previousTable?.fields?.filter((f) => f.name !== undefined).map((f) => [f.name!, f.default]) ??
|
|
235
|
+
[]
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
return filterModifiableFields(table.fields, existingColumns, renamedNewNames).flatMap((field) => {
|
|
239
|
+
const currentDefault = 'default' in field ? field.default : undefined
|
|
240
|
+
const previousDefault = previousFieldsByName.get(field.name)
|
|
241
|
+
|
|
242
|
+
// Only generate ALTER statements if default value changed
|
|
243
|
+
if (currentDefault === previousDefault) return []
|
|
244
|
+
|
|
245
|
+
// Case 1: Default removed (was set, now undefined)
|
|
246
|
+
if (previousDefault !== undefined && currentDefault === undefined) {
|
|
247
|
+
return [`ALTER TABLE ${table.name} ALTER COLUMN ${field.name} DROP DEFAULT`]
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Case 2: Default added or modified (generate SET DEFAULT statement)
|
|
251
|
+
if (currentDefault !== undefined) {
|
|
252
|
+
const columnDef = generateColumnDefinition(field, false, table.fields)
|
|
253
|
+
// Extract DEFAULT clause from column definition
|
|
254
|
+
// Handle quoted strings (DEFAULT 'value'), unquoted values (DEFAULT 42), and functions (DEFAULT NOW())
|
|
255
|
+
const defaultMatch = columnDef.match(/DEFAULT\s+('(?:[^']|'')*'|[^\s]+)/)
|
|
256
|
+
if (defaultMatch) {
|
|
257
|
+
const defaultClause = defaultMatch[1]
|
|
258
|
+
return [`ALTER TABLE ${table.name} ALTER COLUMN ${field.name} SET DEFAULT ${defaultClause}`]
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return []
|
|
263
|
+
})
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Build drop/add column statements from the computed columns to modify
|
|
268
|
+
*/
|
|
269
|
+
export const buildColumnStatements = (options: {
|
|
270
|
+
readonly tableName: string
|
|
271
|
+
readonly columnsToDrop: readonly string[]
|
|
272
|
+
readonly columnsToAdd: readonly Fields[number][]
|
|
273
|
+
readonly primaryKeyFields: readonly string[]
|
|
274
|
+
readonly allFields: readonly Fields[number][]
|
|
275
|
+
}): { readonly dropStatements: readonly string[]; readonly addStatements: readonly string[] } => {
|
|
276
|
+
const { tableName, columnsToDrop, columnsToAdd, primaryKeyFields, allFields } = options
|
|
277
|
+
const dropStatements = columnsToDrop.map(
|
|
278
|
+
(columnName) => `ALTER TABLE ${tableName} DROP COLUMN ${columnName} CASCADE`
|
|
279
|
+
)
|
|
280
|
+
const addStatements = columnsToAdd.map((field) => {
|
|
281
|
+
const isPrimaryKey = primaryKeyFields.includes(field.name)
|
|
282
|
+
const columnDef = generateColumnDefinition(field, isPrimaryKey, allFields)
|
|
283
|
+
return `ALTER TABLE ${tableName} ADD COLUMN ${columnDef}`
|
|
284
|
+
})
|
|
285
|
+
return { dropStatements, addStatements }
|
|
286
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 ESSENTIAL SERVICES
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the Business Source License 1.1
|
|
5
|
+
* found in the LICENSE.md file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* System tables that should never be dropped
|
|
10
|
+
* These tables are managed by Better Auth/Drizzle migrations or migration system, not by runtime schema
|
|
11
|
+
* Note: Better Auth tables are in the auth schema with native Better Auth table names
|
|
12
|
+
* System tables are in the system schema (system.*)
|
|
13
|
+
*/
|
|
14
|
+
export const PROTECTED_SYSTEM_TABLES = new Set([
|
|
15
|
+
// Better Auth tables (in auth schema)
|
|
16
|
+
'auth.user',
|
|
17
|
+
'auth.session',
|
|
18
|
+
'auth.account',
|
|
19
|
+
'auth.verification',
|
|
20
|
+
'auth.two_factor',
|
|
21
|
+
'auth.team',
|
|
22
|
+
'auth.team_member',
|
|
23
|
+
'auth.role',
|
|
24
|
+
// Migration system tables (in system schema)
|
|
25
|
+
'system.migration_history',
|
|
26
|
+
'system.migration_log',
|
|
27
|
+
'system.schema_checksum',
|
|
28
|
+
// Activity and comment tables (in system schema)
|
|
29
|
+
'system.activity_logs',
|
|
30
|
+
'system.record_comments',
|
|
31
|
+
])
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 ESSENTIAL SERVICES
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the Business Source License 1.1
|
|
5
|
+
* found in the LICENSE.md file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Effect } from 'effect'
|
|
9
|
+
import {
|
|
10
|
+
executeSQLStatements,
|
|
11
|
+
type TransactionLike,
|
|
12
|
+
type SQLExecutionError,
|
|
13
|
+
} from '../sql/sql-execution'
|
|
14
|
+
import { generateForeignKeyConstraints, generateTableConstraints } from '../sql/sql-generators'
|
|
15
|
+
import type { Table } from '@/domain/models/app/table'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Get drop statements for removed unique constraints
|
|
19
|
+
*/
|
|
20
|
+
const getUniqueConstraintDropStatements = (
|
|
21
|
+
table: Table,
|
|
22
|
+
previousSchema: { readonly tables: readonly object[] } | undefined,
|
|
23
|
+
currentUniqueFields: readonly string[]
|
|
24
|
+
): readonly string[] => {
|
|
25
|
+
if (!previousSchema) return []
|
|
26
|
+
|
|
27
|
+
const previousTable = previousSchema.tables.find(
|
|
28
|
+
(t: object) => 'name' in t && t.name === table.name
|
|
29
|
+
) as
|
|
30
|
+
| {
|
|
31
|
+
name: string
|
|
32
|
+
fields?: readonly { name?: string; unique?: boolean }[]
|
|
33
|
+
uniqueConstraints?: readonly { name: string }[]
|
|
34
|
+
}
|
|
35
|
+
| undefined
|
|
36
|
+
|
|
37
|
+
if (!previousTable) return []
|
|
38
|
+
|
|
39
|
+
// Single-field constraints that were removed
|
|
40
|
+
const previousUniqueFields =
|
|
41
|
+
previousTable.fields?.filter((f) => f.name && 'unique' in f && f.unique).map((f) => f.name!) ??
|
|
42
|
+
[]
|
|
43
|
+
|
|
44
|
+
const removedFields = previousUniqueFields.filter(
|
|
45
|
+
(fieldName) => !currentUniqueFields.includes(fieldName)
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
const singleFieldDrops = removedFields.map((fieldName) => {
|
|
49
|
+
const constraintName = `${table.name}_${fieldName}_key`
|
|
50
|
+
return `ALTER TABLE ${table.name} DROP CONSTRAINT IF EXISTS ${constraintName}`
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
// Composite constraints that were removed
|
|
54
|
+
const previousCompositeNames = previousTable.uniqueConstraints?.map((c) => c.name) ?? []
|
|
55
|
+
const currentCompositeNames = table.uniqueConstraints?.map((c) => c.name) ?? []
|
|
56
|
+
|
|
57
|
+
const removedComposites = previousCompositeNames.filter(
|
|
58
|
+
(name) => !currentCompositeNames.includes(name)
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
const compositeDrops = removedComposites.map(
|
|
62
|
+
(constraintName) => `ALTER TABLE ${table.name} DROP CONSTRAINT IF EXISTS ${constraintName}`
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
return [...singleFieldDrops, ...compositeDrops]
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Build SQL statements to add unique constraints (single-field and composite)
|
|
70
|
+
*/
|
|
71
|
+
const buildUniqueConstraintAddStatements = (
|
|
72
|
+
table: Table,
|
|
73
|
+
uniqueFields: readonly string[]
|
|
74
|
+
): readonly string[] => {
|
|
75
|
+
// Single-field constraints
|
|
76
|
+
const singleFieldStatements = uniqueFields.map((fieldName) => {
|
|
77
|
+
const constraintName = `${table.name}_${fieldName}_key`
|
|
78
|
+
return `
|
|
79
|
+
DO $$
|
|
80
|
+
BEGIN
|
|
81
|
+
IF NOT EXISTS (
|
|
82
|
+
SELECT 1 FROM information_schema.table_constraints
|
|
83
|
+
WHERE table_name = '${table.name}'
|
|
84
|
+
AND constraint_type = 'UNIQUE'
|
|
85
|
+
AND constraint_name = '${constraintName}'
|
|
86
|
+
) THEN
|
|
87
|
+
ALTER TABLE ${table.name} ADD CONSTRAINT ${constraintName} UNIQUE (${fieldName});
|
|
88
|
+
END IF;
|
|
89
|
+
END$$;
|
|
90
|
+
`
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
// Composite constraints
|
|
94
|
+
const compositeStatements =
|
|
95
|
+
table.uniqueConstraints?.map((constraint) => {
|
|
96
|
+
const fields = constraint.fields.join(', ')
|
|
97
|
+
return `
|
|
98
|
+
DO $$
|
|
99
|
+
BEGIN
|
|
100
|
+
IF NOT EXISTS (
|
|
101
|
+
SELECT 1 FROM information_schema.table_constraints
|
|
102
|
+
WHERE table_name = '${table.name}'
|
|
103
|
+
AND constraint_type = 'UNIQUE'
|
|
104
|
+
AND constraint_name = '${constraint.name}'
|
|
105
|
+
) THEN
|
|
106
|
+
ALTER TABLE ${table.name} ADD CONSTRAINT ${constraint.name} UNIQUE (${fields});
|
|
107
|
+
END IF;
|
|
108
|
+
END$$;
|
|
109
|
+
`
|
|
110
|
+
}) ?? []
|
|
111
|
+
|
|
112
|
+
return [...singleFieldStatements, ...compositeStatements]
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Sync unique constraints for existing table
|
|
117
|
+
* Adds named UNIQUE constraints for:
|
|
118
|
+
* 1. Single-field constraints (fields with unique property)
|
|
119
|
+
* 2. Composite unique constraints (table.uniqueConstraints)
|
|
120
|
+
* Removes constraints that are no longer in the schema
|
|
121
|
+
* Uses PostgreSQL default naming convention: {table}_{column}_key for single fields
|
|
122
|
+
*/
|
|
123
|
+
export const syncUniqueConstraints = (
|
|
124
|
+
tx: TransactionLike,
|
|
125
|
+
table: Table,
|
|
126
|
+
previousSchema?: { readonly tables: readonly object[] }
|
|
127
|
+
): Effect.Effect<void, SQLExecutionError> =>
|
|
128
|
+
Effect.gen(function* () {
|
|
129
|
+
const uniqueFields = table.fields.filter((f) => 'unique' in f && f.unique).map((f) => f.name)
|
|
130
|
+
const dropStatements = getUniqueConstraintDropStatements(table, previousSchema, uniqueFields)
|
|
131
|
+
const addStatements = buildUniqueConstraintAddStatements(table, uniqueFields)
|
|
132
|
+
|
|
133
|
+
yield* executeSQLStatements(tx, [...dropStatements, ...addStatements])
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Sync foreign key constraints for existing table
|
|
138
|
+
* Drops and recreates FK constraints to ensure referential actions (ON DELETE, ON UPDATE) are up-to-date
|
|
139
|
+
* This is needed when table schema is updated with new referential actions
|
|
140
|
+
*
|
|
141
|
+
* NOTE: After RENAME COLUMN, PostgreSQL preserves FK constraints but doesn't rename them.
|
|
142
|
+
* We need to drop old constraints by column name, not just by expected constraint name.
|
|
143
|
+
*/
|
|
144
|
+
export const syncForeignKeyConstraints = (
|
|
145
|
+
tx: TransactionLike,
|
|
146
|
+
table: Table,
|
|
147
|
+
tableUsesView?: ReadonlyMap<string, boolean>
|
|
148
|
+
): Effect.Effect<void, SQLExecutionError> =>
|
|
149
|
+
Effect.gen(function* () {
|
|
150
|
+
const fkConstraints = generateForeignKeyConstraints(table.name, table.fields, tableUsesView)
|
|
151
|
+
|
|
152
|
+
// Build drop and add statements for each FK constraint
|
|
153
|
+
const statements = fkConstraints.flatMap((constraint) => {
|
|
154
|
+
// Extract column name from the constraint SQL
|
|
155
|
+
// Format: "CONSTRAINT {constraintName} FOREIGN KEY ({columnName}) REFERENCES ..."
|
|
156
|
+
const match = constraint.match(/CONSTRAINT\s+\w+\s+FOREIGN KEY\s+\((\w+)\)/)
|
|
157
|
+
if (!match) return []
|
|
158
|
+
|
|
159
|
+
const columnName = match[1]
|
|
160
|
+
|
|
161
|
+
// Drop ALL existing FK constraints on this column (handles renamed columns)
|
|
162
|
+
// PostgreSQL doesn't rename constraints when column is renamed, so we need to drop by column
|
|
163
|
+
const dropStatement = `
|
|
164
|
+
DO $$
|
|
165
|
+
DECLARE
|
|
166
|
+
constraint_rec RECORD;
|
|
167
|
+
BEGIN
|
|
168
|
+
FOR constraint_rec IN
|
|
169
|
+
SELECT tc.constraint_name
|
|
170
|
+
FROM information_schema.table_constraints tc
|
|
171
|
+
JOIN information_schema.key_column_usage kcu
|
|
172
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
173
|
+
AND tc.table_schema = kcu.table_schema
|
|
174
|
+
WHERE tc.table_name = '${table.name}'
|
|
175
|
+
AND tc.constraint_type = 'FOREIGN KEY'
|
|
176
|
+
AND kcu.column_name = '${columnName}'
|
|
177
|
+
LOOP
|
|
178
|
+
EXECUTE 'ALTER TABLE ${table.name} DROP CONSTRAINT ' || constraint_rec.constraint_name;
|
|
179
|
+
END LOOP;
|
|
180
|
+
END$$;
|
|
181
|
+
`
|
|
182
|
+
|
|
183
|
+
// Add constraint with updated referential actions
|
|
184
|
+
const addStatement = `ALTER TABLE ${table.name} ADD ${constraint}`
|
|
185
|
+
|
|
186
|
+
return [dropStatement, addStatement]
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
// Execute all FK constraint statements sequentially
|
|
190
|
+
yield* executeSQLStatements(tx, statements)
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Validate existing data against new CHECK constraint before applying
|
|
195
|
+
* Returns validation query that will throw error if data violates constraint
|
|
196
|
+
*/
|
|
197
|
+
const generateConstraintValidationQuery = (
|
|
198
|
+
tableName: string,
|
|
199
|
+
constraint: string
|
|
200
|
+
): string | undefined => {
|
|
201
|
+
// Extract constraint condition from the constraint SQL
|
|
202
|
+
// Format: "CONSTRAINT {constraintName} CHECK ({condition})"
|
|
203
|
+
const match = constraint.match(/CHECK\s*\((.+)\)$/)
|
|
204
|
+
if (!match) return undefined
|
|
205
|
+
|
|
206
|
+
const condition = match[1]
|
|
207
|
+
|
|
208
|
+
// Generate query that will fail if any row violates the new constraint
|
|
209
|
+
// Using NOT ({condition}) to find rows that violate the constraint
|
|
210
|
+
return `
|
|
211
|
+
DO $$
|
|
212
|
+
DECLARE
|
|
213
|
+
violation_count INTEGER;
|
|
214
|
+
BEGIN
|
|
215
|
+
SELECT COUNT(*) INTO violation_count
|
|
216
|
+
FROM ${tableName}
|
|
217
|
+
WHERE NOT (${condition});
|
|
218
|
+
|
|
219
|
+
IF violation_count > 0 THEN
|
|
220
|
+
RAISE EXCEPTION 'Migration failed: existing data violates check constraint. % row(s) in table "${tableName}" violate the new constraint condition.', violation_count;
|
|
221
|
+
END IF;
|
|
222
|
+
END$$;
|
|
223
|
+
`
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Sync CHECK constraints for existing table
|
|
228
|
+
* Adds CHECK constraints for fields with validation requirements (enum values, ranges, formats, etc.)
|
|
229
|
+
* Drops and recreates constraints when they are modified (e.g., min/max value changes)
|
|
230
|
+
* This is needed when fields are added via ALTER TABLE and need their CHECK constraints
|
|
231
|
+
*
|
|
232
|
+
* CRITICAL: Validates existing data before applying new constraints
|
|
233
|
+
* Migration will FAIL if any existing data violates the new constraint
|
|
234
|
+
*/
|
|
235
|
+
export const syncCheckConstraints = (
|
|
236
|
+
tx: TransactionLike,
|
|
237
|
+
table: Table
|
|
238
|
+
): Effect.Effect<void, SQLExecutionError> =>
|
|
239
|
+
Effect.gen(function* () {
|
|
240
|
+
const allConstraints = generateTableConstraints(table, undefined)
|
|
241
|
+
|
|
242
|
+
// Filter only CHECK constraints (not UNIQUE, FK, or PRIMARY KEY)
|
|
243
|
+
const checkConstraints = allConstraints.filter(
|
|
244
|
+
(constraint) =>
|
|
245
|
+
constraint.startsWith('CONSTRAINT') &&
|
|
246
|
+
constraint.includes('CHECK') &&
|
|
247
|
+
!constraint.includes('UNIQUE') &&
|
|
248
|
+
!constraint.includes('FOREIGN KEY') &&
|
|
249
|
+
!constraint.includes('PRIMARY KEY')
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
// Build statements to drop existing constraints and add new ones
|
|
253
|
+
// This ensures constraints are updated when validation rules change (e.g., max value increases)
|
|
254
|
+
const statements = checkConstraints.flatMap((constraint) => {
|
|
255
|
+
// Extract constraint name from the constraint SQL
|
|
256
|
+
// Format: "CONSTRAINT {constraintName} CHECK ..."
|
|
257
|
+
const match = constraint.match(/CONSTRAINT\s+(\w+)\s+CHECK/)
|
|
258
|
+
if (!match) return []
|
|
259
|
+
|
|
260
|
+
const constraintName = match[1]
|
|
261
|
+
|
|
262
|
+
// Generate validation query to check if existing data violates new constraint
|
|
263
|
+
const validationQuery = generateConstraintValidationQuery(table.name, constraint)
|
|
264
|
+
|
|
265
|
+
// Drop existing constraint if it exists, validate data, then add the new constraint
|
|
266
|
+
// This handles both new constraints and modified constraints
|
|
267
|
+
return [
|
|
268
|
+
`
|
|
269
|
+
DO $$
|
|
270
|
+
BEGIN
|
|
271
|
+
IF EXISTS (
|
|
272
|
+
SELECT 1 FROM information_schema.table_constraints
|
|
273
|
+
WHERE table_name = '${table.name}'
|
|
274
|
+
AND constraint_type = 'CHECK'
|
|
275
|
+
AND constraint_name = '${constraintName}'
|
|
276
|
+
) THEN
|
|
277
|
+
ALTER TABLE ${table.name} DROP CONSTRAINT ${constraintName};
|
|
278
|
+
END IF;
|
|
279
|
+
END$$;
|
|
280
|
+
`,
|
|
281
|
+
...(validationQuery ? [validationQuery] : []),
|
|
282
|
+
`ALTER TABLE ${table.name} ADD ${constraint}`,
|
|
283
|
+
]
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
// Execute all statements sequentially (drop, validate, then add for each constraint)
|
|
287
|
+
yield* executeSQLStatements(tx, statements)
|
|
288
|
+
})
|