@memberjunction/archiving-engine 0.0.1 → 5.29.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.
- package/dist/ArchiveEngine.d.ts +73 -0
- package/dist/ArchiveEngine.d.ts.map +1 -0
- package/dist/ArchiveEngine.js +212 -0
- package/dist/ArchiveEngine.js.map +1 -0
- package/dist/ArchiveProcessor.d.ts +78 -0
- package/dist/ArchiveProcessor.d.ts.map +1 -0
- package/dist/ArchiveProcessor.js +222 -0
- package/dist/ArchiveProcessor.js.map +1 -0
- package/dist/ArchiveRecovery.d.ts +47 -0
- package/dist/ArchiveRecovery.d.ts.map +1 -0
- package/dist/ArchiveRecovery.js +125 -0
- package/dist/ArchiveRecovery.js.map +1 -0
- package/dist/ArchiveStorageManager.d.ts +60 -0
- package/dist/ArchiveStorageManager.d.ts.map +1 -0
- package/dist/ArchiveStorageManager.js +101 -0
- package/dist/ArchiveStorageManager.js.map +1 -0
- package/dist/BaseArchiveDriver.d.ts +71 -0
- package/dist/BaseArchiveDriver.d.ts.map +1 -0
- package/dist/BaseArchiveDriver.js +99 -0
- package/dist/BaseArchiveDriver.js.map +1 -0
- package/dist/DefaultArchiveDriver.d.ts +98 -0
- package/dist/DefaultArchiveDriver.d.ts.map +1 -0
- package/dist/DefaultArchiveDriver.js +403 -0
- package/dist/DefaultArchiveDriver.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +140 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +31 -7
- package/README.md +0 -45
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { UserInfo } from '@memberjunction/core';
|
|
2
|
+
import { BaseSingleton } from '@memberjunction/global';
|
|
3
|
+
import { ArchiveRecovery } from './ArchiveRecovery.js';
|
|
4
|
+
import { ArchiveRunResult } from './types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Main orchestrator for archive operations. Singleton that coordinates
|
|
7
|
+
* loading configurations, initializing storage, processing entities,
|
|
8
|
+
* and recording run results.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* ```typescript
|
|
12
|
+
* const result = await ArchiveEngine.Instance.RunArchive(configId, contextUser);
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export declare class ArchiveEngine extends BaseSingleton<ArchiveEngine> {
|
|
16
|
+
/**
|
|
17
|
+
* Returns the global singleton instance of the ArchiveEngine.
|
|
18
|
+
*/
|
|
19
|
+
static get Instance(): ArchiveEngine;
|
|
20
|
+
protected constructor();
|
|
21
|
+
private _recovery;
|
|
22
|
+
/**
|
|
23
|
+
* Provides access to the recovery subsystem for listing and restoring archived versions.
|
|
24
|
+
*/
|
|
25
|
+
get Recovery(): ArchiveRecovery;
|
|
26
|
+
/**
|
|
27
|
+
* Executes a complete archive run for the given configuration.
|
|
28
|
+
*
|
|
29
|
+
* Steps:
|
|
30
|
+
* 1. Validates user authorization
|
|
31
|
+
* 2. Loads the ArchiveConfiguration and its entity configurations
|
|
32
|
+
* 3. Sorts entities for safe processing order (children first for purge safety)
|
|
33
|
+
* 4. Creates an ArchiveRun record
|
|
34
|
+
* 5. Initializes the storage driver
|
|
35
|
+
* 6. Processes each entity via ArchiveProcessor
|
|
36
|
+
* 7. Updates the ArchiveRun with final totals
|
|
37
|
+
*
|
|
38
|
+
* @param configId - ID of the ArchiveConfiguration to execute
|
|
39
|
+
* @param contextUser - User context for authorization and database operations
|
|
40
|
+
* @returns Summary result of the archive run
|
|
41
|
+
*/
|
|
42
|
+
RunArchive(configId: string, contextUser: UserInfo): Promise<ArchiveRunResult>;
|
|
43
|
+
/**
|
|
44
|
+
* Loads the ArchiveConfiguration record by ID.
|
|
45
|
+
*/
|
|
46
|
+
private LoadConfiguration;
|
|
47
|
+
/**
|
|
48
|
+
* Loads all ArchiveConfigurationEntity records for the given configuration,
|
|
49
|
+
* ordered by Priority for controlled execution sequence.
|
|
50
|
+
*/
|
|
51
|
+
private LoadConfigurationEntities;
|
|
52
|
+
/**
|
|
53
|
+
* Creates a new ArchiveRun record to track this execution.
|
|
54
|
+
*/
|
|
55
|
+
private CreateArchiveRun;
|
|
56
|
+
/**
|
|
57
|
+
* Updates the ArchiveRun record with final totals and completion status.
|
|
58
|
+
*/
|
|
59
|
+
private FinalizeArchiveRun;
|
|
60
|
+
/**
|
|
61
|
+
* Initializes the ArchiveStorageManager from the configuration's storage account.
|
|
62
|
+
*/
|
|
63
|
+
private InitializeStorage;
|
|
64
|
+
/**
|
|
65
|
+
* Processes all configured entities sequentially (order matters for dependency safety).
|
|
66
|
+
*/
|
|
67
|
+
private ProcessAllEntities;
|
|
68
|
+
/**
|
|
69
|
+
* Builds a failure ArchiveRunResult with zero counts.
|
|
70
|
+
*/
|
|
71
|
+
private BuildFailureResult;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=ArchiveEngine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ArchiveEngine.d.ts","sourceRoot":"","sources":["../src/ArchiveEngine.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAClH,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAEvD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,EAAE,gBAAgB,EAAuB,MAAM,SAAS,CAAC;AAEhE;;;;;;;;;GASG;AACH,qBAAa,aAAc,SAAQ,aAAa,CAAC,aAAa,CAAC;IAC3D;;OAEG;IACH,WAAkB,QAAQ,IAAI,aAAa,CAE1C;IAED,SAAS;IAIT,OAAO,CAAC,SAAS,CAA0C;IAE3D;;OAEG;IACH,IAAW,QAAQ,IAAI,eAAe,CAErC;IAMD;;;;;;;;;;;;;;;OAeG;IACU,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAkD3F;;OAEG;YACW,iBAAiB;IAO/B;;;OAGG;YACW,yBAAyB;IAoBvC;;OAEG;YACW,gBAAgB;IAkB9B;;OAEG;YACW,kBAAkB;IAuBhC;;OAEG;YACW,iBAAiB;IAe/B;;OAEG;YACW,kBAAkB;IAmChC;;OAEG;IACH,OAAO,CAAC,kBAAkB;CAY7B"}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { CompositeKey, LogError, LogStatus, Metadata, RunView } from '@memberjunction/core';
|
|
2
|
+
import { BaseSingleton } from '@memberjunction/global';
|
|
3
|
+
import { ArchiveProcessor } from './ArchiveProcessor.js';
|
|
4
|
+
import { ArchiveRecovery } from './ArchiveRecovery.js';
|
|
5
|
+
import { ArchiveStorageManager } from './ArchiveStorageManager.js';
|
|
6
|
+
/**
|
|
7
|
+
* Main orchestrator for archive operations. Singleton that coordinates
|
|
8
|
+
* loading configurations, initializing storage, processing entities,
|
|
9
|
+
* and recording run results.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* ```typescript
|
|
13
|
+
* const result = await ArchiveEngine.Instance.RunArchive(configId, contextUser);
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export class ArchiveEngine extends BaseSingleton {
|
|
17
|
+
/**
|
|
18
|
+
* Returns the global singleton instance of the ArchiveEngine.
|
|
19
|
+
*/
|
|
20
|
+
static get Instance() {
|
|
21
|
+
return super.getInstance();
|
|
22
|
+
}
|
|
23
|
+
constructor() {
|
|
24
|
+
super();
|
|
25
|
+
this._recovery = new ArchiveRecovery();
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Provides access to the recovery subsystem for listing and restoring archived versions.
|
|
29
|
+
*/
|
|
30
|
+
get Recovery() {
|
|
31
|
+
return this._recovery;
|
|
32
|
+
}
|
|
33
|
+
// ========================================
|
|
34
|
+
// Main Archive Execution
|
|
35
|
+
// ========================================
|
|
36
|
+
/**
|
|
37
|
+
* Executes a complete archive run for the given configuration.
|
|
38
|
+
*
|
|
39
|
+
* Steps:
|
|
40
|
+
* 1. Validates user authorization
|
|
41
|
+
* 2. Loads the ArchiveConfiguration and its entity configurations
|
|
42
|
+
* 3. Sorts entities for safe processing order (children first for purge safety)
|
|
43
|
+
* 4. Creates an ArchiveRun record
|
|
44
|
+
* 5. Initializes the storage driver
|
|
45
|
+
* 6. Processes each entity via ArchiveProcessor
|
|
46
|
+
* 7. Updates the ArchiveRun with final totals
|
|
47
|
+
*
|
|
48
|
+
* @param configId - ID of the ArchiveConfiguration to execute
|
|
49
|
+
* @param contextUser - User context for authorization and database operations
|
|
50
|
+
* @returns Summary result of the archive run
|
|
51
|
+
*/
|
|
52
|
+
async RunArchive(configId, contextUser) {
|
|
53
|
+
try {
|
|
54
|
+
const config = await this.LoadConfiguration(configId, contextUser);
|
|
55
|
+
if (!config) {
|
|
56
|
+
return this.BuildFailureResult('', `ArchiveConfiguration not found: ${configId}`);
|
|
57
|
+
}
|
|
58
|
+
const isActive = config.Get('IsActive');
|
|
59
|
+
if (!isActive) {
|
|
60
|
+
return this.BuildFailureResult('', `ArchiveConfiguration "${config.Get('Name')}" is not active. Set IsActive to true before running.`);
|
|
61
|
+
}
|
|
62
|
+
const status = config.Get('Status');
|
|
63
|
+
if (status === 'Running') {
|
|
64
|
+
return this.BuildFailureResult('', `ArchiveConfiguration "${config.Get('Name')}" is already running. Wait for the current run to complete.`);
|
|
65
|
+
}
|
|
66
|
+
const configEntities = await this.LoadConfigurationEntities(configId, contextUser);
|
|
67
|
+
if (configEntities.length === 0) {
|
|
68
|
+
return this.BuildFailureResult('', 'No entity configurations found for this archive configuration');
|
|
69
|
+
}
|
|
70
|
+
const archiveRun = await this.CreateArchiveRun(config, contextUser);
|
|
71
|
+
const storageManager = await this.InitializeStorage(config, contextUser);
|
|
72
|
+
LogStatus(`ArchiveEngine: Starting run ${archiveRun.Get('ID')} with ${configEntities.length} entities`);
|
|
73
|
+
const totals = await this.ProcessAllEntities(configEntities, config, archiveRun, storageManager, contextUser);
|
|
74
|
+
await this.FinalizeArchiveRun(archiveRun, totals, contextUser);
|
|
75
|
+
return {
|
|
76
|
+
Success: totals.Failed === 0,
|
|
77
|
+
ArchiveRunId: archiveRun.Get('ID'),
|
|
78
|
+
TotalRecords: totals.Archived + totals.Failed + totals.Skipped,
|
|
79
|
+
ArchivedRecords: totals.Archived,
|
|
80
|
+
FailedRecords: totals.Failed,
|
|
81
|
+
SkippedRecords: totals.Skipped,
|
|
82
|
+
TotalBytesArchived: totals.Bytes,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
87
|
+
LogError(`ArchiveEngine.RunArchive failed: ${message}`);
|
|
88
|
+
return this.BuildFailureResult('', message);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// ========================================
|
|
92
|
+
// Configuration Loading
|
|
93
|
+
// ========================================
|
|
94
|
+
/**
|
|
95
|
+
* Loads the ArchiveConfiguration record by ID.
|
|
96
|
+
*/
|
|
97
|
+
async LoadConfiguration(configId, contextUser) {
|
|
98
|
+
const md = new Metadata();
|
|
99
|
+
const config = await md.GetEntityObject('MJ: Archive Configurations', contextUser);
|
|
100
|
+
const loaded = await config.InnerLoad(CompositeKey.FromKeyValuePair('ID', configId));
|
|
101
|
+
return loaded ? config : null;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Loads all ArchiveConfigurationEntity records for the given configuration,
|
|
105
|
+
* ordered by Priority for controlled execution sequence.
|
|
106
|
+
*/
|
|
107
|
+
async LoadConfigurationEntities(configId, contextUser) {
|
|
108
|
+
const rv = new RunView();
|
|
109
|
+
const result = await rv.RunView({
|
|
110
|
+
EntityName: 'MJ: Archive Configuration Entities',
|
|
111
|
+
ExtraFilter: `ArchiveConfigurationID='${configId}' AND IsActive=1`,
|
|
112
|
+
OrderBy: 'Priority ASC',
|
|
113
|
+
ResultType: 'entity_object',
|
|
114
|
+
}, contextUser);
|
|
115
|
+
if (!result.Success) {
|
|
116
|
+
throw new Error(`Failed to load configuration entities: ${result.ErrorMessage}`);
|
|
117
|
+
}
|
|
118
|
+
return result.Results;
|
|
119
|
+
}
|
|
120
|
+
// ========================================
|
|
121
|
+
// Archive Run Lifecycle
|
|
122
|
+
// ========================================
|
|
123
|
+
/**
|
|
124
|
+
* Creates a new ArchiveRun record to track this execution.
|
|
125
|
+
*/
|
|
126
|
+
async CreateArchiveRun(config, contextUser) {
|
|
127
|
+
const md = new Metadata();
|
|
128
|
+
const run = await md.GetEntityObject('MJ: Archive Runs', contextUser);
|
|
129
|
+
run.Set('ArchiveConfigurationID', config.Get('ID'));
|
|
130
|
+
run.Set('UserID', contextUser.ID);
|
|
131
|
+
run.Set('Status', 'Running');
|
|
132
|
+
run.Set('StartedAt', new Date().toISOString());
|
|
133
|
+
const saved = await run.Save();
|
|
134
|
+
if (!saved) {
|
|
135
|
+
throw new Error(`Failed to create ArchiveRun record: ${run.LatestResult?.Message ?? 'Unknown error'}`);
|
|
136
|
+
}
|
|
137
|
+
LogStatus(`ArchiveEngine: Created ArchiveRun ${run.Get('ID')}`);
|
|
138
|
+
return run;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Updates the ArchiveRun record with final totals and completion status.
|
|
142
|
+
*/
|
|
143
|
+
async FinalizeArchiveRun(archiveRun, totals, contextUser) {
|
|
144
|
+
archiveRun.Set('Status', totals.Failed > 0 ? 'PartialSuccess' : 'Complete');
|
|
145
|
+
archiveRun.Set('CompletedAt', new Date().toISOString());
|
|
146
|
+
archiveRun.Set('TotalRecords', totals.Archived + totals.Failed + totals.Skipped);
|
|
147
|
+
archiveRun.Set('ArchivedRecords', totals.Archived);
|
|
148
|
+
archiveRun.Set('FailedRecords', totals.Failed);
|
|
149
|
+
archiveRun.Set('SkippedRecords', totals.Skipped);
|
|
150
|
+
archiveRun.Set('TotalBytesArchived', totals.Bytes);
|
|
151
|
+
const saved = await archiveRun.Save();
|
|
152
|
+
if (!saved) {
|
|
153
|
+
LogError(`Failed to finalize ArchiveRun ${archiveRun.Get('ID')}: ${archiveRun.LatestResult?.Message ?? 'Unknown error'}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// ========================================
|
|
157
|
+
// Storage Initialization
|
|
158
|
+
// ========================================
|
|
159
|
+
/**
|
|
160
|
+
* Initializes the ArchiveStorageManager from the configuration's storage account.
|
|
161
|
+
*/
|
|
162
|
+
async InitializeStorage(config, contextUser) {
|
|
163
|
+
const storageAccountId = config.Get('StorageAccountID');
|
|
164
|
+
if (!storageAccountId) {
|
|
165
|
+
throw new Error('ArchiveConfiguration has no StorageAccountID configured');
|
|
166
|
+
}
|
|
167
|
+
const manager = new ArchiveStorageManager();
|
|
168
|
+
await manager.Initialize(storageAccountId, contextUser);
|
|
169
|
+
return manager;
|
|
170
|
+
}
|
|
171
|
+
// ========================================
|
|
172
|
+
// Entity Processing
|
|
173
|
+
// ========================================
|
|
174
|
+
/**
|
|
175
|
+
* Processes all configured entities sequentially (order matters for dependency safety).
|
|
176
|
+
*/
|
|
177
|
+
async ProcessAllEntities(configEntities, config, archiveRun, storageManager, contextUser) {
|
|
178
|
+
const totals = { Archived: 0, Failed: 0, Skipped: 0, Bytes: 0 };
|
|
179
|
+
for (const configEntity of configEntities) {
|
|
180
|
+
const processor = new ArchiveProcessor();
|
|
181
|
+
const entityResult = await processor.ProcessEntity(configEntity, config, archiveRun, storageManager, contextUser);
|
|
182
|
+
totals.Archived += entityResult.Archived;
|
|
183
|
+
totals.Failed += entityResult.Failed;
|
|
184
|
+
totals.Skipped += entityResult.Skipped;
|
|
185
|
+
totals.Bytes += entityResult.Bytes;
|
|
186
|
+
const entityName = configEntity.Get('Entity');
|
|
187
|
+
LogStatus(`ArchiveEngine: Entity "${entityName}" complete - ` +
|
|
188
|
+
`archived: ${entityResult.Archived}, failed: ${entityResult.Failed}, ` +
|
|
189
|
+
`skipped: ${entityResult.Skipped}, bytes: ${entityResult.Bytes}`);
|
|
190
|
+
}
|
|
191
|
+
return totals;
|
|
192
|
+
}
|
|
193
|
+
// ========================================
|
|
194
|
+
// Helpers
|
|
195
|
+
// ========================================
|
|
196
|
+
/**
|
|
197
|
+
* Builds a failure ArchiveRunResult with zero counts.
|
|
198
|
+
*/
|
|
199
|
+
BuildFailureResult(archiveRunId, errorMessage) {
|
|
200
|
+
return {
|
|
201
|
+
Success: false,
|
|
202
|
+
ArchiveRunId: archiveRunId,
|
|
203
|
+
TotalRecords: 0,
|
|
204
|
+
ArchivedRecords: 0,
|
|
205
|
+
FailedRecords: 0,
|
|
206
|
+
SkippedRecords: 0,
|
|
207
|
+
TotalBytesArchived: 0,
|
|
208
|
+
ErrorMessage: errorMessage,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
//# sourceMappingURL=ArchiveEngine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ArchiveEngine.js","sourceRoot":"","sources":["../src/ArchiveEngine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { BaseEntity, UserInfo } from '@memberjunction/core';
|
|
2
|
+
import { ArchiveStorageManager } from './ArchiveStorageManager.js';
|
|
3
|
+
/**
|
|
4
|
+
* Result of processing a single entity within an archive run.
|
|
5
|
+
*/
|
|
6
|
+
export interface EntityProcessingResult {
|
|
7
|
+
/** Number of records successfully archived */
|
|
8
|
+
Archived: number;
|
|
9
|
+
/** Number of records that failed to archive */
|
|
10
|
+
Failed: number;
|
|
11
|
+
/** Number of records intentionally skipped */
|
|
12
|
+
Skipped: number;
|
|
13
|
+
/** Total bytes written to storage for this entity */
|
|
14
|
+
Bytes: number;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Handles batch processing of records for a single entity within an archive run.
|
|
18
|
+
* Resolves the appropriate driver, queries eligible records, and processes them
|
|
19
|
+
* in configurable batch sizes.
|
|
20
|
+
*/
|
|
21
|
+
export declare class ArchiveProcessor {
|
|
22
|
+
/**
|
|
23
|
+
* Processes all eligible records for a single entity configuration.
|
|
24
|
+
*
|
|
25
|
+
* @param configEntity - The ArchiveConfigurationEntity record defining which entity/fields to archive
|
|
26
|
+
* @param config - The parent ArchiveConfiguration record
|
|
27
|
+
* @param archiveRun - The current ArchiveRun record for logging
|
|
28
|
+
* @param storageManager - Initialized storage manager for writing documents
|
|
29
|
+
* @param contextUser - User context for all database operations
|
|
30
|
+
* @returns Aggregated counts and bytes for this entity
|
|
31
|
+
*/
|
|
32
|
+
ProcessEntity(configEntity: BaseEntity, config: BaseEntity, archiveRun: BaseEntity, storageManager: ArchiveStorageManager, contextUser: UserInfo): Promise<EntityProcessingResult>;
|
|
33
|
+
/**
|
|
34
|
+
* Resolves the archive driver by class name via ClassFactory, falling back
|
|
35
|
+
* to DefaultArchiveDriver if no class name is specified or resolution fails.
|
|
36
|
+
*/
|
|
37
|
+
private ResolveDriver;
|
|
38
|
+
/**
|
|
39
|
+
* Parses the FieldConfiguration JSON column from the ArchiveConfigurationEntity record.
|
|
40
|
+
* The FieldConfiguration column is NVARCHAR(MAX) storing JSON conforming to ArchiveFieldConfiguration.
|
|
41
|
+
*/
|
|
42
|
+
private ParseFieldConfiguration;
|
|
43
|
+
/**
|
|
44
|
+
* Validates the parsed field configuration has the required structure.
|
|
45
|
+
*/
|
|
46
|
+
private ValidateFieldConfiguration;
|
|
47
|
+
/**
|
|
48
|
+
* Builds the SQL filter expression for querying eligible records.
|
|
49
|
+
* Combines the retention date filter with any custom filter expression.
|
|
50
|
+
*/
|
|
51
|
+
private BuildRecordFilter;
|
|
52
|
+
/**
|
|
53
|
+
* Loads records matching the archive filter from the database.
|
|
54
|
+
*/
|
|
55
|
+
private LoadEligibleRecords;
|
|
56
|
+
/**
|
|
57
|
+
* Determines the batch size from the entity config or parent config defaults.
|
|
58
|
+
*/
|
|
59
|
+
private GetBatchSize;
|
|
60
|
+
/**
|
|
61
|
+
* Processes records in batches, calling the driver for each record and logging results.
|
|
62
|
+
*/
|
|
63
|
+
private ProcessRecordBatches;
|
|
64
|
+
/**
|
|
65
|
+
* Processes a single batch of records.
|
|
66
|
+
*/
|
|
67
|
+
private ProcessSingleBatch;
|
|
68
|
+
/**
|
|
69
|
+
* Processes a single record: checks eligibility, archives, and logs the detail.
|
|
70
|
+
* Returns 'skipped', 'failed', or the number of bytes archived.
|
|
71
|
+
*/
|
|
72
|
+
private ProcessSingleRecord;
|
|
73
|
+
/**
|
|
74
|
+
* Creates an ArchiveRunDetail record to log the result of archiving a single record.
|
|
75
|
+
*/
|
|
76
|
+
private LogArchiveRunDetail;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=ArchiveProcessor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ArchiveProcessor.d.ts","sourceRoot":"","sources":["../src/ArchiveProcessor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAA0C,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAIpG,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAGhE;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACnC,8CAA8C;IAC9C,QAAQ,EAAE,MAAM,CAAC;IACjB,+CAA+C;IAC/C,MAAM,EAAE,MAAM,CAAC;IACf,8CAA8C;IAC9C,OAAO,EAAE,MAAM,CAAC;IAChB,qDAAqD;IACrD,KAAK,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,qBAAa,gBAAgB;IACzB;;;;;;;;;OASG;IACU,aAAa,CACtB,YAAY,EAAE,UAAU,EACxB,MAAM,EAAE,UAAU,EAClB,UAAU,EAAE,UAAU,EACtB,cAAc,EAAE,qBAAqB,EACrC,WAAW,EAAE,QAAQ,GACtB,OAAO,CAAC,sBAAsB,CAAC;IAyBlC;;;OAGG;IACH,OAAO,CAAC,aAAa;IAkBrB;;;OAGG;IACH,OAAO,CAAC,uBAAuB;IAgB/B;;OAEG;IACH,OAAO,CAAC,0BAA0B;IAqBlC;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAkBzB;;OAEG;YACW,mBAAmB;IAmBjC;;OAEG;IACH,OAAO,CAAC,YAAY;IAMpB;;OAEG;YACW,oBAAoB;IA6BlC;;OAEG;YACW,kBAAkB;IA+BhC;;;OAGG;YACW,mBAAmB;IAqCjC;;OAEG;YACW,mBAAmB;CA2BpC"}
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { LogError, LogStatus, Metadata, RunView } from '@memberjunction/core';
|
|
2
|
+
import { MJGlobal } from '@memberjunction/global';
|
|
3
|
+
import { BaseArchiveDriver } from './BaseArchiveDriver.js';
|
|
4
|
+
import { DefaultArchiveDriver } from './DefaultArchiveDriver.js';
|
|
5
|
+
/**
|
|
6
|
+
* Handles batch processing of records for a single entity within an archive run.
|
|
7
|
+
* Resolves the appropriate driver, queries eligible records, and processes them
|
|
8
|
+
* in configurable batch sizes.
|
|
9
|
+
*/
|
|
10
|
+
export class ArchiveProcessor {
|
|
11
|
+
/**
|
|
12
|
+
* Processes all eligible records for a single entity configuration.
|
|
13
|
+
*
|
|
14
|
+
* @param configEntity - The ArchiveConfigurationEntity record defining which entity/fields to archive
|
|
15
|
+
* @param config - The parent ArchiveConfiguration record
|
|
16
|
+
* @param archiveRun - The current ArchiveRun record for logging
|
|
17
|
+
* @param storageManager - Initialized storage manager for writing documents
|
|
18
|
+
* @param contextUser - User context for all database operations
|
|
19
|
+
* @returns Aggregated counts and bytes for this entity
|
|
20
|
+
*/
|
|
21
|
+
async ProcessEntity(configEntity, config, archiveRun, storageManager, contextUser) {
|
|
22
|
+
const entityName = configEntity.Get('Entity');
|
|
23
|
+
const driverClassName = configEntity.Get('DriverClass');
|
|
24
|
+
const batchSize = this.GetBatchSize(configEntity, config);
|
|
25
|
+
const basePath = config.Get('RootPath') ?? '';
|
|
26
|
+
LogStatus(`ArchiveProcessor: Starting processing for entity "${entityName}" (batch size: ${batchSize})`);
|
|
27
|
+
const driver = this.ResolveDriver(driverClassName);
|
|
28
|
+
const fieldConfig = this.ParseFieldConfiguration(configEntity);
|
|
29
|
+
const filter = this.BuildRecordFilter(configEntity);
|
|
30
|
+
const records = await this.LoadEligibleRecords(entityName, filter, contextUser);
|
|
31
|
+
LogStatus(`ArchiveProcessor: Found ${records.length} eligible records for "${entityName}"`);
|
|
32
|
+
return this.ProcessRecordBatches(records, driver, fieldConfig, configEntity, config, archiveRun, storageManager, basePath, batchSize, contextUser);
|
|
33
|
+
}
|
|
34
|
+
// ========================================
|
|
35
|
+
// Driver Resolution
|
|
36
|
+
// ========================================
|
|
37
|
+
/**
|
|
38
|
+
* Resolves the archive driver by class name via ClassFactory, falling back
|
|
39
|
+
* to DefaultArchiveDriver if no class name is specified or resolution fails.
|
|
40
|
+
*/
|
|
41
|
+
ResolveDriver(driverClassName) {
|
|
42
|
+
if (driverClassName) {
|
|
43
|
+
const driver = MJGlobal.Instance.ClassFactory.CreateInstance(BaseArchiveDriver, driverClassName);
|
|
44
|
+
if (driver) {
|
|
45
|
+
return driver;
|
|
46
|
+
}
|
|
47
|
+
LogError(`Failed to resolve archive driver "${driverClassName}", falling back to DefaultArchiveDriver`);
|
|
48
|
+
}
|
|
49
|
+
return new DefaultArchiveDriver();
|
|
50
|
+
}
|
|
51
|
+
// ========================================
|
|
52
|
+
// Field Configuration
|
|
53
|
+
// ========================================
|
|
54
|
+
/**
|
|
55
|
+
* Parses the FieldConfiguration JSON column from the ArchiveConfigurationEntity record.
|
|
56
|
+
* The FieldConfiguration column is NVARCHAR(MAX) storing JSON conforming to ArchiveFieldConfiguration.
|
|
57
|
+
*/
|
|
58
|
+
ParseFieldConfiguration(configEntity) {
|
|
59
|
+
const fieldConfigJson = configEntity.Get('FieldConfiguration');
|
|
60
|
+
if (!fieldConfigJson) {
|
|
61
|
+
throw new Error(`ArchiveConfigurationEntity ${configEntity.Get('ID')} has no FieldConfiguration`);
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const parsed = JSON.parse(fieldConfigJson);
|
|
65
|
+
this.ValidateFieldConfiguration(parsed);
|
|
66
|
+
return parsed;
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
70
|
+
throw new Error(`Invalid FieldConfiguration JSON on ArchiveConfigurationEntity ${configEntity.Get('ID')}: ${message}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Validates the parsed field configuration has the required structure.
|
|
75
|
+
*/
|
|
76
|
+
ValidateFieldConfiguration(config) {
|
|
77
|
+
if (!config.Fields || !Array.isArray(config.Fields)) {
|
|
78
|
+
throw new Error('FieldConfiguration must have a Fields array');
|
|
79
|
+
}
|
|
80
|
+
// Empty Fields array is only valid when ArchiveFullRecord is true
|
|
81
|
+
if (config.Fields.length === 0 && !config.ArchiveFullRecord) {
|
|
82
|
+
throw new Error('FieldConfiguration must have at least one field in the Fields array, or set ArchiveFullRecord to true');
|
|
83
|
+
}
|
|
84
|
+
for (const field of config.Fields) {
|
|
85
|
+
if (!field.FieldName || typeof field.FieldName !== 'string') {
|
|
86
|
+
throw new Error('Each field in FieldConfiguration.Fields must have a non-empty FieldName');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// ========================================
|
|
91
|
+
// Record Querying
|
|
92
|
+
// ========================================
|
|
93
|
+
/**
|
|
94
|
+
* Builds the SQL filter expression for querying eligible records.
|
|
95
|
+
* Combines the retention date filter with any custom filter expression.
|
|
96
|
+
*/
|
|
97
|
+
BuildRecordFilter(configEntity) {
|
|
98
|
+
const retentionDays = configEntity.Get('RetentionDays');
|
|
99
|
+
const dateField = configEntity.Get('DateField');
|
|
100
|
+
const customFilter = configEntity.Get('FilterExpression');
|
|
101
|
+
const filterParts = [];
|
|
102
|
+
if (retentionDays != null && dateField) {
|
|
103
|
+
filterParts.push(`${dateField} < DATEADD(day, -${retentionDays}, GETUTCDATE())`);
|
|
104
|
+
}
|
|
105
|
+
if (customFilter) {
|
|
106
|
+
filterParts.push(`(${customFilter})`);
|
|
107
|
+
}
|
|
108
|
+
return filterParts.length > 0 ? filterParts.join(' AND ') : '';
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Loads records matching the archive filter from the database.
|
|
112
|
+
*/
|
|
113
|
+
async LoadEligibleRecords(entityName, filter, contextUser) {
|
|
114
|
+
const rv = new RunView();
|
|
115
|
+
const result = await rv.RunView({
|
|
116
|
+
EntityName: entityName,
|
|
117
|
+
ExtraFilter: filter || undefined,
|
|
118
|
+
ResultType: 'entity_object',
|
|
119
|
+
}, contextUser);
|
|
120
|
+
if (!result.Success) {
|
|
121
|
+
throw new Error(`Failed to load records for entity "${entityName}": ${result.ErrorMessage}`);
|
|
122
|
+
}
|
|
123
|
+
return result.Results;
|
|
124
|
+
}
|
|
125
|
+
// ========================================
|
|
126
|
+
// Batch Processing
|
|
127
|
+
// ========================================
|
|
128
|
+
/**
|
|
129
|
+
* Determines the batch size from the entity config or parent config defaults.
|
|
130
|
+
*/
|
|
131
|
+
GetBatchSize(configEntity, config) {
|
|
132
|
+
const entityBatchSize = configEntity.Get('BatchSize');
|
|
133
|
+
const defaultBatchSize = config.Get('DefaultBatchSize');
|
|
134
|
+
return entityBatchSize ?? defaultBatchSize ?? 100;
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Processes records in batches, calling the driver for each record and logging results.
|
|
138
|
+
*/
|
|
139
|
+
async ProcessRecordBatches(records, driver, fieldConfig, configEntity, config, archiveRun, storageManager, basePath, batchSize, contextUser) {
|
|
140
|
+
const totals = { Archived: 0, Failed: 0, Skipped: 0, Bytes: 0 };
|
|
141
|
+
for (let offset = 0; offset < records.length; offset += batchSize) {
|
|
142
|
+
const batch = records.slice(offset, offset + batchSize);
|
|
143
|
+
const batchResult = await this.ProcessSingleBatch(batch, driver, fieldConfig, configEntity, config, archiveRun, storageManager, basePath, contextUser);
|
|
144
|
+
totals.Archived += batchResult.Archived;
|
|
145
|
+
totals.Failed += batchResult.Failed;
|
|
146
|
+
totals.Skipped += batchResult.Skipped;
|
|
147
|
+
totals.Bytes += batchResult.Bytes;
|
|
148
|
+
}
|
|
149
|
+
return totals;
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Processes a single batch of records.
|
|
153
|
+
*/
|
|
154
|
+
async ProcessSingleBatch(batch, driver, fieldConfig, configEntity, config, archiveRun, storageManager, basePath, contextUser) {
|
|
155
|
+
const result = { Archived: 0, Failed: 0, Skipped: 0, Bytes: 0 };
|
|
156
|
+
for (const record of batch) {
|
|
157
|
+
const recordResult = await this.ProcessSingleRecord(record, driver, fieldConfig, configEntity, config, archiveRun, storageManager, basePath, contextUser);
|
|
158
|
+
if (recordResult === 'skipped') {
|
|
159
|
+
result.Skipped++;
|
|
160
|
+
}
|
|
161
|
+
else if (recordResult === 'failed') {
|
|
162
|
+
result.Failed++;
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
result.Archived++;
|
|
166
|
+
result.Bytes += recordResult;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Processes a single record: checks eligibility, archives, and logs the detail.
|
|
173
|
+
* Returns 'skipped', 'failed', or the number of bytes archived.
|
|
174
|
+
*/
|
|
175
|
+
async ProcessSingleRecord(record, driver, fieldConfig, configEntity, config, archiveRun, storageManager, basePath, contextUser) {
|
|
176
|
+
const context = {
|
|
177
|
+
Record: record,
|
|
178
|
+
FieldConfig: fieldConfig,
|
|
179
|
+
ConfigEntity: configEntity,
|
|
180
|
+
Config: config,
|
|
181
|
+
StorageDriver: storageManager.Driver,
|
|
182
|
+
BasePath: basePath,
|
|
183
|
+
ContextUser: contextUser,
|
|
184
|
+
ArchiveRun: archiveRun,
|
|
185
|
+
};
|
|
186
|
+
if (!driver.ShouldArchiveRecord(context)) {
|
|
187
|
+
return 'skipped';
|
|
188
|
+
}
|
|
189
|
+
const archiveResult = await driver.ArchiveRecord(context);
|
|
190
|
+
await this.LogArchiveRunDetail(archiveRun, record, archiveResult, contextUser);
|
|
191
|
+
if (!archiveResult.Success) {
|
|
192
|
+
LogError(`Failed to archive record ${record.PrimaryKey.Values()} of "${record.EntityInfo.Name}": ${archiveResult.ErrorMessage}`);
|
|
193
|
+
return 'failed';
|
|
194
|
+
}
|
|
195
|
+
return archiveResult.BytesArchived;
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Creates an ArchiveRunDetail record to log the result of archiving a single record.
|
|
199
|
+
*/
|
|
200
|
+
async LogArchiveRunDetail(archiveRun, record, archiveResult, contextUser) {
|
|
201
|
+
try {
|
|
202
|
+
const md = new Metadata();
|
|
203
|
+
const detail = await md.GetEntityObject('MJ: Archive Run Details', contextUser);
|
|
204
|
+
detail.Set('ArchiveRunID', archiveRun.Get('ID'));
|
|
205
|
+
detail.Set('EntityID', record.EntityInfo.ID);
|
|
206
|
+
detail.Set('RecordID', record.PrimaryKey.Values());
|
|
207
|
+
detail.Set('Status', archiveResult.Success ? 'Success' : 'Failed');
|
|
208
|
+
detail.Set('StoragePath', archiveResult.StoragePath ?? '');
|
|
209
|
+
detail.Set('BytesArchived', archiveResult.BytesArchived);
|
|
210
|
+
detail.Set('ErrorMessage', archiveResult.ErrorMessage ?? null);
|
|
211
|
+
const saved = await detail.Save();
|
|
212
|
+
if (!saved) {
|
|
213
|
+
LogError(`Failed to save ArchiveRunDetail for record ${record.PrimaryKey.Values()}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
218
|
+
LogError(`Failed to log ArchiveRunDetail: ${message}`);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
//# sourceMappingURL=ArchiveProcessor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ArchiveProcessor.js","sourceRoot":"","sources":["../src/ArchiveProcessor.ts"],"names":[],"mappings":"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"}
|