newo 3.3.3 → 3.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/dist/api.d.ts +6 -1
- package/dist/api.js +63 -1
- package/dist/application/migration/MigrationEngine.d.ts +141 -0
- package/dist/application/migration/MigrationEngine.js +322 -0
- package/dist/application/migration/index.d.ts +5 -0
- package/dist/application/migration/index.js +5 -0
- package/dist/application/sync/SyncEngine.d.ts +134 -0
- package/dist/application/sync/SyncEngine.js +335 -0
- package/dist/application/sync/index.d.ts +5 -0
- package/dist/application/sync/index.js +5 -0
- package/dist/cli/commands/add-project.d.ts +3 -0
- package/dist/cli/commands/add-project.js +136 -0
- package/dist/cli/commands/create-customer.d.ts +3 -0
- package/dist/cli/commands/create-customer.js +159 -0
- package/dist/cli/commands/diff.d.ts +6 -0
- package/dist/cli/commands/diff.js +288 -0
- package/dist/cli/commands/help.js +75 -4
- package/dist/cli/commands/list-registries.d.ts +3 -0
- package/dist/cli/commands/list-registries.js +39 -0
- package/dist/cli/commands/list-registry-items.d.ts +3 -0
- package/dist/cli/commands/list-registry-items.js +112 -0
- package/dist/cli/commands/logs.d.ts +18 -0
- package/dist/cli/commands/logs.js +283 -0
- package/dist/cli/commands/pull.js +114 -10
- package/dist/cli/commands/push.js +122 -12
- package/dist/cli/commands/watch.d.ts +6 -0
- package/dist/cli/commands/watch.js +195 -0
- package/dist/cli-new/bootstrap.d.ts +74 -0
- package/dist/cli-new/bootstrap.js +154 -0
- package/dist/cli-new/di/Container.d.ts +64 -0
- package/dist/cli-new/di/Container.js +122 -0
- package/dist/cli-new/di/tokens.d.ts +77 -0
- package/dist/cli-new/di/tokens.js +76 -0
- package/dist/cli.js +28 -0
- package/dist/domain/resources/common/types.d.ts +71 -0
- package/dist/domain/resources/common/types.js +42 -0
- package/dist/domain/strategies/sync/AkbSyncStrategy.d.ts +63 -0
- package/dist/domain/strategies/sync/AkbSyncStrategy.js +274 -0
- package/dist/domain/strategies/sync/AttributeSyncStrategy.d.ts +87 -0
- package/dist/domain/strategies/sync/AttributeSyncStrategy.js +378 -0
- package/dist/domain/strategies/sync/ConversationSyncStrategy.d.ts +61 -0
- package/dist/domain/strategies/sync/ConversationSyncStrategy.js +232 -0
- package/dist/domain/strategies/sync/ISyncStrategy.d.ts +149 -0
- package/dist/domain/strategies/sync/ISyncStrategy.js +24 -0
- package/dist/domain/strategies/sync/IntegrationSyncStrategy.d.ts +68 -0
- package/dist/domain/strategies/sync/IntegrationSyncStrategy.js +413 -0
- package/dist/domain/strategies/sync/ProjectSyncStrategy.d.ts +111 -0
- package/dist/domain/strategies/sync/ProjectSyncStrategy.js +523 -0
- package/dist/domain/strategies/sync/index.d.ts +13 -0
- package/dist/domain/strategies/sync/index.js +19 -0
- package/dist/sync/migrate.js +99 -23
- package/dist/types.d.ts +162 -0
- package/package.json +3 -1
- package/src/api.ts +77 -2
- package/src/application/migration/MigrationEngine.ts +492 -0
- package/src/application/migration/index.ts +5 -0
- package/src/application/sync/SyncEngine.ts +467 -0
- package/src/application/sync/index.ts +5 -0
- package/src/cli/commands/add-project.ts +159 -0
- package/src/cli/commands/create-customer.ts +185 -0
- package/src/cli/commands/diff.ts +360 -0
- package/src/cli/commands/help.ts +75 -4
- package/src/cli/commands/list-registries.ts +53 -0
- package/src/cli/commands/list-registry-items.ts +149 -0
- package/src/cli/commands/logs.ts +329 -0
- package/src/cli/commands/pull.ts +128 -11
- package/src/cli/commands/push.ts +131 -13
- package/src/cli/commands/watch.ts +227 -0
- package/src/cli-new/bootstrap.ts +252 -0
- package/src/cli-new/di/Container.ts +152 -0
- package/src/cli-new/di/tokens.ts +105 -0
- package/src/cli.ts +35 -0
- package/src/domain/resources/common/types.ts +106 -0
- package/src/domain/strategies/sync/AkbSyncStrategy.ts +358 -0
- package/src/domain/strategies/sync/AttributeSyncStrategy.ts +508 -0
- package/src/domain/strategies/sync/ConversationSyncStrategy.ts +299 -0
- package/src/domain/strategies/sync/ISyncStrategy.ts +182 -0
- package/src/domain/strategies/sync/IntegrationSyncStrategy.ts +522 -0
- package/src/domain/strategies/sync/ProjectSyncStrategy.ts +747 -0
- package/src/domain/strategies/sync/index.ts +46 -0
- package/src/sync/migrate.ts +103 -24
- package/src/types.ts +178 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ConversationSyncStrategy - Handles synchronization of Conversation history
|
|
3
|
+
*
|
|
4
|
+
* This strategy implements ISyncStrategy for the Conversations resource.
|
|
5
|
+
* Note: This is a pull-only strategy as conversations are read-only.
|
|
6
|
+
*
|
|
7
|
+
* Key responsibilities:
|
|
8
|
+
* - Pull conversation history from NEWO platform
|
|
9
|
+
* - Process user personas and their acts
|
|
10
|
+
* - Save conversations to YAML format
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type {
|
|
14
|
+
ISyncStrategy,
|
|
15
|
+
PullOptions,
|
|
16
|
+
PullResult,
|
|
17
|
+
PushResult,
|
|
18
|
+
ChangeItem,
|
|
19
|
+
ValidationResult,
|
|
20
|
+
StatusSummary
|
|
21
|
+
} from './ISyncStrategy.js';
|
|
22
|
+
import type { CustomerConfig, ILogger, HashStore } from '../../resources/common/types.js';
|
|
23
|
+
import type { AxiosInstance } from 'axios';
|
|
24
|
+
import type {
|
|
25
|
+
UserPersona,
|
|
26
|
+
ConversationAct,
|
|
27
|
+
ProcessedPersona,
|
|
28
|
+
ProcessedAct
|
|
29
|
+
} from '../../../types.js';
|
|
30
|
+
import fs from 'fs-extra';
|
|
31
|
+
import yaml from 'js-yaml';
|
|
32
|
+
import path from 'path';
|
|
33
|
+
import pLimit from 'p-limit';
|
|
34
|
+
import { listUserPersonas, getChatHistory } from '../../../api.js';
|
|
35
|
+
import { sha256, saveHashes, loadHashes } from '../../../hash.js';
|
|
36
|
+
|
|
37
|
+
// Concurrency limit for API calls
|
|
38
|
+
const concurrencyLimit = pLimit(5);
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Local conversation data for storage
|
|
42
|
+
*/
|
|
43
|
+
export interface LocalConversationData {
|
|
44
|
+
personas: ProcessedPersona[];
|
|
45
|
+
totalActs: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* API client factory type
|
|
50
|
+
*/
|
|
51
|
+
export type ApiClientFactory = (customer: CustomerConfig, verbose: boolean) => Promise<AxiosInstance>;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* ConversationSyncStrategy - Handles conversation synchronization
|
|
55
|
+
*/
|
|
56
|
+
export class ConversationSyncStrategy implements ISyncStrategy<UserPersona, LocalConversationData> {
|
|
57
|
+
readonly resourceType = 'conversations';
|
|
58
|
+
readonly displayName = 'Conversations';
|
|
59
|
+
|
|
60
|
+
constructor(
|
|
61
|
+
private apiClientFactory: ApiClientFactory,
|
|
62
|
+
private logger: ILogger
|
|
63
|
+
) {}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Pull all conversations from NEWO platform
|
|
67
|
+
*/
|
|
68
|
+
async pull(customer: CustomerConfig, options: PullOptions = {}): Promise<PullResult<LocalConversationData>> {
|
|
69
|
+
const client = await this.apiClientFactory(customer, options.verbose ?? false);
|
|
70
|
+
const hashes: HashStore = {};
|
|
71
|
+
|
|
72
|
+
this.logger.verbose(`💬 Fetching conversations for ${customer.idn}...`);
|
|
73
|
+
|
|
74
|
+
const customerDir = path.join(process.cwd(), 'newo_customers', customer.idn);
|
|
75
|
+
await fs.ensureDir(customerDir);
|
|
76
|
+
|
|
77
|
+
// Get all user personas with pagination
|
|
78
|
+
const allPersonas: UserPersona[] = [];
|
|
79
|
+
let page = 1;
|
|
80
|
+
const perPage = 50;
|
|
81
|
+
let hasMore = true;
|
|
82
|
+
|
|
83
|
+
while (hasMore) {
|
|
84
|
+
const response = await listUserPersonas(client, page, perPage);
|
|
85
|
+
allPersonas.push(...response.items);
|
|
86
|
+
|
|
87
|
+
this.logger.verbose(` 📋 Page ${page}: Found ${response.items.length} personas (${allPersonas.length}/${response.metadata.total} total)`);
|
|
88
|
+
|
|
89
|
+
hasMore = response.items.length === perPage && allPersonas.length < response.metadata.total;
|
|
90
|
+
page++;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
this.logger.verbose(`👥 Processing ${allPersonas.length} personas...`);
|
|
94
|
+
|
|
95
|
+
// Process personas concurrently with limited concurrency
|
|
96
|
+
const processedPersonas: ProcessedPersona[] = [];
|
|
97
|
+
let totalActs = 0;
|
|
98
|
+
|
|
99
|
+
await Promise.all(allPersonas.map(persona => concurrencyLimit(async () => {
|
|
100
|
+
try {
|
|
101
|
+
// Extract phone number from actors
|
|
102
|
+
const phoneActor = persona.actors.find(actor =>
|
|
103
|
+
actor.integration_idn === 'newo_voice' &&
|
|
104
|
+
actor.connector_idn === 'newo_voice_connector' &&
|
|
105
|
+
actor.contact_information?.startsWith('+')
|
|
106
|
+
);
|
|
107
|
+
const phone = phoneActor?.contact_information || null;
|
|
108
|
+
|
|
109
|
+
// Get user actor IDs from persona actors
|
|
110
|
+
const userActors = persona.actors.filter(actor =>
|
|
111
|
+
actor.integration_idn === 'newo_voice' &&
|
|
112
|
+
actor.connector_idn === 'newo_voice_connector'
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
if (userActors.length === 0) {
|
|
116
|
+
processedPersonas.push({
|
|
117
|
+
id: persona.id,
|
|
118
|
+
name: persona.name,
|
|
119
|
+
phone,
|
|
120
|
+
act_count: persona.act_count,
|
|
121
|
+
acts: []
|
|
122
|
+
});
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Fetch chat history
|
|
127
|
+
const allActs: ConversationAct[] = [];
|
|
128
|
+
let actPage = 1;
|
|
129
|
+
const actsPerPage = 100;
|
|
130
|
+
let hasMoreActs = true;
|
|
131
|
+
const maxPages = 50;
|
|
132
|
+
|
|
133
|
+
while (hasMoreActs && actPage <= maxPages) {
|
|
134
|
+
try {
|
|
135
|
+
const chatHistoryParams = {
|
|
136
|
+
user_actor_id: userActors[0]!.id,
|
|
137
|
+
page: actPage,
|
|
138
|
+
per: actsPerPage
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const chatResponse = await getChatHistory(client, chatHistoryParams);
|
|
142
|
+
|
|
143
|
+
if (chatResponse.items && chatResponse.items.length > 0) {
|
|
144
|
+
const convertedActs: ConversationAct[] = chatResponse.items.map((item: Record<string, unknown>) => ({
|
|
145
|
+
id: (item.id as string) || `chat_${Math.random()}`,
|
|
146
|
+
command_act_id: null,
|
|
147
|
+
external_event_id: (item.external_event_id as string) || 'chat_history',
|
|
148
|
+
arguments: [],
|
|
149
|
+
reference_idn: item.is_agent === true ? 'agent_message' : 'user_message',
|
|
150
|
+
runtime_context_id: (item.runtime_context_id as string) || 'chat_history',
|
|
151
|
+
source_text: (item.payload as Record<string, unknown>)?.text as string || (item.message as string) || '',
|
|
152
|
+
original_text: (item.payload as Record<string, unknown>)?.text as string || (item.message as string) || '',
|
|
153
|
+
datetime: (item.datetime as string) || (item.created_at as string) || new Date().toISOString(),
|
|
154
|
+
user_actor_id: userActors[0]!.id,
|
|
155
|
+
agent_actor_id: null,
|
|
156
|
+
user_persona_id: persona.id,
|
|
157
|
+
user_persona_name: persona.name,
|
|
158
|
+
agent_persona_id: (item.agent_persona_id as string) || 'unknown',
|
|
159
|
+
external_id: (item.external_id as string) || null,
|
|
160
|
+
integration_idn: 'newo_voice',
|
|
161
|
+
connector_idn: 'newo_voice_connector',
|
|
162
|
+
to_integration_idn: null,
|
|
163
|
+
to_connector_idn: null,
|
|
164
|
+
is_agent: Boolean(item.is_agent === true),
|
|
165
|
+
project_idn: null,
|
|
166
|
+
flow_idn: (item.flow_idn as string) || 'unknown',
|
|
167
|
+
skill_idn: (item.skill_idn as string) || 'unknown',
|
|
168
|
+
session_id: (item.session_id as string) || 'unknown',
|
|
169
|
+
recordings: (item.recordings as unknown[]) || [],
|
|
170
|
+
contact_information: (item.contact_information as string) || null
|
|
171
|
+
}));
|
|
172
|
+
|
|
173
|
+
allActs.push(...convertedActs);
|
|
174
|
+
|
|
175
|
+
hasMoreActs = chatResponse.items.length === actsPerPage &&
|
|
176
|
+
(!chatResponse.metadata?.total || allActs.length < chatResponse.metadata.total);
|
|
177
|
+
actPage++;
|
|
178
|
+
} else {
|
|
179
|
+
hasMoreActs = false;
|
|
180
|
+
}
|
|
181
|
+
} catch (_error) {
|
|
182
|
+
hasMoreActs = false;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Process acts for YAML output
|
|
187
|
+
const processedActs: ProcessedAct[] = allActs
|
|
188
|
+
.sort((a, b) => new Date(a.datetime).getTime() - new Date(b.datetime).getTime())
|
|
189
|
+
.map(act => ({
|
|
190
|
+
datetime: act.datetime,
|
|
191
|
+
type: act.is_agent ? 'agent' : 'user',
|
|
192
|
+
message: act.source_text,
|
|
193
|
+
contact_information: act.contact_information,
|
|
194
|
+
flow_idn: act.flow_idn,
|
|
195
|
+
skill_idn: act.skill_idn,
|
|
196
|
+
session_id: act.session_id
|
|
197
|
+
}));
|
|
198
|
+
|
|
199
|
+
processedPersonas.push({
|
|
200
|
+
id: persona.id,
|
|
201
|
+
name: persona.name,
|
|
202
|
+
phone,
|
|
203
|
+
act_count: persona.act_count,
|
|
204
|
+
acts: processedActs
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
totalActs += processedActs.length;
|
|
208
|
+
|
|
209
|
+
this.logger.verbose(` ✓ Processed ${persona.name}: ${processedActs.length} acts`);
|
|
210
|
+
} catch (error) {
|
|
211
|
+
this.logger.warn(`Failed to process persona ${persona.name}`);
|
|
212
|
+
}
|
|
213
|
+
})));
|
|
214
|
+
|
|
215
|
+
// Sort personas by most recent activity
|
|
216
|
+
processedPersonas.sort((a, b) => {
|
|
217
|
+
const aLastAct = a.acts[a.acts.length - 1]?.datetime;
|
|
218
|
+
const bLastAct = b.acts[b.acts.length - 1]?.datetime;
|
|
219
|
+
if (!aLastAct && !bLastAct) return 0;
|
|
220
|
+
if (!aLastAct) return 1;
|
|
221
|
+
if (!bLastAct) return -1;
|
|
222
|
+
return new Date(bLastAct).getTime() - new Date(aLastAct).getTime();
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// Save to YAML
|
|
226
|
+
const conversationsFile = path.join(customerDir, 'conversations.yaml');
|
|
227
|
+
const yamlContent = yaml.dump({ personas: processedPersonas }, { lineWidth: -1 });
|
|
228
|
+
await fs.writeFile(conversationsFile, yamlContent);
|
|
229
|
+
|
|
230
|
+
hashes[conversationsFile] = sha256(yamlContent);
|
|
231
|
+
|
|
232
|
+
// Save hashes
|
|
233
|
+
const existingHashes = await loadHashes(customer.idn);
|
|
234
|
+
await saveHashes({ ...existingHashes, ...hashes }, customer.idn);
|
|
235
|
+
|
|
236
|
+
this.logger.info(`✅ Saved ${processedPersonas.length} personas with ${totalActs} conversation acts`);
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
items: [{
|
|
240
|
+
personas: processedPersonas,
|
|
241
|
+
totalActs
|
|
242
|
+
}],
|
|
243
|
+
count: 1,
|
|
244
|
+
hashes
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Push is not supported for conversations (read-only)
|
|
250
|
+
*/
|
|
251
|
+
async push(_customer: CustomerConfig, _changes?: ChangeItem<LocalConversationData>[]): Promise<PushResult> {
|
|
252
|
+
this.logger.warn('Conversations are read-only and cannot be pushed');
|
|
253
|
+
return { created: 0, updated: 0, deleted: 0, errors: ['Conversations are read-only'] };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Get changes - conversations are typically regenerated on each pull
|
|
258
|
+
*/
|
|
259
|
+
async getChanges(_customer: CustomerConfig): Promise<ChangeItem<LocalConversationData>[]> {
|
|
260
|
+
// Conversations don't support change detection in the traditional sense
|
|
261
|
+
// They are regenerated on each pull
|
|
262
|
+
return [];
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Validate conversation data
|
|
267
|
+
*/
|
|
268
|
+
async validate(_customer: CustomerConfig, _items: LocalConversationData[]): Promise<ValidationResult> {
|
|
269
|
+
// Conversations are read-only, no validation needed
|
|
270
|
+
return { valid: true, errors: [] };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Get status summary
|
|
275
|
+
*/
|
|
276
|
+
async getStatus(customer: CustomerConfig): Promise<StatusSummary> {
|
|
277
|
+
const customerDir = path.join(process.cwd(), 'newo_customers', customer.idn);
|
|
278
|
+
const conversationsFile = path.join(customerDir, 'conversations.yaml');
|
|
279
|
+
|
|
280
|
+
const exists = await fs.pathExists(conversationsFile);
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
resourceType: this.resourceType,
|
|
284
|
+
displayName: this.displayName,
|
|
285
|
+
changedCount: 0,
|
|
286
|
+
changes: exists ? [] : [{ path: conversationsFile, operation: 'created' }]
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Factory function for creating ConversationSyncStrategy
|
|
293
|
+
*/
|
|
294
|
+
export function createConversationSyncStrategy(
|
|
295
|
+
apiClientFactory: ApiClientFactory,
|
|
296
|
+
logger: ILogger
|
|
297
|
+
): ConversationSyncStrategy {
|
|
298
|
+
return new ConversationSyncStrategy(apiClientFactory, logger);
|
|
299
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic Sync Strategy Interface
|
|
3
|
+
*
|
|
4
|
+
* All resource types (Projects, Integrations, AKB, Attributes) implement this interface.
|
|
5
|
+
* This enables the SyncEngine to handle all resources uniformly.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { CustomerConfig } from '../../resources/common/types.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Validation result for pre-sync validation
|
|
12
|
+
*/
|
|
13
|
+
export interface ValidationResult {
|
|
14
|
+
valid: boolean;
|
|
15
|
+
errors: ValidationError[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ValidationError {
|
|
19
|
+
field: string;
|
|
20
|
+
message: string;
|
|
21
|
+
path?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Change operation types
|
|
26
|
+
*/
|
|
27
|
+
export type ChangeOperation = 'created' | 'modified' | 'deleted';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Generic change item representing a resource change
|
|
31
|
+
*/
|
|
32
|
+
export interface ChangeItem<T = unknown> {
|
|
33
|
+
item: T;
|
|
34
|
+
operation: ChangeOperation;
|
|
35
|
+
path: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Pull result containing resources and metadata
|
|
40
|
+
*/
|
|
41
|
+
export interface PullResult<T = unknown> {
|
|
42
|
+
items: T[];
|
|
43
|
+
count: number;
|
|
44
|
+
hashes: Record<string, string>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Push result containing operation outcomes
|
|
49
|
+
*/
|
|
50
|
+
export interface PushResult {
|
|
51
|
+
created: number;
|
|
52
|
+
updated: number;
|
|
53
|
+
deleted: number;
|
|
54
|
+
errors: string[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Generic Sync Strategy Interface
|
|
59
|
+
*
|
|
60
|
+
* TRemote - Type from the API (e.g., API response types)
|
|
61
|
+
* TLocal - Type for local storage (e.g., YAML/JSON file types)
|
|
62
|
+
*/
|
|
63
|
+
export interface ISyncStrategy<_TRemote = unknown, TLocal = unknown> {
|
|
64
|
+
/**
|
|
65
|
+
* Resource type identifier (e.g., 'projects', 'integrations', 'akb', 'attributes')
|
|
66
|
+
*/
|
|
67
|
+
readonly resourceType: string;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Display name for logging/UI (e.g., 'Projects', 'Integrations')
|
|
71
|
+
*/
|
|
72
|
+
readonly displayName: string;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Pull resources from NEWO platform to local filesystem
|
|
76
|
+
*
|
|
77
|
+
* @param customer - Customer configuration
|
|
78
|
+
* @param options - Optional pull options
|
|
79
|
+
* @returns Pull result with items and hashes
|
|
80
|
+
*/
|
|
81
|
+
pull(customer: CustomerConfig, options?: PullOptions): Promise<PullResult<TLocal>>;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Push local changes to NEWO platform
|
|
85
|
+
*
|
|
86
|
+
* @param customer - Customer configuration
|
|
87
|
+
* @param changes - Changes to push (if not provided, detect changes automatically)
|
|
88
|
+
* @returns Push result with counts
|
|
89
|
+
*/
|
|
90
|
+
push(customer: CustomerConfig, changes?: ChangeItem<TLocal>[]): Promise<PushResult>;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Detect what has changed locally since last sync
|
|
94
|
+
*
|
|
95
|
+
* @param customer - Customer configuration
|
|
96
|
+
* @returns Array of changed items
|
|
97
|
+
*/
|
|
98
|
+
getChanges(customer: CustomerConfig): Promise<ChangeItem<TLocal>[]>;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Validate local state before push
|
|
102
|
+
*
|
|
103
|
+
* @param customer - Customer configuration
|
|
104
|
+
* @param items - Items to validate
|
|
105
|
+
* @returns Validation result
|
|
106
|
+
*/
|
|
107
|
+
validate(customer: CustomerConfig, items: TLocal[]): Promise<ValidationResult>;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get status summary for display
|
|
111
|
+
*
|
|
112
|
+
* @param customer - Customer configuration
|
|
113
|
+
* @returns Status summary
|
|
114
|
+
*/
|
|
115
|
+
getStatus(customer: CustomerConfig): Promise<StatusSummary>;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Pull operation options
|
|
120
|
+
*/
|
|
121
|
+
export interface PullOptions {
|
|
122
|
+
/**
|
|
123
|
+
* Overwrite local changes without prompting
|
|
124
|
+
*/
|
|
125
|
+
silentOverwrite?: boolean;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Enable verbose logging
|
|
129
|
+
*/
|
|
130
|
+
verbose?: boolean;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Specific project ID to pull (for projects strategy)
|
|
134
|
+
*/
|
|
135
|
+
projectId?: string | null;
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Skip deletion detection and cleanup
|
|
139
|
+
*/
|
|
140
|
+
skipCleanup?: boolean;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Status summary for a resource type
|
|
145
|
+
*/
|
|
146
|
+
export interface StatusSummary {
|
|
147
|
+
resourceType: string;
|
|
148
|
+
displayName: string;
|
|
149
|
+
changedCount: number;
|
|
150
|
+
changes: Array<{
|
|
151
|
+
path: string;
|
|
152
|
+
operation: ChangeOperation;
|
|
153
|
+
details?: string;
|
|
154
|
+
}>;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Abstract base class with common strategy functionality
|
|
159
|
+
*/
|
|
160
|
+
export abstract class BaseSyncStrategy<TRemote = unknown, TLocal = unknown> implements ISyncStrategy<TRemote, TLocal> {
|
|
161
|
+
abstract readonly resourceType: string;
|
|
162
|
+
abstract readonly displayName: string;
|
|
163
|
+
|
|
164
|
+
abstract pull(customer: CustomerConfig, options?: PullOptions): Promise<PullResult<TLocal>>;
|
|
165
|
+
abstract push(customer: CustomerConfig, changes?: ChangeItem<TLocal>[]): Promise<PushResult>;
|
|
166
|
+
abstract getChanges(customer: CustomerConfig): Promise<ChangeItem<TLocal>[]>;
|
|
167
|
+
abstract validate(customer: CustomerConfig, items: TLocal[]): Promise<ValidationResult>;
|
|
168
|
+
|
|
169
|
+
async getStatus(customer: CustomerConfig): Promise<StatusSummary> {
|
|
170
|
+
const changes = await this.getChanges(customer);
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
resourceType: this.resourceType,
|
|
174
|
+
displayName: this.displayName,
|
|
175
|
+
changedCount: changes.length,
|
|
176
|
+
changes: changes.map(c => ({
|
|
177
|
+
path: c.path,
|
|
178
|
+
operation: c.operation
|
|
179
|
+
}))
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
}
|