@memberjunction/metadata-sync 2.116.0 → 2.118.0

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 (37) hide show
  1. package/README.md +24 -0
  2. package/dist/index.d.ts +9 -0
  3. package/dist/index.js +12 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/lib/database-reference-scanner.d.ts +56 -0
  6. package/dist/lib/database-reference-scanner.js +175 -0
  7. package/dist/lib/database-reference-scanner.js.map +1 -0
  8. package/dist/lib/deletion-auditor.d.ts +76 -0
  9. package/dist/lib/deletion-auditor.js +219 -0
  10. package/dist/lib/deletion-auditor.js.map +1 -0
  11. package/dist/lib/deletion-report-generator.d.ts +58 -0
  12. package/dist/lib/deletion-report-generator.js +287 -0
  13. package/dist/lib/deletion-report-generator.js.map +1 -0
  14. package/dist/lib/entity-foreign-key-helper.d.ts +51 -0
  15. package/dist/lib/entity-foreign-key-helper.js +83 -0
  16. package/dist/lib/entity-foreign-key-helper.js.map +1 -0
  17. package/dist/lib/provider-utils.d.ts +9 -1
  18. package/dist/lib/provider-utils.js +42 -5
  19. package/dist/lib/provider-utils.js.map +1 -1
  20. package/dist/lib/record-dependency-analyzer.d.ts +44 -0
  21. package/dist/lib/record-dependency-analyzer.js +133 -0
  22. package/dist/lib/record-dependency-analyzer.js.map +1 -1
  23. package/dist/services/PullService.d.ts +2 -0
  24. package/dist/services/PullService.js +4 -0
  25. package/dist/services/PullService.js.map +1 -1
  26. package/dist/services/PushService.d.ts +42 -2
  27. package/dist/services/PushService.js +451 -109
  28. package/dist/services/PushService.js.map +1 -1
  29. package/dist/services/StatusService.d.ts +2 -0
  30. package/dist/services/StatusService.js +5 -1
  31. package/dist/services/StatusService.js.map +1 -1
  32. package/dist/services/ValidationService.d.ts +4 -0
  33. package/dist/services/ValidationService.js +32 -2
  34. package/dist/services/ValidationService.js.map +1 -1
  35. package/dist/types/validation.d.ts +2 -0
  36. package/dist/types/validation.js.map +1 -1
  37. package/package.json +9 -8
package/README.md CHANGED
@@ -77,7 +77,18 @@ The Metadata Sync tool bridges the gap between database-stored metadata and file
77
77
  - **Push**: Upload local file changes to database
78
78
  - Process embedded collections automatically
79
79
  - Verbose mode (`-v`) for detailed output
80
+ - Directory filtering with `--include` and `--exclude` options
80
81
  - **Status**: Show what would change without making modifications
82
+ - Directory filtering with `--include` and `--exclude` options
83
+
84
+ ### Directory Filtering
85
+ - **Selective Processing**: Use `--include` or `--exclude` to filter which entity directories are processed
86
+ - **Pattern Support**: Supports glob patterns like `ai-*`, `*-test`, etc.
87
+ - **Mutually Exclusive**: Cannot use both `--include` and `--exclude` together
88
+ - **Use Cases**:
89
+ - Speed up development by excluding large/slow directories
90
+ - Focus on specific entities during debugging
91
+ - Create specialized sync workflows for different teams
81
92
 
82
93
  ### Development Workflow Integration
83
94
  - Watch mode for automatic syncing during development
@@ -1363,6 +1374,19 @@ mj sync push --dry-run
1363
1374
  # Push with parallel processing
1364
1375
  mj sync push --parallel-batch-size=20 # Process 20 records in parallel (default: 10, max: 50)
1365
1376
 
1377
+ # Directory filtering - exclude specific directories
1378
+ mj sync push --exclude="actions" # Exclude single directory
1379
+ mj sync push --exclude="actions,templates" # Exclude multiple directories (comma-separated)
1380
+ mj sync push --exclude="*-test,*-old" # Exclude using glob patterns
1381
+
1382
+ # Directory filtering - include only specific directories
1383
+ mj sync push --include="prompts,agent-types" # Include only these directories
1384
+ mj sync push --include="ai-*" # Include using glob patterns
1385
+
1386
+ # Filtering works with other options
1387
+ mj sync push --dir="metadata" --exclude="actions" --dry-run
1388
+ mj sync status --exclude="actions,templates"
1389
+
1366
1390
  # Show status of local vs database
1367
1391
  mj sync status
1368
1392
 
package/dist/index.d.ts CHANGED
@@ -7,6 +7,15 @@ export { SQLLogger } from './lib/sql-logger';
7
7
  export { TransactionManager } from './lib/transaction-manager';
8
8
  export { JsonWriteHelper } from './lib/json-write-helper';
9
9
  export { FileWriteBatch } from './lib/file-write-batch';
