@slingr/cli 0.0.2 → 0.0.4

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 (251) hide show
  1. package/LICENSE.txt +202 -0
  2. package/README.md +490 -319
  3. package/bin/dev.cmd +2 -2
  4. package/bin/dev.js +5 -5
  5. package/bin/run.cmd +2 -2
  6. package/bin/run.js +4 -4
  7. package/bin/slingr +1 -0
  8. package/dist/commands/build.d.ts +20 -0
  9. package/dist/commands/build.d.ts.map +1 -0
  10. package/dist/commands/build.js +206 -0
  11. package/dist/commands/build.js.map +1 -0
  12. package/dist/commands/create-app.d.ts +0 -1
  13. package/dist/commands/create-app.d.ts.map +1 -1
  14. package/dist/commands/create-app.js +38 -57
  15. package/dist/commands/create-app.js.map +1 -1
  16. package/dist/commands/debug.d.ts +28 -0
  17. package/dist/commands/debug.d.ts.map +1 -0
  18. package/dist/commands/debug.js +474 -0
  19. package/dist/commands/debug.js.map +1 -0
  20. package/dist/commands/ds.d.ts +14 -1
  21. package/dist/commands/ds.d.ts.map +1 -1
  22. package/dist/commands/ds.js +450 -121
  23. package/dist/commands/ds.js.map +1 -1
  24. package/dist/commands/gql.d.ts +1 -1
  25. package/dist/commands/gql.d.ts.map +1 -1
  26. package/dist/commands/gql.js +190 -184
  27. package/dist/commands/gql.js.map +1 -1
  28. package/dist/commands/infra/down.d.ts.map +1 -1
  29. package/dist/commands/infra/down.js +8 -7
  30. package/dist/commands/infra/down.js.map +1 -1
  31. package/dist/commands/infra/up.d.ts.map +1 -1
  32. package/dist/commands/infra/up.js +8 -7
  33. package/dist/commands/infra/up.js.map +1 -1
  34. package/dist/commands/infra/update.d.ts +1 -0
  35. package/dist/commands/infra/update.d.ts.map +1 -1
  36. package/dist/commands/infra/update.js +33 -69
  37. package/dist/commands/infra/update.js.map +1 -1
  38. package/dist/commands/run.d.ts +29 -2
  39. package/dist/commands/run.d.ts.map +1 -1
  40. package/dist/commands/run.js +628 -130
  41. package/dist/commands/run.js.map +1 -1
  42. package/dist/commands/setup.d.ts +1 -1
  43. package/dist/commands/setup.d.ts.map +1 -1
  44. package/dist/commands/setup.js +34 -71
  45. package/dist/commands/setup.js.map +1 -1
  46. package/dist/commands/sync-metadata.d.ts +15 -0
  47. package/dist/commands/sync-metadata.d.ts.map +1 -0
  48. package/dist/commands/sync-metadata.js +225 -0
  49. package/dist/commands/sync-metadata.js.map +1 -0
  50. package/dist/commands/users.d.ts +30 -0
  51. package/dist/commands/users.d.ts.map +1 -0
  52. package/dist/commands/users.js +472 -0
  53. package/dist/commands/users.js.map +1 -0
  54. package/dist/commands/views.d.ts +11 -0
  55. package/dist/commands/views.d.ts.map +1 -0
  56. package/dist/commands/views.js +73 -0
  57. package/dist/commands/views.js.map +1 -0
  58. package/dist/projectStructure.d.ts +2 -2
  59. package/dist/projectStructure.d.ts.map +1 -1
  60. package/dist/projectStructure.js +281 -69
  61. package/dist/projectStructure.js.map +1 -1
  62. package/dist/scripts/generate-metadata.d.ts +13 -0
  63. package/dist/scripts/generate-metadata.d.ts.map +1 -0
  64. package/dist/scripts/generate-metadata.js +412 -0
  65. package/dist/scripts/generate-metadata.js.map +1 -0
  66. package/dist/scripts/generate-metadata.ts +498 -0
  67. package/dist/scripts/generate-schema.d.ts +1 -1
  68. package/dist/scripts/generate-schema.js +168 -74
  69. package/dist/scripts/generate-schema.js.map +1 -1
  70. package/dist/scripts/generate-schema.ts +258 -143
  71. package/dist/templates/.env.template +23 -0
  72. package/dist/templates/.firebaserc.template +5 -0
  73. package/dist/templates/.github/copilot-instructions.md.template +652 -17
  74. package/dist/templates/backend/Dockerfile.template +30 -0
  75. package/dist/templates/config/datasource.ts.template +12 -9
  76. package/dist/templates/config/jest.config.ts +30 -30
  77. package/dist/templates/config/jest.setup.ts +1 -1
  78. package/dist/templates/config/tsconfig.json.template +50 -29
  79. package/dist/templates/dataSources/mysql.ts.template +16 -13
  80. package/dist/templates/dataSources/postgres.ts.template +15 -13
  81. package/dist/templates/dataset-generator-script.ts.template +139 -139
  82. package/dist/templates/datasets/mysql-default/.slingr-schema.json.template +5 -0
  83. package/dist/templates/datasets/mysql-default/Address.jsonl.template +3 -3
  84. package/dist/templates/datasets/mysql-default/App.jsonl.template +4 -4
  85. package/dist/templates/datasets/mysql-default/Company.jsonl.template +3 -3
  86. package/dist/templates/datasets/mysql-default/Person.jsonl.template +2 -2
  87. package/dist/templates/datasets/mysql-default/User.jsonl.template +1 -0
  88. package/dist/templates/datasets/mysql-default/instructions.md.template +1 -0
  89. package/dist/templates/datasets/postgres-default/.slingr-schema.json.template +5 -0
  90. package/dist/templates/datasets/postgres-default/Address.jsonl.template +3 -3
  91. package/dist/templates/datasets/postgres-default/App.jsonl.template +4 -4
  92. package/dist/templates/datasets/postgres-default/Company.jsonl.template +3 -3
  93. package/dist/templates/datasets/postgres-default/Person.jsonl.template +2 -2
  94. package/dist/templates/datasets/postgres-default/User.jsonl.template +1 -0
  95. package/dist/templates/datasets/postgres-default/instructions.md.template +1 -0
  96. package/dist/templates/docker-compose.prod-test.yml.template +32 -0
  97. package/dist/templates/docker-compose.yml.template +24 -0
  98. package/dist/templates/docs/app-description.md.template +33 -33
  99. package/dist/templates/firebase.json.template +68 -0
  100. package/dist/templates/frontend/.umirc.ts.template +23 -0
  101. package/dist/templates/frontend/package.json.template +45 -0
  102. package/dist/templates/frontend/public/config.json +6 -0
  103. package/dist/templates/frontend/public/logo.svg +6 -0
  104. package/dist/templates/frontend/src/app.tsx.template +44 -0
  105. package/dist/templates/frontend/src/global.less.template +117 -0
  106. package/dist/templates/frontend/src/layouts/MainLayout.tsx.template +75 -0
  107. package/dist/templates/frontend/src/types/graphql-augmentation.d.ts.template +44 -0
  108. package/dist/templates/frontend/src/views/customViews/user/UserCreateView.tsx.template +18 -0
  109. package/dist/templates/frontend/src/views/customViews/user/UserEditView.tsx.template +29 -0
  110. package/dist/templates/frontend/src/views/customViews/user/UserReadView.tsx.template +24 -0
  111. package/dist/templates/frontend/src/views/customViews/user/UserTableView.tsx.template +38 -0
  112. package/dist/templates/frontend/src/views/customViews/welcome.tsx.template +34 -0
  113. package/dist/templates/frontend/tsconfig.json.template +50 -0
  114. package/dist/templates/gql/codegen.yml.template +25 -25
  115. package/dist/templates/gql/index.ts.template +17 -24
  116. package/dist/templates/gql/operations.graphql.template +30 -30
  117. package/dist/templates/ops/README.md.template +1045 -0
  118. package/dist/templates/ops/cloudbuild.yaml.template +161 -0
  119. package/dist/templates/ops/scripts/_utils.js.template +217 -0
  120. package/dist/templates/ops/scripts/deploy.js.template +145 -0
  121. package/dist/templates/ops/scripts/setup-gcp.js.template +330 -0
  122. package/dist/templates/ops/scripts/setup-secrets.js.template +76 -0
  123. package/dist/templates/ops/scripts/test-prod-local.js.template +49 -0
  124. package/dist/templates/package.json.template +50 -38
  125. package/dist/templates/pnpm-workspace.yaml.template +3 -0
  126. package/dist/templates/prompt-analysis.md.template +110 -110
  127. package/dist/templates/prompt-script-generation.md.template +258 -258
  128. package/dist/templates/src/Address.ts.template +28 -31
  129. package/dist/templates/src/App.ts.template +17 -61
  130. package/dist/templates/src/Company.ts.template +41 -47
  131. package/dist/templates/src/Models.test.ts.template +654 -654
  132. package/dist/templates/src/Person.test.ts.template +289 -289
  133. package/dist/templates/src/Person.ts.template +90 -105
  134. package/dist/templates/src/actions/index.ts.template +11 -11
  135. package/dist/templates/src/auth/permissions.ts.template +34 -0
  136. package/dist/templates/src/data/App.ts.template +48 -0
  137. package/dist/templates/src/data/User.ts.template +35 -0
  138. package/dist/templates/src/types/gql.d.ts.template +17 -17
  139. package/dist/templates/vscode/extensions.json +4 -3
  140. package/dist/templates/vscode/settings.json +17 -11
  141. package/dist/templates/workspace-package.json.template +21 -0
  142. package/dist/utils/buildCache.d.ts +12 -0
  143. package/dist/utils/buildCache.d.ts.map +1 -0
  144. package/dist/utils/buildCache.js +102 -0
  145. package/dist/utils/buildCache.js.map +1 -0
  146. package/dist/utils/checkFramework.d.ts +27 -0
  147. package/dist/utils/checkFramework.d.ts.map +1 -0
  148. package/dist/utils/checkFramework.js +104 -0
  149. package/dist/utils/checkFramework.js.map +1 -0
  150. package/dist/utils/datasourceParser.d.ts +11 -0
  151. package/dist/utils/datasourceParser.d.ts.map +1 -1
  152. package/dist/utils/datasourceParser.js +154 -56
  153. package/dist/utils/datasourceParser.js.map +1 -1
  154. package/dist/utils/dockerManager.d.ts +25 -0
  155. package/dist/utils/dockerManager.d.ts.map +1 -0
  156. package/dist/utils/dockerManager.js +281 -0
  157. package/dist/utils/dockerManager.js.map +1 -0
  158. package/dist/utils/infraFileParser.d.ts +26 -0
  159. package/dist/utils/infraFileParser.d.ts.map +1 -0
  160. package/dist/utils/infraFileParser.js +75 -0
  161. package/dist/utils/infraFileParser.js.map +1 -0
  162. package/dist/utils/jsonlLoader.d.ts +91 -12
  163. package/dist/utils/jsonlLoader.d.ts.map +1 -1
  164. package/dist/utils/jsonlLoader.js +674 -63
  165. package/dist/utils/jsonlLoader.js.map +1 -1
  166. package/dist/utils/model-analyzer.d.ts.map +1 -1
  167. package/dist/utils/model-analyzer.js +67 -13
  168. package/dist/utils/model-analyzer.js.map +1 -1
  169. package/dist/utils/userManagement.d.ts +57 -0
  170. package/dist/utils/userManagement.d.ts.map +1 -0
  171. package/dist/utils/userManagement.js +288 -0
  172. package/dist/utils/userManagement.js.map +1 -0
  173. package/dist/utils/viewsGenerator.d.ts +15 -0
  174. package/dist/utils/viewsGenerator.d.ts.map +1 -0
  175. package/dist/utils/viewsGenerator.js +311 -0
  176. package/dist/utils/viewsGenerator.js.map +1 -0
  177. package/oclif.manifest.json +445 -20
  178. package/package.json +29 -27
  179. package/src/templates/.env.template +23 -0
  180. package/src/templates/.firebaserc.template +5 -0
  181. package/src/templates/.github/copilot-instructions.md.template +652 -17
  182. package/src/templates/backend/Dockerfile.template +30 -0
  183. package/src/templates/config/datasource.ts.template +12 -9
  184. package/src/templates/config/jest.config.ts +30 -30
  185. package/src/templates/config/jest.setup.ts +1 -1
  186. package/src/templates/config/tsconfig.json.template +50 -29
  187. package/src/templates/dataSources/mysql.ts.template +16 -13
  188. package/src/templates/dataSources/postgres.ts.template +15 -13
  189. package/src/templates/dataset-generator-script.ts.template +139 -139
  190. package/src/templates/datasets/mysql-default/.slingr-schema.json.template +5 -0
  191. package/src/templates/datasets/mysql-default/Address.jsonl.template +3 -3
  192. package/src/templates/datasets/mysql-default/App.jsonl.template +4 -4
  193. package/src/templates/datasets/mysql-default/Company.jsonl.template +3 -3
  194. package/src/templates/datasets/mysql-default/Person.jsonl.template +2 -2
  195. package/src/templates/datasets/mysql-default/User.jsonl.template +1 -0
  196. package/src/templates/datasets/mysql-default/instructions.md.template +1 -0
  197. package/src/templates/datasets/postgres-default/.slingr-schema.json.template +5 -0
  198. package/src/templates/datasets/postgres-default/Address.jsonl.template +3 -3
  199. package/src/templates/datasets/postgres-default/App.jsonl.template +4 -4
  200. package/src/templates/datasets/postgres-default/Company.jsonl.template +3 -3
  201. package/src/templates/datasets/postgres-default/Person.jsonl.template +2 -2
  202. package/src/templates/datasets/postgres-default/User.jsonl.template +1 -0
  203. package/src/templates/datasets/postgres-default/instructions.md.template +1 -0
  204. package/src/templates/docker-compose.prod-test.yml.template +32 -0
  205. package/src/templates/docker-compose.yml.template +24 -0
  206. package/src/templates/docs/app-description.md.template +33 -33
  207. package/src/templates/firebase.json.template +68 -0
  208. package/src/templates/frontend/.umirc.ts.template +23 -0
  209. package/src/templates/frontend/package.json.template +45 -0
  210. package/src/templates/frontend/public/config.json +6 -0
  211. package/src/templates/frontend/public/logo.svg +6 -0
  212. package/src/templates/frontend/src/app.tsx.template +44 -0
  213. package/src/templates/frontend/src/global.less.template +117 -0
  214. package/src/templates/frontend/src/layouts/MainLayout.tsx.template +75 -0
  215. package/src/templates/frontend/src/types/graphql-augmentation.d.ts.template +44 -0
  216. package/src/templates/frontend/src/views/customViews/user/UserCreateView.tsx.template +18 -0
  217. package/src/templates/frontend/src/views/customViews/user/UserEditView.tsx.template +29 -0
  218. package/src/templates/frontend/src/views/customViews/user/UserReadView.tsx.template +24 -0
  219. package/src/templates/frontend/src/views/customViews/user/UserTableView.tsx.template +38 -0
  220. package/src/templates/frontend/src/views/customViews/welcome.tsx.template +34 -0
  221. package/src/templates/frontend/tsconfig.json.template +50 -0
  222. package/src/templates/gql/codegen.yml.template +25 -25
  223. package/src/templates/gql/index.ts.template +17 -24
  224. package/src/templates/gql/operations.graphql.template +30 -30
  225. package/src/templates/ops/README.md.template +1045 -0
  226. package/src/templates/ops/cloudbuild.yaml.template +161 -0
  227. package/src/templates/ops/scripts/_utils.js.template +217 -0
  228. package/src/templates/ops/scripts/deploy.js.template +145 -0
  229. package/src/templates/ops/scripts/setup-gcp.js.template +330 -0
  230. package/src/templates/ops/scripts/setup-secrets.js.template +76 -0
  231. package/src/templates/ops/scripts/test-prod-local.js.template +49 -0
  232. package/src/templates/package.json.template +50 -38
  233. package/src/templates/pnpm-workspace.yaml.template +3 -0
  234. package/src/templates/prompt-analysis.md.template +110 -110
  235. package/src/templates/prompt-script-generation.md.template +258 -258
  236. package/src/templates/src/Address.ts.template +28 -31
  237. package/src/templates/src/App.ts.template +17 -61
  238. package/src/templates/src/Company.ts.template +41 -47
  239. package/src/templates/src/Models.test.ts.template +654 -654
  240. package/src/templates/src/Person.test.ts.template +289 -289
  241. package/src/templates/src/Person.ts.template +90 -105
  242. package/src/templates/src/actions/index.ts.template +11 -11
  243. package/src/templates/src/auth/permissions.ts.template +34 -0
  244. package/src/templates/src/data/App.ts.template +48 -0
  245. package/src/templates/src/data/User.ts.template +35 -0
  246. package/src/templates/src/types/gql.d.ts.template +17 -17
  247. package/src/templates/vscode/extensions.json +4 -3
  248. package/src/templates/vscode/settings.json +17 -11
  249. package/src/templates/workspace-package.json.template +21 -0
  250. package/dist/templates/src/index.ts +0 -66
  251. package/src/templates/src/index.ts +0 -66
