@solidstarters/solid-core 1.2.200 → 1.2.201
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/dist/commands/fixtures/fixtures-setup.command.d.ts +15 -0
- package/dist/commands/fixtures/fixtures-setup.command.d.ts.map +1 -0
- package/dist/commands/fixtures/fixtures-setup.command.js +58 -0
- package/dist/commands/fixtures/fixtures-setup.command.js.map +1 -0
- package/dist/commands/fixtures/fixtures-tear-down.command.d.ts +16 -0
- package/dist/commands/fixtures/fixtures-tear-down.command.d.ts.map +1 -0
- package/dist/commands/fixtures/fixtures-tear-down.command.js +59 -0
- package/dist/commands/fixtures/fixtures-tear-down.command.js.map +1 -0
- package/dist/commands/refresh-model.command.d.ts.map +1 -1
- package/dist/commands/refresh-model.command.js +4 -0
- package/dist/commands/refresh-model.command.js.map +1 -1
- package/dist/constants/error-messages.d.ts +2 -0
- package/dist/constants/error-messages.d.ts.map +1 -1
- package/dist/constants/error-messages.js +4 -0
- package/dist/constants/error-messages.js.map +1 -1
- package/dist/controllers/model-sequence.controller.d.ts +43 -0
- package/dist/controllers/model-sequence.controller.d.ts.map +1 -0
- package/dist/controllers/model-sequence.controller.js +179 -0
- package/dist/controllers/model-sequence.controller.js.map +1 -0
- package/dist/controllers/setting.controller.d.ts +1 -0
- package/dist/controllers/setting.controller.d.ts.map +1 -1
- package/dist/controllers/setting.controller.js +15 -0
- package/dist/controllers/setting.controller.js.map +1 -1
- package/dist/dtos/basic-filters.dto.d.ts +3 -1
- package/dist/dtos/basic-filters.dto.d.ts.map +1 -1
- package/dist/dtos/basic-filters.dto.js +8 -2
- package/dist/dtos/basic-filters.dto.js.map +1 -1
- package/dist/dtos/basic-group-filters.dto.d.ts +6 -0
- package/dist/dtos/basic-group-filters.dto.d.ts.map +1 -0
- package/dist/dtos/basic-group-filters.dto.js +46 -0
- package/dist/dtos/basic-group-filters.dto.js.map +1 -0
- package/dist/dtos/create-field-metadata.dto.js +2 -2
- package/dist/dtos/create-field-metadata.dto.js.map +1 -1
- package/dist/dtos/create-model-sequence.dto.d.ts +14 -0
- package/dist/dtos/create-model-sequence.dto.d.ts.map +1 -0
- package/dist/dtos/create-model-sequence.dto.js +90 -0
- package/dist/dtos/create-model-sequence.dto.js.map +1 -0
- package/dist/dtos/create-role-metadata.dto.d.ts.map +1 -1
- package/dist/dtos/create-role-metadata.dto.js +1 -0
- package/dist/dtos/create-role-metadata.dto.js.map +1 -1
- package/dist/dtos/get-mcp-url.dto.d.ts +5 -0
- package/dist/dtos/get-mcp-url.dto.d.ts.map +1 -0
- package/dist/dtos/get-mcp-url.dto.js +31 -0
- package/dist/dtos/get-mcp-url.dto.js.map +1 -0
- package/dist/dtos/resolve-s3-url.dto.d.ts +5 -5
- package/dist/dtos/resolve-s3-url.dto.d.ts.map +1 -1
- package/dist/dtos/resolve-s3-url.dto.js +7 -7
- package/dist/dtos/resolve-s3-url.dto.js.map +1 -1
- package/dist/dtos/update-model-sequence.dto.d.ts +15 -0
- package/dist/dtos/update-model-sequence.dto.d.ts.map +1 -0
- package/dist/dtos/update-model-sequence.dto.js +94 -0
- package/dist/dtos/update-model-sequence.dto.js.map +1 -0
- package/dist/entities/common.entity.d.ts.map +1 -1
- package/dist/entities/common.entity.js +1 -0
- package/dist/entities/common.entity.js.map +1 -1
- package/dist/entities/legacy-common.entity.d.ts.map +1 -1
- package/dist/entities/legacy-common.entity.js +1 -0
- package/dist/entities/legacy-common.entity.js.map +1 -1
- package/dist/entities/model-sequence.entity.d.ts +15 -0
- package/dist/entities/model-sequence.entity.d.ts.map +1 -0
- package/dist/entities/model-sequence.entity.js +67 -0
- package/dist/entities/model-sequence.entity.js.map +1 -0
- package/dist/helpers/field-crud-managers/BigIntFieldCrudManager.d.ts.map +1 -1
- package/dist/helpers/field-crud-managers/BigIntFieldCrudManager.js +13 -2
- package/dist/helpers/field-crud-managers/BigIntFieldCrudManager.js.map +1 -1
- package/dist/helpers/field-crud-managers/ManyToManyRelationFieldCrudManager.d.ts +0 -1
- package/dist/helpers/field-crud-managers/ManyToManyRelationFieldCrudManager.d.ts.map +1 -1
- package/dist/helpers/field-crud-managers/ManyToManyRelationFieldCrudManager.js +4 -9
- package/dist/helpers/field-crud-managers/ManyToManyRelationFieldCrudManager.js.map +1 -1
- package/dist/helpers/field-crud-managers/ManyToOneRelationFieldCrudManager.d.ts +0 -1
- package/dist/helpers/field-crud-managers/ManyToOneRelationFieldCrudManager.d.ts.map +1 -1
- package/dist/helpers/field-crud-managers/ManyToOneRelationFieldCrudManager.js +7 -8
- package/dist/helpers/field-crud-managers/ManyToOneRelationFieldCrudManager.js.map +1 -1
- package/dist/helpers/field-crud-managers/OneToManyRelationFieldCrudManager.d.ts +0 -1
- package/dist/helpers/field-crud-managers/OneToManyRelationFieldCrudManager.d.ts.map +1 -1
- package/dist/helpers/field-crud-managers/OneToManyRelationFieldCrudManager.js +4 -9
- package/dist/helpers/field-crud-managers/OneToManyRelationFieldCrudManager.js.map +1 -1
- package/dist/helpers/model-metadata-helper.service.d.ts.map +1 -1
- package/dist/helpers/model-metadata-helper.service.js +6 -2
- package/dist/helpers/model-metadata-helper.service.js.map +1 -1
- package/dist/helpers/module-metadata-helper.service.d.ts +1 -0
- package/dist/helpers/module-metadata-helper.service.d.ts.map +1 -1
- package/dist/helpers/module-metadata-helper.service.js +9 -0
- package/dist/helpers/module-metadata-helper.service.js.map +1 -1
- package/dist/helpers/module.helper.d.ts +1 -0
- package/dist/helpers/module.helper.d.ts.map +1 -1
- package/dist/helpers/module.helper.js +29 -3
- package/dist/helpers/module.helper.js.map +1 -1
- package/dist/helpers/solid-registry.d.ts +11 -0
- package/dist/helpers/solid-registry.d.ts.map +1 -1
- package/dist/helpers/solid-registry.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/jobs/computed-field-evaluation-subscriber.service.d.ts +1 -0
- package/dist/jobs/computed-field-evaluation-subscriber.service.d.ts.map +1 -1
- package/dist/jobs/computed-field-evaluation-subscriber.service.js +16 -4
- package/dist/jobs/computed-field-evaluation-subscriber.service.js.map +1 -1
- package/dist/repository/media.repository.d.ts.map +1 -1
- package/dist/repository/media.repository.js +4 -0
- package/dist/repository/media.repository.js.map +1 -1
- package/dist/repository/model-sequence.repository.d.ts +14 -0
- package/dist/repository/model-sequence.repository.d.ts.map +1 -0
- package/dist/repository/model-sequence.repository.js +103 -0
- package/dist/repository/model-sequence.repository.js.map +1 -0
- package/dist/seeders/module-metadata-seeder.service.d.ts +7 -12
- package/dist/seeders/module-metadata-seeder.service.d.ts.map +1 -1
- package/dist/seeders/module-metadata-seeder.service.js +64 -26
- package/dist/seeders/module-metadata-seeder.service.js.map +1 -1
- package/dist/seeders/seed-data/solid-core-metadata.json +343 -27
- package/dist/seeders/system-fields-seeder.service.d.ts +1 -0
- package/dist/seeders/system-fields-seeder.service.d.ts.map +1 -1
- package/dist/seeders/system-fields-seeder.service.js +11 -2
- package/dist/seeders/system-fields-seeder.service.js.map +1 -1
- package/dist/services/action-metadata.service.d.ts.map +1 -1
- package/dist/services/action-metadata.service.js +1 -0
- package/dist/services/action-metadata.service.js.map +1 -1
- package/dist/services/ai-interaction.service.d.ts.map +1 -1
- package/dist/services/ai-interaction.service.js +1 -0
- package/dist/services/ai-interaction.service.js.map +1 -1
- package/dist/services/authentication.service.d.ts.map +1 -1
- package/dist/services/authentication.service.js +22 -14
- package/dist/services/authentication.service.js.map +1 -1
- package/dist/services/chatter-message-details.service.d.ts.map +1 -1
- package/dist/services/chatter-message-details.service.js +1 -0
- package/dist/services/chatter-message-details.service.js.map +1 -1
- package/dist/services/chatter-message.service.d.ts.map +1 -1
- package/dist/services/chatter-message.service.js +7 -3
- package/dist/services/chatter-message.service.js.map +1 -1
- package/dist/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.d.ts.map +1 -1
- package/dist/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.js +7 -5
- package/dist/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.js.map +1 -1
- package/dist/services/computed-fields/entity/sequence-num-computed-field-provider.d.ts +15 -0
- package/dist/services/computed-fields/entity/sequence-num-computed-field-provider.d.ts.map +1 -0
- package/dist/services/computed-fields/entity/sequence-num-computed-field-provider.js +72 -0
- package/dist/services/computed-fields/entity/sequence-num-computed-field-provider.js.map +1 -0
- package/dist/services/crud-helper.service.d.ts +23 -6
- package/dist/services/crud-helper.service.d.ts.map +1 -1
- package/dist/services/crud-helper.service.js +257 -45
- package/dist/services/crud-helper.service.js.map +1 -1
- package/dist/services/crud.service.d.ts +3 -1
- package/dist/services/crud.service.d.ts.map +1 -1
- package/dist/services/crud.service.js +53 -24
- package/dist/services/crud.service.js.map +1 -1
- package/dist/services/database/database-bootstrap.service.d.ts +12 -0
- package/dist/services/database/database-bootstrap.service.d.ts.map +1 -0
- package/dist/services/database/database-bootstrap.service.js +115 -0
- package/dist/services/database/database-bootstrap.service.js.map +1 -0
- package/dist/services/email-template.service.d.ts +7 -7
- package/dist/services/email-template.service.d.ts.map +1 -1
- package/dist/services/email-template.service.js +8 -7
- package/dist/services/email-template.service.js.map +1 -1
- package/dist/services/excel.service.d.ts +10 -0
- package/dist/services/excel.service.d.ts.map +1 -1
- package/dist/services/excel.service.js +100 -0
- package/dist/services/excel.service.js.map +1 -1
- package/dist/services/field-metadata.service.d.ts +4 -1
- package/dist/services/field-metadata.service.d.ts.map +1 -1
- package/dist/services/field-metadata.service.js +35 -30
- package/dist/services/field-metadata.service.js.map +1 -1
- package/dist/services/file.service.d.ts +1 -0
- package/dist/services/file.service.d.ts.map +1 -1
- package/dist/services/file.service.js +9 -0
- package/dist/services/file.service.js.map +1 -1
- package/dist/services/fixtures.service.d.ts +13 -0
- package/dist/services/fixtures.service.d.ts.map +1 -0
- package/dist/services/fixtures.service.js +95 -0
- package/dist/services/fixtures.service.js.map +1 -0
- package/dist/services/genai/ingest-metadata.service.d.ts.map +1 -1
- package/dist/services/genai/ingest-metadata.service.js +1 -1
- package/dist/services/genai/ingest-metadata.service.js.map +1 -1
- package/dist/services/import-transaction-error-log.service.d.ts.map +1 -1
- package/dist/services/import-transaction-error-log.service.js +1 -0
- package/dist/services/import-transaction-error-log.service.js.map +1 -1
- package/dist/services/import-transaction.service.d.ts.map +1 -1
- package/dist/services/import-transaction.service.js +7 -1
- package/dist/services/import-transaction.service.js.map +1 -1
- package/dist/services/list-of-values.service.d.ts +2 -2
- package/dist/services/list-of-values.service.d.ts.map +1 -1
- package/dist/services/list-of-values.service.js +2 -1
- package/dist/services/list-of-values.service.js.map +1 -1
- package/dist/services/locale.service.d.ts.map +1 -1
- package/dist/services/locale.service.js +1 -0
- package/dist/services/locale.service.js.map +1 -1
- package/dist/services/mail/smtp-email.service.js +0 -1
- package/dist/services/mail/smtp-email.service.js.map +1 -1
- package/dist/services/media.service.d.ts +3 -3
- package/dist/services/media.service.d.ts.map +1 -1
- package/dist/services/media.service.js +6 -4
- package/dist/services/media.service.js.map +1 -1
- package/dist/services/mediaStorageProviders/file-s3-storage-provider.d.ts.map +1 -1
- package/dist/services/mediaStorageProviders/file-s3-storage-provider.js +17 -6
- package/dist/services/mediaStorageProviders/file-s3-storage-provider.js.map +1 -1
- package/dist/services/mediaStorageProviders/file-storage-provider.d.ts.map +1 -1
- package/dist/services/mediaStorageProviders/file-storage-provider.js +0 -13
- package/dist/services/mediaStorageProviders/file-storage-provider.js.map +1 -1
- package/dist/services/menu-item-metadata.service.d.ts.map +1 -1
- package/dist/services/menu-item-metadata.service.js +4 -0
- package/dist/services/menu-item-metadata.service.js.map +1 -1
- package/dist/services/model-metadata.service.d.ts.map +1 -1
- package/dist/services/model-metadata.service.js +2 -42
- package/dist/services/model-metadata.service.js.map +1 -1
- package/dist/services/model-sequence.service.d.ts +23 -0
- package/dist/services/model-sequence.service.d.ts.map +1 -0
- package/dist/services/model-sequence.service.js +55 -0
- package/dist/services/model-sequence.service.js.map +1 -0
- package/dist/services/module-metadata.service.d.ts +1 -0
- package/dist/services/module-metadata.service.d.ts.map +1 -1
- package/dist/services/module-metadata.service.js +35 -1
- package/dist/services/module-metadata.service.js.map +1 -1
- package/dist/services/permission-metadata.service.d.ts +5 -5
- package/dist/services/permission-metadata.service.d.ts.map +1 -1
- package/dist/services/permission-metadata.service.js +6 -5
- package/dist/services/permission-metadata.service.js.map +1 -1
- package/dist/services/queues/database-subscriber.service.d.ts.map +1 -1
- package/dist/services/queues/database-subscriber.service.js +2 -1
- package/dist/services/queues/database-subscriber.service.js.map +1 -1
- package/dist/services/queues/rabbitmq-subscriber.service.d.ts.map +1 -1
- package/dist/services/queues/rabbitmq-subscriber.service.js +2 -2
- package/dist/services/queues/rabbitmq-subscriber.service.js.map +1 -1
- package/dist/services/role-metadata.service.d.ts.map +1 -1
- package/dist/services/role-metadata.service.js +1 -0
- package/dist/services/role-metadata.service.js.map +1 -1
- package/dist/services/scheduled-job.service.d.ts +6 -6
- package/dist/services/scheduled-job.service.d.ts.map +1 -1
- package/dist/services/scheduled-job.service.js +8 -8
- package/dist/services/scheduled-job.service.js.map +1 -1
- package/dist/services/scheduled-jobs/scheduler.service.d.ts.map +1 -1
- package/dist/services/scheduled-jobs/scheduler.service.js +4 -0
- package/dist/services/scheduled-jobs/scheduler.service.js.map +1 -1
- package/dist/services/security-rule.service.d.ts.map +1 -1
- package/dist/services/security-rule.service.js +1 -0
- package/dist/services/security-rule.service.js.map +1 -1
- package/dist/services/selection-providers/list-of-models-selection-provider.service.d.ts.map +1 -1
- package/dist/services/selection-providers/list-of-models-selection-provider.service.js +4 -0
- package/dist/services/selection-providers/list-of-models-selection-provider.service.js.map +1 -1
- package/dist/services/setting.service.d.ts +7 -5
- package/dist/services/setting.service.d.ts.map +1 -1
- package/dist/services/setting.service.js +26 -4
- package/dist/services/setting.service.js.map +1 -1
- package/dist/services/sms-template.service.d.ts +7 -7
- package/dist/services/sms-template.service.d.ts.map +1 -1
- package/dist/services/sms-template.service.js +8 -7
- package/dist/services/sms-template.service.js.map +1 -1
- package/dist/services/solid-introspect.service.d.ts +4 -13
- package/dist/services/solid-introspect.service.d.ts.map +1 -1
- package/dist/services/solid-introspect.service.js +4 -22
- package/dist/services/solid-introspect.service.js.map +1 -1
- package/dist/services/solid-ts-morph.service.js +2 -2
- package/dist/services/solid-ts-morph.service.js.map +1 -1
- package/dist/services/user-activity-history.service.d.ts.map +1 -1
- package/dist/services/user-activity-history.service.js +1 -0
- package/dist/services/user-activity-history.service.js.map +1 -1
- package/dist/services/user-view-metadata.service.d.ts.map +1 -1
- package/dist/services/user-view-metadata.service.js +3 -2
- package/dist/services/user-view-metadata.service.js.map +1 -1
- package/dist/services/user.service.d.ts.map +1 -1
- package/dist/services/user.service.js +1 -0
- package/dist/services/user.service.js.map +1 -1
- package/dist/services/view-metadata.service.d.ts +1 -1
- package/dist/services/view-metadata.service.d.ts.map +1 -1
- package/dist/services/view-metadata.service.js +3 -1
- package/dist/services/view-metadata.service.js.map +1 -1
- package/dist/solid-core-cli-db.module.d.ts.map +1 -1
- package/dist/solid-core-cli-db.module.js +5 -2
- package/dist/solid-core-cli-db.module.js.map +1 -1
- package/dist/solid-core.module.d.ts.map +1 -1
- package/dist/solid-core.module.js +18 -0
- package/dist/solid-core.module.js.map +1 -1
- package/dist/subscribers/audit.subscriber.d.ts.map +1 -1
- package/dist/subscribers/audit.subscriber.js +5 -1
- package/dist/subscribers/audit.subscriber.js.map +1 -1
- package/dist/subscribers/computed-entity-field.subscriber.d.ts +4 -2
- package/dist/subscribers/computed-entity-field.subscriber.d.ts.map +1 -1
- package/dist/subscribers/computed-entity-field.subscriber.js +53 -12
- package/dist/subscribers/computed-entity-field.subscriber.js.map +1 -1
- package/dist/transformers/typeorm/local-date-time-transformer.d.ts +5 -0
- package/dist/transformers/typeorm/local-date-time-transformer.d.ts.map +1 -0
- package/dist/transformers/typeorm/local-date-time-transformer.js +26 -0
- package/dist/transformers/typeorm/local-date-time-transformer.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/docs/grouping-enhancements.md +89 -0
- package/package.json +1 -1
- package/src/commands/fixtures/fixtures-setup.command.ts +44 -0
- package/src/commands/fixtures/fixtures-tear-down.command.ts +45 -0
- package/src/commands/refresh-model.command.ts +3 -1
- package/src/constants/error-messages.ts +7 -1
- package/src/controllers/model-sequence.controller.ts +93 -0
- package/src/controllers/setting.controller.ts +33 -21
- package/src/dtos/basic-filters.dto.ts +6 -1
- package/src/dtos/basic-group-filters.dto.ts +23 -0
- package/src/dtos/create-field-metadata.dto.ts +1 -1
- package/src/dtos/create-model-sequence.dto.ts +51 -0
- package/src/dtos/create-role-metadata.dto.ts +16 -3
- package/src/dtos/get-mcp-url.dto.ts +13 -0
- package/src/dtos/resolve-s3-url.dto.ts +9 -11
- package/src/dtos/update-model-sequence.dto.ts +53 -0
- package/src/entities/common.entity.ts +2 -2
- package/src/entities/legacy-common.entity.ts +2 -1
- package/src/entities/model-sequence.entity.ts +32 -0
- package/src/helpers/field-crud-managers/BigIntFieldCrudManager.ts +18 -5
- package/src/helpers/field-crud-managers/ManyToManyRelationFieldCrudManager.ts +9 -9
- package/src/helpers/field-crud-managers/ManyToOneRelationFieldCrudManager.ts +16 -8
- package/src/helpers/field-crud-managers/OneToManyRelationFieldCrudManager.ts +9 -9
- package/src/helpers/model-metadata-helper.service.ts +6 -4
- package/src/helpers/module-metadata-helper.service.ts +18 -1
- package/src/helpers/module.helper.ts +40 -5
- package/src/helpers/solid-registry.ts +14 -0
- package/src/index.ts +3 -1
- package/src/jobs/computed-field-evaluation-subscriber.service.ts +15 -4
- package/src/repository/media.repository.ts +3 -2
- package/src/repository/model-sequence.repository.ts +97 -0
- package/src/seeders/module-metadata-seeder.service.ts +103 -29
- package/src/seeders/seed-data/solid-core-metadata.json +343 -27
- package/src/seeders/system-fields-seeder.service.ts +6 -2
- package/src/services/action-metadata.service.ts +3 -2
- package/src/services/ai-interaction.service.ts +2 -1
- package/src/services/authentication.service.ts +46 -14
- package/src/services/chatter-message-details.service.ts +2 -1
- package/src/services/chatter-message.service.ts +10 -4
- package/src/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.ts +8 -7
- package/src/services/computed-fields/entity/sequence-num-computed-field-provider.ts +86 -0
- package/src/services/crud-helper.service.ts +287 -49
- package/src/services/crud.service.ts +83 -32
- package/src/services/database/database-bootstrap.service.ts +91 -0
- package/src/services/email-template.service.ts +11 -13
- package/src/services/excel.service.ts +146 -3
- package/src/services/field-metadata.service.ts +102 -55
- package/src/services/file.service.ts +9 -0
- package/src/services/fixtures.service.ts +108 -0
- package/src/services/genai/ingest-metadata.service.ts +4 -3
- package/src/services/import-transaction-error-log.service.ts +2 -1
- package/src/services/import-transaction.service.ts +8 -4
- package/src/services/list-of-values.service.ts +4 -4
- package/src/services/locale.service.ts +2 -1
- package/src/services/mail/smtp-email.service.ts +1 -1
- package/src/services/media.service.ts +10 -11
- package/src/services/mediaStorageProviders/file-s3-storage-provider.ts +22 -7
- package/src/services/mediaStorageProviders/file-storage-provider.ts +18 -13
- package/src/services/menu-item-metadata.service.ts +6 -2
- package/src/services/model-metadata.service.ts +50 -44
- package/src/services/model-sequence.service.ts +33 -0
- package/src/services/module-metadata.service.ts +49 -2
- package/src/services/permission-metadata.service.ts +8 -9
- package/src/services/queues/database-subscriber.service.ts +3 -1
- package/src/services/queues/rabbitmq-subscriber.service.ts +4 -2
- package/src/services/role-metadata.service.ts +1 -0
- package/src/services/scheduled-job.service.ts +9 -9
- package/src/services/scheduled-jobs/scheduler.service.ts +5 -0
- package/src/services/security-rule.service.ts +1 -0
- package/src/services/selection-providers/list-of-models-selection-provider.service.ts +5 -2
- package/src/services/setting.service.ts +33 -6
- package/src/services/sms-template.service.ts +11 -13
- package/src/services/solid-introspect.service.ts +6 -19
- package/src/services/solid-ts-morph.service.ts +2 -2
- package/src/services/user-activity-history.service.ts +3 -2
- package/src/services/user-view-metadata.service.ts +4 -3
- package/src/services/user.service.ts +2 -1
- package/src/services/view-metadata.service.ts +5 -4
- package/src/solid-core-cli-db.module.ts +5 -4
- package/src/solid-core.module.ts +18 -0
- package/src/subscribers/audit.subscriber.ts +3 -2
- package/src/subscribers/computed-entity-field.subscriber.ts +60 -17
- package/src/transformers/typeorm/local-date-time-transformer.ts +30 -0
- /package/sql/{mssql → default/mssql}/proc_CleanupModelMetadata.sql +0 -0
- /package/sql/{mssql → default/mssql}/proc_CleanupModuleMetadata.sql +0 -0
- /package/sql/{mssql/scratchpad.sql → default/mssql/scratchpad.sql.txt} +0 -0
- /package/sql/{postgres → default/postgres}/proc_CleanupModelMetadata.sql +0 -0
- /package/sql/{postgres → default/postgres}/proc_CleanupModuleMetadata.sql +0 -0
- /package/sql/{postgres/scratchpad.sql → default/postgres/scratchpad.sql.txt} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Injectable } from '@nestjs/common';
|
|
1
|
+
import { forwardRef, Inject, Injectable } from '@nestjs/common';
|
|
2
2
|
import { DiscoveryService, ModuleRef } from "@nestjs/core";
|
|
3
3
|
import { InjectEntityManager } from '@nestjs/typeorm';
|
|
4
4
|
import { Brackets, EntityManager, EntityMetadata } from 'typeorm';
|
|
@@ -26,6 +26,7 @@ import { RequestContextService } from './request-context.service';
|
|
|
26
26
|
@Injectable()
|
|
27
27
|
export class ChatterMessageService extends CRUDService<ChatterMessage> {
|
|
28
28
|
constructor(
|
|
29
|
+
@Inject(forwardRef(() => ModelMetadataService))
|
|
29
30
|
readonly modelMetadataService: ModelMetadataService,
|
|
30
31
|
readonly moduleMetadataService: ModuleMetadataService,
|
|
31
32
|
readonly configService: ConfigService,
|
|
@@ -44,6 +45,7 @@ export class ChatterMessageService extends CRUDService<ChatterMessage> {
|
|
|
44
45
|
readonly moduleRef: ModuleRef,
|
|
45
46
|
// @InjectRepository(ModelMetadata)
|
|
46
47
|
// private readonly modelMetadataRepo: Repository<ModelMetadata>,
|
|
48
|
+
@Inject(forwardRef(() => ModelMetadataRepository))
|
|
47
49
|
private readonly modelMetadataRepo: ModelMetadataRepository,
|
|
48
50
|
readonly requestContextService: RequestContextService,
|
|
49
51
|
private readonly modelMetadataHelperService: ModelMetadataHelperService,
|
|
@@ -116,7 +118,7 @@ export class ChatterMessageService extends CRUDService<ChatterMessage> {
|
|
|
116
118
|
|
|
117
119
|
const auditFields = model.fields.filter(field =>
|
|
118
120
|
field.enableAuditTracking &&
|
|
119
|
-
!['mediaSingle', 'mediaMultiple', '
|
|
121
|
+
!['mediaSingle', 'mediaMultiple', 'richText', 'json'].includes(field.type) &&
|
|
120
122
|
!(field.type === 'relation' && field.relationType === 'one-to-many')
|
|
121
123
|
);
|
|
122
124
|
|
|
@@ -179,7 +181,7 @@ export class ChatterMessageService extends CRUDService<ChatterMessage> {
|
|
|
179
181
|
|
|
180
182
|
const auditFields = modelFields.filter(field =>
|
|
181
183
|
field.enableAuditTracking &&
|
|
182
|
-
!['mediaSingle', 'mediaMultiple', '
|
|
184
|
+
!['mediaSingle', 'mediaMultiple', 'richText', 'json'].includes(field.type) &&
|
|
183
185
|
!(field.type === 'relation' && field.relationType === 'one-to-many')
|
|
184
186
|
);
|
|
185
187
|
|
|
@@ -542,7 +544,11 @@ export class ChatterMessageService extends CRUDService<ChatterMessage> {
|
|
|
542
544
|
});
|
|
543
545
|
|
|
544
546
|
if (coModel) {
|
|
545
|
-
const relatedEntityRepository = this.entityManager.getRepository(classify(coModelName));
|
|
547
|
+
//const relatedEntityRepository = this.entityManager.getRepository(classify(coModelName));
|
|
548
|
+
const dsName = coModel.dataSource || 'default';
|
|
549
|
+
const em = dsName === 'default' ? this.entityManager : this.moduleRef.get(`${dsName}EntityManager`, { strict: false });
|
|
550
|
+
|
|
551
|
+
const relatedEntityRepository = em.getRepository(classify(coModelName));
|
|
546
552
|
|
|
547
553
|
const relatedEntities = await relatedEntityRepository.find({
|
|
548
554
|
where: { [coModelFieldName]: { id: entityId } }
|
package/src/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.ts
CHANGED
|
@@ -30,8 +30,9 @@ export class AlphaNumExternalIdComputationProvider<T extends CommonEntity> imple
|
|
|
30
30
|
|
|
31
31
|
async preComputeValue(triggerEntity: T, computedFieldMetadata: ComputedFieldMetadata<AlphaNumExternalIdContext>
|
|
32
32
|
) {
|
|
33
|
-
const { prefix, length, dynamicFieldPrefix } =
|
|
34
|
-
|
|
33
|
+
const { prefix, length, dynamicFieldPrefix } = computedFieldMetadata.computedFieldValueProviderCtxt;
|
|
34
|
+
const eventContext = computedFieldMetadata.eventContext;
|
|
35
|
+
const entityName = eventContext?.metadataName ?? eventContext.databaseEntity?.constructor?.name ?? '';
|
|
35
36
|
|
|
36
37
|
const codeLength = length || 5;
|
|
37
38
|
|
|
@@ -45,7 +46,7 @@ export class AlphaNumExternalIdComputationProvider<T extends CommonEntity> imple
|
|
|
45
46
|
}
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
const uniqueCode = await this.generateUniqueExternalId(resolvedPrefix, codeLength,
|
|
49
|
+
const uniqueCode = await this.generateUniqueExternalId(resolvedPrefix, codeLength, computedFieldMetadata.fieldName, entityName);
|
|
49
50
|
const finalExternalId = resolvedPrefix ? `${resolvedPrefix}-${uniqueCode}` : uniqueCode;
|
|
50
51
|
|
|
51
52
|
triggerEntity[computedFieldMetadata.fieldName] = finalExternalId;
|
|
@@ -60,8 +61,8 @@ export class AlphaNumExternalIdComputationProvider<T extends CommonEntity> imple
|
|
|
60
61
|
return result;
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
private async isExternalIdUnique(externalId: string,
|
|
64
|
-
const count = await this.entityManager.count(
|
|
64
|
+
private async isExternalIdUnique(externalId: string, fieldName: string, entityName: string): Promise<boolean> {
|
|
65
|
+
const count = await this.entityManager.count(entityName as any,
|
|
65
66
|
{
|
|
66
67
|
where: { [fieldName]: externalId },
|
|
67
68
|
}
|
|
@@ -69,7 +70,7 @@ export class AlphaNumExternalIdComputationProvider<T extends CommonEntity> imple
|
|
|
69
70
|
return count === 0;
|
|
70
71
|
}
|
|
71
72
|
|
|
72
|
-
private async generateUniqueExternalId(resolvedPrefix: string, codeLength: number,
|
|
73
|
+
private async generateUniqueExternalId(resolvedPrefix: string, codeLength: number, fieldName: string, entityName: string): Promise<string> {
|
|
73
74
|
const maxAttempts = 10;
|
|
74
75
|
|
|
75
76
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
@@ -78,7 +79,7 @@ export class AlphaNumExternalIdComputationProvider<T extends CommonEntity> imple
|
|
|
78
79
|
|
|
79
80
|
const fullId = resolvedPrefix ? `${resolvedPrefix}-${newId}` : newId;
|
|
80
81
|
|
|
81
|
-
const isUnique = await this.isExternalIdUnique(fullId,
|
|
82
|
+
const isUnique = await this.isExternalIdUnique(fullId, fieldName, entityName);
|
|
82
83
|
|
|
83
84
|
if (isUnique) {
|
|
84
85
|
return newId;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Injectable } from "@nestjs/common";
|
|
2
|
+
import { InjectDataSource } from "@nestjs/typeorm";
|
|
3
|
+
import { ComputedFieldProvider } from "src/decorators/computed-field-provider.decorator";
|
|
4
|
+
import { CommonEntity } from "src/entities/common.entity";
|
|
5
|
+
import { ModelSequence } from "src/entities/model-sequence.entity";
|
|
6
|
+
import { ComputedFieldMetadata } from "src/helpers/solid-registry";
|
|
7
|
+
import { IEntityPreComputeFieldProvider } from "src/interfaces";
|
|
8
|
+
import { DataSource, EntityTarget } from "typeorm";
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
export interface SequenceNumComputedFieldContext {
|
|
12
|
+
sequenceName: string; // The separator to use between concatenated values
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@ComputedFieldProvider()
|
|
16
|
+
@Injectable()
|
|
17
|
+
export class SequenceNumComputedFieldProvider<T extends CommonEntity> implements IEntityPreComputeFieldProvider<T, SequenceNumComputedFieldContext> {
|
|
18
|
+
constructor(
|
|
19
|
+
@InjectDataSource()
|
|
20
|
+
private readonly dataSource: DataSource
|
|
21
|
+
) { }
|
|
22
|
+
|
|
23
|
+
name(): string {
|
|
24
|
+
return "SequenceNumComputedFieldProvider";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
help(): string {
|
|
28
|
+
return "Computed field provider used to create fields whose value is based on some prefix, padding & sequence number.";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async preComputeValue(triggerEntity: T, computedFieldMetadata: ComputedFieldMetadata<SequenceNumComputedFieldContext>) {
|
|
32
|
+
const { sequenceName } =
|
|
33
|
+
computedFieldMetadata.computedFieldValueProviderCtxt ?? {};
|
|
34
|
+
|
|
35
|
+
if (!sequenceName) {
|
|
36
|
+
throw new Error("sequenceName is required for sequence computation");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
await this.dataSource.transaction(async (manager) => {
|
|
40
|
+
/**
|
|
41
|
+
* 1️⃣ Lock sequence row (prevents race conditions)
|
|
42
|
+
*/
|
|
43
|
+
// 1️⃣ Fetch sequence row
|
|
44
|
+
const modelSequenceRepo = manager.getRepository(ModelSequence)
|
|
45
|
+
const modelSequence = await modelSequenceRepo.findOne({
|
|
46
|
+
where: { sequenceName },
|
|
47
|
+
lock: { mode: "pessimistic_write" }
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (!modelSequence) {
|
|
51
|
+
throw new Error(`ModelSequence not found for ${sequenceName}`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 2️⃣ Generate next sequence value
|
|
55
|
+
const nextValue = modelSequence.currentValue + 1;
|
|
56
|
+
|
|
57
|
+
const paddedValue = String(nextValue).padStart(modelSequence.padding ?? 5, "0");
|
|
58
|
+
|
|
59
|
+
const prefix = modelSequence.prefix ?? "";
|
|
60
|
+
const separator = modelSequence.separator ?? "";
|
|
61
|
+
|
|
62
|
+
const sequenceString = `${prefix}${separator}${paddedValue}`;
|
|
63
|
+
|
|
64
|
+
// 3️⃣ Duplicate check on TARGET ENTITY (extra safety)
|
|
65
|
+
const entityRepo = manager.getRepository(triggerEntity.constructor as any);
|
|
66
|
+
|
|
67
|
+
const existing = await entityRepo.findOne({
|
|
68
|
+
where: {
|
|
69
|
+
[computedFieldMetadata.fieldName]: sequenceString,
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
if (existing) {
|
|
74
|
+
throw new Error(`Duplicate Sequence generated: ${sequenceString}`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 4️⃣ set the computed field on the entity
|
|
78
|
+
(triggerEntity as any)[computedFieldMetadata.fieldName] = sequenceString;
|
|
79
|
+
|
|
80
|
+
// 5️⃣ Persist updated sequence current value
|
|
81
|
+
modelSequence.currentValue = nextValue;
|
|
82
|
+
await modelSequenceRepo.save(modelSequence);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
}
|
|
@@ -24,11 +24,20 @@ export class CrudHelperService {
|
|
|
24
24
|
private orderOptions(sort: any[] = []) {
|
|
25
25
|
const orderOptions = {};
|
|
26
26
|
sort.forEach((s: string) => {
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
const parts = s.split(':');
|
|
28
|
+
let order: string | undefined;
|
|
29
|
+
let field: string;
|
|
30
|
+
if (parts.length > 1) {
|
|
31
|
+
order = parts.pop();
|
|
32
|
+
field = parts.join(':');
|
|
33
|
+
} else {
|
|
34
|
+
field = parts[0];
|
|
35
|
+
}
|
|
36
|
+
const normalizedOrder = order ? order.toUpperCase() : 'ASC';
|
|
37
|
+
if (!['ASC', 'DESC'].includes(normalizedOrder)) {
|
|
30
38
|
throw new Error(`Invalid sort order provided: ${order}`);
|
|
31
39
|
}
|
|
40
|
+
orderOptions[field] = normalizedOrder;
|
|
32
41
|
});
|
|
33
42
|
return orderOptions;
|
|
34
43
|
}
|
|
@@ -185,10 +194,12 @@ export class CrudHelperService {
|
|
|
185
194
|
internationalisation?: boolean,
|
|
186
195
|
draftPublishWorkflow?: boolean,
|
|
187
196
|
moduleRef?: any,
|
|
188
|
-
filterCombinator: FilterCombinator = FilterCombinator.AND
|
|
197
|
+
filterCombinator: FilterCombinator = FilterCombinator.AND,
|
|
198
|
+
applyPagination: boolean = true,
|
|
199
|
+
applySorting: boolean = true
|
|
189
200
|
): SelectQueryBuilder<any> { // TODO : Check how to pass a type to SelectQueryBuilder instead of any
|
|
190
201
|
let { limit, offset, showSoftDeleted, filters } = basicFilterDto;
|
|
191
|
-
const { fields, sort,
|
|
202
|
+
const { fields, sort, populate = [], populateMedia = [], locale, status } = basicFilterDto;
|
|
192
203
|
|
|
193
204
|
// Normalize the fields, sort, groupBy and populate options i.e (since they can be either a string or an array of strings, when coming from the request)
|
|
194
205
|
const normalizedFields = this.normalize(fields);
|
|
@@ -201,10 +212,6 @@ export class CrudHelperService {
|
|
|
201
212
|
normalizedAndFilteredPopulateAttributes.push(...additionalPopulate.filter((relation) => !normalizedAndFilteredPopulateAttributes.includes(relation)));
|
|
202
213
|
|
|
203
214
|
const normalizedSort = this.normalize(sort);
|
|
204
|
-
const normalizedGroupBy = this.normalize(groupBy);
|
|
205
|
-
if (normalizedGroupBy.length > 1) {
|
|
206
|
-
throw new Error(ERROR_MESSAGES.GROUP_BY_LIMIT);
|
|
207
|
-
}
|
|
208
215
|
|
|
209
216
|
// Depending upon the populate option, apply the join clause
|
|
210
217
|
if (normalizedAndFilteredPopulateAttributes && normalizedAndFilteredPopulateAttributes.length) {
|
|
@@ -255,7 +262,7 @@ export class CrudHelperService {
|
|
|
255
262
|
}
|
|
256
263
|
|
|
257
264
|
// Depending upon the order option, apply the order by clause
|
|
258
|
-
if (normalizedSort && normalizedSort.length) {
|
|
265
|
+
if (applySorting && normalizedSort && normalizedSort.length) {
|
|
259
266
|
const orderOptions = this.orderOptions(normalizedSort);
|
|
260
267
|
if (orderOptions) {
|
|
261
268
|
const orderOptionKeys = Object.keys(orderOptions) as Array<keyof typeof orderOptions>;
|
|
@@ -276,16 +283,11 @@ export class CrudHelperService {
|
|
|
276
283
|
qb.where(`${entityAlias}.deletedAt IS NOT NULL`);
|
|
277
284
|
}
|
|
278
285
|
|
|
279
|
-
// Apply the group by options
|
|
280
|
-
if (normalizedGroupBy && normalizedGroupBy.length) {
|
|
281
|
-
normalizedGroupBy.forEach((field: string) => {
|
|
282
|
-
qb.addGroupBy(`${entityAlias}.${field}`);
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
|
|
286
286
|
// Apply the pagination options & handle the case when the query has joins
|
|
287
|
-
if (
|
|
288
|
-
|
|
287
|
+
if (applyPagination) {
|
|
288
|
+
if (limit) this.hasJoins(qb) ? qb.take(limit) : qb.limit(limit);
|
|
289
|
+
if (offset) this.hasJoins(qb) ? qb.skip(offset) : qb.offset(offset);
|
|
290
|
+
}
|
|
289
291
|
return qb;
|
|
290
292
|
}
|
|
291
293
|
|
|
@@ -307,6 +309,185 @@ export class CrudHelperService {
|
|
|
307
309
|
return qb;
|
|
308
310
|
}
|
|
309
311
|
|
|
312
|
+
private sanitizeAlias(alias: string) {
|
|
313
|
+
return alias.replace(/[^a-zA-Z0-9_]/g, '_');
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
private isAliasJoined(queryBuilder: SelectQueryBuilder<any>, alias: string): boolean {
|
|
317
|
+
return queryBuilder.expressionMap.joinAttributes.some(join => join.alias?.name === alias);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
private getExistingJoinAlias(qb: SelectQueryBuilder<any>, joinProperty: string): string | undefined {
|
|
321
|
+
const existingJoin = qb.expressionMap.joinAttributes.find(join => join.entityOrProperty === joinProperty);
|
|
322
|
+
return existingJoin?.alias?.name;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private ensureRelationPathJoined(qb: SelectQueryBuilder<any>, rootAlias: string, pathParts: string[]) {
|
|
326
|
+
const mainAlias =
|
|
327
|
+
qb.expressionMap?.mainAlias?.name ||
|
|
328
|
+
qb.expressionMap?.aliases?.find(a => a.metadata)?.name ||
|
|
329
|
+
qb.expressionMap?.aliases?.[0]?.name;
|
|
330
|
+
let parentAlias = mainAlias || rootAlias;
|
|
331
|
+
for (let i = 0; i < pathParts.length - 1; i++) {
|
|
332
|
+
const part = pathParts[i];
|
|
333
|
+
const joinProperty = `${parentAlias}.${part}`;
|
|
334
|
+
const existingAlias = this.getExistingJoinAlias(qb, joinProperty);
|
|
335
|
+
const joinAlias = existingAlias ?? this.sanitizeAlias(`${parentAlias}_${part}`);
|
|
336
|
+
if (!existingAlias && !this.isRelationJoined(qb, joinProperty) && !this.isAliasJoined(qb, joinAlias)) {
|
|
337
|
+
qb.leftJoin(joinProperty, joinAlias);
|
|
338
|
+
}
|
|
339
|
+
parentAlias = joinAlias;
|
|
340
|
+
}
|
|
341
|
+
return { alias: parentAlias, property: pathParts[pathParts.length - 1] };
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
private getDriver(qb: SelectQueryBuilder<any>) {
|
|
345
|
+
return qb.connection.options.type as string;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
private buildDateGranularityExpression(driver: string, columnExpr: string, granularity: string) {
|
|
349
|
+
switch (driver) {
|
|
350
|
+
case 'postgres':
|
|
351
|
+
case 'cockroachdb':
|
|
352
|
+
return `DATE_TRUNC('${granularity}', ${columnExpr})`;
|
|
353
|
+
case 'mysql':
|
|
354
|
+
case 'mariadb':
|
|
355
|
+
switch (granularity) {
|
|
356
|
+
case 'day': return `DATE(${columnExpr})`;
|
|
357
|
+
case 'week': return `STR_TO_DATE(DATE_FORMAT(${columnExpr}, '%x-%v-1'), '%x-%v-%w')`;
|
|
358
|
+
case 'month': return `DATE_FORMAT(${columnExpr}, '%Y-%m-01')`;
|
|
359
|
+
case 'year': return `DATE_FORMAT(${columnExpr}, '%Y-01-01')`;
|
|
360
|
+
default: throw new Error(`Unsupported granularity ${granularity} for driver ${driver}`);
|
|
361
|
+
}
|
|
362
|
+
case 'mssql':
|
|
363
|
+
case 'sqlserver':
|
|
364
|
+
switch (granularity) {
|
|
365
|
+
case 'day': return `CONVERT(date, ${columnExpr})`;
|
|
366
|
+
case 'week': return `DATEADD(week, DATEDIFF(week, 0, ${columnExpr}), 0)`;
|
|
367
|
+
case 'month': return `DATEFROMPARTS(YEAR(${columnExpr}), MONTH(${columnExpr}), 1)`;
|
|
368
|
+
case 'year': return `DATEFROMPARTS(YEAR(${columnExpr}), 1, 1)`;
|
|
369
|
+
default: throw new Error(`Unsupported granularity ${granularity} for driver ${driver}`);
|
|
370
|
+
}
|
|
371
|
+
default:
|
|
372
|
+
throw new Error(`Granularity not supported for driver ${driver}`);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
private buildGroupByExpression(qb: SelectQueryBuilder<any>, rootAlias: string, field: string) {
|
|
377
|
+
const parts = field.split(':');
|
|
378
|
+
const rawField = parts[0];
|
|
379
|
+
const granularity = parts[1];
|
|
380
|
+
const format = parts[2];
|
|
381
|
+
const pathParts = rawField.split('.');
|
|
382
|
+
const { alias, property } = this.ensureRelationPathJoined(qb, rootAlias, pathParts);
|
|
383
|
+
const columnExpr = `${alias}.${property}`;
|
|
384
|
+
const groupExpr = granularity ? this.buildDateGranularityExpression(this.getDriver(qb), columnExpr, granularity) : columnExpr;
|
|
385
|
+
const selectAlias = this.sanitizeAlias(`${rawField.replace(/\./g, '_')}${granularity ? '_' + granularity : ''}`);
|
|
386
|
+
return { groupExpr, selectAlias, sourceKey: field, format };
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
applyGroupBySelections(
|
|
390
|
+
qb: SelectQueryBuilder<any>,
|
|
391
|
+
groupBy: string[],
|
|
392
|
+
entityAlias: string
|
|
393
|
+
) {
|
|
394
|
+
const aliasMap: Record<string, string> = {};
|
|
395
|
+
const formatMap: Record<string, string | undefined> = {};
|
|
396
|
+
const expressionMap: Record<string, string> = {};
|
|
397
|
+
qb.select([]);
|
|
398
|
+
groupBy.forEach((field) => {
|
|
399
|
+
const { groupExpr, selectAlias, sourceKey, format } = this.buildGroupByExpression(qb, entityAlias, field);
|
|
400
|
+
qb.addSelect(groupExpr, selectAlias);
|
|
401
|
+
qb.addGroupBy(groupExpr);
|
|
402
|
+
aliasMap[sourceKey] = selectAlias;
|
|
403
|
+
formatMap[selectAlias] = format;
|
|
404
|
+
expressionMap[selectAlias] = groupExpr;
|
|
405
|
+
});
|
|
406
|
+
return { aliasMap, formatMap, expressionMap };
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
private buildAggregateExpression(qb: SelectQueryBuilder<any>, rootAlias: string, aggregate: string) {
|
|
410
|
+
const [rawField, rawFn] = aggregate.split(':');
|
|
411
|
+
const fn = (rawFn || 'count').toLowerCase();
|
|
412
|
+
if ((!rawField || rawField.toLowerCase() === 'count') && fn === 'count') {
|
|
413
|
+
return { expression: 'COUNT(*)', selectAlias: 'count' };
|
|
414
|
+
}
|
|
415
|
+
if (!rawField) throw new Error(`Invalid aggregate specification: ${aggregate}`);
|
|
416
|
+
const pathParts = rawField.split('.');
|
|
417
|
+
const { alias, property } = this.ensureRelationPathJoined(qb, rootAlias, pathParts);
|
|
418
|
+
const columnExpr = `${alias}.${property}`;
|
|
419
|
+
const selectAlias = this.sanitizeAlias(`${rawField.replace(/\./g, '_')}_${fn}`);
|
|
420
|
+
let expression = '';
|
|
421
|
+
switch (fn) {
|
|
422
|
+
case 'count': expression = `COUNT(${columnExpr})`; break;
|
|
423
|
+
case 'count_distinct': expression = `COUNT(DISTINCT ${columnExpr})`; break;
|
|
424
|
+
case 'sum': expression = `SUM(${columnExpr})`; break;
|
|
425
|
+
case 'avg': expression = `AVG(${columnExpr})`; break;
|
|
426
|
+
case 'min': expression = `MIN(${columnExpr})`; break;
|
|
427
|
+
case 'max': expression = `MAX(${columnExpr})`; break;
|
|
428
|
+
default: throw new Error(`Unsupported aggregate function ${fn}`);
|
|
429
|
+
}
|
|
430
|
+
return { expression, selectAlias, sourceKey: aggregate };
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
applyAggregates(
|
|
434
|
+
qb: SelectQueryBuilder<any>,
|
|
435
|
+
aggregates: string[] | undefined,
|
|
436
|
+
entityAlias: string
|
|
437
|
+
) {
|
|
438
|
+
const aggregateList = this.normalize(aggregates);
|
|
439
|
+
const aggregateAliasMap: Record<string, string> = {};
|
|
440
|
+
if (!aggregateList.length) {
|
|
441
|
+
qb.addSelect('COUNT(*)', 'count');
|
|
442
|
+
aggregateAliasMap['count'] = 'count';
|
|
443
|
+
return aggregateAliasMap;
|
|
444
|
+
}
|
|
445
|
+
aggregateList.forEach((agg) => {
|
|
446
|
+
const { expression, selectAlias, sourceKey } = this.buildAggregateExpression(qb, entityAlias, agg);
|
|
447
|
+
qb.addSelect(expression, selectAlias);
|
|
448
|
+
aggregateAliasMap[sourceKey] = selectAlias;
|
|
449
|
+
});
|
|
450
|
+
return aggregateAliasMap;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
applyGroupSortingAndPagination(
|
|
454
|
+
qb: SelectQueryBuilder<any>,
|
|
455
|
+
sort: string[] | undefined,
|
|
456
|
+
aliasMap: Record<string, string>,
|
|
457
|
+
limit?: number,
|
|
458
|
+
offset?: number
|
|
459
|
+
) {
|
|
460
|
+
const normalizedSort = this.normalize(sort);
|
|
461
|
+
if (normalizedSort.length) {
|
|
462
|
+
const orderOptions = this.orderOptions(normalizedSort);
|
|
463
|
+
const orderOptionKeys = Object.keys(orderOptions) as Array<keyof typeof orderOptions>;
|
|
464
|
+
orderOptionKeys.forEach((key) => {
|
|
465
|
+
const resolvedKey = aliasMap[key] || key as string;
|
|
466
|
+
const value = orderOptions[key] as 'ASC' | 'DESC';
|
|
467
|
+
qb.addOrderBy(`"${resolvedKey}"`, value);
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
const hasLimit = limit !== undefined && limit !== null;
|
|
471
|
+
const hasOffset = offset !== undefined && offset !== null;
|
|
472
|
+
|
|
473
|
+
// Use both take/skip and limit/offset to ensure pagination is applied even when joins are present.
|
|
474
|
+
if (hasLimit) {
|
|
475
|
+
qb.take(limit);
|
|
476
|
+
qb.limit(limit);
|
|
477
|
+
}
|
|
478
|
+
if (hasOffset) {
|
|
479
|
+
qb.skip(offset);
|
|
480
|
+
qb.offset(offset);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
async countGroups(qb: SelectQueryBuilder<any>) {
|
|
485
|
+
const clone = qb.clone();
|
|
486
|
+
clone.limit(undefined).offset(undefined).take(undefined).skip(undefined);
|
|
487
|
+
const rows = await clone.getRawMany();
|
|
488
|
+
return rows.length;
|
|
489
|
+
}
|
|
490
|
+
|
|
310
491
|
private buildJoinQueryForRelation(qb: SelectQueryBuilder<any>, entityAlias: string, relation: string) {
|
|
311
492
|
// We split the joinProperty to get the alias of the entity we are joining
|
|
312
493
|
const relationParts = relation.split('.');
|
|
@@ -341,46 +522,109 @@ export class CrudHelperService {
|
|
|
341
522
|
return field.includes('(');
|
|
342
523
|
}
|
|
343
524
|
|
|
344
|
-
isAggregateFieldKey(key: string,
|
|
345
|
-
return
|
|
525
|
+
isAggregateFieldKey(key: string, aggregateAliases: Set<string>): boolean {
|
|
526
|
+
return aggregateAliases.has(key);
|
|
346
527
|
}
|
|
347
528
|
|
|
348
529
|
getFieldFromQueryFieldKey(queryFieldKey: string, alias: string): string {
|
|
349
530
|
return queryFieldKey.replace(`${alias}_`, '');
|
|
350
531
|
}
|
|
351
532
|
|
|
352
|
-
buildGroupByRecordsQuery(
|
|
533
|
+
buildGroupByRecordsQuery(
|
|
534
|
+
qb: SelectQueryBuilder<any>,
|
|
535
|
+
group: any,
|
|
536
|
+
alias: string,
|
|
537
|
+
groupAliasMap: Record<string, string> = {},
|
|
538
|
+
aggregateAliasMap: Record<string, string> = {},
|
|
539
|
+
groupExpressionMap: Record<string, string> = {}
|
|
540
|
+
): SelectQueryBuilder<any> {
|
|
541
|
+
const rootAlias = qb.expressionMap?.mainAlias?.name
|
|
542
|
+
?? qb.expressionMap?.aliases?.find(a => a.metadata)?.name
|
|
543
|
+
?? qb.expressionMap?.aliases?.[0]?.name
|
|
544
|
+
?? (qb as any).alias
|
|
545
|
+
?? alias;
|
|
353
546
|
qb.andWhere(new Brackets(qb => {
|
|
547
|
+
const aggregateAliasSet = new Set(Object.values(aggregateAliasMap));
|
|
548
|
+
const reverseGroupAliasMap = Object.entries(groupAliasMap).reduce((acc, [sourceKey, aliasKey]) => {
|
|
549
|
+
acc[aliasKey] = sourceKey;
|
|
550
|
+
return acc;
|
|
551
|
+
}, {} as Record<string, string>);
|
|
354
552
|
for (const key in group) {
|
|
355
|
-
if (group.hasOwnProperty(key) && !this.isAggregateFieldKey(key,
|
|
553
|
+
if (group.hasOwnProperty(key) && !this.isAggregateFieldKey(key, aggregateAliasSet)) {
|
|
356
554
|
const value = group[key];
|
|
357
|
-
const
|
|
358
|
-
|
|
555
|
+
const sourceField = reverseGroupAliasMap[key] || key;
|
|
556
|
+
const cleanedField = sourceField.split(':')[0];
|
|
557
|
+
const pathParts = cleanedField.split('.');
|
|
558
|
+
const { alias: resolvedAlias, property } = this.ensureRelationPathJoined(qb as any, rootAlias, pathParts);
|
|
559
|
+
const paramKey = this.sanitizeAlias(`${resolvedAlias}_${property}_${key}`);
|
|
560
|
+
const expr = (sourceField.includes(':') && groupExpressionMap[key])
|
|
561
|
+
? groupExpressionMap[key]
|
|
562
|
+
: `${resolvedAlias}.${property}`;
|
|
563
|
+
qb.andWhere(`${expr} = :${paramKey}`, { [paramKey]: value });
|
|
359
564
|
}
|
|
360
565
|
}
|
|
361
566
|
}));
|
|
362
567
|
return qb;
|
|
363
568
|
}
|
|
364
569
|
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
570
|
+
private formatGroupValue(value: any, format?: string) {
|
|
571
|
+
if (!format) return value;
|
|
572
|
+
if (value === null || value === undefined) return value;
|
|
573
|
+
const dateVal = value instanceof Date ? value : new Date(value);
|
|
574
|
+
if (isNaN(dateVal.getTime())) return value;
|
|
575
|
+
switch (format) {
|
|
576
|
+
case 'MMM':
|
|
577
|
+
return dateVal.toLocaleString('en', { month: 'short' });
|
|
578
|
+
case 'MMMM':
|
|
579
|
+
return dateVal.toLocaleString('en', { month: 'long' });
|
|
580
|
+
case 'YYYY':
|
|
581
|
+
return dateVal.getFullYear();
|
|
582
|
+
case 'YYYY-MM':
|
|
583
|
+
return `${dateVal.getFullYear()}-${String(dateVal.getMonth() + 1).padStart(2, '0')}`;
|
|
584
|
+
case 'YYYY-MM-DD':
|
|
585
|
+
return `${dateVal.getFullYear()}-${String(dateVal.getMonth() + 1).padStart(2, '0')}-${String(dateVal.getDate()).padStart(2, '0')}`;
|
|
586
|
+
default:
|
|
587
|
+
return value;
|
|
588
|
+
}
|
|
370
589
|
}
|
|
371
590
|
|
|
372
|
-
|
|
373
|
-
|
|
591
|
+
getGroupName(
|
|
592
|
+
group: any,
|
|
593
|
+
aggregateAliases: Set<string>,
|
|
594
|
+
groupByFields: string[],
|
|
595
|
+
groupAliasMap: Record<string, string>,
|
|
596
|
+
groupFormatMap: Record<string, string | undefined>
|
|
597
|
+
): string {
|
|
598
|
+
const orderedValues = groupByFields
|
|
599
|
+
.map(field => {
|
|
600
|
+
const alias = groupAliasMap[field] ?? this.sanitizeAlias(field.replace(/\./g, '_'));
|
|
601
|
+
const rawVal = group[alias] ?? group[field] ?? group[field.replace(/\./g, '_')];
|
|
602
|
+
return this.formatGroupValue(rawVal, groupFormatMap[alias]);
|
|
603
|
+
})
|
|
604
|
+
.filter(v => v !== undefined && v !== null);
|
|
605
|
+
|
|
606
|
+
if (orderedValues.length === 0) {
|
|
607
|
+
return Object.keys(group)
|
|
608
|
+
.filter(key => !this.isAggregateFieldKey(key, aggregateAliases))
|
|
609
|
+
.map(key => group[key])
|
|
610
|
+
.join('_');
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
return orderedValues.join('_');
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
createGroupRecords(group: any, aggregateAliases: Set<string>, groupData: any, groupByFields: string[], groupAliasMap: Record<string, string>, groupFormatMap: Record<string, string | undefined>) {
|
|
617
|
+
const groupName = this.getGroupName(group, aggregateAliases, groupByFields, groupAliasMap, groupFormatMap);
|
|
374
618
|
return {
|
|
375
619
|
groupName,
|
|
376
620
|
groupData
|
|
377
621
|
}
|
|
378
622
|
}
|
|
379
|
-
createGroupMeta(group: any,
|
|
380
|
-
const groupName = this.getGroupName(group,
|
|
623
|
+
createGroupMeta(group: any, aggregateAliases: Set<string>, groupByFields: string[], groupAliasMap: Record<string, string>, groupFormatMap: Record<string, string | undefined>) {
|
|
624
|
+
const groupName = this.getGroupName(group, aggregateAliases, groupByFields, groupAliasMap, groupFormatMap);
|
|
381
625
|
const groupAggregateValues = {}
|
|
382
626
|
for (const key in group) {
|
|
383
|
-
if (group.hasOwnProperty(key) && this.isAggregateFieldKey(key,
|
|
627
|
+
if (group.hasOwnProperty(key) && this.isAggregateFieldKey(key, aggregateAliases)) {
|
|
384
628
|
const value = group[key];
|
|
385
629
|
groupAggregateValues[key] = value;
|
|
386
630
|
}
|
|
@@ -393,27 +637,21 @@ export class CrudHelperService {
|
|
|
393
637
|
|
|
394
638
|
async countGroupedRecords(qb: SelectQueryBuilder<any>, basicFilterDto: BasicFilterDto, entityAlias: string) { //TODO : Check how to pass a type to SelectQueryBuilder instead of any
|
|
395
639
|
const { limit, offset, ...rest } = basicFilterDto;
|
|
396
|
-
|
|
397
640
|
const filteredDto = { ...rest, limit: undefined, offset: undefined };
|
|
398
641
|
|
|
399
|
-
const filteredQB = this.buildFilterQuery(qb, filteredDto as BasicFilterDto, entityAlias);
|
|
642
|
+
const filteredQB = this.buildFilterQuery(qb, filteredDto as BasicFilterDto, entityAlias, undefined, undefined, undefined, FilterCombinator.AND, false, false);
|
|
400
643
|
|
|
401
|
-
|
|
402
|
-
const groupByField = filteredDto.groupBy;
|
|
644
|
+
const groupByFields = this.normalize(filteredDto.groupBy);
|
|
403
645
|
|
|
404
|
-
if (!
|
|
646
|
+
if (!groupByFields || groupByFields.length === 0) {
|
|
405
647
|
throw new Error(ERROR_MESSAGES.INVALID_GROUP_BY_COUNT);
|
|
406
648
|
}
|
|
407
649
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
.
|
|
411
|
-
.addSelect(`${entityAlias}.${field}`, 'groupField')
|
|
412
|
-
.groupBy(`${entityAlias}.${field}`)
|
|
413
|
-
.limit(undefined) // Important: prevent LIMIT 1 from propagating
|
|
414
|
-
.offset(undefined)
|
|
415
|
-
.getRawMany();
|
|
650
|
+
this.applyGroupBySelections(filteredQB, groupByFields, entityAlias);
|
|
651
|
+
this.applyAggregates(filteredQB, ['count'], entityAlias);
|
|
652
|
+
filteredQB.limit(undefined).offset(undefined).take(undefined).skip(undefined);
|
|
416
653
|
|
|
654
|
+
const rawResults = await filteredQB.getRawMany();
|
|
417
655
|
return rawResults.length;
|
|
418
656
|
}
|
|
419
657
|
|
|
@@ -465,4 +703,4 @@ export class CrudHelperService {
|
|
|
465
703
|
|
|
466
704
|
|
|
467
705
|
|
|
468
|
-
}
|
|
706
|
+
}
|