10
+ export { RecordDependencyAnalyzer } from './lib/record-dependency-analyzer';
11
+ export type { FlattenedRecord, ReverseDependency, DependencyAnalysisResult } from './lib/record-dependency-analyzer';
12
+ export { EntityForeignKeyHelper } from './lib/entity-foreign-key-helper';
13
+ export type { ReverseFKInfo } from './lib/entity-foreign-key-helper';
14
+ export { DatabaseReferenceScanner } from './lib/database-reference-scanner';
15
+ export type { DatabaseReference } from './lib/database-reference-scanner';
16
+ export { DeletionAuditor } from './lib/deletion-auditor';
17
+ export type { DeletionAudit } from './lib/deletion-auditor';
18
+ export { DeletionReportGenerator } from './lib/deletion-report-generator';
10
19
  export { InitService } from './services/InitService';
11
20
  export type { InitOptions, InitCallbacks } from './services/InitService';
12
21
  export { PullService } from './services/PullService';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getDataProvider = exports.findEntityDirectories = exports.getSystemUser = exports.initializeProvider = exports.loadFolderConfig = exports.loadEntityConfig = exports.loadSyncConfig = exports.loadMJConfig = exports.FormattingService = exports.ValidationService = exports.WatchService = exports.FileResetService = exports.StatusService = exports.PushService = exports.PullService = exports.InitService = exports.FileWriteBatch = exports.JsonWriteHelper = exports.TransactionManager = exports.SQLLogger = exports.resetSyncEngine = exports.getSyncEngine = exports.configManager = exports.ConfigManager = exports.SyncEngine = exports.FileBackupManager = void 0;
3
+ exports.getDataProvider = exports.findEntityDirectories = exports.getSystemUser = exports.initializeProvider = exports.loadFolderConfig = exports.loadEntityConfig = exports.loadSyncConfig = exports.loadMJConfig = exports.FormattingService = exports.ValidationService = exports.WatchService = exports.FileResetService = exports.StatusService = exports.PushService = exports.PullService = exports.InitService = exports.DeletionReportGenerator = exports.DeletionAuditor = exports.DatabaseReferenceScanner = exports.EntityForeignKeyHelper = exports.RecordDependencyAnalyzer = exports.FileWriteBatch = exports.JsonWriteHelper = exports.TransactionManager = exports.SQLLogger = exports.resetSyncEngine = exports.getSyncEngine = exports.configManager = exports.ConfigManager = exports.SyncEngine = exports.FileBackupManager = void 0;
4
4
  const core_entities_server_1 = require("@memberjunction/core-entities-server");
5
5
  // Core library exports
6
6
  var file_backup_manager_1 = require("./lib/file-backup-manager");
@@ -21,6 +21,17 @@ var json_write_helper_1 = require("./lib/json-write-helper");
21
21
  Object.defineProperty(exports, "JsonWriteHelper", { enumerable: true, get: function () { return json_write_helper_1.JsonWriteHelper; } });
22
22
  var file_write_batch_1 = require("./lib/file-write-batch");
23
23
  Object.defineProperty(exports, "FileWriteBatch", { enumerable: true, get: function () { return file_write_batch_1.FileWriteBatch; } });
24
+ // Deletion audit exports
25
+ var record_dependency_analyzer_1 = require("./lib/record-dependency-analyzer");
26
+ Object.defineProperty(exports, "RecordDependencyAnalyzer", { enumerable: true, get: function () { return record_dependency_analyzer_1.RecordDependencyAnalyzer; } });
27
+ var entity_foreign_key_helper_1 = require("./lib/entity-foreign-key-helper");
28
+ Object.defineProperty(exports, "EntityForeignKeyHelper", { enumerable: true, get: function () { return entity_foreign_key_helper_1.EntityForeignKeyHelper; } });
29
+ var database_reference_scanner_1 = require("./lib/database-reference-scanner");
30
+ Object.defineProperty(exports, "DatabaseReferenceScanner", { enumerable: true, get: function () { return database_reference_scanner_1.DatabaseReferenceScanner; } });
31
+ var deletion_auditor_1 = require("./lib/deletion-auditor");
32
+ Object.defineProperty(exports, "DeletionAuditor", { enumerable: true, get: function () { return deletion_auditor_1.DeletionAuditor; } });
33
+ var deletion_report_generator_1 = require("./lib/deletion-report-generator");
34
+ Object.defineProperty(exports, "DeletionReportGenerator", { enumerable: true, get: function () { return deletion_report_generator_1.DeletionReportGenerator; } });
24
35
  // Service exports
25
36
  var InitService_1 = require("./services/InitService");
