@solidxai/core 0.1.10 → 0.1.11-beta.1

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.
Files changed (158) hide show
  1. package/dist/controllers/datasource-introspection.controller.d.ts +234 -0
  2. package/dist/controllers/datasource-introspection.controller.d.ts.map +1 -0
  3. package/dist/controllers/datasource-introspection.controller.js +98 -0
  4. package/dist/controllers/datasource-introspection.controller.js.map +1 -0
  5. package/dist/controllers/datasource-management.controller.d.ts +18 -0
  6. package/dist/controllers/datasource-management.controller.d.ts.map +1 -0
  7. package/dist/controllers/datasource-management.controller.js +55 -0
  8. package/dist/controllers/datasource-management.controller.js.map +1 -0
  9. package/dist/dtos/create-datasource-management.dto.d.ts +25 -0
  10. package/dist/dtos/create-datasource-management.dto.d.ts.map +1 -0
  11. package/dist/dtos/create-datasource-management.dto.js +169 -0
  12. package/dist/dtos/create-datasource-management.dto.js.map +1 -0
  13. package/dist/dtos/datasource-introspection-mapping.dto.d.ts +45 -0
  14. package/dist/dtos/datasource-introspection-mapping.dto.d.ts.map +1 -0
  15. package/dist/dtos/datasource-introspection-mapping.dto.js +233 -0
  16. package/dist/dtos/datasource-introspection-mapping.dto.js.map +1 -0
  17. package/dist/index.d.ts +5 -0
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +5 -0
  20. package/dist/index.js.map +1 -1
  21. package/dist/interfaces/datasource-introspection-provider.interface.d.ts +26 -0
  22. package/dist/interfaces/datasource-introspection-provider.interface.d.ts.map +1 -0
  23. package/dist/interfaces/datasource-introspection-provider.interface.js +3 -0
  24. package/dist/interfaces/datasource-introspection-provider.interface.js.map +1 -0
  25. package/dist/repository/scheduled-job.repository.d.ts +3 -0
  26. package/dist/repository/scheduled-job.repository.d.ts.map +1 -1
  27. package/dist/repository/scheduled-job.repository.js +60 -0
  28. package/dist/repository/scheduled-job.repository.js.map +1 -1
  29. package/dist/seeders/module-metadata-seeder.service.d.ts +32 -1
  30. package/dist/seeders/module-metadata-seeder.service.d.ts.map +1 -1
  31. package/dist/seeders/module-metadata-seeder.service.js +1011 -230
  32. package/dist/seeders/module-metadata-seeder.service.js.map +1 -1
  33. package/dist/seeders/seed-data/solid-core-metadata.json +21 -0
  34. package/dist/services/action-metadata.service.d.ts +7 -0
  35. package/dist/services/action-metadata.service.d.ts.map +1 -1
  36. package/dist/services/action-metadata.service.js +76 -3
  37. package/dist/services/action-metadata.service.js.map +1 -1
  38. package/dist/services/authentication.service.js +1 -1
  39. package/dist/services/authentication.service.js.map +1 -1
  40. package/dist/services/dashboard-providers/mq-dashboard-latency-trend-provider.service.d.ts.map +1 -1
  41. package/dist/services/dashboard-providers/mq-dashboard-latency-trend-provider.service.js +2 -2
  42. package/dist/services/dashboard-providers/mq-dashboard-latency-trend-provider.service.js.map +1 -1
  43. package/dist/services/dashboard-providers/mq-dashboard-messages-over-time-provider.service.d.ts.map +1 -1
  44. package/dist/services/dashboard-providers/mq-dashboard-messages-over-time-provider.service.js +2 -2
  45. package/dist/services/dashboard-providers/mq-dashboard-messages-over-time-provider.service.js.map +1 -1
  46. package/dist/services/dashboard-providers/mq-dashboard-provider-utils.d.ts +3 -0
  47. package/dist/services/dashboard-providers/mq-dashboard-provider-utils.d.ts.map +1 -1
  48. package/dist/services/dashboard-providers/mq-dashboard-provider-utils.js +71 -0
  49. package/dist/services/dashboard-providers/mq-dashboard-provider-utils.js.map +1 -1
  50. package/dist/services/dashboard-providers/mq-dashboard-queue-sla-heatmap-provider.service.d.ts.map +1 -1
  51. package/dist/services/dashboard-providers/mq-dashboard-queue-sla-heatmap-provider.service.js +3 -3
  52. package/dist/services/dashboard-providers/mq-dashboard-queue-sla-heatmap-provider.service.js.map +1 -1
  53. package/dist/services/datasource-introspection/mssql-datasource-introspection-provider.service.d.ts +11 -0
  54. package/dist/services/datasource-introspection/mssql-datasource-introspection-provider.service.d.ts.map +1 -0
  55. package/dist/services/datasource-introspection/mssql-datasource-introspection-provider.service.js +181 -0
  56. package/dist/services/datasource-introspection/mssql-datasource-introspection-provider.service.js.map +1 -0
  57. package/dist/services/datasource-introspection/mysql-datasource-introspection-provider.service.d.ts +11 -0
  58. package/dist/services/datasource-introspection/mysql-datasource-introspection-provider.service.d.ts.map +1 -0
  59. package/dist/services/datasource-introspection/mysql-datasource-introspection-provider.service.js +90 -0
  60. package/dist/services/datasource-introspection/mysql-datasource-introspection-provider.service.js.map +1 -0
  61. package/dist/services/datasource-introspection/postgres-datasource-introspection-provider.service.d.ts +11 -0
  62. package/dist/services/datasource-introspection/postgres-datasource-introspection-provider.service.d.ts.map +1 -0
  63. package/dist/services/datasource-introspection/postgres-datasource-introspection-provider.service.js +156 -0
  64. package/dist/services/datasource-introspection/postgres-datasource-introspection-provider.service.js.map +1 -0
  65. package/dist/services/datasource-introspection.service.d.ts +288 -0
  66. package/dist/services/datasource-introspection.service.d.ts.map +1 -0
  67. package/dist/services/datasource-introspection.service.js +1278 -0
  68. package/dist/services/datasource-introspection.service.js.map +1 -0
  69. package/dist/services/datasource-management.service.d.ts +60 -0
  70. package/dist/services/datasource-management.service.d.ts.map +1 -0
  71. package/dist/services/datasource-management.service.js +461 -0
  72. package/dist/services/datasource-management.service.js.map +1 -0
  73. package/dist/services/field-metadata.service.d.ts.map +1 -1
  74. package/dist/services/field-metadata.service.js +19 -0
  75. package/dist/services/field-metadata.service.js.map +1 -1
  76. package/dist/services/model-metadata.service.d.ts +1 -3
  77. package/dist/services/model-metadata.service.d.ts.map +1 -1
  78. package/dist/services/model-metadata.service.js +47 -15
  79. package/dist/services/model-metadata.service.js.map +1 -1
  80. package/dist/services/role-metadata.service.d.ts +5 -0
  81. package/dist/services/role-metadata.service.d.ts.map +1 -1
  82. package/dist/services/role-metadata.service.js +85 -31
  83. package/dist/services/role-metadata.service.js.map +1 -1
  84. package/dist/services/selection-providers/mq-dashboard-message-broker-variable-options-provider.service.d.ts.map +1 -1
  85. package/dist/services/selection-providers/mq-dashboard-message-broker-variable-options-provider.service.js +2 -3
  86. package/dist/services/selection-providers/mq-dashboard-message-broker-variable-options-provider.service.js.map +1 -1
  87. package/dist/services/selection-providers/mq-dashboard-queue-name-variable-options-provider.service.d.ts.map +1 -1
  88. package/dist/services/selection-providers/mq-dashboard-queue-name-variable-options-provider.service.js +2 -3
  89. package/dist/services/selection-providers/mq-dashboard-queue-name-variable-options-provider.service.js.map +1 -1
  90. package/dist/services/setting.service.d.ts +1 -0
  91. package/dist/services/setting.service.d.ts.map +1 -1
  92. package/dist/services/setting.service.js +32 -8
  93. package/dist/services/setting.service.js.map +1 -1
  94. package/dist/services/solid-ts-morph.service.d.ts +1 -1
  95. package/dist/services/solid-ts-morph.service.d.ts.map +1 -1
  96. package/dist/services/solid-ts-morph.service.js.map +1 -1
  97. package/dist/services/view-metadata.service.d.ts +6 -0
  98. package/dist/services/view-metadata.service.d.ts.map +1 -1
  99. package/dist/services/view-metadata.service.js +66 -1
  100. package/dist/services/view-metadata.service.js.map +1 -1
  101. package/dist/solid-core.module.d.ts.map +1 -1
  102. package/dist/solid-core.module.js +16 -0
  103. package/dist/solid-core.module.js.map +1 -1
  104. package/dist-tests/api/authenticate.spec.js +119 -0
  105. package/dist-tests/api/authenticate.spec.js.map +1 -0
  106. package/dist-tests/api/crud-service.findOne.cityMaster.spec.js +97 -0
  107. package/dist-tests/api/crud-service.findOne.cityMaster.spec.js.map +1 -0
  108. package/dist-tests/api/ping.spec.js +21 -0
  109. package/dist-tests/api/ping.spec.js.map +1 -0
  110. package/dist-tests/helpers/auth.js +41 -0
  111. package/dist-tests/helpers/auth.js.map +1 -0
  112. package/dist-tests/helpers/env.js +11 -0
  113. package/dist-tests/helpers/env.js.map +1 -0
  114. package/docs/agent-hub-grooming.md +301 -0
  115. package/docs/dashboards/AGENTIC_DASHBOARD_IMPLEMENTATION_PLAN.md +438 -0
  116. package/docs/dashboards/dashboard-curl-smoke-tests.txt +146 -0
  117. package/docs/dashboards/delete-legacy-dashboard-metadata.sql +172 -0
  118. package/docs/datasource-introspection-ddl-analysis.md +326 -0
  119. package/docs/datasource-introspection-implementation-plan.md +306 -0
  120. package/docs/grouping-enhancements.md +89 -0
  121. package/docs/java-spring/README.md +3 -0
  122. package/docs/java-spring/solid-core-module-deep-dive-report.md +1317 -0
  123. package/docs/module-package-import-handoff.md +691 -0
  124. package/docs/seed-changes.md +65 -0
  125. package/docs/test-data-workflow.md +200 -0
  126. package/docs/type-declaration-import-issue.md +24 -0
  127. package/package.json +1 -1
  128. package/src/controllers/datasource-introspection.controller.ts +58 -0
  129. package/src/controllers/datasource-management.controller.ts +24 -0
  130. package/src/dtos/create-datasource-management.dto.ts +139 -0
  131. package/src/dtos/datasource-introspection-mapping.dto.ts +182 -0
  132. package/src/index.ts +5 -0
  133. package/src/interfaces/datasource-introspection-provider.interface.ts +28 -0
  134. package/src/repository/scheduled-job.repository.ts +73 -0
  135. package/src/seeders/module-metadata-seeder.service.ts +1223 -262
  136. package/src/seeders/seed-data/solid-core-metadata.json +22 -1
  137. package/src/services/action-metadata.service.ts +87 -2
  138. package/src/services/authentication.service.ts +1 -1
  139. package/src/services/dashboard-providers/mq-dashboard-latency-trend-provider.service.ts +9 -4
  140. package/src/services/dashboard-providers/mq-dashboard-messages-over-time-provider.service.ts +9 -4
  141. package/src/services/dashboard-providers/mq-dashboard-provider-utils.ts +82 -0
  142. package/src/services/dashboard-providers/mq-dashboard-queue-sla-heatmap-provider.service.ts +10 -4
  143. package/src/services/datasource-introspection/mssql-datasource-introspection-provider.service.ts +185 -0
  144. package/src/services/datasource-introspection/mysql-datasource-introspection-provider.service.ts +95 -0
  145. package/src/services/datasource-introspection/postgres-datasource-introspection-provider.service.ts +160 -0
  146. package/src/services/datasource-introspection.service.ts +1536 -0
  147. package/src/services/datasource-management.service.ts +566 -0
  148. package/src/services/field-metadata.service.ts +21 -1
  149. package/src/services/model-metadata.service.ts +50 -13
  150. package/src/services/role-metadata.service.ts +100 -47
  151. package/src/services/selection-providers/mq-dashboard-message-broker-variable-options-provider.service.ts +2 -4
  152. package/src/services/selection-providers/mq-dashboard-queue-name-variable-options-provider.service.ts +2 -4
  153. package/src/services/setting.service.ts +43 -12
  154. package/src/services/solid-ts-morph.service.ts +2 -2
  155. package/src/services/view-metadata.service.ts +79 -1
  156. package/src/solid-core.module.ts +16 -0
  157. package/.claude/settings.local.json +0 -15
  158. package/src/services/1.js +0 -6
