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,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 ESSENTIAL SERVICES
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the Business Source License 1.1
|
|
5
|
+
* found in the LICENSE.md file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { sql } from 'drizzle-orm'
|
|
9
|
+
import { Effect } from 'effect'
|
|
10
|
+
import { db, SessionContextError, type DrizzleTransaction } from '@/infrastructure/database'
|
|
11
|
+
import { logActivity } from '../query-helpers/activity-log-helpers'
|
|
12
|
+
import { wrapDatabaseError } from '../shared/error-handling'
|
|
13
|
+
import { validateTableName } from '../shared/validation'
|
|
14
|
+
import { runEffectInTx } from './batch-helpers'
|
|
15
|
+
import type { Session } from '@/infrastructure/auth/better-auth/schema'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Validate records exist and filter to only soft-deleted ones
|
|
19
|
+
* Returns array of record IDs that are actually soft-deleted
|
|
20
|
+
* Throws error if any record is not found (404)
|
|
21
|
+
*/
|
|
22
|
+
async function validateAndFilterRecordsForRestore(
|
|
23
|
+
tx: Readonly<DrizzleTransaction>,
|
|
24
|
+
tableIdent: Readonly<ReturnType<typeof sql.identifier>>,
|
|
25
|
+
recordIds: readonly string[]
|
|
26
|
+
): Promise<readonly string[]> {
|
|
27
|
+
const validationResults = await Promise.all(
|
|
28
|
+
recordIds.map(async (recordId) => {
|
|
29
|
+
const checkResult = (await tx.execute(
|
|
30
|
+
sql`SELECT id, deleted_at FROM ${tableIdent} WHERE id = ${recordId} LIMIT 1`
|
|
31
|
+
)) as readonly Record<string, unknown>[]
|
|
32
|
+
|
|
33
|
+
if (checkResult.length === 0) return { recordId, error: 'not found', isDeleted: false }
|
|
34
|
+
|
|
35
|
+
const record = checkResult[0]
|
|
36
|
+
const isDeleted = Boolean(record?.deleted_at)
|
|
37
|
+
|
|
38
|
+
return { recordId, error: undefined, isDeleted }
|
|
39
|
+
})
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
// Check for "not found" errors first (these should return 404)
|
|
43
|
+
const notFoundError = validationResults.find((result) => result.error === 'not found')
|
|
44
|
+
if (notFoundError) {
|
|
45
|
+
// eslint-disable-next-line functional/no-throw-statements -- Required for Effect.tryPromise error handling
|
|
46
|
+
throw new Error(`Record ${notFoundError.recordId} not found`)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Filter to only records that are actually soft-deleted (skip active records)
|
|
50
|
+
return validationResults.filter((result) => result.isDeleted).map((result) => result.recordId)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Validate and filter records for restore with Effect error handling
|
|
55
|
+
* Returns array of record IDs that are actually soft-deleted
|
|
56
|
+
*/
|
|
57
|
+
function validateAndFilterRecordsWithEffect(
|
|
58
|
+
tx: Readonly<DrizzleTransaction>,
|
|
59
|
+
tableIdent: Readonly<ReturnType<typeof sql.identifier>>,
|
|
60
|
+
recordIds: readonly string[]
|
|
61
|
+
): Effect.Effect<readonly string[], SessionContextError> {
|
|
62
|
+
return Effect.tryPromise({
|
|
63
|
+
try: () => validateAndFilterRecordsForRestore(tx, tableIdent, recordIds),
|
|
64
|
+
catch: (error) => {
|
|
65
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
|
66
|
+
return new SessionContextError(`Validation failed: ${errorMessage}`, error)
|
|
67
|
+
},
|
|
68
|
+
})
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Execute restore query using parameterized IN clause
|
|
73
|
+
*/
|
|
74
|
+
function executeRestoreQuery(
|
|
75
|
+
tx: Readonly<DrizzleTransaction>,
|
|
76
|
+
tableIdent: Readonly<ReturnType<typeof sql.identifier>>,
|
|
77
|
+
tableName: string,
|
|
78
|
+
recordIds: readonly string[]
|
|
79
|
+
): Effect.Effect<readonly Record<string, unknown>[], SessionContextError> {
|
|
80
|
+
return Effect.tryPromise({
|
|
81
|
+
try: async () => {
|
|
82
|
+
const idParams = sql.join(
|
|
83
|
+
recordIds.map((id) => sql`${id}`),
|
|
84
|
+
sql.raw(', ')
|
|
85
|
+
)
|
|
86
|
+
const result = (await tx.execute(
|
|
87
|
+
sql`UPDATE ${tableIdent} SET deleted_at = NULL WHERE id IN (${idParams}) RETURNING *`
|
|
88
|
+
)) as readonly Record<string, unknown>[]
|
|
89
|
+
return result
|
|
90
|
+
},
|
|
91
|
+
catch: (error) => new SessionContextError(`Failed to restore records in ${tableName}`, error),
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Log restore activities for all restored records
|
|
97
|
+
*/
|
|
98
|
+
function logRestoreActivities(
|
|
99
|
+
session: Readonly<Session>,
|
|
100
|
+
tableName: string,
|
|
101
|
+
restoredRecords: readonly Record<string, unknown>[]
|
|
102
|
+
): Effect.Effect<void, never> {
|
|
103
|
+
return Effect.forEach(restoredRecords, (record) =>
|
|
104
|
+
logActivity({
|
|
105
|
+
session,
|
|
106
|
+
tableName,
|
|
107
|
+
action: 'restore',
|
|
108
|
+
recordId: String(record.id),
|
|
109
|
+
changes: { after: record },
|
|
110
|
+
})
|
|
111
|
+
).pipe(Effect.asVoid)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Batch restore soft-deleted records
|
|
116
|
+
*
|
|
117
|
+
* Restores multiple soft-deleted records in a transaction.
|
|
118
|
+
* Validates all records exist and are soft-deleted before restoring any.
|
|
119
|
+
* Rolls back if any record fails validation.
|
|
120
|
+
* Permissions enforced at the presentation layer (route handler).
|
|
121
|
+
*
|
|
122
|
+
* @param session - Better Auth session
|
|
123
|
+
* @param tableName - Name of the table
|
|
124
|
+
* @param recordIds - Array of record IDs to restore
|
|
125
|
+
* @returns Effect resolving to number of restored records or error
|
|
126
|
+
*/
|
|
127
|
+
export function batchRestoreRecords(
|
|
128
|
+
session: Readonly<Session>,
|
|
129
|
+
tableName: string,
|
|
130
|
+
recordIds: readonly string[]
|
|
131
|
+
): Effect.Effect<number, SessionContextError> {
|
|
132
|
+
return Effect.gen(function* () {
|
|
133
|
+
const restoredRecords = yield* Effect.tryPromise({
|
|
134
|
+
try: () =>
|
|
135
|
+
db.transaction(async (tx) => {
|
|
136
|
+
validateTableName(tableName)
|
|
137
|
+
const tableIdent = sql.identifier(tableName)
|
|
138
|
+
|
|
139
|
+
// Validate and filter to only soft-deleted records
|
|
140
|
+
const deletedRecordIds = await runEffectInTx(
|
|
141
|
+
validateAndFilterRecordsWithEffect(tx, tableIdent, recordIds)
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
// If no records to restore, return empty array
|
|
145
|
+
if (deletedRecordIds.length === 0) {
|
|
146
|
+
return []
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Restore only the filtered soft-deleted records
|
|
150
|
+
return await runEffectInTx(
|
|
151
|
+
executeRestoreQuery(tx, tableIdent, tableName, deletedRecordIds)
|
|
152
|
+
)
|
|
153
|
+
}),
|
|
154
|
+
catch: wrapDatabaseError(`Failed to restore records in ${tableName}`),
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
yield* logRestoreActivities(session, tableName, restoredRecords)
|
|
158
|
+
|
|
159
|
+
return restoredRecords.length
|
|
160
|
+
})
|
|
161
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 ESSENTIAL SERVICES
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the Business Source License 1.1
|
|
5
|
+
* found in the LICENSE.md file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { sql } from 'drizzle-orm'
|
|
9
|
+
import { Effect } from 'effect'
|
|
10
|
+
import {
|
|
11
|
+
db,
|
|
12
|
+
ValidationError,
|
|
13
|
+
type DrizzleTransaction,
|
|
14
|
+
type SessionContextError,
|
|
15
|
+
} from '@/infrastructure/database'
|
|
16
|
+
import { injectUpdateAuthorship } from '../mutation-helpers/authorship-helpers'
|
|
17
|
+
import { fetchRecordByIdEffect } from '../mutation-helpers/record-fetch-helpers'
|
|
18
|
+
import { buildUpdateSetClauseCRUD } from '../mutation-helpers/update-helpers'
|
|
19
|
+
import { logActivity } from '../query-helpers/activity-log-helpers'
|
|
20
|
+
import { wrapDatabaseErrorWithValidation } from '../shared/error-handling'
|
|
21
|
+
import { validateTableName } from '../shared/validation'
|
|
22
|
+
import { runEffectInTx } from './batch-helpers'
|
|
23
|
+
import type { Session } from '@/infrastructure/auth/better-auth/schema'
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Extract fields from update object (requires nested format)
|
|
27
|
+
*/
|
|
28
|
+
function extractFieldsFromUpdate(update: {
|
|
29
|
+
readonly id: string
|
|
30
|
+
readonly fields?: Readonly<Record<string, unknown>>
|
|
31
|
+
}): Readonly<Record<string, unknown>> {
|
|
32
|
+
// Return fields property or empty object if not provided
|
|
33
|
+
return update.fields ?? {}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Execute UPDATE query and return updated record
|
|
38
|
+
*/
|
|
39
|
+
function executeRecordUpdate(
|
|
40
|
+
tx: Readonly<DrizzleTransaction>,
|
|
41
|
+
tableName: string,
|
|
42
|
+
recordId: string,
|
|
43
|
+
setClause: Readonly<ReturnType<typeof sql.join>>
|
|
44
|
+
): Effect.Effect<Record<string, unknown> | undefined, ValidationError> {
|
|
45
|
+
return Effect.tryPromise({
|
|
46
|
+
try: async () => {
|
|
47
|
+
const result = (await tx.execute(
|
|
48
|
+
sql`UPDATE ${sql.identifier(tableName)} SET ${setClause} WHERE id = ${recordId} RETURNING *`
|
|
49
|
+
)) as readonly Record<string, unknown>[]
|
|
50
|
+
return result[0]
|
|
51
|
+
},
|
|
52
|
+
catch: (error) => {
|
|
53
|
+
// PostgreSQL error codes: https://www.postgresql.org/docs/current/errcodes-appendix.html
|
|
54
|
+
// 23502 = not_null_violation
|
|
55
|
+
const pgError = error as { code?: string; message?: string; constraint?: string }
|
|
56
|
+
if (pgError.code === '23502' || pgError.message?.includes('null value in column')) {
|
|
57
|
+
// Extract field name from error message if possible
|
|
58
|
+
const fieldMatch = pgError.message?.match(/column "([^"]+)"/)
|
|
59
|
+
const fieldName: string = fieldMatch?.[1] ?? 'unknown'
|
|
60
|
+
return new ValidationError(`Cannot set required field '${fieldName}' to null`, [
|
|
61
|
+
{ record: 0, field: fieldName, error: 'Required field cannot be null' },
|
|
62
|
+
])
|
|
63
|
+
}
|
|
64
|
+
// For other errors, return generic validation error
|
|
65
|
+
const errorMessage: string =
|
|
66
|
+
pgError.message !== undefined
|
|
67
|
+
? pgError.message
|
|
68
|
+
: 'Update failed due to constraint violation'
|
|
69
|
+
return new ValidationError(errorMessage, [])
|
|
70
|
+
},
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Update a single record within a batch operation
|
|
76
|
+
*/
|
|
77
|
+
function updateSingleRecordInBatch(
|
|
78
|
+
tx: Readonly<DrizzleTransaction>,
|
|
79
|
+
tableName: string,
|
|
80
|
+
session: Readonly<Session>,
|
|
81
|
+
update: { readonly id: string; readonly fields?: Record<string, unknown> }
|
|
82
|
+
): Effect.Effect<Record<string, unknown> | undefined, ValidationError> {
|
|
83
|
+
return Effect.gen(function* () {
|
|
84
|
+
const fieldsToUpdate = extractFieldsFromUpdate(update)
|
|
85
|
+
|
|
86
|
+
if (Object.keys(fieldsToUpdate).length === 0) return undefined
|
|
87
|
+
|
|
88
|
+
// Inject updated_by authorship metadata
|
|
89
|
+
const fieldsWithAuthorship = yield* Effect.promise(() =>
|
|
90
|
+
injectUpdateAuthorship(fieldsToUpdate, session.userId, tx, tableName)
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
const entries = Object.entries(fieldsWithAuthorship)
|
|
94
|
+
const recordBefore = yield* fetchRecordByIdEffect(tx, tableName, update.id)
|
|
95
|
+
const setClause = buildUpdateSetClauseCRUD(entries)
|
|
96
|
+
const updatedRecord = yield* executeRecordUpdate(tx, tableName, update.id, setClause)
|
|
97
|
+
|
|
98
|
+
if (updatedRecord) {
|
|
99
|
+
yield* logActivity({
|
|
100
|
+
session,
|
|
101
|
+
tableName,
|
|
102
|
+
action: 'update',
|
|
103
|
+
recordId: String(update.id),
|
|
104
|
+
changes: { before: recordBefore, after: updatedRecord },
|
|
105
|
+
})
|
|
106
|
+
return updatedRecord
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return undefined
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Batch update records
|
|
115
|
+
*
|
|
116
|
+
* Updates multiple records in a transaction with permission enforcement.
|
|
117
|
+
* Only records the user has permission to update will be affected.
|
|
118
|
+
* Records without permission are silently skipped.
|
|
119
|
+
*
|
|
120
|
+
* @param session - Better Auth session
|
|
121
|
+
* @param tableName - Name of the table
|
|
122
|
+
* @param updates - Array of records with id and fields to update (requires nested format)
|
|
123
|
+
* @returns Effect resolving to array of updated records
|
|
124
|
+
*/
|
|
125
|
+
export function batchUpdateRecords(
|
|
126
|
+
session: Readonly<Session>,
|
|
127
|
+
tableName: string,
|
|
128
|
+
updates: readonly { readonly id: string; readonly fields?: Record<string, unknown> }[]
|
|
129
|
+
): Effect.Effect<readonly Record<string, unknown>[], SessionContextError | ValidationError> {
|
|
130
|
+
return Effect.tryPromise({
|
|
131
|
+
try: () =>
|
|
132
|
+
db.transaction(async (tx) => {
|
|
133
|
+
validateTableName(tableName)
|
|
134
|
+
|
|
135
|
+
return await runEffectInTx(
|
|
136
|
+
// Process updates sequentially with immutable array building
|
|
137
|
+
Effect.reduce(updates, [] as readonly Record<string, unknown>[], (acc, update) =>
|
|
138
|
+
updateSingleRecordInBatch(tx, tableName, session, update).pipe(
|
|
139
|
+
Effect.map((record) => (record ? [...acc, record] : acc))
|
|
140
|
+
)
|
|
141
|
+
)
|
|
142
|
+
)
|
|
143
|
+
}),
|
|
144
|
+
catch: wrapDatabaseErrorWithValidation(`Failed to batch update records in ${tableName}`),
|
|
145
|
+
})
|
|
146
|
+
}
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 ESSENTIAL SERVICES
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the Business Source License 1.1
|
|
5
|
+
* found in the LICENSE.md file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { sql } from 'drizzle-orm'
|
|
9
|
+
import { Effect } from 'effect'
|
|
10
|
+
import {
|
|
11
|
+
db,
|
|
12
|
+
SessionContextError,
|
|
13
|
+
ValidationError,
|
|
14
|
+
type DrizzleTransaction,
|
|
15
|
+
} from '@/infrastructure/database'
|
|
16
|
+
import { logActivity } from '../query-helpers/activity-log-helpers'
|
|
17
|
+
import { typedExecute } from '../shared/typed-execute'
|
|
18
|
+
import { validateTableName, validateColumnName } from '../shared/validation'
|
|
19
|
+
import { BatchValidationError, runEffectInTx, createSingleRecord } from './batch-helpers'
|
|
20
|
+
import type { Session } from '@/infrastructure/auth/better-auth/schema'
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Helper to update existing record
|
|
24
|
+
*/
|
|
25
|
+
async function updateSingleRecord(
|
|
26
|
+
tx: Readonly<DrizzleTransaction>,
|
|
27
|
+
tableName: string,
|
|
28
|
+
recordId: string,
|
|
29
|
+
params: { readonly fields: Record<string, unknown>; readonly fieldsToMergeOn: readonly string[] }
|
|
30
|
+
): Promise<Record<string, unknown> | undefined> {
|
|
31
|
+
const updateEntries = Object.entries(params.fields).filter(
|
|
32
|
+
([key]) => !params.fieldsToMergeOn.includes(key)
|
|
33
|
+
)
|
|
34
|
+
if (updateEntries.length === 0) return undefined
|
|
35
|
+
|
|
36
|
+
const setExpressions = updateEntries.map(([key, value]) => {
|
|
37
|
+
validateColumnName(key)
|
|
38
|
+
return sql`${sql.identifier(key)} = ${value}`
|
|
39
|
+
})
|
|
40
|
+
const setClause = sql.join(setExpressions, sql.raw(', '))
|
|
41
|
+
|
|
42
|
+
const result = (await tx.execute(
|
|
43
|
+
sql`UPDATE ${sql.identifier(tableName)} SET ${setClause} WHERE id = ${recordId} RETURNING *`
|
|
44
|
+
)) as readonly Record<string, unknown>[]
|
|
45
|
+
|
|
46
|
+
return result[0] ?? undefined
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
type UpsertResult = {
|
|
50
|
+
readonly records: readonly Record<string, unknown>[]
|
|
51
|
+
readonly created: number
|
|
52
|
+
readonly updated: number
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Check if record exists based on merge fields
|
|
57
|
+
*/
|
|
58
|
+
function findExistingRecord(
|
|
59
|
+
tx: Readonly<DrizzleTransaction>,
|
|
60
|
+
tableName: string,
|
|
61
|
+
fields: Readonly<Record<string, unknown>>,
|
|
62
|
+
fieldsToMergeOn: readonly string[]
|
|
63
|
+
): Effect.Effect<Readonly<Record<string, unknown>> | undefined, SessionContextError> {
|
|
64
|
+
const whereConditions = fieldsToMergeOn.map((field) => {
|
|
65
|
+
validateColumnName(field)
|
|
66
|
+
return sql`${sql.identifier(field)} = ${fields[field]}`
|
|
67
|
+
})
|
|
68
|
+
const whereClause = sql.join(whereConditions, sql.raw(' AND '))
|
|
69
|
+
|
|
70
|
+
return Effect.tryPromise({
|
|
71
|
+
try: async () => {
|
|
72
|
+
const result = (await tx.execute(
|
|
73
|
+
sql`SELECT * FROM ${sql.identifier(tableName)} WHERE ${whereClause} LIMIT 1`
|
|
74
|
+
)) as readonly Record<string, unknown>[]
|
|
75
|
+
return result[0]
|
|
76
|
+
},
|
|
77
|
+
catch: (error) =>
|
|
78
|
+
new SessionContextError(`Failed to check existing record in ${tableName}`, error),
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Handle update path in upsert
|
|
84
|
+
*/
|
|
85
|
+
function handleUpsertUpdate(
|
|
86
|
+
tx: Readonly<DrizzleTransaction>,
|
|
87
|
+
params: {
|
|
88
|
+
readonly session: Readonly<Session>
|
|
89
|
+
readonly tableName: string
|
|
90
|
+
readonly fields: Record<string, unknown>
|
|
91
|
+
readonly fieldsToMergeOn: readonly string[]
|
|
92
|
+
readonly existing: Record<string, unknown>
|
|
93
|
+
readonly acc: UpsertResult
|
|
94
|
+
}
|
|
95
|
+
): Effect.Effect<UpsertResult, SessionContextError> {
|
|
96
|
+
return Effect.gen(function* () {
|
|
97
|
+
const recordId = String(params.existing.id)
|
|
98
|
+
const updated = yield* Effect.tryPromise({
|
|
99
|
+
try: async () =>
|
|
100
|
+
updateSingleRecord(tx, params.tableName, recordId, {
|
|
101
|
+
fields: params.fields,
|
|
102
|
+
fieldsToMergeOn: params.fieldsToMergeOn,
|
|
103
|
+
}),
|
|
104
|
+
catch: (error) =>
|
|
105
|
+
new SessionContextError(`Failed to update record in ${params.tableName}`, error),
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
if (!updated) {
|
|
109
|
+
return {
|
|
110
|
+
records: [...params.acc.records, params.existing],
|
|
111
|
+
created: params.acc.created,
|
|
112
|
+
updated: params.acc.updated + 1,
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
yield* logActivity({
|
|
117
|
+
session: params.session,
|
|
118
|
+
tableName: params.tableName,
|
|
119
|
+
action: 'update',
|
|
120
|
+
recordId,
|
|
121
|
+
changes: { before: params.existing, after: updated },
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
records: [...params.acc.records, updated],
|
|
126
|
+
created: params.acc.created,
|
|
127
|
+
updated: params.acc.updated + 1,
|
|
128
|
+
}
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Handle create path in upsert
|
|
134
|
+
*/
|
|
135
|
+
function handleUpsertCreate(
|
|
136
|
+
tx: Readonly<DrizzleTransaction>,
|
|
137
|
+
params: {
|
|
138
|
+
readonly session: Readonly<Session>
|
|
139
|
+
readonly tableName: string
|
|
140
|
+
readonly fields: Record<string, unknown>
|
|
141
|
+
readonly acc: UpsertResult
|
|
142
|
+
}
|
|
143
|
+
): Effect.Effect<UpsertResult, SessionContextError | ValidationError> {
|
|
144
|
+
return Effect.gen(function* () {
|
|
145
|
+
const created = yield* Effect.tryPromise({
|
|
146
|
+
try: async () => createSingleRecord(tx, params.tableName, params.fields),
|
|
147
|
+
catch: (error) => {
|
|
148
|
+
// If this is a ValidationError, propagate it as-is
|
|
149
|
+
if (error instanceof ValidationError) {
|
|
150
|
+
return error
|
|
151
|
+
}
|
|
152
|
+
return new SessionContextError(`Failed to create record in ${params.tableName}`, error)
|
|
153
|
+
},
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
if (!created) return params.acc
|
|
157
|
+
|
|
158
|
+
yield* logActivity({
|
|
159
|
+
session: params.session,
|
|
160
|
+
tableName: params.tableName,
|
|
161
|
+
action: 'create',
|
|
162
|
+
recordId: String(created.id),
|
|
163
|
+
changes: { after: created },
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
records: [...params.acc.records, created],
|
|
168
|
+
created: params.acc.created + 1,
|
|
169
|
+
updated: params.acc.updated,
|
|
170
|
+
}
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Process single upsert operation
|
|
176
|
+
*/
|
|
177
|
+
function processSingleUpsert(
|
|
178
|
+
tx: Readonly<DrizzleTransaction>,
|
|
179
|
+
params: {
|
|
180
|
+
readonly session: Readonly<Session>
|
|
181
|
+
readonly tableName: string
|
|
182
|
+
readonly fields: Record<string, unknown>
|
|
183
|
+
readonly fieldsToMergeOn: readonly string[]
|
|
184
|
+
readonly acc: UpsertResult
|
|
185
|
+
}
|
|
186
|
+
): Effect.Effect<UpsertResult, SessionContextError | ValidationError> {
|
|
187
|
+
return Effect.gen(function* () {
|
|
188
|
+
const existing = yield* findExistingRecord(
|
|
189
|
+
tx,
|
|
190
|
+
params.tableName,
|
|
191
|
+
params.fields,
|
|
192
|
+
params.fieldsToMergeOn
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
if (existing) {
|
|
196
|
+
return yield* handleUpsertUpdate(tx, { ...params, existing })
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return yield* handleUpsertCreate(tx, params)
|
|
200
|
+
})
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Validate merge fields are present in all records
|
|
205
|
+
*/
|
|
206
|
+
function validateMergeFieldsPresent(
|
|
207
|
+
recordsData: readonly Record<string, unknown>[],
|
|
208
|
+
fieldsToMergeOn: readonly string[]
|
|
209
|
+
): Effect.Effect<void, BatchValidationError> {
|
|
210
|
+
const errors = recordsData.flatMap((record, index) => {
|
|
211
|
+
const missingFields = fieldsToMergeOn.filter((field) => !(field in record))
|
|
212
|
+
return missingFields.length > 0
|
|
213
|
+
? [`Record ${index}: Missing merge field(s) ${missingFields.join(', ')}`]
|
|
214
|
+
: []
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
if (errors.length > 0) {
|
|
218
|
+
return Effect.fail(
|
|
219
|
+
new BatchValidationError({
|
|
220
|
+
message: 'Batch validation failed',
|
|
221
|
+
details: errors,
|
|
222
|
+
})
|
|
223
|
+
)
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return Effect.void
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Validate required fields are present in record (for creates)
|
|
231
|
+
* This prevents database NOT NULL constraint violations
|
|
232
|
+
*/
|
|
233
|
+
async function validateRequiredFieldsInRecord(
|
|
234
|
+
tx: Readonly<DrizzleTransaction>,
|
|
235
|
+
tableName: string,
|
|
236
|
+
record: Readonly<Record<string, unknown>>,
|
|
237
|
+
recordIndex: number
|
|
238
|
+
): Promise<readonly string[]> {
|
|
239
|
+
// Query table schema to get required fields
|
|
240
|
+
const schemaQuery = await typedExecute<{ column_name: string }>(
|
|
241
|
+
tx,
|
|
242
|
+
sql`
|
|
243
|
+
SELECT column_name, is_nullable, column_default
|
|
244
|
+
FROM information_schema.columns
|
|
245
|
+
WHERE table_name = ${tableName}
|
|
246
|
+
AND table_schema = 'public'
|
|
247
|
+
AND is_nullable = 'NO'
|
|
248
|
+
AND column_default IS NULL
|
|
249
|
+
`
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
const requiredFields = schemaQuery.map((row) => row.column_name)
|
|
253
|
+
|
|
254
|
+
// System fields that are auto-generated (exclude from validation)
|
|
255
|
+
const autoFields = new Set(['id', 'created_at', 'updated_at'])
|
|
256
|
+
|
|
257
|
+
const missingFields = requiredFields.filter(
|
|
258
|
+
(field) => !autoFields.has(field) && !(field in record)
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
if (missingFields.length > 0) {
|
|
262
|
+
return [`Record ${recordIndex}: Missing required field(s) ${missingFields.join(', ')}`]
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return []
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Validate all records have required fields BEFORE processing
|
|
270
|
+
*/
|
|
271
|
+
function validateAllRecordsHaveRequiredFields(
|
|
272
|
+
tx: Readonly<DrizzleTransaction>,
|
|
273
|
+
tableName: string,
|
|
274
|
+
recordsData: readonly Record<string, unknown>[]
|
|
275
|
+
): Effect.Effect<void, BatchValidationError> {
|
|
276
|
+
return Effect.gen(function* () {
|
|
277
|
+
const allErrors = yield* Effect.reduce(
|
|
278
|
+
recordsData,
|
|
279
|
+
[] as readonly string[],
|
|
280
|
+
(acc, record, index) =>
|
|
281
|
+
Effect.tryPromise({
|
|
282
|
+
try: () => validateRequiredFieldsInRecord(tx, tableName, record, index),
|
|
283
|
+
catch: (error) =>
|
|
284
|
+
new BatchValidationError({
|
|
285
|
+
message: 'Failed to validate record',
|
|
286
|
+
details: [String(error)],
|
|
287
|
+
}),
|
|
288
|
+
}).pipe(Effect.map((recordErrors) => [...acc, ...recordErrors]))
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
if (allErrors.length > 0) {
|
|
292
|
+
return yield* new BatchValidationError({
|
|
293
|
+
message: 'Batch validation failed',
|
|
294
|
+
details: allErrors,
|
|
295
|
+
})
|
|
296
|
+
}
|
|
297
|
+
})
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Upsert records (create or update based on merge fields)
|
|
302
|
+
*/
|
|
303
|
+
export function upsertRecords(
|
|
304
|
+
session: Readonly<Session>,
|
|
305
|
+
tableName: string,
|
|
306
|
+
recordsData: readonly Record<string, unknown>[],
|
|
307
|
+
fieldsToMergeOn: readonly string[]
|
|
308
|
+
): Effect.Effect<UpsertResult, SessionContextError | BatchValidationError | ValidationError> {
|
|
309
|
+
return Effect.gen(function* () {
|
|
310
|
+
validateTableName(tableName)
|
|
311
|
+
|
|
312
|
+
if (recordsData.length === 0) {
|
|
313
|
+
return yield* Effect.fail(
|
|
314
|
+
new SessionContextError('Cannot upsert batch with no records', undefined)
|
|
315
|
+
)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (fieldsToMergeOn.length === 0) {
|
|
319
|
+
return yield* Effect.fail(
|
|
320
|
+
new SessionContextError('Cannot upsert without merge fields', undefined)
|
|
321
|
+
)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
fieldsToMergeOn.forEach((field) => validateColumnName(field))
|
|
325
|
+
|
|
326
|
+
// Validate merge fields are present in all records BEFORE processing
|
|
327
|
+
yield* validateMergeFieldsPresent(recordsData, fieldsToMergeOn)
|
|
328
|
+
|
|
329
|
+
// Execute upsert in a transaction
|
|
330
|
+
const result = yield* Effect.tryPromise({
|
|
331
|
+
try: () =>
|
|
332
|
+
db.transaction(async (tx) => {
|
|
333
|
+
// eslint-disable-next-line functional/no-expression-statements -- Required for transaction validation
|
|
334
|
+
await runEffectInTx(validateAllRecordsHaveRequiredFields(tx, tableName, recordsData))
|
|
335
|
+
|
|
336
|
+
return await runEffectInTx(
|
|
337
|
+
Effect.reduce(
|
|
338
|
+
recordsData,
|
|
339
|
+
{ records: [], created: 0, updated: 0 } as UpsertResult,
|
|
340
|
+
(acc, fields) =>
|
|
341
|
+
processSingleUpsert(tx, { session, tableName, fields, fieldsToMergeOn, acc })
|
|
342
|
+
)
|
|
343
|
+
)
|
|
344
|
+
}),
|
|
345
|
+
catch: (error) => {
|
|
346
|
+
if (error instanceof SessionContextError) return error
|
|
347
|
+
if (error instanceof BatchValidationError) {
|
|
348
|
+
// Re-wrap BatchValidationError as SessionContextError to match return type
|
|
349
|
+
return new SessionContextError(error.message, error)
|
|
350
|
+
}
|
|
351
|
+
return new SessionContextError(`Failed to upsert records in ${tableName}`, error)
|
|
352
|
+
},
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
return result
|
|
356
|
+
})
|
|
357
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
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
|
+
// Re-export all batch operations from modular files
|
|
9
|
+
export { BatchValidationError } from './batch-helpers'
|
|
10
|
+
export { batchCreateRecords } from './batch-create'
|
|
11
|
+
export { upsertRecords } from './batch-upsert'
|
|
12
|
+
export { batchRestoreRecords } from './batch-restore'
|
|
13
|
+
export { batchUpdateRecords } from './batch-update'
|
|
14
|
+
export { batchDeleteRecords } from './batch-delete'
|