26
37
  Object.defineProperty(exports, "InitService", { enumerable: true, get: function () { return InitService_1.InitService; } });
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,+EAAgG;AAEhG,uBAAuB;AACvB,iEAA8D;AAArD,wHAAA,iBAAiB,OAAA;AAC1B,iDAA+C;AAAtC,yGAAA,UAAU,OAAA;AAEnB,uDAAoE;AAA3D,+GAAA,aAAa,OAAA;AAAE,+GAAA,aAAa,OAAA;AACrC,6DAAyE;AAAhE,kHAAA,aAAa,OAAA;AAAE,oHAAA,eAAe,OAAA;AACvC,+CAA6C;AAApC,uGAAA,SAAS,OAAA;AAClB,iEAA+D;AAAtD,yHAAA,kBAAkB,OAAA;AAC3B,6DAA0D;AAAjD,oHAAA,eAAe,OAAA;AACxB,2DAAwD;AAA/C,kHAAA,cAAc,OAAA;AAEvB,kBAAkB;AAClB,sDAAqD;AAA5C,0GAAA,WAAW,OAAA;AAGpB,sDAAqD;AAA5C,0GAAA,WAAW,OAAA;AAGpB,sDAAqD;AAA5C,0GAAA,WAAW,OAAA;AAGpB,0DAAyD;AAAhD,8GAAA,aAAa,OAAA;AAGtB,gEAA+D;AAAtD,oHAAA,gBAAgB,OAAA;AAGzB,wDAAuD;AAA9C,4GAAA,YAAY,OAAA;AAGrB,kEAAiE;AAAxD,sHAAA,iBAAiB,OAAA;AAC1B,kEAAiE;AAAxD,sHAAA,iBAAiB,OAAA;AAE1B,sBAAsB;AACtB,mCAQkB;AAPhB,sGAAA,YAAY,OAAA;AACZ,wGAAA,cAAc,OAAA;AACd,0GAAA,gBAAgB,OAAA;AAChB,0GAAA,gBAAgB,OAAA;AAMlB,qBAAqB;AACrB,uDAK8B;AAJ5B,oHAAA,kBAAkB,OAAA;AAClB,+GAAA,aAAa,OAAA;AACb,uHAAA,qBAAqB,OAAA;AACrB,iHAAA,eAAe,OAAA;AAejB,IAAA,+DAAwC,GAAE,CAAC","sourcesContent":["import { LoadAIPromptEntityExtendedServerSubClass } from '@memberjunction/core-entities-server';\n\n// Core library exports\nexport { FileBackupManager } from './lib/file-backup-manager';\nexport { SyncEngine } from './lib/sync-engine';\nexport type { RecordData } from './lib/sync-engine';\nexport { ConfigManager, configManager } from './lib/config-manager';\nexport { getSyncEngine, resetSyncEngine } from './lib/singleton-manager';\nexport { SQLLogger } from './lib/sql-logger';\nexport { TransactionManager } from './lib/transaction-manager';\nexport { JsonWriteHelper } from './lib/json-write-helper';\nexport { FileWriteBatch } from './lib/file-write-batch';\n\n// Service exports\nexport { InitService } from './services/InitService';\nexport type { InitOptions, InitCallbacks } from './services/InitService';\n\nexport { PullService } from './services/PullService';\nexport type { PullOptions, PullCallbacks, PullResult } from './services/PullService';\n\nexport { PushService } from './services/PushService';\nexport type { PushOptions, PushCallbacks, PushResult } from './services/PushService';\n\nexport { StatusService } from './services/StatusService';\nexport type { StatusOptions, StatusCallbacks, StatusResult } from './services/StatusService';\n\nexport { FileResetService } from './services/FileResetService';\nexport type { FileResetOptions, FileResetCallbacks, FileResetResult } from './services/FileResetService';\n\nexport { WatchService } from './services/WatchService';\nexport type { WatchOptions, WatchCallbacks, WatchResult } from './services/WatchService';\n\nexport { ValidationService } from './services/ValidationService';\nexport { FormattingService } from './services/FormattingService';\n\n// Configuration types\nexport {\n loadMJConfig,\n loadSyncConfig,\n loadEntityConfig,\n loadFolderConfig,\n type EntityConfig,\n type FolderConfig,\n type RelatedEntityConfig\n} from './config';\n\n// Provider utilities\nexport {\n initializeProvider,\n getSystemUser,\n findEntityDirectories,\n getDataProvider\n} from './lib/provider-utils';\n\n// Validation types\nexport type {\n ValidationResult,\n ValidationError,\n ValidationWarning,\n EntityDependency,\n FileValidationResult,\n ValidationOptions,\n ReferenceType,\n ParsedReference\n} from './types/validation';\n\nLoadAIPromptEntityExtendedServerSubClass();"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,+EAAgG;AAEhG,uBAAuB;AACvB,iEAA8D;AAArD,wHAAA,iBAAiB,OAAA;AAC1B,iDAA+C;AAAtC,yGAAA,UAAU,OAAA;AAEnB,uDAAoE;AAA3D,+GAAA,aAAa,OAAA;AAAE,+GAAA,aAAa,OAAA;AACrC,6DAAyE;AAAhE,kHAAA,aAAa,OAAA;AAAE,oHAAA,eAAe,OAAA;AACvC,+CAA6C;AAApC,uGAAA,SAAS,OAAA;AAClB,iEAA+D;AAAtD,yHAAA,kBAAkB,OAAA;AAC3B,6DAA0D;AAAjD,oHAAA,eAAe,OAAA;AACxB,2DAAwD;AAA/C,kHAAA,cAAc,OAAA;AAEvB,yBAAyB;AACzB,+EAA4E;AAAnE,sIAAA,wBAAwB,OAAA;AAEjC,6EAAyE;AAAhE,mIAAA,sBAAsB,OAAA;AAE/B,+EAA4E;AAAnE,sIAAA,wBAAwB,OAAA;AAEjC,2DAAyD;AAAhD,mHAAA,eAAe,OAAA;AAExB,6EAA0E;AAAjE,oIAAA,uBAAuB,OAAA;AAEhC,kBAAkB;AAClB,sDAAqD;AAA5C,0GAAA,WAAW,OAAA;AAGpB,sDAAqD;AAA5C,0GAAA,WAAW,OAAA;AAGpB,sDAAqD;AAA5C,0GAAA,WAAW,OAAA;AAGpB,0DAAyD;AAAhD,8GAAA,aAAa,OAAA;AAGtB,gEAA+D;AAAtD,oHAAA,gBAAgB,OAAA;AAGzB,wDAAuD;AAA9C,4GAAA,YAAY,OAAA;AAGrB,kEAAiE;AAAxD,sHAAA,iBAAiB,OAAA;AAC1B,kEAAiE;AAAxD,sHAAA,iBAAiB,OAAA;AAE1B,sBAAsB;AACtB,mCAQkB;AAPhB,sGAAA,YAAY,OAAA;AACZ,wGAAA,cAAc,OAAA;AACd,0GAAA,gBAAgB,OAAA;AAChB,0GAAA,gBAAgB,OAAA;AAMlB,qBAAqB;AACrB,uDAK8B;AAJ5B,oHAAA,kBAAkB,OAAA;AAClB,+GAAA,aAAa,OAAA;AACb,uHAAA,qBAAqB,OAAA;AACrB,iHAAA,eAAe,OAAA;AAejB,IAAA,+DAAwC,GAAE,CAAC","sourcesContent":["import { LoadAIPromptEntityExtendedServerSubClass } from '@memberjunction/core-entities-server';\n\n// Core library exports\nexport { FileBackupManager } from './lib/file-backup-manager';\nexport { SyncEngine } from './lib/sync-engine';\nexport type { RecordData } from './lib/sync-engine';\nexport { ConfigManager, configManager } from './lib/config-manager';\nexport { getSyncEngine, resetSyncEngine } from './lib/singleton-manager';\nexport { SQLLogger } from './lib/sql-logger';\nexport { TransactionManager } from './lib/transaction-manager';\nexport { JsonWriteHelper } from './lib/json-write-helper';\nexport { FileWriteBatch } from './lib/file-write-batch';\n\n// Deletion audit exports\nexport { RecordDependencyAnalyzer } from './lib/record-dependency-analyzer';\nexport type { FlattenedRecord, ReverseDependency, DependencyAnalysisResult } from './lib/record-dependency-analyzer';\nexport { EntityForeignKeyHelper } from './lib/entity-foreign-key-helper';\nexport type { ReverseFKInfo } from './lib/entity-foreign-key-helper';\nexport { DatabaseReferenceScanner } from './lib/database-reference-scanner';\nexport type { DatabaseReference } from './lib/database-reference-scanner';\nexport { DeletionAuditor } from './lib/deletion-auditor';\nexport type { DeletionAudit } from './lib/deletion-auditor';\nexport { DeletionReportGenerator } from './lib/deletion-report-generator';\n\n// Service exports\nexport { InitService } from './services/InitService';\nexport type { InitOptions, InitCallbacks } from './services/InitService';\n\nexport { PullService } from './services/PullService';\nexport type { PullOptions, PullCallbacks, PullResult } from './services/PullService';\n\nexport { PushService } from './services/PushService';\nexport type { PushOptions, PushCallbacks, PushResult } from './services/PushService';\n\nexport { StatusService } from './services/StatusService';\nexport type { StatusOptions, StatusCallbacks, StatusResult } from './services/StatusService';\n\nexport { FileResetService } from './services/FileResetService';\nexport type { FileResetOptions, FileResetCallbacks, FileResetResult } from './services/FileResetService';\n\nexport { WatchService } from './services/WatchService';\nexport type { WatchOptions, WatchCallbacks, WatchResult } from './services/WatchService';\n\nexport { ValidationService } from './services/ValidationService';\nexport { FormattingService } from './services/FormattingService';\n\n// Configuration types\nexport {\n loadMJConfig,\n loadSyncConfig,\n loadEntityConfig,\n loadFolderConfig,\n type EntityConfig,\n type FolderConfig,\n type RelatedEntityConfig\n} from './config';\n\n// Provider utilities\nexport {\n initializeProvider,\n getSystemUser,\n findEntityDirectories,\n getDataProvider\n} from './lib/provider-utils';\n\n// Validation types\nexport type {\n ValidationResult,\n ValidationError,\n ValidationWarning,\n EntityDependency,\n FileValidationResult,\n ValidationOptions,\n ReferenceType,\n ParsedReference\n} from './types/validation';\n\nLoadAIPromptEntityExtendedServerSubClass();"]}
@@ -0,0 +1,56 @@
1
+ import { Metadata, UserInfo, CompositeKey } from '@memberjunction/core';
2
+ import { FlattenedRecord } from './record-dependency-analyzer';
3
+ import { ReverseFKInfo } from './entity-foreign-key-helper';
4
+ /**
5
+ * Represents a database record that references a record being deleted
6
+ */
7
+ export interface DatabaseReference {
8
+ entityName: string;
9
+ primaryKey: CompositeKey;
10
+ referencingField: string;
11
+ referencedEntity: string;
12
+ referencedKey: CompositeKey;
13
+ existsInMetadata: boolean;
14
+ }
15
+ /**
16
+ * Scans the database for existing records that reference records marked for deletion
17
+ * This helps identify:
18
+ * 1. Database-only records that will prevent deletion
19
+ * 2. Records that should be included in the deletion plan
20
+ */
21
+ export declare class DatabaseReferenceScanner {
22
+ private metadata;
23
+ private contextUser;
24
+ constructor(metadata: Metadata, contextUser: UserInfo);
25
+ /**
26
+ * Scan database for records that reference records marked for deletion
27
+ *
28
+ * @param recordsToDelete Records that will be deleted
29
+ * @param reverseFKMap Map of entity -> entities that reference it
30
+ * @param allMetadataRecords All records from metadata (for checking if DB record exists in metadata)
31
+ * @returns Array of database references found
32
+ */
33
+ scanForReferences(recordsToDelete: FlattenedRecord[], reverseFKMap: Map<string, ReverseFKInfo[]>, allMetadataRecords: FlattenedRecord[]): Promise<DatabaseReference[]>;
34
+ /**
35
+ * Extract primary key from a flattened record
36
+ */
37
+ private extractPrimaryKey;
38
+ /**
39
+ * Get primary key from a simple record (plain object from RunView with ResultType: 'simple')
40
+ */
41
+ private getPrimaryKeyFromSimpleRecord;
42
+ /**
43
+ * Check if a database record exists in metadata files
44
+ */
45
+ private checkIfInMetadata;
46
+ /**
47
+ * Get orphaned references (database-only records not in metadata)
48
+ * These will prevent deletion unless handled
49
+ */
50
+ getOrphanedReferences(references: DatabaseReference[]): DatabaseReference[];
51
+ /**
52
+ * Get metadata references (records in metadata that reference deletion targets)
53
+ * These should already be marked for deletion if the user set things up correctly
54
+ */
55
+ getMetadataReferences(references: DatabaseReference[]): DatabaseReference[];
56
+ }
@@ -0,0 +1,175 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DatabaseReferenceScanner = void 0;
4
+ const core_1 = require("@memberjunction/core");
5
+ /**
6
+ * Scans the database for existing records that reference records marked for deletion
7
+ * This helps identify:
8
+ * 1. Database-only records that will prevent deletion
9
+ * 2. Records that should be included in the deletion plan
10
+ */
11
+ class DatabaseReferenceScanner {
12
+ metadata;
13
+ contextUser;
14
+ constructor(metadata, contextUser) {
15
+ this.metadata = metadata;
16
+ this.contextUser = contextUser;
17
+ }
18
+ /**
19
+ * Scan database for records that reference records marked for deletion
20
+ *
21
+ * @param recordsToDelete Records that will be deleted
22
+ * @param reverseFKMap Map of entity -> entities that reference it
23
+ * @param allMetadataRecords All records from metadata (for checking if DB record exists in metadata)
24
+ * @returns Array of database references found
25
+ */
26
+ async scanForReferences(recordsToDelete, reverseFKMap, allMetadataRecords) {
27
+ const references = [];
28
+ const rv = new core_1.RunView();
29
+ for (const record of recordsToDelete) {
30
+ const entityName = record.entityName;
31
+ const primaryKey = this.extractPrimaryKey(record);
32
+ if (!primaryKey) {
33
+ console.warn(`Cannot scan references for ${entityName}: no primary key found`);
34
+ continue;
35
+ }
36
+ // Find all entities that could reference this one
37
+ const referencingEntities = reverseFKMap.get(entityName) || [];
38
+ for (const refInfo of referencingEntities) {
39
+ try {
40
+ // Query database for existing references
41
+ // Use the actual value from the primary key, not the concatenated string format
42
+ const pkValue = primaryKey.KeyValuePairs.length === 1
43
+ ? primaryKey.KeyValuePairs[0].Value
44
+ : primaryKey.ToConcatenatedString();
45
+ const filter = `${refInfo.fieldName} = '${pkValue}'`;
46
+ const result = await rv.RunView({
47
+ EntityName: refInfo.entityName,
48
+ ExtraFilter: filter,
49
+ ResultType: 'simple' // More efficient - we don't need entity objects
50
+ }, this.contextUser);
51
+ if (result.Success && result.Results && result.Results.length > 0) {
52
+ // Found database records that reference this record
53
+ for (const dbRecord of result.Results) {
54
+ const refPrimaryKey = this.getPrimaryKeyFromSimpleRecord(dbRecord, refInfo.entityName);
55
+ if (!refPrimaryKey) {
56
+ console.warn(`Cannot get primary key for ${refInfo.entityName} record`);
57
+ continue;
58
+ }
59
+ references.push({
60
+ entityName: refInfo.entityName,
61
+ primaryKey: refPrimaryKey,
62
+ referencingField: refInfo.fieldName,
63
+ referencedEntity: entityName,
64
+ referencedKey: primaryKey,
65
+ existsInMetadata: this.checkIfInMetadata(refInfo.entityName, refPrimaryKey, allMetadataRecords // Check against ALL metadata, not just deletes
66
+ )
67
+ });
68
+ }
69
+ }
70
+ }
71
+ catch (error) {
72
+ console.error(`Error scanning references from ${refInfo.entityName}.${refInfo.fieldName} ` +
73
+ `to ${entityName}:`, error);
74
+ }
75
+ }
76
+ }
77
+ return references;
78
+ }
79
+ /**
80
+ * Extract primary key from a flattened record
81
+ */
82
+ extractPrimaryKey(record) {
83
+ const entityInfo = this.metadata.Entities.find(e => e.Name === record.entityName);
84
+ if (!entityInfo) {
85
+ return null;
86
+ }
87
+ const primaryKeys = entityInfo.PrimaryKeys;
88
+ if (primaryKeys.length === 0) {
89
+ return null;
90
+ }
91
+ // Single primary key
92
+ if (primaryKeys.length === 1) {
93
+ const pkField = primaryKeys[0].Name;
94
+ const pkValue = record.record.primaryKey?.[pkField] || record.record.fields?.[pkField];
95
+ if (!pkValue) {
96
+ return null;
97
+ }
98
+ return core_1.CompositeKey.FromID(pkValue.toString());
99
+ }
100
+ // Composite primary key
101
+ const keyPairs = [];
102
+ for (const pk of primaryKeys) {
103
+ const value = record.record.primaryKey?.[pk.Name] || record.record.fields?.[pk.Name];
104
+ if (!value) {
105
+ return null;
106
+ }
107
+ keyPairs.push({ FieldName: pk.Name, Value: value.toString() });
108
+ }
109
+ return core_1.CompositeKey.FromKeyValuePairs(keyPairs);
110
+ }
111
+ /**
112
+ * Get primary key from a simple record (plain object from RunView with ResultType: 'simple')
113
+ */
114
+ getPrimaryKeyFromSimpleRecord(record, entityName) {
115
+ const entityInfo = this.metadata.Entities.find(e => e.Name === entityName);
116
+ if (!entityInfo) {
117
+ return null;
118
+ }
119
+ const primaryKeys = entityInfo.PrimaryKeys;
120
+ if (primaryKeys.length === 0) {
121
+ return null;
122
+ }
123
+ // Single primary key
124
+ if (primaryKeys.length === 1) {
125
+ const pkField = primaryKeys[0].Name;
126
+ const pkValue = record[pkField];
127
+ if (!pkValue) {
128
+ return null;
129
+ }
130
+ return core_1.CompositeKey.FromID(pkValue.toString());
131
+ }
132
+ // Composite primary key
133
+ const keyPairs = [];
134
+ for (const pk of primaryKeys) {
135
+ const value = record[pk.Name];
136
+ if (!value) {
137
+ return null;
138
+ }
139
+ keyPairs.push({ FieldName: pk.Name, Value: value.toString() });
140
+ }
141
+ return core_1.CompositeKey.FromKeyValuePairs(keyPairs);
142
+ }
143
+ /**
144
+ * Check if a database record exists in metadata files
145
+ */
146
+ checkIfInMetadata(entityName, primaryKey, records) {
147
+ const keyString = primaryKey.ToConcatenatedString();
148
+ for (const record of records) {
149
+ if (record.entityName !== entityName) {
150
+ continue;
151
+ }
152
+ const recordKey = this.extractPrimaryKey(record);
153
+ if (recordKey && recordKey.ToConcatenatedString() === keyString) {
154
+ return true;
155
+ }
156
+ }
157
+ return false;
158
+ }
159
+ /**
160
+ * Get orphaned references (database-only records not in metadata)
161
+ * These will prevent deletion unless handled
162
+ */
163
+ getOrphanedReferences(references) {
164
+ return references.filter(ref => !ref.existsInMetadata);
165
+ }
166
+ /**
167
+ * Get metadata references (records in metadata that reference deletion targets)
168
+ * These should already be marked for deletion if the user set things up correctly
169
+ */
170
+ getMetadataReferences(references) {
171
+ return references.filter(ref => ref.existsInMetadata);
172
+ }
173
+ }
174
+ exports.DatabaseReferenceScanner = DatabaseReferenceScanner;
175
+ //# sourceMappingURL=database-reference-scanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"database-reference-scanner.js","sourceRoot":"","sources":["../../src/lib/database-reference-scanner.ts"],"names":[],"mappings":"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","sourcesContent":["import { Metadata, RunView, UserInfo, CompositeKey } from '@memberjunction/core';\nimport { FlattenedRecord } from './record-dependency-analyzer';\nimport { ReverseFKInfo } from './entity-foreign-key-helper';\n\n/**\n * Represents a database record that references a record being deleted\n */\nexport interface DatabaseReference {\n entityName: string; // Entity containing the referencing record\n primaryKey: CompositeKey; // Primary key of the referencing record\n referencingField: string; // Foreign key field making the reference\n referencedEntity: string; // Entity being referenced\n referencedKey: CompositeKey; // Primary key of referenced record\n existsInMetadata: boolean; // Whether this record exists in metadata files\n}\n\n/**\n * Scans the database for existing records that reference records marked for deletion\n * This helps identify:\n * 1. Database-only records that will prevent deletion\n * 2. Records that should be included in the deletion plan\n */\nexport class DatabaseReferenceScanner {\n constructor(\n private metadata: Metadata,\n private contextUser: UserInfo\n ) {}\n\n /**\n * Scan database for records that reference records marked for deletion\n *\n * @param recordsToDelete Records that will be deleted\n * @param reverseFKMap Map of entity -> entities that reference it\n * @param allMetadataRecords All records from metadata (for checking if DB record exists in metadata)\n * @returns Array of database references found\n */\n async scanForReferences(\n recordsToDelete: FlattenedRecord[],\n reverseFKMap: Map<string, ReverseFKInfo[]>,\n allMetadataRecords: FlattenedRecord[]\n ): Promise<DatabaseReference[]> {\n const references: DatabaseReference[] = [];\n const rv = new RunView();\n\n for (const record of recordsToDelete) {\n const entityName = record.entityName;\n const primaryKey = this.extractPrimaryKey(record);\n\n if (!primaryKey) {\n console.warn(`Cannot scan references for ${entityName}: no primary key found`);\n continue;\n }\n\n // Find all entities that could reference this one\n const referencingEntities = reverseFKMap.get(entityName) || [];\n\n for (const refInfo of referencingEntities) {\n try {\n // Query database for existing references\n // Use the actual value from the primary key, not the concatenated string format\n const pkValue = primaryKey.KeyValuePairs.length === 1\n ? primaryKey.KeyValuePairs[0].Value\n : primaryKey.ToConcatenatedString();\n\n const filter = `${refInfo.fieldName} = '${pkValue}'`;\n\n const result = await rv.RunView({\n EntityName: refInfo.entityName,\n ExtraFilter: filter,\n ResultType: 'simple' // More efficient - we don't need entity objects\n }, this.contextUser);\n\n if (result.Success && result.Results && result.Results.length > 0) {\n // Found database records that reference this record\n for (const dbRecord of result.Results) {\n const refPrimaryKey = this.getPrimaryKeyFromSimpleRecord(\n dbRecord,\n refInfo.entityName\n );\n\n if (!refPrimaryKey) {\n console.warn(`Cannot get primary key for ${refInfo.entityName} record`);\n continue;\n }\n\n references.push({\n entityName: refInfo.entityName,\n primaryKey: refPrimaryKey,\n referencingField: refInfo.fieldName,\n referencedEntity: entityName,\n referencedKey: primaryKey,\n existsInMetadata: this.checkIfInMetadata(\n refInfo.entityName,\n refPrimaryKey,\n allMetadataRecords // Check against ALL metadata, not just deletes\n )\n });\n }\n }\n } catch (error) {\n console.error(\n `Error scanning references from ${refInfo.entityName}.${refInfo.fieldName} ` +\n `to ${entityName}:`,\n error\n );\n }\n }\n }\n\n return references;\n }\n\n /**\n * Extract primary key from a flattened record\n */\n private extractPrimaryKey(record: FlattenedRecord): CompositeKey | null {\n const entityInfo = this.metadata.Entities.find(e => e.Name === record.entityName);\n if (!entityInfo) {\n return null;\n }\n\n const primaryKeys = entityInfo.PrimaryKeys;\n if (primaryKeys.length === 0) {\n return null;\n }\n\n // Single primary key\n if (primaryKeys.length === 1) {\n const pkField = primaryKeys[0].Name;\n const pkValue = record.record.primaryKey?.[pkField] || record.record.fields?.[pkField];\n\n if (!pkValue) {\n return null;\n }\n\n return CompositeKey.FromID(pkValue.toString());\n }\n\n // Composite primary key\n const keyPairs: { FieldName: string; Value: string }[] = [];\n for (const pk of primaryKeys) {\n const value = record.record.primaryKey?.[pk.Name] || record.record.fields?.[pk.Name];\n if (!value) {\n return null;\n }\n keyPairs.push({ FieldName: pk.Name, Value: value.toString() });\n }\n\n return CompositeKey.FromKeyValuePairs(keyPairs);\n }\n\n /**\n * Get primary key from a simple record (plain object from RunView with ResultType: 'simple')\n */\n private getPrimaryKeyFromSimpleRecord(record: Record<string, unknown>, entityName: string): CompositeKey | null {\n const entityInfo = this.metadata.Entities.find(e => e.Name === entityName);\n if (!entityInfo) {\n return null;\n }\n\n const primaryKeys = entityInfo.PrimaryKeys;\n if (primaryKeys.length === 0) {\n return null;\n }\n\n // Single primary key\n if (primaryKeys.length === 1) {\n const pkField = primaryKeys[0].Name;\n const pkValue = record[pkField];\n\n if (!pkValue) {\n return null;\n }\n\n return CompositeKey.FromID(pkValue.toString());\n }\n\n // Composite primary key\n const keyPairs: { FieldName: string; Value: string }[] = [];\n for (const pk of primaryKeys) {\n const value = record[pk.Name];\n if (!value) {\n return null;\n }\n keyPairs.push({ FieldName: pk.Name, Value: value.toString() });\n }\n\n return CompositeKey.FromKeyValuePairs(keyPairs);\n }\n\n /**\n * Check if a database record exists in metadata files\n */\n private checkIfInMetadata(\n entityName: string,\n primaryKey: CompositeKey,\n records: FlattenedRecord[]\n ): boolean {\n const keyString = primaryKey.ToConcatenatedString();\n\n for (const record of records) {\n if (record.entityName !== entityName) {\n continue;\n }\n\n const recordKey = this.extractPrimaryKey(record);\n if (recordKey && recordKey.ToConcatenatedString() === keyString) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Get orphaned references (database-only records not in metadata)\n * These will prevent deletion unless handled\n */\n getOrphanedReferences(references: DatabaseReference[]): DatabaseReference[] {\n return references.filter(ref => !ref.existsInMetadata);\n }\n\n /**\n * Get metadata references (records in metadata that reference deletion targets)\n * These should already be marked for deletion if the user set things up correctly\n */\n getMetadataReferences(references: DatabaseReference[]): DatabaseReference[] {\n return references.filter(ref => ref.existsInMetadata);\n }\n}\n"]}
@@ -0,0 +1,76 @@
1
+ import { Metadata, UserInfo } from '@memberjunction/core';
2
+ import { FlattenedRecord, ReverseDependency } from './record-dependency-analyzer';
3
+ import { DatabaseReference } from './database-reference-scanner';
4
+ /**
5
+ * Complete audit result for deletion operations
6
+ */
7
+ export interface DeletionAudit {
8
+ explicitDeletes: Map<string, FlattenedRecord>;
9
+ implicitDeletes: Map<string, FlattenedRecord>;
10
+ alreadyDeleted: Map<string, FlattenedRecord>;
11
+ databaseOnlyReferences: DatabaseReference[];
12
+ reverseDependencies: Map<string, ReverseDependency[]>;
13
+ deletionLevels: FlattenedRecord[][];
14
+ circularDependencies: string[][];
15
+ orphanedReferences: ReverseDependency[];
16
+ }
17
+ /**
18
+ * Performs comprehensive deletion auditing across all metadata files
19
+ * Identifies all records that need to be deleted, in what order, and potential issues
20
+ */
21
+ export declare class DeletionAuditor {
22
+ private metadata;
23
+ private contextUser;
24
+ constructor(metadata: Metadata, contextUser: UserInfo);
25
+ /**
26
+ * Perform comprehensive deletion audit across all metadata files
27
+ *
28
+ * This analyzes:
29
+ * 1. Which records are explicitly marked for deletion
30
+ * 2. Which records must be implicitly deleted (due to FK constraints)
31
+ * 3. Database-only references that will prevent deletion
32
+ * 4. Correct deletion order (reverse topological sort)
33
+ * 5. Circular dependencies
34
+ *
35
+ * @param allRecords All flattened records from all metadata files
36
+ * @returns Complete deletion audit
37
+ */
38
+ auditDeletions(allRecords: FlattenedRecord[]): Promise<DeletionAudit>;
39
+ /**
40
+ * Find all records marked with delete: true
41
+ */
42
+ private findExplicitDeletes;
43
+ /**
44
+ * Find all records that depend on records being deleted (implicit deletes)
45
+ * Uses BFS to find transitive dependents
46
+ */
47
+ private findImplicitDeletes;
48
+ /**
49
+ * Check which records actually exist in the database
50
+ * Returns records separated into existing (need deletion) and already deleted
51
+ */
52
+ private checkRecordExistence;
53
+ /**
54
+ * Find orphaned references (database records not in metadata files)
55
+ * These will prevent deletion unless handled
56
+ */
57
+ private findOrphanedReferences;
58
+ /**
59
+ * Check for circular dependencies among records to delete
60
+ * This would prevent safe deletion order
61
+ */
62
+ private findCircularDependencies;
63
+ /**
64
+ * Get a display name for a record (for error messages)
65
+ */
66
+ private getRecordDisplayName;
67
+ /**
68
+ * Check if deletion audit has blocking issues
69
+ */
70
+ isValid(audit: DeletionAudit): boolean;
71
+ /**
72
+ * Check if deletion audit requires user confirmation
73
+ * (due to implicit deletes)
74
+ */
75
+ requiresConfirmation(audit: DeletionAudit): boolean;
76
+ }