@@ -24,6 +24,7 @@ import { ImportTransaction } from '../entities/import-transaction.entity';
24
24
  import { MenuItemMetadata } from '../entities/menu-item-metadata.entity';
25
25
  import { UserViewMetadata } from '../entities/user-view-metadata.entity';
26
26
  import { PermissionMetadata } from '../entities/permission-metadata.entity';
27
+ import { RoleMetadata } from '../entities/role-metadata.entity';
27
28
  import { ViewMetadata } from '../entities/view-metadata.entity';
28
29
  import { CommandService } from '../helpers/command.service';
29
30
  import {
@@ -37,7 +38,6 @@ import { CrudHelperService, FilterCombinator } from './crud-helper.service';
37
38
  import { CRUDService } from './crud.service';
38
39
  import { FieldMetadataService } from './field-metadata.service';
39
40
  import { MediaStorageProviderMetadataService } from './media-storage-provider-metadata.service';
40
- import { RoleMetadataService } from './role-metadata.service';
41
41
  import { SolidIntrospectService } from './solid-introspect.service';
42
42
  import { SolidTsMorphService } from './solid-ts-morph.service';
43
43
 
@@ -59,7 +59,6 @@ export class ModelMetadataService {
59
59
  private readonly crudHelperService: CrudHelperService,
60
60
  private readonly mediaStorageProviderMetadataService: MediaStorageProviderMetadataService,
61
61
  private readonly fieldMetadataService: FieldMetadataService,
62
- private readonly roleService: RoleMetadataService,
63
62
  private readonly moduleMetadataHelperService: ModuleMetadataHelperService,
64
63
  readonly introspectService: SolidIntrospectService,
65
64
  private readonly solidTsMorphService: SolidTsMorphService,
@@ -501,6 +500,27 @@ export class ModelMetadataService {
501
500
 
502
501
  // if found
503
502
  if (existingModelMetadata) {
503
+ const hasChanges = Object.entries(updateDto).some(([key, value]) => {
504
+ if (key === 'module' || key === 'parentModel' || key === 'userKeyField') {
505
+ return existingModelMetadata[key]?.id !== value?.id;
506
+ }
507
+ if (key === 'fields') {
508
+ return JSON.stringify(existingModelMetadata.fields ?? null) !== JSON.stringify(value ?? null);
509
+ }
510
+ const currentValue = existingModelMetadata[key];
511
+ if (Array.isArray(currentValue) || Array.isArray(value)) {
512
+ return JSON.stringify(currentValue ?? null) !== JSON.stringify(value ?? null);
513
+ }
514
+ if (value && typeof value === 'object') {
515
+ return JSON.stringify(currentValue ?? null) !== JSON.stringify(value ?? null);
516
+ }
517
+ return currentValue !== value;
518
+ });
519
+
520
+ if (!hasChanges) {
521
+ return existingModelMetadata;
522
+ }
523
+
504
524
  const updatedModelMetadata = { ...existingModelMetadata, ...updateDto };
505
525
  const updatedModel = await this.modelMetadataRepo.save(updatedModelMetadata);
506
526
  return updatedModel
@@ -635,22 +655,27 @@ export class ModelMetadataService {
635
655
  const metaData = await this.moduleMetadataHelperService.getModuleMetadataConfiguration(filePath);
636
656
  if (metaData) {
637
657
  const moduleMetadata = metaData?.moduleMetadata ?? metaData;
658
+ const vamMetadata =
659
+ Array.isArray(metaData?.views) || Array.isArray(metaData?.actions) || Array.isArray(metaData?.menus)
660
+ ? metaData
661
+ : moduleMetadata;
638
662
  const removedActionNameSet = new Set(removedActionNames);
639
663
  const removedMenuNameSet = new Set(removedMenuNames);
640
664
  const removedViewNameSet = new Set(removedViewNames);
641
665
 
642
- const existingModelIndex = metaData.moduleMetadata.models.findIndex(
666
+ const existingModels = Array.isArray(moduleMetadata?.models) ? moduleMetadata.models : [];
667
+ const existingModelIndex = existingModels.findIndex(
643
668
  (existingModel: any) => existingModel.singularName === modelEntity.singularName
644
669
  );
645
670
 
646
671
  // Remove the model to be deleted from the metadata
647
672
  if (existingModelIndex !== -1) {
648
- metaData.moduleMetadata.models.splice(existingModelIndex, 1);
673
+ existingModels.splice(existingModelIndex, 1);
649
674
  }
650
675
 
651
676
  // Remove references to this model in the menu, action & view sections.
652
- const existingViews = Array.isArray(moduleMetadata?.views) ? moduleMetadata.views : [];
653
- moduleMetadata.views = existingViews.filter((view: any) => {
677
+ const existingViews = Array.isArray(vamMetadata?.views) ? vamMetadata.views : [];
678
+ vamMetadata.views = existingViews.filter((view: any) => {
654
679
  const shouldRemove = view?.modelUserKey === modelEntity.singularName || removedViewNameSet.has(view?.name);
655
680
  if (shouldRemove && view?.name) {
656
681
  removedViewNameSet.add(view.name);
@@ -658,8 +683,8 @@ export class ModelMetadataService {
658
683
  return !shouldRemove;
659
684
  });
660
685
 
661
- const existingActions = Array.isArray(moduleMetadata?.actions) ? moduleMetadata.actions : [];
662
- moduleMetadata.actions = existingActions.filter((action: any) => {
686
+ const existingActions = Array.isArray(vamMetadata?.actions) ? vamMetadata.actions : [];
687
+ vamMetadata.actions = existingActions.filter((action: any) => {
663
688
  const shouldRemove =
664
689
  action?.modelUserKey === modelEntity.singularName ||
665
690
  removedActionNameSet.has(action?.name) ||
@@ -671,7 +696,7 @@ export class ModelMetadataService {
671
696
  return !shouldRemove;
672
697
  });
673
698
 
674
- let pendingMenus = Array.isArray(moduleMetadata?.menus) ? [...moduleMetadata.menus] : [];
699
+ let pendingMenus = Array.isArray(vamMetadata?.menus) ? [...vamMetadata.menus] : [];
675
700
  let menuRemovedOnPass = true;
676
701
  while (menuRemovedOnPass) {
677
702
  menuRemovedOnPass = false;
@@ -693,7 +718,7 @@ export class ModelMetadataService {
693
718
  return true;
694
719
  });
695
720
  }
696
- moduleMetadata.menus = pendingMenus;
721
+ vamMetadata.menus = pendingMenus;
697
722
 
698
723
  const updatedContent = JSON.stringify(metaData, null, 2);
699
724
  await fs.writeFile(filePath, updatedContent);
@@ -1386,7 +1411,15 @@ export class ModelMetadataService {
1386
1411
  existingTreeViewAction = await actionRepo.save(createdTreeViewAction);
1387
1412
  }
1388
1413
 
1389
- const adminRole = await this.roleService.findRoleByName('Admin');
1414
+ const roleRepo = this.dataSource.getRepository(RoleMetadata);
1415
+ const adminRole = await roleRepo.findOne({
1416
+ where: { name: 'Admin' },
1417
+ select: { id: true, name: true }
1418
+ });
1419
+
1420
+ if (!adminRole) {
1421
+ throw new NotFoundException(`Entity #Admin not found`);
1422
+ }
1390
1423
 
1391
1424
  const menuData = {
1392
1425
  displayName: `${model.displayName}`,
@@ -1394,7 +1427,6 @@ export class ModelMetadataService {
1394
1427
  sequenceNumber: 1,
1395
1428
  action: existingAction,
1396
1429
  module: resolvedModule,
1397
- roles: [adminRole],
1398
1430
  parentMenuItemUserKey: ""
1399
1431
  };
1400
1432
 
@@ -1402,7 +1434,12 @@ export class ModelMetadataService {
1402
1434
 
1403
1435
  if (!existingMenu) {
1404
1436
  const createdMenu = menuRepo.create(menuData);
1405
- await menuRepo.save(createdMenu);
1437
+ existingMenu = await menuRepo.save(createdMenu);
1438
+ await menuRepo
1439
+ .createQueryBuilder()
1440
+ .relation(MenuItemMetadata, 'roles')
1441
+ .of(existingMenu.id)
1442
+ .add(adminRole.id);
1406
1443
  }
1407
1444
  }
1408
1445
 
@@ -14,6 +14,7 @@ import { RoleMetadata } from '../entities/role-metadata.entity';
14
14
  @Injectable()
15
15
  export class RoleMetadataService extends CRUDService<RoleMetadata> {
16
16
  private readonly logger = new Logger(RoleMetadataService.name);
17
+ private readonly permissionAssignmentBatchSize = 500;
17
18
 
18
19
  constructor(
19
20
  @InjectEntityManager()
@@ -45,6 +46,20 @@ export class RoleMetadataService extends CRUDService<RoleMetadata> {
45
46
  return entity;
46
47
  }
47
48
 
49
+ async assertRoleExistsByName(roleName: string): Promise<void> {
50
+ const entity = await this.repo.findOne({
51
+ where: {
52
+ name: roleName
53
+ },
54
+ select: {
55
+ id: true
56
+ }
57
+ });
58
+ if (!entity) {
59
+ throw new NotFoundException(`Entity #${roleName} not found`);
60
+ }
61
+ }
62
+
48
63
  // OK
49
64
  async createRolesIfNotExists(roles: CreateRoleMetadataDto[]) {
50
65
  for (let id = 0; id < roles.length; id++) {
@@ -112,66 +127,47 @@ export class RoleMetadataService extends CRUDService<RoleMetadata> {
112
127
  private async _addPermissionsToRole(roleName: string, permissionNames: string[]): Promise<RoleMetadata> {
113
128
  const role = await this.repo.findOne({
114
129
  where: { name: roleName },
115
- relations: {
116
- permissions: true
130
+ select: {
131
+ id: true,
132
+ name: true,
117
133
  }
118
134
  });
119
135
  if (!role) {
120
136
  throw new Error(`Role '${roleName}' not found.`);
121
137
  }
122
138
 
123
- // this.logger.log(`Found role ${roleName}`);
124
-
125
- // The new set of permissions which are to be added to this role.
126
- let newPermissions: PermissionMetadata[];
139
+ const joinInfo = this.getRolePermissionJoinInfo();
140
+ const targetPermissions = await this.loadTargetPermissions(permissionNames);
127
141
 
128
- // Load all the specified permissions in the system.
129
- if (permissionNames && permissionNames.length != 0) {
130
- // this.logger.log(`Loading specified permissions.`);
142
+ if (!permissionNames || permissionNames.length === 0) {
143
+ await this.entityManager
144
+ .createQueryBuilder()
145
+ .delete()
146
+ .from(joinInfo.tableName)
147
+ .where(`${joinInfo.roleIdColumn} = :roleId`, { roleId: role.id })
148
+ .execute();
131
149
 
132
- newPermissions = await this.permissionRepository.find({ where: { name: In(permissionNames) } });
133
- if (newPermissions.length !== permissionNames.length) {
134
- throw new Error(`One or more permissions not found.`);
135
- }
150
+ await this.insertRolePermissionMappings(role.id, targetPermissions.map((permission) => permission.id), joinInfo);
136
151
  }
137
152
  else {
138
- // this.logger.log(`Loading all permissions in system.`);
139
-
140
- // Load all permissions in the system.
141
- // TODO: Do we want to convert this to a paginated query to avoid having to load a very large permissions table into memory?
142
- newPermissions = await this.permissionRepository.find();
143
- if (newPermissions.length == 0) {
144
- throw new Error(`No permissions configured in the system. Did you forget to run the PermissionSeederService?`);
145
- }
146
- }
147
-
148
- // this.logger.log(`Adding ${newPermissions.length} permissions to role ${roleName}.`);
149
-
150
- // if there are already permissions assigned.
151
- if (role.permissions && role.permissions.length > 0) {
152
- for (let i = 0; i < newPermissions.length; i++) {
153
- const newPermission = newPermissions[i];
154
- let newPermissionFound = true;
155
- for (let j = 0; j < role.permissions.length; j++) {
156
- const existingPermission = role.permissions[j];
157
- if (existingPermission.name === newPermission.name) {
158
- newPermissionFound = false;
159
- break;
160
- }
161
- }
162
-
163
- if (newPermissionFound) {
164
- role.permissions.push(newPermission);
165
- }
153
+ const existingRows = await this.entityManager
154
+ .createQueryBuilder()
155
+ .select(joinInfo.permissionIdColumn, 'permissionId')
156
+ .from(joinInfo.tableName, 'role_permission')
157
+ .where(`${joinInfo.roleIdColumn} = :roleId`, { roleId: role.id })
158
+ .getRawMany();
159
+
160
+ const existingPermissionIds = new Set(existingRows.map((row) => Number(row.permissionId)));
161
+ const missingPermissionIds = targetPermissions
162
+ .map((permission) => permission.id)
163
+ .filter((permissionId) => !existingPermissionIds.has(permissionId));
164
+
165
+ if (missingPermissionIds.length > 0) {
166
+ await this.insertRolePermissionMappings(role.id, missingPermissionIds, joinInfo);
166
167
  }
167
-
168
- }
169
- // else we create a new permissions set.
170
- else {
171
- role.permissions = newPermissions;
172
168
  }
173
169
 
174
- return await this.repo.save(role);
170
+ return await this.findRoleByName(roleName);
175
171
  }
176
172
 
177
173
  async removePermissionsFromRole(roleName: string, permissionNames: string[]): Promise<RoleMetadata> {
@@ -206,5 +202,62 @@ export class RoleMetadataService extends CRUDService<RoleMetadata> {
206
202
  return existingPermission;
207
203
  }
208
204
 
205
+ private async loadTargetPermissions(permissionNames: string[] | null): Promise<PermissionMetadata[]> {
206
+ if (permissionNames && permissionNames.length !== 0) {
207
+ const uniquePermissionNames = [...new Set(permissionNames)];
208
+ const permissions = await this.permissionRepository.find({ where: { name: In(uniquePermissionNames) } });
209
+ if (permissions.length !== uniquePermissionNames.length) {
210
+ throw new Error(`One or more permissions not found.`);
211
+ }
212
+ return permissions;
213
+ }
214
+
215
+ const permissions = await this.permissionRepository.find();
216
+ if (permissions.length === 0) {
217
+ throw new Error(`No permissions configured in the system. Did you forget to run the PermissionSeederService?`);
218
+ }
219
+ return permissions;
220
+ }
221
+
222
+ private getRolePermissionJoinInfo(): { tableName: string; roleIdColumn: string; permissionIdColumn: string } {
223
+ const relation = this.repo.metadata.relations.find((candidate) => candidate.propertyName === 'permissions');
224
+ if (!relation?.junctionEntityMetadata) {
225
+ throw new Error('Role-permission join table metadata not found.');
226
+ }
227
+
228
+ const roleIdColumn = relation.joinColumns[0]?.databaseName;
229
+ const permissionIdColumn = relation.inverseJoinColumns[0]?.databaseName;
230
+ if (!roleIdColumn || !permissionIdColumn) {
231
+ throw new Error('Role-permission join table column metadata not found.');
232
+ }
233
+
234
+ return {
235
+ tableName: relation.junctionEntityMetadata.tableName,
236
+ roleIdColumn,
237
+ permissionIdColumn,
238
+ };
239
+ }
240
+
241
+ private async insertRolePermissionMappings(
242
+ roleId: number,
243
+ permissionIds: number[],
244
+ joinInfo: { tableName: string; roleIdColumn: string; permissionIdColumn: string },
245
+ ): Promise<void> {
246
+ for (let index = 0; index < permissionIds.length; index += this.permissionAssignmentBatchSize) {
247
+ const batch = permissionIds.slice(index, index + this.permissionAssignmentBatchSize);
248
+ await this.entityManager
249
+ .createQueryBuilder()
250
+ .insert()
251
+ .into(joinInfo.tableName)
252
+ .values(
253
+ batch.map((permissionId) => ({
254
+ [joinInfo.roleIdColumn]: roleId,
255
+ [joinInfo.permissionIdColumn]: permissionId,
256
+ })),
257
+ )
258
+ .execute();
259
+ }
260
+ }
261
+
209
262
 
210
263
  }
@@ -2,6 +2,7 @@ import { Injectable } from "@nestjs/common";
2
2
  import { SelectionProvider } from "src/decorators/selection-provider.decorator";
3
3
  import { MqMessageRepository } from "src/repository/mq-message.repository";
4
4
  import { ISelectionProvider, ISelectionProviderContext, ISelectionProviderValues } from "src/interfaces";
5
+ import { applyCaseInsensitiveContainsFilter } from "src/services/dashboard-providers/mq-dashboard-provider-utils";
5
6
 
6
7
  @SelectionProvider()
7
8
  @Injectable()
@@ -31,9 +32,7 @@ export class MqDashboardMessageBrokerVariableOptionsProvider implements ISelecti
31
32
  qb.select("DISTINCT mqMessage.messageBroker", "value")
32
33
  .where("mqMessage.messageBroker IS NOT NULL");
33
34
 
34
- if (query) {
35
- qb.andWhere("mqMessage.messageBroker ILIKE :query", { query: `%${query}%` });
36
- }
35
+ applyCaseInsensitiveContainsFilter(qb, "mqMessage.messageBroker", query, "query");
37
36
 
38
37
  const rows = await qb
39
38
  .orderBy("value", "ASC")
@@ -49,4 +48,3 @@ export class MqDashboardMessageBrokerVariableOptionsProvider implements ISelecti
49
48
  }));
50
49
  }
51
50
  }
52
-
@@ -2,6 +2,7 @@ import { Injectable } from "@nestjs/common";
2
2
  import { SelectionProvider } from "src/decorators/selection-provider.decorator";
3
3
  import { MqMessageQueueRepository } from "src/repository/mq-message-queue.repository";
4
4
  import { ISelectionProvider, ISelectionProviderContext, ISelectionProviderValues } from "src/interfaces";
5
+ import { applyCaseInsensitiveContainsFilter } from "src/services/dashboard-providers/mq-dashboard-provider-utils";
5
6
 
6
7
  @SelectionProvider()
7
8
  @Injectable()
@@ -32,9 +33,7 @@ export class MqDashboardQueueNameVariableOptionsProvider implements ISelectionPr
32
33
  const limit = Math.min(Math.max(ctxt?.limit ?? 25, 1), 200);
33
34
  const offset = Math.max(ctxt?.offset ?? 0, 0);
34
35
 
35
- if (query) {
36
- qb.andWhere("mqMessageQueue.name ILIKE :query", { query: `%${query}%` });
37
- }
36
+ applyCaseInsensitiveContainsFilter(qb, "mqMessageQueue.name", query, "query");
38
37
 
39
38
  const records = await qb
40
39
  .select(["mqMessageQueue.name"])
@@ -48,4 +47,3 @@ export class MqDashboardQueueNameVariableOptionsProvider implements ISelectionPr
48
47
  .map((r) => ({ label: r.name, value: r.name }));
49
48
  }
50
49
  }
51
-
@@ -51,6 +51,10 @@ export class SettingService {
51
51
  return settings;
52
52
  }
53
53
 
54
+ private async getSettingsFromDbForSeeding(): Promise<Setting[]> {
55
+ return this.repo.find({ relations: ['moduleMetadata'] });
56
+ }
57
+
54
58
  private parseSettingValue(value: string, key: string): any {
55
59
  try {
56
60
  return JSON.parse(value);
@@ -194,15 +198,30 @@ export class SettingService {
194
198
  const saEditableAndAbove = this.getAllSettingsFromProviders().filter(i => [SettingLevel.SystemAdminEditable, SettingLevel.InternalUser].includes(i.level));
195
199
 
196
200
  // Get hold of the current values from the database.
197
- const existingSettingsFromDb: Setting[] = await this.getSettingsFromDb();
201
+ const existingSettingsFromDb: Setting[] = await this.getSettingsFromDbForSeeding();
198
202
 
199
203
  // const existingKeysFromDb = new Set(existingSettingsFromDb.map(s => s.key));
200
204
  const existingSettingsFromDbByKey = new Map(existingSettingsFromDb.map(setting => [setting.key, setting]));
201
- const settingsToMutate: Setting[] = [];
202
- // const settingsToUpdate: Setting[] = [];
205
+ const settingsToCreate: Setting[] = [];
206
+ const settingsToUpdate: Setting[] = [];
207
+ const moduleNames = Array.from(
208
+ new Set(
209
+ saEditableAndAbove
210
+ .map(setting => setting.moduleName)
211
+ .filter((moduleName): moduleName is string => !!moduleName)
212
+ )
213
+ );
214
+ const moduleMetadataByName = new Map<string, Awaited<ReturnType<ModuleMetadataRepository['findOneBy']>>>();
215
+
216
+ await Promise.all(
217
+ moduleNames.map(async (moduleName) => {
218
+ const moduleMetadata = await this.moduleMetadataRepo.findOneBy({ name: moduleName });
219
+ moduleMetadataByName.set(moduleName, moduleMetadata);
220
+ })
221
+ );
203
222
 
204
223
  for (const { key, value, level, moduleName, encrypted } of saEditableAndAbove) {
205
- const moduleMetadata = await this.moduleMetadataRepo.findOneBy({ name: moduleName });
224
+ const moduleMetadata = moduleName ? moduleMetadataByName.get(moduleName) : null;
206
225
  if (!existingSettingsFromDbByKey.has(key)) {
207
226
  const setting = new Setting();
208
227
  setting.key = key;
@@ -227,24 +246,36 @@ export class SettingService {
227
246
  }
228
247
  setting.value = rawValue;
229
248
 
230
- settingsToMutate.push(setting);
249
+ settingsToCreate.push(setting);
231
250
  }
232
251
  else {
233
252
  const setting = existingSettingsFromDbByKey.get(key);
253
+ const existingModuleMetadataId = setting.moduleMetadata?.id ?? null;
254
+ const nextModuleMetadataId = moduleMetadata?.id ?? null;
255
+ const levelChanged = setting.level !== level;
256
+ const encryptedChanged = setting.encrypted !== !!encrypted;
257
+ const moduleChanged = !!moduleMetadata && existingModuleMetadataId !== nextModuleMetadataId;
258
+
259
+ if (!levelChanged && !encryptedChanged && !moduleChanged) {
260
+ continue;
261
+ }
262
+
234
263
  setting.level = level;
235
264
  setting.encrypted = !!encrypted;
236
- if (moduleMetadata)
265
+ if (moduleMetadata) {
237
266
  setting.moduleMetadata = moduleMetadata;
238
- settingsToMutate.push(setting);
267
+ }
268
+ settingsToUpdate.push(setting);
239
269
  }
240
270
  }
241
271
 
242
- if (settingsToMutate.length > 0) {
243
- await this.repo.save(settingsToMutate);
272
+ if (settingsToCreate.length > 0) {
273
+ await this.repo.save(settingsToCreate);
274
+ }
275
+
276
+ if (settingsToUpdate.length > 0) {
277
+ await this.repo.save(settingsToUpdate);
244
278
  }
245
- // if (settingsToUpdate.length > 0) {
246
- // await this.repo.save(settingsToUpdate);
247
- // }
248
279
  }
249
280
 
250
281
  /**
@@ -5,7 +5,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
5
  import { Project, Node, ObjectLiteralExpression, ArrayLiteralExpression, QuoteKind, IndentationText } from "ts-morph";
6
6
  import { MethodDeclarationStructure, StructureKind } from "ts-morph";
7
7
 
8
- type Bucket = "providers" | "exports";
8
+ type Bucket = "imports" | "providers" | "exports";
9
9
 
10
10
  // interface SolidTsMorphOptions {
11
11
  // /** Absolute path to monorepo root. Defaults to auto-detect or ENV. */
@@ -470,4 +470,4 @@ export class SolidTsMorphService {
470
470
 
471
471
 
472
472
 
473
- }
473
+ }
@@ -21,6 +21,74 @@ import { MenuItemMetadata } from 'src/entities/menu-item-metadata.entity';
21
21
 
22
22
  @Injectable()
23
23
  export class ViewMetadataService extends CRUDService<ViewMetadata> {
24
+ private readonly comparableFields = [
25
+ 'name',
26
+ 'displayName',
27
+ 'type',
28
+ 'context',
29
+ 'layout',
30
+ 'module',
31
+ 'model',
32
+ ] as const;
33
+ private readonly relationCompareFields = new Set(['module', 'model']);
34
+ private readonly jsonCompareFields = new Set(['layout', 'context']);
35
+
36
+ private normalizeJsonFieldValue(value: any): any {
37
+ if (typeof value === 'string') {
38
+ const trimmedValue = value.trim();
39
+ if (
40
+ (trimmedValue.startsWith('{') && trimmedValue.endsWith('}')) ||
41
+ (trimmedValue.startsWith('[') && trimmedValue.endsWith(']'))
42
+ ) {
43
+ try {
44
+ return this.normalizeJsonFieldValue(JSON.parse(trimmedValue));
45
+ } catch {
46
+ return value;
47
+ }
48
+ }
49
+ return value;
50
+ }
51
+
52
+ if (Array.isArray(value)) {
53
+ return value.map((item) => this.normalizeJsonFieldValue(item));
54
+ }
55
+
56
+ if (value && typeof value === 'object') {
57
+ return Object.keys(value)
58
+ .sort()
59
+ .reduce((result, key) => {
60
+ result[key] = this.normalizeJsonFieldValue(value[key]);
61
+ return result;
62
+ }, {} as Record<string, any>);
63
+ }
64
+
65
+ return value;
66
+ }
67
+
68
+ private getCanonicalJsonFieldString(value: any): string {
69
+ return JSON.stringify(this.normalizeJsonFieldValue(value) ?? null);
70
+ }
71
+
72
+ private hasChanges(existingSolidView: ViewMetadata, updateSolidViewDto: UpdateViewMetadataDto): boolean {
73
+ return this.comparableFields.some((key) => {
74
+ const value = (updateSolidViewDto as any)[key];
75
+ if (typeof value === 'undefined') {
76
+ return false;
77
+ }
78
+
79
+ if (this.relationCompareFields.has(key)) {
80
+ const relationValue = value as any;
81
+ return (existingSolidView as any)[key]?.id !== relationValue?.id;
82
+ }
83
+
84
+ if (this.jsonCompareFields.has(key)) {
85
+ return this.getCanonicalJsonFieldString((existingSolidView as any)[key]) !== this.getCanonicalJsonFieldString(value);
86
+ }
87
+
88
+ return (existingSolidView as any)[key] !== value;
89
+ });
90
+ }
91
+
24
92
  constructor(
25
93
  readonly actionMetadataService: ActionMetadataService,
26
94
  readonly menuItemMetadataService: MenuItemMetadataService,
@@ -438,10 +506,20 @@ export class ViewMetadataService extends CRUDService<ViewMetadata> {
438
506
 
439
507
  async upsert(updateSolidViewDto: UpdateViewMetadataDto) {
440
508
  // First check if module already exists using name
441
- const existingSolidView = await this.findOneByUserKey(updateSolidViewDto.name);
509
+ const existingSolidView = await this.findOneByUserKey(updateSolidViewDto.name, {
510
+ module: true,
511
+ model: true,
512
+ });
442
513
 
443
514
  // if found
444
515
  if (existingSolidView) {
516
+ const hasChanges = this.hasChanges(existingSolidView, updateSolidViewDto);
517
+
518
+ if (!hasChanges) {
519
+ this.logger.debug(`Skipping view upsert for ${updateSolidViewDto.name}; no changes detected.`);
520
+ return existingSolidView;
521
+ }
522
+
445
523
  const updatedSolidViewDto = { ...existingSolidView, ...updateSolidViewDto };
446
524
  return this.repo.save(updatedSolidViewDto);
447
525
  }
@@ -20,6 +20,8 @@ import { ModelMetadataController } from "./controllers/model-metadata.controller
20
20
  import { ModuleMetadataExplorerController } from "./controllers/module-metadata-explorer.controller";
21
21
  import { ModuleMetadataController } from "./controllers/module-metadata.controller";
22
22
  import { ModulePackageController } from "./controllers/module-package.controller";
23
+ import { DatasourceManagementController } from "./controllers/datasource-management.controller";
24
+ import { DatasourceIntrospectionController } from "./controllers/datasource-introspection.controller";
23
25
  import { TestController } from "./controllers/test.controller";
24
26
  import { FieldMetadata } from "./entities/field-metadata.entity";
25
27
  import { ListOfValues } from "./entities/list-of-values.entity";
@@ -36,6 +38,8 @@ import { PseudoForeignKeySelectionProvider } from "./services/selection-provider
36
38
  import { ModuleMetadataSeederService } from "./seeders/module-metadata-seeder.service";
37
39
  import { ModuleTestDataService } from "./seeders/module-test-data.service";
38
40
  import { CrudHelperService } from "./services/crud-helper.service";
41
+ import { DatasourceManagementService } from "./services/datasource-management.service";
42
+ import { DatasourceIntrospectionService } from "./services/datasource-introspection.service";
39
43
  import { FieldMetadataService } from "./services/field-metadata.service";
40
44
  import { DashboardRuntimeService } from "./services/dashboard-runtime.service";
41
45
  import { ListOfValuesService } from "./services/list-of-values.service";
@@ -379,6 +383,9 @@ import { DashboardUserLayout } from './entities/dashboard-user-layout.entity';
379
383
  import { DashboardUserLayoutService } from './services/dashboard-user-layout.service';
380
384
  import { DashboardUserLayoutController } from './controllers/dashboard-user-layout.controller';
381
385
  import { DashboardUserLayoutRepository } from './repositories/dashboard-user-layout.repository';
386
+ import { MssqlDatasourceIntrospectionProviderService } from "./services/datasource-introspection/mssql-datasource-introspection-provider.service";
387
+ import { MysqlDatasourceIntrospectionProviderService } from "./services/datasource-introspection/mysql-datasource-introspection-provider.service";
388
+ import { PostgresDatasourceIntrospectionProviderService } from "./services/datasource-introspection/postgres-datasource-introspection-provider.service";
382
389
 
383
390
  @Global()
384
391
  @Module({
@@ -463,6 +470,8 @@ import { DashboardUserLayoutRepository } from './repositories/dashboard-user-lay
463
470
  ExportTransactionController,
464
471
  FieldMetadataController,
465
472
  DashboardController,
473
+ DatasourceManagementController,
474
+ DatasourceIntrospectionController,
466
475
  GoogleAuthenticationController,
467
476
  FacebookAuthenticationController,
468
477
  MicrosoftAuthenticationController,
@@ -526,6 +535,8 @@ import { DashboardUserLayoutRepository } from './repositories/dashboard-user-lay
526
535
  useClass: HttpExceptionFilter,
527
536
  },
528
537
  ModuleMetadataService,
538
+ DatasourceManagementService,
539
+ DatasourceIntrospectionService,
529
540
  ModuleMetadataExplorerService,
530
541
  ModuleMetadataHelperService,
531
542
  ModulePackageService,
@@ -723,7 +734,11 @@ import { DashboardUserLayoutRepository } from './repositories/dashboard-user-lay
723
734
  ExportTransactionService,
724
735
  ExcelService,
725
736
  CsvService,
737
+ DatasourceManagementService,
726
738
  DashboardRuntimeService,
739
+ MssqlDatasourceIntrospectionProviderService,
740
+ MysqlDatasourceIntrospectionProviderService,
741
+ PostgresDatasourceIntrospectionProviderService,
727
742
  ImportTransactionService,
728
743
  ImportTransactionErrorLogService,
729
744
  CreatedByUpdatedBySubscriber,
@@ -823,6 +838,7 @@ import { DashboardUserLayoutRepository } from './repositories/dashboard-user-lay
823
838
  ModelMetadataHelperService,
824
839
  ModelMetadataService,
825
840
  ModuleMetadataService,
841
+ DatasourceIntrospectionService,
826
842
  ModuleMetadataExplorerService,
827
843
  ModulePackageService,
828
844
  MqMessageQueueService,
@@ -1,15 +0,0 @@
1
- {
2
- "permissions": {
3
- "allow": [
4
- "Bash(node -e:*)",
5
- "WebFetch(domain:docs.solidxai.com)",
6
- "WebFetch(domain:github.com)",
7
- "Read(//Users/oswald/projects/Solid_Starters/solidctl/**)",
8
- "Read(//Users/oswald/projects/Solid_Starters/**)",
9
- "Bash(find /Users/oswald/projects/Solid_Starters/solid-core-module -type d -name migration* -o -name *database*)"
10
- ],
11
- "additionalDirectories": [
12
- "/Users/oswald/projects/Solid_Starters/solidctl"
13
- ]
14
- }
15
- }