@solidxai/core 0.1.11-beta.1 → 0.1.11-beta.11

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 (244) hide show
  1. package/.claude/settings.local.json +16 -0
  2. package/CLAUDE.md +26 -0
  3. package/dist/commands/migrate-removed-fields.command.d.ts +19 -0
  4. package/dist/commands/migrate-removed-fields.command.d.ts.map +1 -0
  5. package/dist/commands/migrate-removed-fields.command.js +77 -0
  6. package/dist/commands/migrate-removed-fields.command.js.map +1 -0
  7. package/dist/controllers/datasource-introspection.controller.d.ts +46 -1
  8. package/dist/controllers/datasource-introspection.controller.d.ts.map +1 -1
  9. package/dist/controllers/datasource-introspection.controller.js +38 -0
  10. package/dist/controllers/datasource-introspection.controller.js.map +1 -1
  11. package/dist/controllers/service.controller.d.ts +1 -0
  12. package/dist/controllers/service.controller.d.ts.map +1 -1
  13. package/dist/controllers/service.controller.js +2 -2
  14. package/dist/controllers/service.controller.js.map +1 -1
  15. package/dist/dtos/datasource-introspection-mapping.dto.d.ts +5 -0
  16. package/dist/dtos/datasource-introspection-mapping.dto.d.ts.map +1 -1
  17. package/dist/dtos/datasource-introspection-mapping.dto.js +24 -2
  18. package/dist/dtos/datasource-introspection-mapping.dto.js.map +1 -1
  19. package/dist/entities/{legacy-common-with-id.entity.d.ts → legacy-common-with-generated-id.entity.d.ts} +2 -2
  20. package/dist/entities/legacy-common-with-generated-id.entity.d.ts.map +1 -0
  21. package/dist/entities/{legacy-common-with-id.entity.js → legacy-common-with-generated-id.entity.js} +4 -4
  22. package/dist/entities/legacy-common-with-generated-id.entity.js.map +1 -0
  23. package/dist/entities/{legacy-common.entity.d.ts → legacy-common.entity-with-existing-id.entity.d.ts} +1 -1
  24. package/dist/entities/legacy-common.entity-with-existing-id.entity.d.ts.map +1 -0
  25. package/dist/entities/{legacy-common.entity.js → legacy-common.entity-with-existing-id.entity.js} +1 -1
  26. package/dist/entities/legacy-common.entity-with-existing-id.entity.js.map +1 -0
  27. package/dist/helpers/command.service.d.ts +2 -0
  28. package/dist/helpers/command.service.d.ts.map +1 -1
  29. package/dist/helpers/command.service.js +6 -2
  30. package/dist/helpers/command.service.js.map +1 -1
  31. package/dist/helpers/environment.helper.d.ts +1 -0
  32. package/dist/helpers/environment.helper.d.ts.map +1 -1
  33. package/dist/helpers/environment.helper.js +4 -0
  34. package/dist/helpers/environment.helper.js.map +1 -1
  35. package/dist/helpers/field-crud-managers/IntFieldCrudManager.d.ts.map +1 -1
  36. package/dist/helpers/field-crud-managers/IntFieldCrudManager.js +4 -1
  37. package/dist/helpers/field-crud-managers/IntFieldCrudManager.js.map +1 -1
  38. package/dist/helpers/model-metadata-helper.service.d.ts +2 -1
  39. package/dist/helpers/model-metadata-helper.service.d.ts.map +1 -1
  40. package/dist/helpers/model-metadata-helper.service.js +9 -5
  41. package/dist/helpers/model-metadata-helper.service.js.map +1 -1
  42. package/dist/helpers/nodemon-heartbeat.d.ts +5 -0
  43. package/dist/helpers/nodemon-heartbeat.d.ts.map +1 -0
  44. package/dist/helpers/nodemon-heartbeat.js +67 -0
  45. package/dist/helpers/nodemon-heartbeat.js.map +1 -0
  46. package/dist/helpers/solid-registry.d.ts +2 -1
  47. package/dist/helpers/solid-registry.d.ts.map +1 -1
  48. package/dist/helpers/solid-registry.js.map +1 -1
  49. package/dist/helpers/typeorm-db-helper.d.ts +1 -0
  50. package/dist/helpers/typeorm-db-helper.d.ts.map +1 -1
  51. package/dist/helpers/typeorm-db-helper.js +12 -0
  52. package/dist/helpers/typeorm-db-helper.js.map +1 -1
  53. package/dist/index.d.ts +3 -2
  54. package/dist/index.d.ts.map +1 -1
  55. package/dist/index.js +3 -2
  56. package/dist/index.js.map +1 -1
  57. package/dist/interfaces/datasource-introspection-provider.interface.d.ts +1 -0
  58. package/dist/interfaces/datasource-introspection-provider.interface.d.ts.map +1 -1
  59. package/dist/interfaces/datasource-introspection-provider.interface.js.map +1 -1
  60. package/dist/interfaces.d.ts +1 -0
  61. package/dist/interfaces.d.ts.map +1 -1
  62. package/dist/interfaces.js.map +1 -1
  63. package/dist/repository/scheduled-job.repository.d.ts +2 -2
  64. package/dist/repository/scheduled-job.repository.d.ts.map +1 -1
  65. package/dist/repository/scheduled-job.repository.js +6 -3
  66. package/dist/repository/scheduled-job.repository.js.map +1 -1
  67. package/dist/repository/security-rule.repository.d.ts +2 -2
  68. package/dist/repository/security-rule.repository.d.ts.map +1 -1
  69. package/dist/repository/security-rule.repository.js +6 -3
  70. package/dist/repository/security-rule.repository.js.map +1 -1
  71. package/dist/seeders/module-metadata-seeder.service.d.ts +11 -1
  72. package/dist/seeders/module-metadata-seeder.service.d.ts.map +1 -1
  73. package/dist/seeders/module-metadata-seeder.service.js +163 -3
  74. package/dist/seeders/module-metadata-seeder.service.js.map +1 -1
  75. package/dist/seeders/seed-data/solid-core-metadata.json +128 -34
  76. package/dist/services/api-key.service.d.ts.map +1 -1
  77. package/dist/services/api-key.service.js.map +1 -1
  78. package/dist/services/authentication.service.d.ts +2 -0
  79. package/dist/services/authentication.service.d.ts.map +1 -1
  80. package/dist/services/authentication.service.js +32 -11
  81. package/dist/services/authentication.service.js.map +1 -1
  82. package/dist/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.d.ts.map +1 -1
  83. package/dist/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.js +7 -5
  84. package/dist/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.js.map +1 -1
  85. package/dist/services/computed-fields/entity/concat-entity-computed-field-provider.service.d.ts +4 -0
  86. package/dist/services/computed-fields/entity/concat-entity-computed-field-provider.service.d.ts.map +1 -1
  87. package/dist/services/computed-fields/entity/concat-entity-computed-field-provider.service.js +18 -1
  88. package/dist/services/computed-fields/entity/concat-entity-computed-field-provider.service.js.map +1 -1
  89. package/dist/services/crud-helper.service.d.ts.map +1 -1
  90. package/dist/services/crud-helper.service.js +25 -12
  91. package/dist/services/crud-helper.service.js.map +1 -1
  92. package/dist/services/crud.service.d.ts +1 -0
  93. package/dist/services/crud.service.d.ts.map +1 -1
  94. package/dist/services/crud.service.js +27 -0
  95. package/dist/services/crud.service.js.map +1 -1
  96. package/dist/services/datasource-introspection/mssql-datasource-introspection-provider.service.d.ts.map +1 -1
  97. package/dist/services/datasource-introspection/mssql-datasource-introspection-provider.service.js +12 -0
  98. package/dist/services/datasource-introspection/mssql-datasource-introspection-provider.service.js.map +1 -1
  99. package/dist/services/datasource-introspection/mysql-datasource-introspection-provider.service.d.ts.map +1 -1
  100. package/dist/services/datasource-introspection/mysql-datasource-introspection-provider.service.js +2 -0
  101. package/dist/services/datasource-introspection/mysql-datasource-introspection-provider.service.js.map +1 -1
  102. package/dist/services/datasource-introspection/postgres-datasource-introspection-provider.service.d.ts.map +1 -1
  103. package/dist/services/datasource-introspection/postgres-datasource-introspection-provider.service.js +28 -15
  104. package/dist/services/datasource-introspection/postgres-datasource-introspection-provider.service.js.map +1 -1
  105. package/dist/services/datasource-introspection.service.d.ts +62 -2
  106. package/dist/services/datasource-introspection.service.d.ts.map +1 -1
  107. package/dist/services/datasource-introspection.service.js +654 -186
  108. package/dist/services/datasource-introspection.service.js.map +1 -1
  109. package/dist/services/import-transaction.service.d.ts.map +1 -1
  110. package/dist/services/import-transaction.service.js +3 -0
  111. package/dist/services/import-transaction.service.js.map +1 -1
  112. package/dist/services/locale.service.d.ts +14 -0
  113. package/dist/services/locale.service.d.ts.map +1 -1
  114. package/dist/services/locale.service.js +83 -0
  115. package/dist/services/locale.service.js.map +1 -1
  116. package/dist/services/mediaStorageProviders/file-s3-storage-provider.js +10 -10
  117. package/dist/services/mediaStorageProviders/file-s3-storage-provider.js.map +1 -1
  118. package/dist/services/menu-item-metadata.service.d.ts.map +1 -1
  119. package/dist/services/menu-item-metadata.service.js +1 -0
  120. package/dist/services/menu-item-metadata.service.js.map +1 -1
  121. package/dist/services/model-metadata.service.d.ts +3 -0
  122. package/dist/services/model-metadata.service.d.ts.map +1 -1
  123. package/dist/services/model-metadata.service.js +78 -0
  124. package/dist/services/model-metadata.service.js.map +1 -1
  125. package/dist/services/module-metadata.service.d.ts.map +1 -1
  126. package/dist/services/module-metadata.service.js +28 -0
  127. package/dist/services/module-metadata.service.js.map +1 -1
  128. package/dist/services/removed-field-migration.service.d.ts +30 -0
  129. package/dist/services/removed-field-migration.service.d.ts.map +1 -0
  130. package/dist/services/removed-field-migration.service.js +319 -0
  131. package/dist/services/removed-field-migration.service.js.map +1 -0
  132. package/dist/services/role-metadata.service.d.ts +1 -0
  133. package/dist/services/role-metadata.service.d.ts.map +1 -1
  134. package/dist/services/role-metadata.service.js +23 -2
  135. package/dist/services/role-metadata.service.js.map +1 -1
  136. package/dist/services/settings/default-settings-provider.service.d.ts +244 -0
  137. package/dist/services/settings/default-settings-provider.service.d.ts.map +1 -1
  138. package/dist/services/settings/default-settings-provider.service.js +120 -0
  139. package/dist/services/settings/default-settings-provider.service.js.map +1 -1
  140. package/dist/services/solid-ts-morph.service.d.ts +6 -0
  141. package/dist/services/solid-ts-morph.service.d.ts.map +1 -1
  142. package/dist/services/solid-ts-morph.service.js +43 -0
  143. package/dist/services/solid-ts-morph.service.js.map +1 -1
  144. package/dist/services/view-metadata.service.d.ts.map +1 -1
  145. package/dist/services/view-metadata.service.js +4 -7
  146. package/dist/services/view-metadata.service.js.map +1 -1
  147. package/dist/solid-core.module.d.ts.map +1 -1
  148. package/dist/solid-core.module.js +4 -0
  149. package/dist/solid-core.module.js.map +1 -1
  150. package/dist/subscribers/audit.subscriber.d.ts +5 -1
  151. package/dist/subscribers/audit.subscriber.d.ts.map +1 -1
  152. package/dist/subscribers/audit.subscriber.js +76 -12
  153. package/dist/subscribers/audit.subscriber.js.map +1 -1
  154. package/dist/subscribers/computed-entity-field.subscriber.d.ts +9 -1
  155. package/dist/subscribers/computed-entity-field.subscriber.d.ts.map +1 -1
  156. package/dist/subscribers/computed-entity-field.subscriber.js +45 -11
  157. package/dist/subscribers/computed-entity-field.subscriber.js.map +1 -1
  158. package/dist/subscribers/created-by-updated-by.subscriber.d.ts.map +1 -1
  159. package/dist/subscribers/created-by-updated-by.subscriber.js.map +1 -1
  160. package/dist/subscribers/field-metadata.subscriber.d.ts.map +1 -1
  161. package/dist/subscribers/field-metadata.subscriber.js.map +1 -1
  162. package/dist/subscribers/scheduled-job.subscriber.d.ts.map +1 -1
  163. package/dist/subscribers/scheduled-job.subscriber.js +3 -2
  164. package/dist/subscribers/scheduled-job.subscriber.js.map +1 -1
  165. package/dist/subscribers/security-rule.subscriber.d.ts.map +1 -1
  166. package/dist/subscribers/security-rule.subscriber.js +5 -3
  167. package/dist/subscribers/security-rule.subscriber.js.map +1 -1
  168. package/package.json +1 -1
  169. package/src/commands/migrate-removed-fields.command.ts +81 -0
  170. package/src/controllers/datasource-introspection.controller.ts +27 -1
  171. package/src/controllers/service.controller.ts +3 -2
  172. package/src/dtos/datasource-introspection-mapping.dto.ts +15 -0
  173. package/src/entities/{legacy-common-with-id.entity.ts → legacy-common-with-generated-id.entity.ts} +1 -1
  174. package/src/helpers/command.service.ts +10 -2
  175. package/src/helpers/environment.helper.ts +11 -0
  176. package/src/helpers/field-crud-managers/IntFieldCrudManager.ts +4 -1
  177. package/src/helpers/model-metadata-helper.service.ts +8 -3
  178. package/src/helpers/nodemon-heartbeat.ts +59 -0
  179. package/src/helpers/solid-registry.ts +7 -1
  180. package/src/helpers/typeorm-db-helper.ts +15 -0
  181. package/src/index.ts +3 -2
  182. package/src/interfaces/datasource-introspection-provider.interface.ts +1 -0
  183. package/src/interfaces.ts +1 -0
  184. package/src/repository/scheduled-job.repository.ts +10 -4
  185. package/src/repository/security-rule.repository.ts +10 -4
  186. package/src/seeders/module-metadata-seeder.service.ts +192 -2
  187. package/src/seeders/seed-data/solid-core-metadata.json +128 -34
  188. package/src/services/1.js +6 -0
  189. package/src/services/api-key.service.ts +36 -36
  190. package/src/services/authentication.service.ts +46 -12
  191. package/src/services/computed-fields/entity/alpha-num-external-id-computed-field-provider.ts +11 -5
  192. package/src/services/computed-fields/entity/concat-entity-computed-field-provider.service.ts +15 -1
  193. package/src/services/crud-helper.service.ts +25 -12
  194. package/src/services/crud.service.ts +36 -11
  195. package/src/services/datasource-introspection/mssql-datasource-introspection-provider.service.ts +12 -0
  196. package/src/services/datasource-introspection/mysql-datasource-introspection-provider.service.ts +2 -0
  197. package/src/services/datasource-introspection/postgres-datasource-introspection-provider.service.ts +28 -15
  198. package/src/services/datasource-introspection.service.ts +811 -161
  199. package/src/services/import-transaction.service.ts +5 -0
  200. package/src/services/locale.service.ts +111 -3
  201. package/src/services/mediaStorageProviders/file-s3-storage-provider.ts +2 -2
  202. package/src/services/menu-item-metadata.service.ts +2 -1
  203. package/src/services/model-metadata.service.ts +109 -1
  204. package/src/services/module-metadata.service.ts +36 -0
  205. package/src/services/removed-field-migration.service.ts +334 -0
  206. package/src/services/role-metadata.service.ts +26 -2
  207. package/src/services/settings/default-settings-provider.service.ts +120 -0
  208. package/src/services/solid-ts-morph.service.ts +61 -1
  209. package/src/services/view-metadata.service.ts +4 -8
  210. package/src/solid-core.module.ts +4 -0
  211. package/src/subscribers/audit.subscriber.ts +101 -18
  212. package/src/subscribers/computed-entity-field.subscriber.ts +81 -28
  213. package/src/subscribers/created-by-updated-by.subscriber.ts +3 -0
  214. package/src/subscribers/field-metadata.subscriber.ts +3 -0
  215. package/src/subscribers/scheduled-job.subscriber.ts +3 -2
  216. package/src/subscribers/security-rule.subscriber.ts +8 -3
  217. package/dist/entities/legacy-common-with-id.entity.d.ts.map +0 -1
  218. package/dist/entities/legacy-common-with-id.entity.js.map +0 -1
  219. package/dist/entities/legacy-common.entity.d.ts.map +0 -1
  220. package/dist/entities/legacy-common.entity.js.map +0 -1
  221. package/dist-tests/api/authenticate.spec.js +0 -119
  222. package/dist-tests/api/authenticate.spec.js.map +0 -1
  223. package/dist-tests/api/crud-service.findOne.cityMaster.spec.js +0 -97
  224. package/dist-tests/api/crud-service.findOne.cityMaster.spec.js.map +0 -1
  225. package/dist-tests/api/ping.spec.js +0 -21
  226. package/dist-tests/api/ping.spec.js.map +0 -1
  227. package/dist-tests/helpers/auth.js +0 -41
  228. package/dist-tests/helpers/auth.js.map +0 -1
  229. package/dist-tests/helpers/env.js +0 -11
  230. package/dist-tests/helpers/env.js.map +0 -1
  231. package/docs/agent-hub-grooming.md +0 -301
  232. package/docs/dashboards/AGENTIC_DASHBOARD_IMPLEMENTATION_PLAN.md +0 -438
  233. package/docs/dashboards/dashboard-curl-smoke-tests.txt +0 -146
  234. package/docs/dashboards/delete-legacy-dashboard-metadata.sql +0 -172
  235. package/docs/datasource-introspection-ddl-analysis.md +0 -326
  236. package/docs/datasource-introspection-implementation-plan.md +0 -306
  237. package/docs/grouping-enhancements.md +0 -89
  238. package/docs/java-spring/README.md +0 -3
  239. package/docs/java-spring/solid-core-module-deep-dive-report.md +0 -1317
  240. package/docs/module-package-import-handoff.md +0 -691
  241. package/docs/seed-changes.md +0 -65
  242. package/docs/test-data-workflow.md +0 -200
  243. package/docs/type-declaration-import-issue.md +0 -24
  244. /package/src/entities/{legacy-common.entity.ts → legacy-common.entity-with-existing-id.entity.ts} +0 -0