@@ -1,4 +1,37 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
@@ -7,15 +40,19 @@ exports.JsonlDatasetLoader = void 0;
7
40
  exports.discoverModels = discoverModels;
8
41
  const fs_extra_1 = __importDefault(require("fs-extra"));
9
42
  const node_path_1 = __importDefault(require("node:path"));
43
+ const framework_backend_1 = require("@slingr/framework-backend");
10
44
  require("reflect-metadata");
45
+ const cliProgress = __importStar(require("cli-progress"));
11
46
  /**
12
47
  * Utility class for loading datasets from JSONL files using Slingr model fromJSON() functionality
13
48
  */
14
49
  class JsonlDatasetLoader {
50
+ /** Per-run cache to avoid repeated DB lookups for the same referenced record. */
51
+ dbLookupCache = new Map();
15
52
  /**
16
53
  * Analyze model dependencies to determine the correct loading order
17
54
  */
18
- analyzeDependencies(modelMap, verbose = false) {
55
+ analyzeDependencies(modelMap, verbose = false, availableModels) {
19
56
  const dependencies = {};
20
57
  // Initialize dependency tracking for all models
21
58
  for (const modelName of Object.keys(modelMap)) {
@@ -27,7 +64,7 @@ class JsonlDatasetLoader {
27
64
  }
28
65
  // Analyze each model for dependencies
29
66
  for (const [modelName, ModelClass] of Object.entries(modelMap)) {
30
- const modelDeps = this.extractModelDependencies(ModelClass, verbose);
67
+ const modelDeps = this.extractModelDependencies(ModelClass, verbose, availableModels);
31
68
  dependencies[modelName].dependencies = modelDeps;
32
69
  // Update dependents for referenced models
33
70
  for (const depName of modelDeps) {
@@ -50,7 +87,7 @@ class JsonlDatasetLoader {
50
87
  /**
51
88
  * Extract dependencies from a model class by analyzing its decorators/metadata
52
89
  */
53
- extractModelDependencies(ModelClass, verbose = false) {
90
+ extractModelDependencies(ModelClass, verbose = false, availableModels) {
54
91
  const dependencies = [];
55
92
  if (verbose) {
56
93
  console.log(` 🔍 Analyzing ${ModelClass.name} for dependencies...`);
@@ -60,9 +97,17 @@ class JsonlDatasetLoader {
60
97
  const instance = new ModelClass();
61
98
  // Get all property descriptors from the prototype and instance
62
99
  const proto = Object.getPrototypeOf(instance);
63
- const propertyNames = Object.getOwnPropertyNames(proto).concat(Object.getOwnPropertyNames(instance));
100
+ const prototypeProps = Object.getOwnPropertyNames(proto).concat(Object.getOwnPropertyNames(instance));
101
+ // Also get fields from MODEL_FIELDS metadata (stores all decorated field names)
102
+ // This is critical for fields like @ReferenceField that may not have values assigned
103
+ const modelFields = Reflect.getMetadata?.('model:fields', ModelClass) || [];
104
+ // Combine and deduplicate property names
105
+ const propertyNames = [...new Set([...prototypeProps, ...modelFields])];
64
106
  if (verbose) {
65
107
  console.log(` Properties found:`, propertyNames.filter(p => p !== 'constructor'));
108
+ if (modelFields.length > 0) {
109
+ console.log(` Model fields from metadata:`, modelFields);
110
+ }
66
111
  }
67
112
  for (const propertyName of propertyNames) {
68
113
  if (propertyName === 'constructor') {
@@ -87,46 +132,63 @@ class JsonlDatasetLoader {
87
132
  }
88
133
  // Check for Reference and Composition relationships
89
134
  if (fieldType === 'relationship' && relationshipType && designType) {
90
- // For relationships, designType contains the name of the referenced model
91
- if (typeof designType === 'string') {
92
- dependencies.push(designType);
93
- if (verbose) {
94
- console.log(` ✅ Found dependency: ${ModelClass.name}.${propertyName} → ${designType} (${relationshipType})`);
95
- }
96
- }
97
- else if (typeof designType === 'function' && designType.name) {
98
- dependencies.push(designType.name);
135
+ // Skip regular compositions - in compositions, the child depends on the parent, not the other way around
136
+ // The child's @OwnerReferenceField creates the dependency from child to parent
137
+ if (relationshipType === 'composition') {
99
138
  if (verbose) {
100
- console.log(` Found dependency: ${ModelClass.name}.${propertyName} → ${designType.name} (${relationshipType})`);
139
+ console.log(` ⏭️ Skipping composition: ${ModelClass.name}.${propertyName} → ${designType?.name || designType} (child depends on parent, not parent on child)`);
101
140
  }
141
+ continue;
102
142
  }
103
- }
104
- // Also check property getter/setter for reference information
105
- const descriptor = Object.getOwnPropertyDescriptor(instance, propertyName) ||
106
- Object.getOwnPropertyDescriptor(proto, propertyName);
107
- if (descriptor) {
108
- // Check if the property has reference metadata
109
- const refMetadata = Reflect.getMetadata?.('slingr:reference', instance, propertyName);
110
- if (refMetadata && refMetadata.elementType) {
111
- const referencedType = refMetadata.elementType();
112
- if (referencedType && referencedType.name) {
113
- dependencies.push(referencedType.name);
143
+ // For references and sharedCompositions, check if the target has a JSONL file
144
+ // Only add as dependency if the file exists (meaning it's loaded separately)
145
+ const targetName = typeof designType === 'string' ? designType : designType.name;
146
+ // If availableModels is provided, only add dependency if the model has a JSONL file
147
+ // This allows compositions to be embedded OR loaded from separate files
148
+ if (targetName) {
149
+ const shouldAddDependency = !availableModels || availableModels.has(targetName);
150
+ if (shouldAddDependency) {
151
+ dependencies.push(targetName);
114
152
  if (verbose) {
115
- console.log(` Found reference dependency: ${ModelClass.name}.${propertyName} → ${referencedType.name}`);
153
+ console.log(` Found dependency: ${ModelClass.name}.${propertyName} → ${targetName} (${relationshipType}, has JSONL file)`);
116
154
  }
117
155
  }
156
+ else if (verbose) {
157
+ console.log(` ⏭️ Skipping dependency: ${ModelClass.name}.${propertyName} → ${targetName} (${relationshipType}, no JSONL file - will use embedded data)`);
158
+ }
118
159
  }
119
- // Check composition metadata
120
- const compMetadata = Reflect.getMetadata?.('slingr:composition', instance, propertyName);
121
- if (compMetadata && compMetadata.elementType) {
122
- const composedType = compMetadata.elementType();
123
- if (composedType && composedType.name) {
124
- dependencies.push(composedType.name);
125
- if (verbose) {
126
- console.log(` Found composition dependency: ${ModelClass.name}.${propertyName} → ${composedType.name}`);
160
+ }
161
+ // Check field:type:options for elementType (handles array references and compositions)
162
+ // This is the correct way to get the target class for relationships in the Slingr framework
163
+ const fieldOptions = Reflect.getMetadata?.('field:type:options', proto, propertyName);
164
+ if (fieldOptions?.elementType && typeof fieldOptions.elementType === 'function') {
165
+ try {
166
+ const targetClass = fieldOptions.elementType();
167
+ if (targetClass?.name && !dependencies.includes(targetClass.name)) {
168
+ // Skip if this is a regular composition field
169
+ if (relationshipType === 'composition') {
170
+ if (verbose) {
171
+ console.log(` ⏭️ Skipping composition via type: ${ModelClass.name}.${propertyName} → ${targetClass.name}`);
172
+ }
173
+ }
174
+ else {
175
+ // Only add dependency if the model has a JSONL file
176
+ const shouldAddDependency = !availableModels || availableModels.has(targetClass.name);
177
+ if (shouldAddDependency) {
178
+ dependencies.push(targetClass.name);
179
+ if (verbose) {
180
+ console.log(` ✅ Found dependency via type: ${ModelClass.name}.${propertyName} → ${targetClass.name} (has JSONL file)`);
181
+ }
182
+ }
183
+ else if (verbose) {
184
+ console.log(` ⏭️ Skipping dependency via type: ${ModelClass.name}.${propertyName} → ${targetClass.name} (no JSONL file - embedded)`);
185
+ }
127
186
  }
128
187
  }
129
188
  }
189
+ catch {
190
+ // elementType() can fail if referenced class is not loaded yet, ignore
191
+ }
130
192
  }
131
193
  }
132
194
  catch (error) {
@@ -145,6 +207,29 @@ class JsonlDatasetLoader {
145
207
  // Remove duplicates and self-references
146
208
  return Array.from(new Set(dependencies)).filter(dep => dep !== ModelClass.name);
147
209
  }
210
+ /**
211
+ * Get the referenced model name from field metadata
212
+ */
213
+ getReferencedModelName(ModelClass, fieldName) {
214
+ const designType = Reflect.getMetadata('design:type', ModelClass.prototype, fieldName);
215
+ if (designType && designType.name && designType.name !== 'Array') {
216
+ return designType.name;
217
+ }
218
+ // Try elementType for arrays
219
+ const fieldOptions = Reflect.getMetadata('field:type:options', ModelClass.prototype, fieldName);
220
+ if (fieldOptions?.elementType && typeof fieldOptions.elementType === 'function') {
221
+ try {
222
+ const targetClass = fieldOptions.elementType();
223
+ if (targetClass?.name) {
224
+ return targetClass.name;
225
+ }
226
+ }
227
+ catch {
228
+ // Ignore
229
+ }
230
+ }
231
+ return null;
232
+ }
148
233
  /**
149
234
  * Perform topological sort to determine loading order
150
235
  */
@@ -154,6 +239,9 @@ class JsonlDatasetLoader {
154
239
  const result = [];
155
240
  const visit = (modelName) => {
156
241
  if (visiting.has(modelName)) {
242
+ console.error(`\n❌ Circular dependency detected!`);
243
+ console.error(`Currently visiting chain:`, Array.from(visiting));
244
+ console.error(`Trying to visit again:`, modelName);
157
245
  throw new Error(`Circular dependency detected involving: ${modelName}`);
158
246
  }
159
247
  if (visited.has(modelName)) {
@@ -183,33 +271,99 @@ class JsonlDatasetLoader {
183
271
  * Load all JSONL files from a dataset directory
184
272
  */
185
273
  async loadDataset(options) {
186
- const { datasetPath, modelMap, validateRecords = true, verbose = false } = options;
274
+ const { datasetPath, modelMap, validateRecords = true, verbose = false, includeModels, excludeModels, dataSource, } = options;
275
+ // Reset per-run cache so stale entries from a previous call don't bleed through.
276
+ this.dbLookupCache.clear();
187
277
  if (!(await fs_extra_1.default.pathExists(datasetPath))) {
188
278
  throw new Error(`Dataset directory not found: ${datasetPath}`);
189
279
  }
190
- // Analyze dependencies first to determine correct loading order
191
- const dependencyAnalysis = this.analyzeDependencies(modelMap, verbose);
192
- // Find all JSONL files in the dataset directory
280
+ // Find all JSONL files in the dataset directory first
193
281
  const files = await fs_extra_1.default.readdir(datasetPath);
194
- const jsonlFiles = files.filter(f => f.endsWith('.jsonl'));
282
+ let jsonlFiles = files.filter(f => f.endsWith('.jsonl'));
283
+ if (verbose) {
284
+ console.log(`📁 Found ${jsonlFiles.length} JSONL files in dataset directory:`, jsonlFiles.map(f => f.replace(/\.jsonl$/i, '')));
285
+ }
286
+ // Apply model filtering before dependency analysis (case-insensitive)
287
+ if (includeModels && includeModels.length > 0) {
288
+ // Create a case-insensitive lookup map: lowercase -> original model name
289
+ const includeLookup = new Map(includeModels.map(name => [name.toLowerCase(), name]));
290
+ if (verbose) {
291
+ console.log(`🔍 Filtering to include only:`, includeModels);
292
+ }
293
+ jsonlFiles = jsonlFiles.filter(f => {
294
+ const modelName = f.replace(/\.jsonl$/i, '');
295
+ const matched = includeLookup.has(modelName.toLowerCase());
296
+ if (verbose && !matched) {
297
+ console.log(` ⏭️ Skipping ${f} (not in include list)`);
298
+ }
299
+ return matched;
300
+ });
301
+ if (verbose) {
302
+ console.log(`✅ Filtered to ${jsonlFiles.length} files:`, jsonlFiles.map(f => f.replace(/\.jsonl$/i, '')));
303
+ }
304
+ }
305
+ else if (excludeModels && excludeModels.length > 0) {
306
+ // Create a case-insensitive lookup set
307
+ const excludeLookup = new Set(excludeModels.map(name => name.toLowerCase()));
308
+ if (verbose) {
309
+ console.log(`🚫 Filtering to exclude:`, excludeModels);
310
+ }
311
+ jsonlFiles = jsonlFiles.filter(f => {
312
+ const modelName = f.replace(/\.jsonl$/i, '');
313
+ const excluded = excludeLookup.has(modelName.toLowerCase());
314
+ if (verbose && excluded) {
315
+ console.log(` ⏭️ Excluding ${f}`);
316
+ }
317
+ return !excluded;
318
+ });
319
+ if (verbose) {
320
+ console.log(`✅ After exclusion: ${jsonlFiles.length} files:`, jsonlFiles.map(f => f.replace(/\.jsonl$/i, '')));
321
+ }
322
+ }
323
+ // Create a case-insensitive lookup for available models
324
+ const availableModels = new Set(jsonlFiles.map(f => f.replace(/\.jsonl$/i, '')));
325
+ const availableModelsLowercase = new Set(Array.from(availableModels).map(name => name.toLowerCase()));
326
+ // Filter modelMap to only include models that have JSONL files (case-insensitive)
327
+ const filteredModelMap = Object.fromEntries(Object.entries(modelMap).filter(([modelName]) => {
328
+ const matched = availableModelsLowercase.has(modelName.toLowerCase());
329
+ if (verbose && !matched) {
330
+ console.log(` ⏭️ Skipping model ${modelName} (no corresponding JSONL file)`);
331
+ }
332
+ return matched;
333
+ }));
334
+ if (verbose) {
335
+ console.log(`📦 Filtered model map contains ${Object.keys(filteredModelMap).length} models:`, Object.keys(filteredModelMap));
336
+ }
337
+ // Analyze dependencies to determine correct loading order
338
+ const dependencyAnalysis = this.analyzeDependencies(filteredModelMap, verbose, availableModels);
339
+ if (verbose) {
340
+ console.log('\n📋 Loading order determined:', dependencyAnalysis.loadOrder.join(' → '));
341
+ console.log('');
342
+ }
195
343
  if (jsonlFiles.length === 0) {
196
344
  throw new Error(`No JSONL files found in dataset directory: ${datasetPath}`);
197
345
  }
198
346
  if (verbose) {
199
347
  console.log(`Found ${jsonlFiles.length} JSONL files to process:`, jsonlFiles);
200
348
  }
349
+ // Create a case-insensitive lookup: lowercase model name -> actual filename
350
+ const filenameLookup = new Map(jsonlFiles.map(filename => {
351
+ const modelName = filename.replace(/\.jsonl$/i, '');
352
+ return [modelName.toLowerCase(), filename];
353
+ }));
201
354
  const results = [];
202
355
  const loadedEntities = {};
203
356
  // Process files in dependency order instead of arbitrary order
204
357
  for (const modelName of dependencyAnalysis.loadOrder) {
205
- const fileName = `${modelName}.jsonl`;
206
- if (!jsonlFiles.includes(fileName)) {
358
+ // Find the actual filename (case-insensitive lookup)
359
+ const fileName = filenameLookup.get(modelName.toLowerCase());
360
+ if (!fileName) {
207
361
  if (verbose) {
208
- console.log(`📄 No JSONL file found for model: ${modelName} (expected: ${fileName}). Skipping...`);
362
+ console.log(`📄 No JSONL file found for model: ${modelName} (expected: ${modelName}.jsonl). Skipping...`);
209
363
  }
210
364
  continue;
211
365
  }
212
- const ModelClass = modelMap[modelName];
366
+ const ModelClass = filteredModelMap[modelName];
213
367
  if (!ModelClass) {
214
368
  if (verbose) {
215
369
  console.warn(`No model mapping found for: ${modelName}. Skipping...`);
@@ -217,7 +371,8 @@ class JsonlDatasetLoader {
217
371
  continue;
218
372
  }
219
373
  const filePath = node_path_1.default.join(datasetPath, fileName);
220
- const result = await this.loadJsonlFile(filePath, ModelClass, modelName, loadedEntities, validateRecords, verbose);
374
+ const result = await this.loadJsonlFile(filePath, ModelClass, modelName, loadedEntities, validateRecords, verbose, dataSource, modelMap // Pass full modelMap for reference resolution
375
+ );
221
376
  // Store successfully loaded entities for future reference resolution
222
377
  if (result.records.length > 0) {
223
378
  loadedEntities[modelName] = {};
@@ -236,7 +391,9 @@ class JsonlDatasetLoader {
236
391
  * Load a single JSONL file and convert records using the model's fromJSON method
237
392
  */
238
393
  /**
239
- * Resolves simple references in format {"id": "uuid"} to full objects from loaded entities
394
+ * Resolves simple references in format {"id": "uuid"} to full objects from loaded entities.
395
+ * Only resolves compositions and sharedCompositions, NOT regular references (to avoid circular dependencies).
396
+ * Also handles arrays of compositions.
240
397
  */
241
398
  async resolveSimpleReferences(data, ModelClass, loadedEntities, verbose = false) {
242
399
  if (!data || typeof data !== 'object') {
@@ -249,8 +406,46 @@ class JsonlDatasetLoader {
249
406
  const fieldType = Reflect.getMetadata('field:type', ModelClass.prototype, fieldName);
250
407
  const relationshipType = Reflect.getMetadata('field:relationship:type', ModelClass.prototype, fieldName);
251
408
  const designType = Reflect.getMetadata('design:type', ModelClass.prototype, fieldName);
252
- // If it's a reference/composition and has simple format {"id": "uuid"}
253
- if ((fieldType === 'reference' || fieldType === 'composition' || relationshipType) &&
409
+ // Handle arrays of compositions (not references)
410
+ if (Array.isArray(resolvedData[fieldName])) {
411
+ // Only resolve if this is a composition/sharedComposition, not a reference
412
+ if (relationshipType === 'composition' || relationshipType === 'sharedComposition') {
413
+ const resolvedArray = [];
414
+ for (const item of resolvedData[fieldName]) {
415
+ if (item && typeof item === 'object' && item.id && Object.keys(item).length === 1) {
416
+ // Try to resolve the composition
417
+ const refId = item.id;
418
+ let resolved = false;
419
+ // Try to find the model name and resolve from loadedEntities
420
+ const refModelName = this.getReferencedModelName(ModelClass, fieldName);
421
+ if (refModelName && loadedEntities[refModelName] && loadedEntities[refModelName][refId]) {
422
+ resolvedArray.push(loadedEntities[refModelName][refId]);
423
+ resolved = true;
424
+ if (verbose) {
425
+ console.log(` Resolved composition array item ${fieldName}[].id=${refId} → ${refModelName} object`);
426
+ }
427
+ }
428
+ if (!resolved) {
429
+ // Keep the ID-only reference if we can't resolve it
430
+ resolvedArray.push(item);
431
+ if (verbose && refModelName) {
432
+ console.log(` Warning: Could not resolve composition ${fieldName}[].id=${refId} (${refModelName} not found)`);
433
+ }
434
+ }
435
+ }
436
+ else {
437
+ // Item already has full data, keep it as is
438
+ resolvedArray.push(item);
439
+ }
440
+ }
441
+ resolvedData[fieldName] = resolvedArray;
442
+ }
443
+ // For regular reference arrays, leave them as-is (don't resolve to avoid cycles)
444
+ continue;
445
+ }
446
+ // Only resolve compositions and sharedCompositions, NOT regular references
447
+ // This prevents circular dependency issues with fromJSON
448
+ if ((relationshipType === 'composition' || relationshipType === 'sharedComposition') &&
254
449
  resolvedData[fieldName] &&
255
450
  typeof resolvedData[fieldName] === 'object' &&
256
451
  resolvedData[fieldName].id &&
@@ -314,7 +509,315 @@ class JsonlDatasetLoader {
314
509
  }
315
510
  return resolvedData;
316
511
  }
317
- async loadJsonlFile(filePath, ModelClass, modelName, loadedEntities, validateRecords = true, verbose = false) {
512
+ /**
513
+ * Fetches a referenced entity from the database, using an in-memory cache keyed by
514
+ * `ClassName:id` to eliminate redundant queries for the same record within a load run.
515
+ *
516
+ * When the entity is not found (or an error occurs), returns a minimal object
517
+ * `{ id: referenceId }` so that TypeORM can still extract the FK on save.
518
+ */
519
+ async fetchReferenceFromDb(dataSource, TargetClass, referenceId, label, verbose) {
520
+ const cacheKey = `${TargetClass.name}:${referenceId}`;
521
+ if (this.dbLookupCache.has(cacheKey)) {
522
+ // null in the cache means a previous lookup returned nothing — return normalized stub.
523
+ return this.dbLookupCache.get(cacheKey) ?? { id: referenceId };
524
+ }
525
+ try {
526
+ const loaded = await dataSource.findOneBy(TargetClass, { id: referenceId });
527
+ this.dbLookupCache.set(cacheKey, loaded ?? null);
528
+ if (loaded) {
529
+ if (verbose) {
530
+ console.log(` ↳ Loaded ${label}="${referenceId}" from database (${TargetClass.name})`);
531
+ }
532
+ return loaded;
533
+ }
534
+ if (verbose) {
535
+ console.log(` ⚠️ Could not resolve ${label}="${referenceId}" (not found in database)`);
536
+ }
537
+ }
538
+ catch (error) {
539
+ this.dbLookupCache.set(cacheKey, null);
540
+ if (verbose) {
541
+ console.log(` ⚠️ Error loading ${label}="${referenceId}" from database:`, error.message);
542
+ }
543
+ }
544
+ return { id: referenceId };
545
+ }
546
+ /**
547
+ * Resolves unresolved references from the database before saving.
548
+ * This handles references that couldn't be resolved from loadedEntities (excluded models).
549
+ * For single references, sets the FK column directly for efficiency.
550
+ * For array references, loads entities from DB to populate the relationship.
551
+ */
552
+ async resolveUnresolvedReferencesFromDb(modelInstance, dataSource, verbose = false) {
553
+ const proto = Object.getPrototypeOf(modelInstance);
554
+ const ModelClass = modelInstance.constructor;
555
+ const modelFields = Reflect.getMetadata?.('model:fields', ModelClass) || [];
556
+ const instanceProps = Object.getOwnPropertyNames(modelInstance);
557
+ const propertyNames = [...new Set([...instanceProps, ...modelFields])];
558
+ for (const propertyName of propertyNames) {
559
+ if (propertyName === 'constructor') {
560
+ continue;
561
+ }
562
+ try {
563
+ const fieldType = Reflect.getMetadata?.('field:type', modelInstance, propertyName);
564
+ const relationshipType = Reflect.getMetadata?.('field:relationship:type', modelInstance, propertyName);
565
+ // Only process reference fields
566
+ if (fieldType !== 'relationship' || relationshipType !== 'reference') {
567
+ continue;
568
+ }
569
+ const currentValue = modelInstance[propertyName];
570
+ // Skip if value is already resolved (BaseDataModel instance) or truly undefined/null
571
+ if (currentValue === undefined || currentValue === null) {
572
+ continue;
573
+ }
574
+ // Also skip if it's already a BaseDataModel instance
575
+ const { BaseDataModel } = await import('@slingr/framework-backend');
576
+ if (currentValue instanceof BaseDataModel) {
577
+ continue;
578
+ }
579
+ // Get the referenced model class
580
+ const fieldOptions = Reflect.getMetadata?.('field:type:options', proto, propertyName);
581
+ if (!fieldOptions?.elementType || typeof fieldOptions.elementType !== 'function') {
582
+ continue;
583
+ }
584
+ const TargetClass = fieldOptions.elementType();
585
+ if (!TargetClass) {
586
+ continue;
587
+ }
588
+ // Handle array references
589
+ if (Array.isArray(currentValue)) {
590
+ const resolvedArray = [];
591
+ for (const item of currentValue) {
592
+ let referenceId;
593
+ if (typeof item === 'string') {
594
+ referenceId = item;
595
+ }
596
+ else if (item && typeof item === 'object' && 'id' in item) {
597
+ referenceId = item.id;
598
+ }
599
+ if (!referenceId) {
600
+ resolvedArray.push(item);
601
+ continue;
602
+ }
603
+ const entity = await this.fetchReferenceFromDb(dataSource, TargetClass, referenceId, `${propertyName}[${resolvedArray.length}]`, verbose);
604
+ resolvedArray.push(entity);
605
+ }
606
+ modelInstance[propertyName] = resolvedArray;
607
+ }
608
+ else {
609
+ let referenceId;
610
+ if (typeof currentValue === 'string') {
611
+ referenceId = currentValue;
612
+ }
613
+ else if (currentValue && typeof currentValue === 'object' && 'id' in currentValue) {
614
+ referenceId = currentValue.id;
615
+ }
616
+ if (referenceId) {
617
+ modelInstance[propertyName] = await this.fetchReferenceFromDb(dataSource, TargetClass, referenceId, propertyName, verbose);
618
+ }
619
+ }
620
+ }
621
+ catch (error) {
622
+ // Ignore errors for individual properties
623
+ if (verbose) {
624
+ console.log(` ⚠️ Error resolving ${propertyName}:`, error.message);
625
+ }
626
+ }
627
+ }
628
+ }
629
+ /**
630
+ * Resolves string UUID reference fields on a model instance to their loaded BaseDataModel entities.
631
+ *
632
+ * During dataset loading, reference fields are stored as plain UUID strings in JSONL files.
633
+ * After fromJSON(), these remain as strings. However, filter validation (ValidateRelationshipFilter)
634
+ * requires actual BaseDataModel instances to properly evaluate filter criteria like:
635
+ * filter: (company) => ({ state: company.state })
636
+ *
637
+ * Without this resolution, company.state would be a plain string, and the filter validator
638
+ * wouldn't be able to check if the county's state matches. This mirrors what
639
+ * preprocessReferenceFields does in the GraphQL/UI path (fetching from DB), but uses
640
+ * already-loaded entities from the dataset instead.
641
+ *
642
+ * When dataSource is provided, missing array references will be loaded from the database.
643
+ * This is necessary for validation when referenced entities were loaded in a previous session.
644
+ *
645
+ * @param modelMap Full map of all model classes (not filtered) - needed to look up referenced model classes
646
+ */
647
+ async resolveReferenceFieldsToEntities(modelInstance, ModelClass, loadedEntities, verbose = false, dataSource, modelMap) {
648
+ const proto = Object.getPrototypeOf(modelInstance);
649
+ const modelFields = Reflect.getMetadata?.('model:fields', ModelClass) || [];
650
+ const instanceProps = Object.getOwnPropertyNames(modelInstance);
651
+ const propertyNames = [...new Set([...instanceProps, ...modelFields])];
652
+ for (const propertyName of propertyNames) {
653
+ if (propertyName === 'constructor') {
654
+ continue;
655
+ }
656
+ try {
657
+ const fieldType = Reflect.getMetadata?.('field:type', modelInstance, propertyName);
658
+ const relationshipType = Reflect.getMetadata?.('field:relationship:type', modelInstance, propertyName);
659
+ // Only process reference fields (not compositions, which are handled differently)
660
+ if (fieldType !== 'relationship' || relationshipType !== 'reference') {
661
+ continue;
662
+ }
663
+ const currentValue = modelInstance[propertyName];
664
+ // Check if the value is an array (for many-to-many or one-to-many relationships)
665
+ if (Array.isArray(currentValue)) {
666
+ // Process each item in the array
667
+ const resolvedArray = [];
668
+ let hasUnresolved = false;
669
+ for (const item of currentValue) {
670
+ // Extract ID from each item
671
+ let referenceId;
672
+ if (typeof item === 'string') {
673
+ referenceId = item;
674
+ }
675
+ else if (item instanceof framework_backend_1.BaseDataModel && item.id) {
676
+ referenceId = item.id;
677
+ }
678
+ else if (item && typeof item === 'object' && 'id' in item) {
679
+ referenceId = item.id;
680
+ }
681
+ if (!referenceId) {
682
+ resolvedArray.push(item);
683
+ continue;
684
+ }
685
+ // Get elementType from field options
686
+ const fieldOptions = Reflect.getMetadata?.('field:type:options', proto, propertyName);
687
+ let targetClassName = null;
688
+ let TargetClass = null;
689
+ if (fieldOptions?.elementType && typeof fieldOptions.elementType === 'function') {
690
+ try {
691
+ const targetClass = fieldOptions.elementType();
692
+ if (targetClass?.name) {
693
+ targetClassName = targetClass.name;
694
+ TargetClass = targetClass;
695
+ }
696
+ }
697
+ catch {
698
+ // Ignore - elementType() might fail if model not imported in current context
699
+ }
700
+ }
701
+ // If elementType() failed or wasn't available, try to get class from modelMap
702
+ if (!TargetClass && targetClassName && modelMap) {
703
+ TargetClass = modelMap[targetClassName];
704
+ if (verbose && TargetClass) {
705
+ console.log(` ↳ Resolved ${targetClassName} class from modelMap for database query`);
706
+ }
707
+ }
708
+ if (!targetClassName) {
709
+ resolvedArray.push(item);
710
+ continue;
711
+ }
712
+ // Try to resolve from loadedEntities
713
+ const entityMap = loadedEntities[targetClassName];
714
+ if (entityMap && entityMap[referenceId]) {
715
+ resolvedArray.push(entityMap[referenceId]);
716
+ }
717
+ else if (dataSource && TargetClass) {
718
+ const entity = await this.fetchReferenceFromDb(dataSource, TargetClass, referenceId, `${propertyName}[${resolvedArray.length}]`, verbose);
719
+ resolvedArray.push(entity);
720
+ if (!(entity instanceof framework_backend_1.BaseDataModel)) {
721
+ hasUnresolved = true;
722
+ }
723
+ }
724
+ else {
725
+ // No dataSource — normalize to a minimal stub so TypeORM can derive the FK on save.
726
+ resolvedArray.push({ id: referenceId });
727
+ hasUnresolved = true;
728
+ if (verbose) {
729
+ console.log(` ⚠️ Could not resolve array reference ${propertyName}[${resolvedArray.length - 1}]="${referenceId}" (${targetClassName} not found in loaded entities, no dataSource available)`);
730
+ }
731
+ }
732
+ }
733
+ // Always set the array - either fully resolved or with plain objects containing IDs
734
+ // Unresolved items will be loaded from the database before saving
735
+ modelInstance[propertyName] = resolvedArray;
736
+ if (hasUnresolved && verbose) {
737
+ console.log(` → Array ${propertyName} contains unresolved references - will be loaded from database before saving`);
738
+ }
739
+ continue;
740
+ }
741
+ // Handle single reference (non-array)
742
+ // Extract the ID to look up from loadedEntities.
743
+ // Handles three formats:
744
+ // 1. Plain string UUID: "554aef99-..."
745
+ // 2. Partial BaseDataModel: State { id: "554aef99-...", name: undefined }
746
+ // 3. Object with id: { id: "554aef99-..." }
747
+ let referenceId;
748
+ if (typeof currentValue === 'string') {
749
+ referenceId = currentValue;
750
+ }
751
+ else if (currentValue instanceof framework_backend_1.BaseDataModel && currentValue.id) {
752
+ referenceId = currentValue.id;
753
+ }
754
+ else if (currentValue && typeof currentValue === 'object' && 'id' in currentValue) {
755
+ referenceId = currentValue.id;
756
+ }
757
+ if (!referenceId) {
758
+ continue;
759
+ }
760
+ // Determine the referenced model class
761
+ let targetClassName = null;
762
+ let TargetClass = null;
763
+ // Try elementType from field options (most reliable for Slingr framework)
764
+ const fieldOptions = Reflect.getMetadata?.('field:type:options', proto, propertyName);
765
+ if (fieldOptions?.elementType && typeof fieldOptions.elementType === 'function') {
766
+ try {
767
+ const targetClass = fieldOptions.elementType();
768
+ if (targetClass?.name) {
769
+ targetClassName = targetClass.name;
770
+ TargetClass = targetClass;
771
+ }
772
+ }
773
+ catch {
774
+ // Ignore elementType resolution errors
775
+ }
776
+ }
777
+ // Fallback to design:type
778
+ if (!targetClassName) {
779
+ const designType = Reflect.getMetadata?.('design:type', modelInstance, propertyName);
780
+ if (designType && designType.name && designType.name !== 'Array' && designType.name !== 'String') {
781
+ targetClassName = designType.name;
782
+ TargetClass = designType;
783
+ }
784
+ }
785
+ // If elementType/designType didn't give us a class, try modelMap
786
+ if (!TargetClass && targetClassName && modelMap) {
787
+ TargetClass = modelMap[targetClassName];
788
+ if (verbose && TargetClass) {
789
+ console.log(` ↳ Resolved ${targetClassName} class from modelMap for database query`);
790
+ }
791
+ }
792
+ if (!targetClassName) {
793
+ continue;
794
+ }
795
+ // Look up the entity in loadedEntities
796
+ const entityMap = loadedEntities[targetClassName];
797
+ if (entityMap && entityMap[referenceId]) {
798
+ modelInstance[propertyName] = entityMap[referenceId];
799
+ if (verbose) {
800
+ console.log(` ↳ Resolved reference ${propertyName}="${referenceId}" → ${targetClassName} instance`);
801
+ }
802
+ }
803
+ else if (dataSource && TargetClass) {
804
+ modelInstance[propertyName] = await this.fetchReferenceFromDb(dataSource, TargetClass, referenceId, propertyName, verbose);
805
+ }
806
+ else {
807
+ // No dataSource — normalize to a minimal stub so TypeORM can derive the FK on save.
808
+ // Setting the property to a plain string UUID would be silently ignored by TypeORM.
809
+ modelInstance[propertyName] = { id: referenceId };
810
+ if (verbose) {
811
+ console.log(` ⚠️ Could not resolve reference ${propertyName}="${referenceId}" (${targetClassName} not found in loaded entities, no dataSource available)`);
812
+ }
813
+ }
814
+ }
815
+ catch {
816
+ // Ignore errors for individual properties
817
+ }
818
+ }
819
+ }
820
+ async loadJsonlFile(filePath, ModelClass, modelName, loadedEntities, validateRecords = true, verbose = false, dataSource, modelMap) {
318
821
  if (verbose) {
319
822
  console.log(`Loading JSONL file: ${filePath} for model: ${modelName}`);
320
823
  }
@@ -326,6 +829,9 @@ class JsonlDatasetLoader {
326
829
  successCount: 0,
327
830
  errorCount: 0,
328
831
  errors: [],
832
+ saveSuccessCount: 0,
833
+ saveErrorCount: 0,
834
+ saveErrors: [],
329
835
  };
330
836
  for (let i = 0; i < lines.length; i++) {
331
837
  try {
@@ -337,6 +843,24 @@ class JsonlDatasetLoader {
337
843
  const resolvedData = await this.resolveSimpleReferences(rawData, ModelClass, loadedEntities, verbose);
338
844
  // Use the model's fromJSON method to create the instance
339
845
  const modelInstance = ModelClass.fromJSON(resolvedData);
846
+ // Restore fields that were excluded by class-transformer (e.g., fields with available: false)
847
+ // During dataset loading, we want to include ALL fields from the JSONL file,
848
+ // even those marked as excluded for API/JSON serialization
849
+ for (const key of Object.keys(resolvedData)) {
850
+ // If the field exists in resolvedData but is undefined/missing in modelInstance,
851
+ // it was likely excluded by @Exclude() or similar decorators
852
+ if (resolvedData[key] !== undefined && modelInstance[key] === undefined) {
853
+ modelInstance[key] = resolvedData[key];
854
+ if (verbose) {
855
+ console.log(` ↳ Restored excluded field '${key}' for ${ModelClass.name}`);
856
+ }
857
+ }
858
+ }
859
+ // Resolve string UUID reference fields to loaded BaseDataModel entities.
860
+ // This is needed for filter validation: filters like { state: company.state }
861
+ // need company.state to be a BaseDataModel instance (not a plain UUID string)
862
+ // so the validator can properly check referenced objects against filter criteria.
863
+ await this.resolveReferenceFieldsToEntities(modelInstance, ModelClass, loadedEntities, verbose, dataSource, modelMap);
340
864
  // Validate the instance if requested
341
865
  if (validateRecords) {
342
866
  const validationErrors = await modelInstance.validate();
@@ -411,6 +935,30 @@ class JsonlDatasetLoader {
411
935
  camelToSnakeCase(str) {
412
936
  return str.replace(/([A-Z])/g, '_$1').toLowerCase();
413
937
  }
938
+ /**
939
+ * Recursively format validation error constraints for display
940
+ */
941
+ formatValidationConstraints(validationErrors, prefix = '', isRoot = true) {
942
+ const lines = [];
943
+ for (const error of validationErrors) {
944
+ const propertyPath = prefix ? `${prefix}.${error.property}` : error.property;
945
+ // Add constraints for this property if they exist
946
+ if (error.constraints) {
947
+ const constraintEntries = Object.entries(error.constraints);
948
+ if (constraintEntries.length > 0) {
949
+ lines.push(`${propertyPath}: ${JSON.stringify(error.constraints)}`);
950
+ }
951
+ }
952
+ // Recursively process children
953
+ if (error.children && error.children.length > 0) {
954
+ const childLines = this.formatValidationConstraints(error.children, propertyPath, false);
955
+ if (childLines) {
956
+ lines.push(childLines);
957
+ }
958
+ }
959
+ }
960
+ return lines.join('\n');
961
+ }
414
962
  /**
415
963
  * Load dataset into database using TypeORM with dynamic table creation
416
964
  * This method creates tables dynamically and uses TypeORM for data insertion
@@ -421,12 +969,26 @@ class JsonlDatasetLoader {
421
969
  if (!typeormDataSource || !typeormDataSource.isInitialized) {
422
970
  throw new Error('TypeORM DataSource is not initialized');
423
971
  }
972
+ // If verbose, enable logging. If not verbose, disable logging to improve performance
973
+ if (verbose) {
974
+ typeormDataSource.setOptions({ logging: ['error', 'warn', 'info'] });
975
+ }
976
+ else {
977
+ typeormDataSource.setOptions({ logging: false });
978
+ }
424
979
  // Process results in the order they were provided (which should be dependency order)
425
980
  for (const result of results) {
426
- if (result.errorCount > 0 && verbose) {
981
+ if (result.errorCount > 0) {
427
982
  console.log(`⚠️ Model ${result.modelName} has ${result.errorCount} validation errors:`);
428
983
  for (const error of result.errors) {
429
- console.log(` Record ${error.recordIndex + 1}: ${JSON.stringify(error.validationErrors)}`);
984
+ const formattedErrors = this.formatValidationConstraints(error.validationErrors);
985
+ console.log(` Record ${error.recordIndex + 1}:`);
986
+ if (formattedErrors) {
987
+ console.log(` ${formattedErrors.replace(/\n/g, '\n ')}`);
988
+ }
989
+ else {
990
+ console.log(` (no constraint details available)`);
991
+ }
430
992
  }
431
993
  }
432
994
  if (result.successCount === 0) {
@@ -438,18 +1000,58 @@ class JsonlDatasetLoader {
438
1000
  if (verbose) {
439
1001
  console.log(`📊 Processing ${result.modelName}: ${result.successCount} valid records`);
440
1002
  }
441
- try {
442
- // Save records using the framework's save method which respects relationships
443
- for (const record of result.records) {
1003
+ // Create progress bar for this model
1004
+ const progressBar = new cliProgress.SingleBar({
1005
+ format: ` Loading ${result.modelName} [{bar}] {percentage}% | {value}/{total} records | Success: {success} | Failed: {failed}`,
1006
+ barCompleteChar: '█',
1007
+ barIncompleteChar: '░',
1008
+ hideCursor: true,
1009
+ });
1010
+ progressBar.start(result.records.length, 0, { success: 0, failed: 0 });
1011
+ // Save records using the framework's save method which respects relationships
1012
+ // Continue on errors instead of stopping
1013
+ for (let i = 0; i < result.records.length; i++) {
1014
+ const record = result.records[i];
1015
+ try {
1016
+ // Before saving, resolve any unresolved references from the database
1017
+ // This handles cases where referenced models were excluded from loading but exist in DB
1018
+ await this.resolveUnresolvedReferencesFromDb(record, dataSource, verbose);
444
1019
  await dataSource.save(record);
1020
+ result.saveSuccessCount++;
445
1021
  }
446
- if (verbose) {
447
- console.log(`✅ Successfully loaded ${result.successCount} records for ${result.modelName}`);
1022
+ catch (error) {
1023
+ result.saveErrorCount++;
1024
+ result.saveErrors.push({
1025
+ recordIndex: i,
1026
+ record: record,
1027
+ error: error.message,
1028
+ });
448
1029
  }
1030
+ // Update progress bar
1031
+ progressBar.update(i + 1, {
1032
+ success: result.saveSuccessCount,
1033
+ failed: result.saveErrorCount,
1034
+ });
449
1035
  }
450
- catch (error) {
451
- console.error(`❌ Failed to load ${result.modelName}:`, error);
452
- throw error;
1036
+ progressBar.stop();
1037
+ // Show summary for this model
1038
+ if (result.saveSuccessCount > 0) {
1039
+ console.log(` ✅ Successfully saved ${result.saveSuccessCount} records`);
1040
+ }
1041
+ if (result.saveErrorCount > 0) {
1042
+ console.log(` ❌ Failed to save ${result.saveErrorCount} records`);
1043
+ // Show first few errors if verbose
1044
+ if (verbose && result.saveErrors.length > 0) {
1045
+ const errorsToShow = Math.min(3, result.saveErrors.length);
1046
+ console.log(` First ${errorsToShow} errors:`);
1047
+ for (let i = 0; i < errorsToShow; i++) {
1048
+ const saveError = result.saveErrors[i];
1049
+ console.log(` Record ${saveError.recordIndex + 1}: ${saveError.error}`);
1050
+ }
1051
+ if (result.saveErrors.length > errorsToShow) {
1052
+ console.log(` ... and ${result.saveErrors.length - errorsToShow} more errors`);
1053
+ }
1054
+ }
453
1055
  }
454
1056
  }
455
1057
  }
@@ -552,15 +1154,20 @@ exports.JsonlDatasetLoader = JsonlDatasetLoader;
552
1154
  /**
553
1155
  * Auto-discover models from the compiled JavaScript files
554
1156
  */
555
- async function discoverModels(distPath) {
1157
+ async function discoverModels(distPath, verbose = false) {
556
1158
  const modelMap = {};
557
1159
  if (!(await fs_extra_1.default.pathExists(distPath))) {
558
1160
  throw new Error(`Dist directory not found: ${distPath}. Please run 'npm run build' first.`);
559
1161
  }
560
1162
  // Import glob dynamically since it might be ESM
561
1163
  const { glob } = await import('glob');
562
- // Only look for model files, not test files
563
- const entityFiles = await glob(node_path_1.default.join(distPath, 'data', '**', '*.js').replace(/\\/g, '/')).then((files) => files.filter((file) => !file.includes('.test.js')));
1164
+ // Check both dist/data (legacy) and dist/src/data (current TypeScript output)
1165
+ // Try primary location first
1166
+ let entityFiles = await glob(node_path_1.default.join(distPath, 'src', 'data', '**', '*.js').replace(/\\/g, '/')).then((files) => files.filter((file) => !file.includes('.test.js')));
1167
+ // If no files found, try alternative path (dist/src/data)
1168
+ if (entityFiles.length === 0) {
1169
+ entityFiles = await glob(node_path_1.default.join(distPath, 'data', '**', '*.js').replace(/\\/g, '/')).then((files) => files.filter((file) => !file.includes('.test.js')));
1170
+ }
564
1171
  for (const file of entityFiles) {
565
1172
  try {
566
1173
  // Clear the require cache to ensure fresh loading
@@ -569,13 +1176,17 @@ async function discoverModels(distPath) {
569
1176
  const exports = Object.values(module);
570
1177
  for (const exp of exports) {
571
1178
  if (typeof exp === 'function' && exp.prototype && exp.name && typeof exp.fromJSON === 'function') {
572
- console.log(`✅ Found model: ${exp.name} in ${file}`);
1179
+ if (verbose) {
1180
+ console.log(`✅ Found model: ${exp.name} in ${file}`);
1181
+ }
573
1182
  modelMap[exp.name] = exp;
574
1183
  }
575
1184
  }
576
1185
  }
577
1186
  catch (error) {
578
- console.warn(`Failed to load entity from file: ${file}:`, error);
1187
+ if (verbose) {
1188
+ console.warn(`Failed to load entity from file: ${file}:`, error);
1189
+ }
579
1190
  }
580
1191
  }
581
1192
  return modelMap;