@@ -22,6 +22,10 @@ export class DatasourceIntrospectionMappingColumnDto {
22
22
  @IsString()
23
23
  fieldName: string;
24
24
 
25
+ @ApiProperty()
26
+ @IsString()
27
+ displayName: string;
28
+
25
29
  @ApiProperty()
26
30
  @IsString()
27
31
  dataType: string;
@@ -91,6 +95,11 @@ export class DatasourceIntrospectionMappingColumnDto {
91
95
  @ApiProperty()
92
96
  @IsBoolean()
93
97
  isPrimaryKey: boolean;
98
+
99
+ @ApiProperty({ required: false, type: "object", additionalProperties: true })
100
+ @IsOptional()
101
+ @IsObject()
102
+ fieldConfig?: Record<string, any>;
94
103
  }
95
104
 
96
105
  export class DatasourceIntrospectionMappingDto {
@@ -180,3 +189,9 @@ export class DatasourceIntrospectionMappingDto {
180
189
  @IsObject()
181
190
  reviewedModel?: Record<string, any>;
182
191
  }
192
+
193
+ export class DatasourceIntrospectionRunMigrationDto {
194
+ @ApiProperty()
195
+ @IsString()
196
+ datasource: string;
197
+ }
@@ -1,6 +1,6 @@
1
1
  import { Exclude, Expose } from "class-transformer";
2
2
  import { Column, Generated } from "typeorm";
3
- import { LEGACY_TABLE_FIELDS_PREFIX, LegacyCommonEntityWithExistingId } from "./legacy-common.entity";
3
+ import { LEGACY_TABLE_FIELDS_PREFIX, LegacyCommonEntityWithExistingId } from "./legacy-common.entity-with-existing-id.entity";
4
4
 
5
5
  @Exclude()
6
6
  export abstract class LegacyCommonEntityWithGeneratedId extends LegacyCommonEntityWithExistingId {
@@ -10,6 +10,10 @@ export type CommandWithArgs = {
10
10
  @Injectable()
11
11
  export class CommandService {
12
12
  private readonly logger = new Logger(CommandService.name);
13
+ private readonly ansiEscapePattern =
14
+ // Covers standard ANSI CSI, OSC, and related terminal escape sequences.
15
+ // eslint-disable-next-line no-control-regex
16
+ /[\u001B\u009B][[\]()#;?]*(?:(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><~]|(?:][^\u0007]*(?:\u0007|\u001B\\)))/g;
13
17
 
14
18
  /**
15
19
  * Escape an argument for Windows CMD shell
@@ -51,11 +55,11 @@ export class CommandService {
51
55
  let stderr = '';
52
56
 
53
57
  child.stdout.on('data', (data) => {
54
- stdout += data.toString();
58
+ stdout += this.stripAnsi(data.toString());
55
59
  });
56
60
 
57
61
  child.stderr.on('data', (data) => {
58
- stderr += data.toString();
62
+ stderr += this.stripAnsi(data.toString());
59
63
  });
60
64
 
61
65
  child.on('error', (error) => {
@@ -73,4 +77,8 @@ export class CommandService {
73
77
  });
74
78
  });
75
79
  }
80
+
81
+ private stripAnsi(value: string): string {
82
+ return value.replace(this.ansiEscapePattern, '');
83
+ }
76
84
  }
@@ -13,4 +13,15 @@ export function isRedisConfigured(configService: ConfigService): boolean {
13
13
  const host = configService.get<string>('REDIS_HOST');
14
14
  const port = configService.get<string>('REDIS_PORT');
15
15
  return host && port && isNumber(parseInt(port));
16
+ }
17
+
18
+ /**
19
+ * Returns true when the project is configured to use an embedded PGlite
20
+ * database with a single-connection pool. Several TypeORM subscriber and
21
+ * code-generation workarounds are only necessary (and only applied) under
22
+ * this constraint; on a regular multi-connection Postgres pool the original
23
+ * behaviour is preserved.
24
+ */
25
+ export function isEmbeddedDb(): boolean {
26
+ return process.env.DEFAULT_DATABASE_DRIVER === 'pglite';
16
27
  }
@@ -15,7 +15,10 @@ export class IntFieldCrudManager implements FieldCrudManager {
15
15
 
16
16
  validate(createDto: any): ValidationError[] {
17
17
  const fieldValue: any = createDto[this.options.fieldName];
18
- return this.applyValidations(fieldValue);
18
+ if (typeof fieldValue === 'string' && /^\d+$/.test(fieldValue.trim())) {
19
+ createDto[this.options.fieldName] = Number.parseInt(fieldValue, 10);
20
+ }
21
+ return this.applyValidations(createDto[this.options.fieldName]);
19
22
  }
20
23
 
21
24
  private applyValidations(fieldValue: any): ValidationError[] {
@@ -2,10 +2,12 @@
2
2
 
3
3
  import { forwardRef, Inject, Injectable, Logger } from "@nestjs/common";
4
4
  import { _ } from "lodash";
5
- import { LEGACY_TABLE_FIELDS_PREFIX } from "src/entities/legacy-common.entity";
5
+ import { LEGACY_TABLE_FIELDS_PREFIX } from "src/entities/legacy-common.entity-with-existing-id.entity";
6
6
  import { LegacyTableType } from "src/enums/legacy-table-type.enum";
7
+ import { ModelMetadata } from "src/entities/model-metadata.entity";
7
8
  import { ModelMetadataRepository } from "src/repository/model-metadata.repository";
8
9
  import { SolidRegistry } from "./solid-registry";
10
+ import { EntityManager } from "typeorm";
9
11
 
10
12
  @Injectable()
11
13
  export class ModelMetadataHelperService {
@@ -143,8 +145,11 @@ export class ModelMetadataHelperService {
143
145
  return systemFieldsMetadata;
144
146
  }
145
147
 
146
- async loadFieldHierarchy(modelName: any) {
147
- const model = await this.modelMetadataRepo.findOne({
148
+ async loadFieldHierarchy(modelName: any, manager?: EntityManager) {
149
+ const repo = manager
150
+ ? manager.getRepository(ModelMetadata)
151
+ : this.modelMetadataRepo;
152
+ const model = await repo.findOne({
148
153
  where: {
149
154
  singularName: modelName,
150
155
  },
@@ -0,0 +1,59 @@
1
+ import * as fsSync from 'fs';
2
+ import * as path from 'path';
3
+
4
+ /**
5
+ * The heartbeat file is placed inside the nodemon-watched src/ directory so
6
+ * that touching it resets nodemon's restart delay. This prevents nodemon from
7
+ * restarting the API server while in-process code generation is writing files
8
+ * (which is especially important for embedded PGlite projects where the
9
+ * generate-code endpoint runs schematics in-process).
10
+ *
11
+ * The file uses a .json extension because nodemon is configured to watch
12
+ * ts,json files. It is placed at the src/ root (not inside any metadata
13
+ * directory) so it is not caught by the metadata JSON ignore rule.
14
+ */
15
+ const HEARTBEAT_FILENAME = 'solidx-nodemon-heartbeat.json';
16
+ const HEARTBEAT_INTERVAL_MS = 1500;
17
+
18
+ export interface NodemonHeartbeatHandle {
19
+ stop: () => void;
20
+ }
21
+
22
+ /**
23
+ * Starts a heartbeat that touches a file in the nodemon-watched directory
24
+ * every ~1.5 s. Call `stop()` after the response has been flushed to allow
25
+ * nodemon to restart.
26
+ *
27
+ * @param srcDir Absolute path to the `src/` directory nodemon watches.
28
+ */
29
+ export function startNodemonHeartbeat(srcDir: string): NodemonHeartbeatHandle {
30
+ const filePath = path.join(srcDir, HEARTBEAT_FILENAME);
31
+ let stopped = false;
32
+
33
+ const touch = () => {
34
+ try {
35
+ fsSync.writeFileSync(filePath, String(Date.now()));
36
+ } catch {
37
+ // best-effort — if the file can't be written, nodemon will simply
38
+ // restart as usual, which is the safe fallback.
39
+ }
40
+ };
41
+
42
+ // Write immediately so the delay resets before any schematic output lands.
43
+ touch();
44
+
45
+ const timer = setInterval(touch, HEARTBEAT_INTERVAL_MS);
46
+
47
+ return {
48
+ stop() {
49
+ if (stopped) return;
50
+ stopped = true;
51
+ clearInterval(timer);
52
+ try {
53
+ fsSync.unlinkSync(filePath);
54
+ } catch {
55
+ // file may not exist — ignore
56
+ }
57
+ },
58
+ };
59
+ }
@@ -8,7 +8,7 @@ import { Locale } from 'src/entities/locale.entity';
8
8
  import { SecurityRule } from 'src/entities/security-rule.entity';
9
9
  import { IScheduledJob } from 'src/services/scheduled-jobs/scheduled-job.interface';
10
10
  import { IDashboardWidgetDataProvider, IErrorCodeProvider, ISecurityRuleConfigProvider, ISelectionProvider, ISelectionProviderContext, ISolidDatabaseModule } from "../interfaces";
11
- import { ObjectLiteral } from 'typeorm';
11
+ import { EntityManager, ObjectLiteral } from 'typeorm';
12
12
 
13
13
  type ControllerMetadata = {
14
14
  name: string;
@@ -49,6 +49,12 @@ export interface TypeOrmEventContext {
49
49
  metadataName?: string;
50
50
  updatedColumns?: string[];
51
51
  updatedRelations?: string[];
52
+ // Transaction-bound EntityManager from the originating TypeORM event. Providers that
53
+ // need to query the DB during a pre-compute hook must use this (not an injected
54
+ // DataSource/EntityManager) so the query runs on the active transaction's connection.
55
+ // On single-threaded embedded engines (PGlite), a second connection mid-transaction
56
+ // can deadlock.
57
+ manager?: EntityManager;
52
58
  }
53
59
 
54
60
  export interface ComputedFieldMetadata<TContext = any> {
@@ -45,6 +45,21 @@ const solidCoreDbType: DatasourceType =
45
45
  ? (process.env.SOLID_CORE_DB_TYPE as DatasourceType)
46
46
  : DatasourceType.postgres;
47
47
 
48
+ // Must use ANSI CAST() syntax — not Postgres-specific `::text` — because TypeORM's
49
+ // replacePropertyNamesForTheWholeQuery regex treats ':' as part of the property name,
50
+ // so `alias.col::text` is parsed as property `col::text` (no match → no column substitution).
51
+ export function buildCastToText(driver: string, colExpr: string): string {
52
+ switch (driver) {
53
+ case DatasourceType.mssql:
54
+ return `CAST(${colExpr} AS NVARCHAR(MAX))`;
55
+ case DatasourceType.mysql:
56
+ case DatasourceType.mariadb:
57
+ return `CAST(${colExpr} AS CHAR)`;
58
+ default:
59
+ return `CAST(${colExpr} AS TEXT)`;
60
+ }
61
+ }
62
+
48
63
  export function getColumnType(solidType: string): ColumnOptions {
49
64
  switch (solidType) {
50
65
  case "longText":
package/src/index.ts CHANGED
@@ -119,8 +119,8 @@ export * from './dtos/update-dashboard-user-layout.dto'
119
119
 
120
120
  export * from './entities/action-metadata.entity'
121
121
  export * from './entities/common.entity'
122
- export * from './entities/legacy-common.entity'
123
- export * from './entities/legacy-common-with-id.entity'
122
+ export * from './entities/legacy-common.entity-with-existing-id.entity'
123
+ export * from './entities/legacy-common-with-generated-id.entity'
124
124
  export * from './entities/email-attachment.entity'
125
125
  export * from './entities/email-template.entity'
126
126
  export * from './entities/field-metadata.entity'
@@ -171,6 +171,7 @@ export * from './guards/permissions.guard'
171
171
  export * from './guards/roles.guard'
172
172
 
173
173
  export * from './helpers/command.service'
174
+ export * from './helpers/nodemon-heartbeat'
174
175
  export * from './helpers/module.helper'
175
176
  export * from './helpers/schematic.service'
176
177
  export * from './helpers/solid-registry'
@@ -11,6 +11,7 @@ export type DatasourceIntrospectionColumn = {
11
11
  columnName: string;
12
12
  dataType: string;
13
13
  isAutoGenerated: boolean;
14
+ isPrimaryKey: boolean;
14
15
  isNullable: boolean;
15
16
  defaultValue: string | null;
16
17
  characterMaximumLength: number | null;
package/src/interfaces.ts CHANGED
@@ -89,6 +89,7 @@ export interface SettingDefinition<T = any> {
89
89
  encrypted?: boolean;
90
90
  label?: string;
91
91
  description?: string;
92
+ helpText?: string;
92
93
  placeholder?: string;
93
94
  group?: string;
94
95
  sortOrder?: number;
@@ -3,7 +3,7 @@ import { CreateScheduledJobDto } from "src/dtos/create-scheduled-job.dto";
3
3
  import { ModuleMetadata } from "src/entities/module-metadata.entity";
4
4
  import { ScheduledJob } from "src/entities/scheduled-job.entity";
5
5
  import { RequestContextService } from "src/services/request-context.service";
6
- import { DataSource } from "typeorm";
6
+ import { DataSource, EntityManager } from "typeorm";
7
7
  import { SecurityRuleRepository } from "./security-rule.repository";
8
8
  import { SolidBaseRepository } from "./solid-base.repository";
9
9
 
@@ -101,19 +101,25 @@ export class ScheduledJobRepository extends SolidBaseRepository<ScheduledJob> {
101
101
 
102
102
  /**
103
103
  * Converts an entity to a plain DTO object.
104
+ * When manager is provided (e.g. from a TypeORM subscriber event), the
105
+ * fallback relation load uses it so the query runs on the active
106
+ * transaction's connection instead of the default DataSource pool.
104
107
  */
105
- async toDto(scheduledJob: ScheduledJob): Promise<CreateScheduledJobDto> {
108
+ async toDto(scheduledJob: ScheduledJob, manager?: EntityManager): Promise<CreateScheduledJobDto> {
106
109
  let populatedScheduledJob: ScheduledJob = scheduledJob;
107
110
  // If the scheduled job does not have the module relation loaded, load it
108
111
  if (!scheduledJob.module) {
109
- populatedScheduledJob = await this.findOne({
112
+ const repo = manager
113
+ ? manager.getRepository(ScheduledJob)
114
+ : this;
115
+ populatedScheduledJob = await repo.findOne({
110
116
  where: {
111
117
  id: scheduledJob.id,
112
118
  },
113
119
  relations: {
114
120
  module: true,
115
121
  },
116
- });
122
+ }) ?? scheduledJob;
117
123
  }
118
124
 
119
125
  if (!populatedScheduledJob.module) {
@@ -9,7 +9,7 @@ import { SecurityRule } from 'src/entities/security-rule.entity';
9
9
  import { SolidRegistry } from 'src/helpers/solid-registry';
10
10
  import { ActiveUserData } from 'src/interfaces/active-user-data.interface';
11
11
  import { CrudHelperService } from 'src/services/crud-helper.service';
12
- import { Brackets, DataSource, SelectQueryBuilder } from 'typeorm';
12
+ import { Brackets, DataSource, EntityManager, SelectQueryBuilder } from 'typeorm';
13
13
  import { SolidBaseRepository } from './solid-base.repository';
14
14
 
15
15
  @Injectable()
@@ -84,12 +84,18 @@ export class SecurityRuleRepository extends SolidBaseRepository<SecurityRule> {
84
84
  return configString.replace('$activeUserId', activeUser.sub.toString());
85
85
  }
86
86
 
87
- async toDto(securityRule: SecurityRule): Promise<UpdateSecurityRuleDto> {
87
+ async toDto(securityRule: SecurityRule, manager?: EntityManager): Promise<UpdateSecurityRuleDto> {
88
88
  // load the role and model relations for the security rule
89
89
  let populatedSecurityRule: SecurityRule = securityRule;
90
90
  // If the security rule does not have the role and model relations loaded, load them
91
91
  if (!securityRule.role || !securityRule.modelMetadata) {
92
- populatedSecurityRule = await this.findOne({
92
+ // When a manager is provided (e.g. from a TypeORM subscriber event),
93
+ // use it so the query runs on the active transaction's connection
94
+ // instead of trying to acquire a second connection from the pool.
95
+ const repo = manager
96
+ ? manager.getRepository(SecurityRule)
97
+ : this;
98
+ populatedSecurityRule = await repo.findOne({
93
99
  where: {
94
100
  id: securityRule.id,
95
101
  },
@@ -97,7 +103,7 @@ export class SecurityRuleRepository extends SolidBaseRepository<SecurityRule> {
97
103
  role: true,
98
104
  modelMetadata: true,
99
105
  },
100
- });
106
+ }) ?? securityRule;
101
107
  }
102
108
 
103
109
  return {
@@ -1,5 +1,6 @@
1
1
  import { forwardRef, Inject, Injectable, Logger } from '@nestjs/common';
2
2
  import * as fs from 'fs';
3
+ import * as os from 'os';
3
4
  import * as path from 'path';
4
5
  import { v4 as uuidv4 } from 'uuid';
5
6
 
@@ -20,6 +21,7 @@ import { CreateModuleMetadataDto } from '../dtos/create-module-metadata.dto';
20
21
  import { getDynamicModuleNamesBasedOnMetadata } from '../helpers/module.helper';
21
22
  import { SolidRegistry } from '../helpers/solid-registry';
22
23
  import { ActionMetadataService } from '../services/action-metadata.service';
24
+ import { ApiKeyService } from '../services/api-key.service';
23
25
  import { FieldMetadataService } from '../services/field-metadata.service';
24
26
  import { MediaStorageProviderMetadataService } from '../services/media-storage-provider-metadata.service';
25
27
  import { ModelMetadataService } from '../services/model-metadata.service';
@@ -33,18 +35,22 @@ import { ActionMetadata } from '../entities/action-metadata.entity';
33
35
  import { MenuItemMetadata } from '../entities/menu-item-metadata.entity';
34
36
  import { ModuleMetadata } from '../entities/module-metadata.entity';
35
37
  import { RoleMetadata } from '../entities/role-metadata.entity';
38
+ import { User } from '../entities/user.entity';
36
39
  import { MENU_ROLE_JOIN_TABLE_NAME, MENU_ROLE_JOIN_TABLE_NAME_MENU_COL, MENU_ROLE_JOIN_TABLE_NAME_ROLE_COL } from '../dtos/create-menu-item-metadata.dto';
37
40
  import { DEFAULT_SA_PASSWORD } from '../dtos/create-user.dto';
38
41
  import { SignUpDto } from '../dtos/sign-up.dto';
39
42
  import { ADMIN_ROLE_NAME, CreateRoleMetadataDto } from 'src/dtos/create-role-metadata.dto';
40
43
  import { CreateSavedFiltersDto } from 'src/dtos/create-saved-filters.dto';
41
44
  import { CreateScheduledJobDto } from 'src/dtos/create-scheduled-job.dto';
45
+ import { CreateLocaleDto } from 'src/dtos/create-locale.dto';
42
46
  import { PermissionMetadataRepository } from 'src/repository/permission-metadata.repository';
43
47
  import { SavedFiltersRepository } from 'src/repository/saved-filters.repository';
44
48
  import { ScheduledJobRepository } from 'src/repository/scheduled-job.repository';
45
49
  import { SettingRepository } from 'src/repository/setting.repository';
50
+ import { UserApiKeyRepository } from 'src/repository/user-api-key.repository';
46
51
  import { CreateModelSequenceDto } from 'src/dtos/create-model-sequence.dto';
47
52
  import { ModelSequenceRepository } from 'src/repository/model-sequence.repository';
53
+ import { LocaleRepository } from 'src/repository/locale.repository';
48
54
  import { ModelSequence } from 'src/entities/model-sequence.entity';
49
55
  import { SavedFilters } from 'src/entities/saved-filters.entity';
50
56
  import { ScheduledJob } from 'src/entities/scheduled-job.entity';
@@ -97,11 +103,13 @@ export class ModuleMetadataSeederService {
97
103
  private readonly roleService: RoleMetadataService,
98
104
  private readonly userService: UserService,
99
105
  private readonly authenticationService: AuthenticationService,
106
+ private readonly apiKeyService: ApiKeyService,
100
107
  private readonly solidActionService: ActionMetadataService,
101
108
  private readonly solidViewService: ViewMetadataService,
102
109
  private readonly emailTemplateService: EmailTemplateService,
103
110
  private readonly smsTemplateService: SmsTemplateService,
104
111
  private readonly listOfValuesService: ListOfValuesService,
112
+ private readonly userApiKeyRepository: UserApiKeyRepository,
105
113
  // @InjectRepository(PermissionMetadata)
106
114
  // private readonly permissionRepo: Repository<PermissionMetadata>,
107
115
  private readonly permissionRepo: PermissionMetadataRepository,
@@ -116,6 +124,7 @@ export class ModuleMetadataSeederService {
116
124
  readonly savedFiltersRepo: SavedFiltersRepository,
117
125
  readonly dataSource: DataSource,
118
126
  readonly modelSequenceRepo: ModelSequenceRepository,
127
+ readonly localeRepo: LocaleRepository,
119
128
  ) { }
120
129
 
121
130
  async seed(conf?: any) {
@@ -194,6 +203,11 @@ export class ModuleMetadataSeederService {
194
203
  const moduleModelFieldCounts = await this.timeOperation('seed-module-model-fields', () => this.seedModuleModelFields(moduleMetadata), { moduleName: moduleMetadata.name, component: 'module-model-fields' });
195
204
  console.log(`${this.formatSeedResult(moduleMetadata.name, 'Module/Model/Fields', moduleModelFieldCounts)}`);
196
205
 
206
+ currentStep = 'seedLocales';
207
+ this.logger.log(`Seeding Locales`);
208
+ const localeCounts = await this.seedLocales(overallMetadata);
209
+ console.log(`${this.formatSeedResult(moduleMetadata.name, 'Locales', localeCounts)}`);
210
+
197
211
  currentStep = 'seedPermissions';
198
212
  this.logger.log(`Seeding Permissions`);
199
213
  const permissionCounts = await this.timeOperation('seed-permissions', () => this.seedPermissions(overallMetadata), { moduleName: moduleMetadata.name, component: 'permissions' });
@@ -466,6 +480,22 @@ export class ModuleMetadataSeederService {
466
480
  return { pruned, upserted: listOfValues.length };
467
481
  }
468
482
 
483
+ private async seedLocales(overallMetadata: any): Promise<{ pruned: number; upserted: number }> {
484
+ this.logger.debug(`[Start] Processing locales`);
485
+ const locales = this.getSeedArray<CreateLocaleDto>(overallMetadata?.locales);
486
+ const existingLocales = await this.localeRepo.find();
487
+
488
+ if (existingLocales.length > 0) {
489
+ this.solidRegistry.registerlocales(existingLocales);
490
+ this.logger.debug(`[End] Skipping locale seed because locales already exist`);
491
+ return { pruned: 0, upserted: 0 };
492
+ }
493
+
494
+ await this.handleSeedLocales(locales);
495
+ this.logger.debug(`[End] Processing locales`);
496
+ return { pruned: 0, upserted: locales.length };
497
+ }
498
+
469
499
  private async setupDefaultRolesWithPermissions() {
470
500
  this.logger.debug(`About to add all permissions to the Admin role`);
471
501
  await this.timeOperation('role-add-all-permissions', () => this.roleService.addAllPermissionsToRole(ADMIN_ROLE_NAME), {
@@ -592,11 +622,15 @@ export class ModuleMetadataSeederService {
592
622
  // OK
593
623
  private async seedRoles(overallMetadata: any): Promise<{ pruned: number; upserted: number }> {
594
624
  const roles = this.getSeedArray<CreateRoleMetadataDto>(overallMetadata?.roles);
595
- // While creating roles we are only passing the role name to be used.
625
+ // Preserve module linkage metadata so RoleMetadata.module is populated from the defining module.
596
626
  await this.timeOperation('roles-create-if-not-exists', () => this.roleService.createRolesIfNotExists(
597
627
  roles
598
628
  .filter((role) => role?.name)
599
- .map((role) => ({ name: role.name } as any)),
629
+ .map((role) => ({
630
+ name: role.name,
631
+ moduleId: role.moduleId,
632
+ moduleUserKey: role.moduleUserKey ?? overallMetadata?.moduleMetadata?.name,
633
+ } as CreateRoleMetadataDto)),
600
634
  ), {
601
635
  moduleName: overallMetadata?.moduleMetadata?.name,
602
636
  component: 'roles',
@@ -1336,6 +1370,7 @@ export class ModuleMetadataSeederService {
1336
1370
 
1337
1371
  for (let l = 0; l < users.length; l++) {
1338
1372
  const user: SignUpDto = users[l];
1373
+ const isSystemAdminUser = user.username === 'sa';
1339
1374
  let exisitingUser = await this.timeOperation('user-find-by-username', () => this.userService.findOneByUsername(user.username), {
1340
1375
  component: 'users',
1341
1376
  serviceCall: 'userService.findOneByUsername',
@@ -1345,14 +1380,32 @@ export class ModuleMetadataSeederService {
1345
1380
  if (user.username === 'sa') {
1346
1381
  user.password = DEFAULT_SA_PASSWORD;
1347
1382
  }
1383
+ if (isSystemAdminUser) {
1384
+ (user as SignUpDto & { isAllowedToGenerateApiKeys?: boolean }).isAllowedToGenerateApiKeys = true;
1385
+ }
1348
1386
 
1349
1387
  exisitingUser = await this.timeOperation('user-sign-up', () => this.authenticationService.signUp(user), {
1350
1388
  component: 'users',
1351
1389
  serviceCall: 'authenticationService.signUp',
1352
1390
  details: `username=${user.username}`,
1353
1391
  });
1392
+ if (isSystemAdminUser) {
1393
+ await this.timeOperation('seed-sa-api-key', async () => {
1394
+ const generatedApiKey = await this.apiKeyService.generate(exisitingUser.id, {
1395
+ name: 'Default SA API Key',
1396
+ });
1397
+ await this.writeSolidxAdminApiKeyConfig(generatedApiKey);
1398
+ }, {
1399
+ component: 'users',
1400
+ serviceCall: 'apiKeyService.generate',
1401
+ details: `username=${user.username}`,
1402
+ });
1403
+ }
1354
1404
  this.logger.log(`Newly created user ${user.username}`);
1355
1405
  }
1406
+ if (isSystemAdminUser) {
1407
+ await this.ensureSystemAdminApiKey(exisitingUser);
1408
+ }
1356
1409
  //FIXME: Create the user roles assignment logic here.
1357
1410
  // now add Roles to user.
1358
1411
  // for (let m = 0; m < roles.length; m++) {
@@ -1362,6 +1415,101 @@ export class ModuleMetadataSeederService {
1362
1415
  }
1363
1416
  }
1364
1417
 
1418
+ private async ensureSystemAdminApiKey(existingUser: { id: number; username: string; isAllowedToGenerateApiKeys?: boolean }) {
1419
+ if (!existingUser?.id) {
1420
+ return;
1421
+ }
1422
+
1423
+ if (!existingUser.isAllowedToGenerateApiKeys) {
1424
+ await this.timeOperation('enable-sa-api-key-generation', () => this.userApiKeyRepository.manager.update(User, { id: existingUser.id }, {
1425
+ isAllowedToGenerateApiKeys: true,
1426
+ }), {
1427
+ component: 'users',
1428
+ serviceCall: 'userRepository.update',
1429
+ details: `username=${existingUser.username}`,
1430
+ });
1431
+ existingUser.isAllowedToGenerateApiKeys = true;
1432
+ }
1433
+
1434
+ const existingDefaultKey = await this.timeOperation('find-sa-default-api-key', () => this.userApiKeyRepository.findOne({
1435
+ where: {
1436
+ user: { id: existingUser.id },
1437
+ name: 'Default SA API Key',
1438
+ },
1439
+ relations: {
1440
+ user: true,
1441
+ },
1442
+ }), {
1443
+ component: 'users',
1444
+ serviceCall: 'userApiKeyRepository.findOne',
1445
+ details: `username=${existingUser.username}`,
1446
+ });
1447
+
1448
+ if (existingDefaultKey) {
1449
+ return;
1450
+ }
1451
+
1452
+ await this.timeOperation('seed-sa-api-key', async () => {
1453
+ const generatedApiKey = await this.apiKeyService.generate(existingUser.id, {
1454
+ name: 'Default SA API Key',
1455
+ });
1456
+ await this.writeSolidxAdminApiKeyConfig(generatedApiKey);
1457
+ }, {
1458
+ component: 'users',
1459
+ serviceCall: 'apiKeyService.generate',
1460
+ details: `username=${existingUser.username}`,
1461
+ });
1462
+ }
1463
+
1464
+ private async writeSolidxAdminApiKeyConfig(generatedApiKey: {
1465
+ apiKey: string;
1466
+ record: {
1467
+ id: number;
1468
+ name: string;
1469
+ maskedKey: string;
1470
+ isActive: boolean;
1471
+ expiresAt: Date | null;
1472
+ createdAt?: Date;
1473
+ updatedAt?: Date;
1474
+ };
1475
+ }) {
1476
+ const consumingProjectRoot = path.resolve(process.cwd(), '..');
1477
+ const consumingProjectName = path.basename(consumingProjectRoot);
1478
+ const solidxDir = path.join(os.homedir(), '.solidx', consumingProjectName);
1479
+ const configPath = path.join(solidxDir, 'mcp.json');
1480
+ await fs.promises.mkdir(solidxDir, { recursive: true });
1481
+
1482
+ let existingConfig: Record<string, any> = {};
1483
+
1484
+ if (fs.existsSync(configPath)) {
1485
+ const rawConfig = await fs.promises.readFile(configPath, 'utf8');
1486
+ if (rawConfig.trim()) {
1487
+ const parsedConfig = JSON.parse(rawConfig);
1488
+ if (parsedConfig && typeof parsedConfig === 'object' && !Array.isArray(parsedConfig)) {
1489
+ existingConfig = parsedConfig;
1490
+ } else {
1491
+ this.logger.warn(`Expected ${configPath} to contain a JSON object. Overwriting with a new object.`);
1492
+ }
1493
+ }
1494
+ }
1495
+
1496
+ const nextConfig = {
1497
+ ...existingConfig,
1498
+ solidxAdminApiKey: {
1499
+ id: generatedApiKey.record.id,
1500
+ name: generatedApiKey.record.name,
1501
+ apiKey: generatedApiKey.apiKey,
1502
+ maskedKey: generatedApiKey.record.maskedKey,
1503
+ isActive: generatedApiKey.record.isActive,
1504
+ expiresAt: generatedApiKey.record.expiresAt,
1505
+ createdAt: generatedApiKey.record.createdAt,
1506
+ updatedAt: generatedApiKey.record.updatedAt,
1507
+ },
1508
+ };
1509
+
1510
+ await fs.promises.writeFile(configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, 'utf8');
1511
+ }
1512
+
1365
1513
  // OK
1366
1514
  private async seedModuleModelFields(moduleMetadata: CreateModuleMetadataDto): Promise<{ pruned: number; upserted: number }> {
1367
1515
  const fieldMetadataRepo = this.dataSource.getRepository(FieldMetadata);
@@ -1872,6 +2020,48 @@ export class ModuleMetadataSeederService {
1872
2020
  }
1873
2021
  }
1874
2022
 
2023
+ private async handleSeedLocales(localesDto: CreateLocaleDto[]) {
2024
+ if (!localesDto || localesDto.length === 0) {
2025
+ this.logger.debug(`No locales found to seed`);
2026
+ return;
2027
+ }
2028
+
2029
+ for (const dto of localesDto) {
2030
+ if (dto.isDefault) {
2031
+ const existingDefaultLocales = await this.localeRepo.find({
2032
+ where: {
2033
+ isDefault: true,
2034
+ } as any,
2035
+ });
2036
+
2037
+ for (const locale of existingDefaultLocales) {
2038
+ if (locale.locale !== dto.locale) {
2039
+ await this.localeRepo.save({
2040
+ ...locale,
2041
+ isDefault: false,
2042
+ });
2043
+ }
2044
+ }
2045
+ }
2046
+
2047
+ const existingLocale = await this.localeRepo.findOne({
2048
+ where: {
2049
+ locale: dto.locale,
2050
+ } as any,
2051
+ });
2052
+
2053
+ await this.localeRepo.save(
2054
+ this.localeRepo.create({
2055
+ ...existingLocale,
2056
+ ...dto,
2057
+ })
2058
+ );
2059
+ }
2060
+
2061
+ const locales = await this.localeRepo.find();
2062
+ this.solidRegistry.registerlocales(locales);
2063
+ }
2064
+
1875
2065
  private validateSavedFilterQueryJsonWrapper(dto: CreateSavedFiltersDto): void {
1876
2066
  const filterName = dto?.name ?? '<unnamed>';
1877
2067
  const filterQueryJson = dto?.filterQueryJson;