interaqt 0.7.2 → 0.7.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.
- package/agent/CLAUDE.md +46 -7
- package/agent/agentspace/knowledge/generator/computation-analysis.md +53 -36
- package/agent/agentspace/knowledge/generator/data-analysis.md +276 -49
- package/agent/agentspace/knowledge/generator/integration-implementation-handler.md +550 -0
- package/agent/agentspace/prompt/requirement_analysis_refactor.md +1 -1
- package/dist/index.js +246 -244
- package/dist/index.js.map +1 -1
- package/dist/runtime/activity/InteractionCall.d.ts.map +1 -1
- package/dist/storage/erstorage/RecordQueryAgent.d.ts.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: integration-implementation-handler
|
|
3
|
+
description: Guide for implementing integrations to connect reactive backend with imperative external systems
|
|
4
|
+
model: inherit
|
|
5
|
+
color: purple
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# Integration Implementation Guide
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
**Integrations** bridge the gap between the reactive backend framework and imperative external systems (APIs, services, databases, message queues, etc.). They allow external non-reactive systems to participate in the reactive data flow.
|
|
13
|
+
|
|
14
|
+
## Integration Interface
|
|
15
|
+
|
|
16
|
+
Every integration must implement the `IIntegration` interface:
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
export type IIntegration = {
|
|
20
|
+
configure?:() => Promise<any>
|
|
21
|
+
setup?:(controller: Controller) => Promise<any>
|
|
22
|
+
createSideEffects:() => RecordMutationSideEffect[]
|
|
23
|
+
createAPIs?: () => APIs
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Constructor Arguments
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
export type IIntegrationConstructorArgs = {
|
|
31
|
+
entities: EntityInstance[],
|
|
32
|
+
relations: RelationInstance[],
|
|
33
|
+
activities: ActivityInstance[],
|
|
34
|
+
interactions: InteractionInstance[],
|
|
35
|
+
dict: DictionaryInstance[]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type IIntegrationHandles = {
|
|
39
|
+
[k:string]: any // External handles like websocketServer, etc.
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
class MyIntegration implements IIntegration {
|
|
43
|
+
constructor(
|
|
44
|
+
public args: IIntegrationConstructorArgs,
|
|
45
|
+
public handles: IIntegrationHandles
|
|
46
|
+
) {}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Lifecycle Methods
|
|
51
|
+
|
|
52
|
+
### 1. `configure()` - Schema Augmentation
|
|
53
|
+
|
|
54
|
+
**Purpose**: Modify the reactive schema before system initialization.
|
|
55
|
+
|
|
56
|
+
**Use Cases**:
|
|
57
|
+
- Inject new entities into the system
|
|
58
|
+
- Add computed properties to existing entities
|
|
59
|
+
- Configure state machines for reactive properties
|
|
60
|
+
- Inject computations into relations
|
|
61
|
+
|
|
62
|
+
**Execution Timing**: Before Controller initialization
|
|
63
|
+
|
|
64
|
+
**Example - Injecting Entity**:
|
|
65
|
+
```typescript
|
|
66
|
+
async configure() {
|
|
67
|
+
// Create and inject a new entity for external events
|
|
68
|
+
const TaskEvent = Entity.create({
|
|
69
|
+
name: 'LLMPicGenAsyncTaskEvent',
|
|
70
|
+
properties: [
|
|
71
|
+
Property.create({ name: 'taskId', type: 'string' }),
|
|
72
|
+
Property.create({ name: 'status', type: 'string' }),
|
|
73
|
+
Property.create({ name: 'result', type: 'object' }),
|
|
74
|
+
]
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// Inject into entities array
|
|
78
|
+
this.args.entities.push(TaskEvent);
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**Example - Injecting Property Computation**:
|
|
83
|
+
```typescript
|
|
84
|
+
async configure() {
|
|
85
|
+
// Find target entities
|
|
86
|
+
const streamEntities = Stream.instances;
|
|
87
|
+
|
|
88
|
+
for (const entity of streamEntities) {
|
|
89
|
+
// Find and inject computation into property
|
|
90
|
+
const urlProperty = entity.properties.find(p => p.name === 'url')!;
|
|
91
|
+
|
|
92
|
+
urlProperty.computation = Custom.create({
|
|
93
|
+
name: 'generateStreamUrl',
|
|
94
|
+
async getInitialValue(this: Controller, record?: any) {
|
|
95
|
+
// Call external API to generate URL
|
|
96
|
+
const timestamp = Math.floor(Date.now() / 1000) + 3600;
|
|
97
|
+
const authString = `/${appName}/${streamName}${key}${timestamp}`;
|
|
98
|
+
const sign = crypto.createHash('md5').update(authString).digest('hex');
|
|
99
|
+
return `rtmp://${domain}/${appName}/${streamName}?volcTime=${timestamp}&volcSecret=${sign}`;
|
|
100
|
+
},
|
|
101
|
+
incrementalCompute: async function(this: { controller: Controller, state: any }, lastValue: any, mutationEvent: any, record: any, dataDeps: any) {
|
|
102
|
+
// Skip recomputation if URL should remain constant
|
|
103
|
+
return ComputationResult.skip();
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Example - Injecting State Machine**:
|
|
111
|
+
```typescript
|
|
112
|
+
async configure() {
|
|
113
|
+
const taskEntities = AsyncTask.instances;
|
|
114
|
+
|
|
115
|
+
for (const taskEntity of taskEntities) {
|
|
116
|
+
const statusProperty = taskEntity.properties.find(p => p.name === 'status');
|
|
117
|
+
|
|
118
|
+
// Create state node
|
|
119
|
+
const statusState = StateNode.create({
|
|
120
|
+
name: 'status',
|
|
121
|
+
computeValue: (lastValue, mutationEvent) => {
|
|
122
|
+
return mutationEvent?.record?.status || lastValue || 'pending';
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Configure state machine
|
|
127
|
+
statusProperty.computation = StateMachine.create({
|
|
128
|
+
states: [statusState],
|
|
129
|
+
initialState: statusState,
|
|
130
|
+
transfers: [
|
|
131
|
+
StateTransfer.create({
|
|
132
|
+
trigger: {
|
|
133
|
+
recordName: 'TaskEvent', // Event entity name
|
|
134
|
+
type: 'create',
|
|
135
|
+
record: { taskType: taskEntity.name }
|
|
136
|
+
},
|
|
137
|
+
current: statusState,
|
|
138
|
+
next: statusState,
|
|
139
|
+
computeTarget: async function(this: Controller, mutationEvent: any) {
|
|
140
|
+
const event = mutationEvent.record;
|
|
141
|
+
if (event.status) {
|
|
142
|
+
return { id: event.taskId }; // Target record to update
|
|
143
|
+
}
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
})
|
|
147
|
+
]
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### 2. `setup()` - Runtime Initialization
|
|
154
|
+
|
|
155
|
+
**Purpose**: Initialize external connections and register runtime handlers.
|
|
156
|
+
|
|
157
|
+
**Use Cases**:
|
|
158
|
+
- Connect to external services (Redis, databases, message queues)
|
|
159
|
+
- Register event listeners on external handles
|
|
160
|
+
- Set up WebSocket connection handlers
|
|
161
|
+
- Initialize API clients
|
|
162
|
+
|
|
163
|
+
**Execution Timing**: After Controller initialization, before server starts
|
|
164
|
+
|
|
165
|
+
**Example - External Service Connection**:
|
|
166
|
+
```typescript
|
|
167
|
+
async setup(controller: Controller) {
|
|
168
|
+
const redisUrl = process.env.REDIS_URL || 'redis://localhost:6379';
|
|
169
|
+
|
|
170
|
+
this.subscriber = createClient({ url: redisUrl });
|
|
171
|
+
this.subscriber.on('error', (err) => console.error('Redis Error:', err));
|
|
172
|
+
await this.subscriber.connect();
|
|
173
|
+
|
|
174
|
+
this.publisher = createClient({ url: redisUrl });
|
|
175
|
+
await this.publisher.connect();
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Example - WebSocket Handler Registration**:
|
|
180
|
+
```typescript
|
|
181
|
+
async setup(controller: Controller) {
|
|
182
|
+
// Register handler for user connection
|
|
183
|
+
this.handles.websocketServer.on('connection-setup-completed', async (ws, request) => {
|
|
184
|
+
const user = ws.user;
|
|
185
|
+
if (!user) return;
|
|
186
|
+
|
|
187
|
+
// Query user's channels
|
|
188
|
+
const userChannels = await controller.system.storage.find(
|
|
189
|
+
'UserChannelRelation',
|
|
190
|
+
MatchExp.atom({ key: 'source.id', value: ['=', user.id] }),
|
|
191
|
+
undefined,
|
|
192
|
+
['id', ['target', { attributeQuery: ['id'] }]]
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
// Subscribe to Redis channels for this user
|
|
196
|
+
for (let relation of userChannels) {
|
|
197
|
+
this.subscriber?.subscribe(relation.target.id, (message) => {
|
|
198
|
+
ws.send(message);
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### 3. `createSideEffects()` - Reactive Synchronization
|
|
206
|
+
|
|
207
|
+
**Purpose**: Define side effects that synchronize reactive data changes to external systems.
|
|
208
|
+
|
|
209
|
+
**Use Cases**:
|
|
210
|
+
- Push data mutations to external APIs
|
|
211
|
+
- Publish messages to message queues
|
|
212
|
+
- Update external database records
|
|
213
|
+
- Trigger external workflows
|
|
214
|
+
|
|
215
|
+
**Return Value**: Array of `RecordMutationSideEffect`
|
|
216
|
+
|
|
217
|
+
**Example - Publishing to Message Queue**:
|
|
218
|
+
```typescript
|
|
219
|
+
createSideEffects(): RecordMutationSideEffect[] {
|
|
220
|
+
const dispatcher = this;
|
|
221
|
+
|
|
222
|
+
return [
|
|
223
|
+
RecordMutationSideEffect.create({
|
|
224
|
+
name: 'channel_message_publish_sideeffect',
|
|
225
|
+
record: {
|
|
226
|
+
name: 'ChannelMessageRelation', // Target relation/entity
|
|
227
|
+
},
|
|
228
|
+
async content(this: Controller, event: RecordMutationEvent) {
|
|
229
|
+
if (event.type === 'create') {
|
|
230
|
+
const channelId = event.record?.source.id;
|
|
231
|
+
const message = event.record?.target;
|
|
232
|
+
|
|
233
|
+
if (message && channelId) {
|
|
234
|
+
const messageData = {
|
|
235
|
+
type: 'message',
|
|
236
|
+
channelId,
|
|
237
|
+
message
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
// Publish to external system
|
|
241
|
+
await dispatcher.publisher?.publish(
|
|
242
|
+
channelId,
|
|
243
|
+
JSON.stringify(messageData)
|
|
244
|
+
);
|
|
245
|
+
console.log('Published to Redis:', channelId);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
})
|
|
250
|
+
];
|
|
251
|
+
}
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
**Example - Multiple Side Effects**:
|
|
255
|
+
```typescript
|
|
256
|
+
createSideEffects(): RecordMutationSideEffect[] {
|
|
257
|
+
return [
|
|
258
|
+
// Handle relation creation
|
|
259
|
+
...UserChannelRelation.instances.map(instance =>
|
|
260
|
+
RecordMutationSideEffect.create({
|
|
261
|
+
name: `channel_user_relation_${instance.name}_create`,
|
|
262
|
+
record: { name: instance.name! },
|
|
263
|
+
async content(this: Controller, event: RecordMutationEvent) {
|
|
264
|
+
if (event.type === 'create') {
|
|
265
|
+
// Subscribe online users to new channels
|
|
266
|
+
const clients = Array.from(websocketServer.clients);
|
|
267
|
+
const wsClient = clients.find(c => c.user.id === event.record?.source.id);
|
|
268
|
+
if (wsClient) {
|
|
269
|
+
subscriber?.subscribe(event.record?.target.id, (msg) => {
|
|
270
|
+
wsClient.send(msg);
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
})
|
|
276
|
+
),
|
|
277
|
+
|
|
278
|
+
// Handle relation deletion
|
|
279
|
+
...UserChannelRelation.instances.map(instance =>
|
|
280
|
+
RecordMutationSideEffect.create({
|
|
281
|
+
name: `channel_user_relation_${instance.name}_delete`,
|
|
282
|
+
record: { name: instance.name! },
|
|
283
|
+
async content(this: Controller, event: RecordMutationEvent) {
|
|
284
|
+
if (event.type === 'delete') {
|
|
285
|
+
// Unsubscribe user from channel
|
|
286
|
+
// ... implementation
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
})
|
|
290
|
+
)
|
|
291
|
+
];
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### 4. `createAPIs()` - Custom Endpoints
|
|
296
|
+
|
|
297
|
+
**Purpose**: Create custom API endpoints for external system interactions.
|
|
298
|
+
|
|
299
|
+
**Use Cases**:
|
|
300
|
+
- Query external service status
|
|
301
|
+
- Trigger external operations
|
|
302
|
+
- Proxy requests to external APIs
|
|
303
|
+
- Implement custom business logic that doesn't fit interactions
|
|
304
|
+
|
|
305
|
+
**Return Value**: Object mapping API names to API definitions
|
|
306
|
+
|
|
307
|
+
**Example - Status Query API**:
|
|
308
|
+
```typescript
|
|
309
|
+
createAPIs(): APIs {
|
|
310
|
+
const apis: APIs = {};
|
|
311
|
+
|
|
312
|
+
apis.queryTaskStatus = createAPI(
|
|
313
|
+
async function(this: Controller, context, params: { taskId: string, taskType: string }) {
|
|
314
|
+
const { taskId, taskType } = params;
|
|
315
|
+
|
|
316
|
+
// Query internal state
|
|
317
|
+
const task = await this.system.storage.findOne(
|
|
318
|
+
taskType,
|
|
319
|
+
MatchExp.atom({ key: 'id', value: ['=', taskId] }),
|
|
320
|
+
undefined,
|
|
321
|
+
['id', 'executionId', 'status', 'result']
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
if (!task) {
|
|
325
|
+
return { error: 'Task not found' };
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Query external system
|
|
329
|
+
const externalStatus = await queryExternalAPI(task.executionId);
|
|
330
|
+
|
|
331
|
+
// Update internal state via event creation
|
|
332
|
+
if (externalStatus.status !== task.status) {
|
|
333
|
+
await this.system.storage.create('TaskEvent', {
|
|
334
|
+
taskId,
|
|
335
|
+
eventType: 'statusUpdated',
|
|
336
|
+
status: externalStatus.status,
|
|
337
|
+
result: externalStatus.result,
|
|
338
|
+
taskType
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return {
|
|
343
|
+
success: true,
|
|
344
|
+
taskId,
|
|
345
|
+
status: externalStatus.status,
|
|
346
|
+
result: externalStatus.result
|
|
347
|
+
};
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
params: { taskId: 'string', taskType: 'string' },
|
|
351
|
+
useNamedParams: true,
|
|
352
|
+
allowAnonymous: false
|
|
353
|
+
}
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
return apis;
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
## Integration Patterns
|
|
361
|
+
|
|
362
|
+
### Pattern 1: External Service Synchronization (Redis, MQ)
|
|
363
|
+
|
|
364
|
+
**Characteristics**:
|
|
365
|
+
- Bidirectional data flow
|
|
366
|
+
- Real-time synchronization
|
|
367
|
+
- Connection management
|
|
368
|
+
|
|
369
|
+
**Implementation**:
|
|
370
|
+
- `setup()`: Establish connections, register listeners
|
|
371
|
+
- `createSideEffects()`: Push internal changes to external system
|
|
372
|
+
- External events → Create records in reactive system
|
|
373
|
+
|
|
374
|
+
**Example**: RedisChannelIntegration
|
|
375
|
+
|
|
376
|
+
### Pattern 2: External Resource Generation (URLs, Tokens)
|
|
377
|
+
|
|
378
|
+
**Characteristics**:
|
|
379
|
+
- One-way data flow (internal → external)
|
|
380
|
+
- Lazy evaluation
|
|
381
|
+
- Resource lifecycle management
|
|
382
|
+
|
|
383
|
+
**Implementation**:
|
|
384
|
+
- `configure()`: Inject Custom computation into properties
|
|
385
|
+
- `getInitialValue()`: Call external API to generate resource
|
|
386
|
+
- `incrementalCompute()`: Usually skip (resources are immutable)
|
|
387
|
+
|
|
388
|
+
**Example**: VolcStreamIntegration
|
|
389
|
+
|
|
390
|
+
### Pattern 3: Async Task Management
|
|
391
|
+
|
|
392
|
+
**Characteristics**:
|
|
393
|
+
- Async operation lifecycle
|
|
394
|
+
- Status polling
|
|
395
|
+
- Result synchronization
|
|
396
|
+
|
|
397
|
+
**Implementation**:
|
|
398
|
+
- `configure()`: Inject event entity, configure state machines
|
|
399
|
+
- `createAPIs()`: Provide status query endpoint
|
|
400
|
+
- Event-driven state updates via injected entity
|
|
401
|
+
|
|
402
|
+
**Example**: VolcPicGenIntegration
|
|
403
|
+
|
|
404
|
+
## Best Practices
|
|
405
|
+
|
|
406
|
+
### 1. Separation of Concerns
|
|
407
|
+
|
|
408
|
+
- **configure()**: Schema modifications only
|
|
409
|
+
- **setup()**: Connection establishment only
|
|
410
|
+
- **createSideEffects()**: Data synchronization only
|
|
411
|
+
- **createAPIs()**: Query/command operations only
|
|
412
|
+
|
|
413
|
+
### 2. Error Handling
|
|
414
|
+
|
|
415
|
+
```typescript
|
|
416
|
+
async setup(controller: Controller) {
|
|
417
|
+
try {
|
|
418
|
+
this.client = await connectToService();
|
|
419
|
+
this.client.on('error', (err) => {
|
|
420
|
+
console.error('Service error:', err);
|
|
421
|
+
// Implement reconnection logic
|
|
422
|
+
});
|
|
423
|
+
} catch (error) {
|
|
424
|
+
console.error('Failed to connect:', error);
|
|
425
|
+
throw error; // Fail fast if critical
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### 3. Event-Driven State Updates
|
|
431
|
+
|
|
432
|
+
**❌ BAD - Direct State Mutation**:
|
|
433
|
+
```typescript
|
|
434
|
+
// Don't directly update entity properties
|
|
435
|
+
await this.system.storage.update(taskType, taskId, {
|
|
436
|
+
status: newStatus // This bypasses reactive system
|
|
437
|
+
});
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
**✅ GOOD - Event-Driven Updates**:
|
|
441
|
+
```typescript
|
|
442
|
+
// Create event records to trigger state machines
|
|
443
|
+
await this.system.storage.create('TaskEvent', {
|
|
444
|
+
taskId,
|
|
445
|
+
eventType: 'statusUpdated',
|
|
446
|
+
status: newStatus,
|
|
447
|
+
taskType
|
|
448
|
+
});
|
|
449
|
+
// State machine will reactively update the target entity
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
### 4. Resource Cleanup
|
|
453
|
+
|
|
454
|
+
```typescript
|
|
455
|
+
async cleanup() {
|
|
456
|
+
if (this.subscriber?.isOpen) {
|
|
457
|
+
await this.subscriber.disconnect();
|
|
458
|
+
}
|
|
459
|
+
if (this.publisher?.isOpen) {
|
|
460
|
+
await this.publisher.disconnect();
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
### 5. Environment Configuration
|
|
466
|
+
|
|
467
|
+
```typescript
|
|
468
|
+
async setup(controller: Controller) {
|
|
469
|
+
const apiKey = process.env.EXTERNAL_API_KEY;
|
|
470
|
+
if (!apiKey) {
|
|
471
|
+
throw new Error('EXTERNAL_API_KEY must be set');
|
|
472
|
+
}
|
|
473
|
+
// ... use apiKey
|
|
474
|
+
}
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### 6. Type Safety with External APIs
|
|
478
|
+
|
|
479
|
+
```typescript
|
|
480
|
+
// Define external API types
|
|
481
|
+
export type ExternalTaskStatus = 'pending' | 'processing' | 'success' | 'failed';
|
|
482
|
+
|
|
483
|
+
// Map to internal types
|
|
484
|
+
function mapExternalStatus(external: string): TaskStatus {
|
|
485
|
+
switch (external) {
|
|
486
|
+
case 'in_queue': return 'pending';
|
|
487
|
+
case 'generating': return 'processing';
|
|
488
|
+
case 'done': return 'success';
|
|
489
|
+
default: return 'failed';
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
## Integration Registration
|
|
495
|
+
|
|
496
|
+
After creating an integration, register it in `integrations/index.ts`:
|
|
497
|
+
|
|
498
|
+
```typescript
|
|
499
|
+
import { MyIntegration } from './myintegration';
|
|
500
|
+
|
|
501
|
+
const AggregatedIntegrationClass = createAggregatedIntegration([
|
|
502
|
+
RedisChannelIntegration,
|
|
503
|
+
VolcStreamIntegration,
|
|
504
|
+
VolcPicGenIntegration,
|
|
505
|
+
MyIntegration // Add your integration
|
|
506
|
+
]);
|
|
507
|
+
|
|
508
|
+
export default AggregatedIntegrationClass;
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
## Testing Integrations
|
|
512
|
+
|
|
513
|
+
1. **Unit Tests**: Test integration logic in isolation
|
|
514
|
+
2. **Integration Tests**: Test with mock external services
|
|
515
|
+
3. **E2E Tests**: Test with real external services in staging
|
|
516
|
+
|
|
517
|
+
```typescript
|
|
518
|
+
// Mock external service for testing
|
|
519
|
+
class MockExternalService {
|
|
520
|
+
async query(id: string) {
|
|
521
|
+
return { status: 'success', result: { data: 'test' } };
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Use in tests
|
|
526
|
+
const integration = new MyIntegration(args, {
|
|
527
|
+
externalService: new MockExternalService()
|
|
528
|
+
});
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
## Common Pitfalls
|
|
532
|
+
|
|
533
|
+
1. **❌ Modifying schema in setup()**: Schema must be finalized before Controller initialization
|
|
534
|
+
2. **❌ Blocking operations in configure()**: Avoid heavy I/O, keep it fast
|
|
535
|
+
3. **❌ Forgetting error handlers**: Always handle connection errors
|
|
536
|
+
4. **❌ Direct state mutations**: Use event-driven updates instead
|
|
537
|
+
5. **❌ Ignoring cleanup**: Implement proper resource cleanup
|
|
538
|
+
6. **❌ Hardcoding configuration**: Use environment variables
|
|
539
|
+
|
|
540
|
+
## Summary
|
|
541
|
+
|
|
542
|
+
Integrations enable reactive systems to communicate with external imperative systems through four key mechanisms:
|
|
543
|
+
|
|
544
|
+
1. **configure()**: Augment reactive schema
|
|
545
|
+
2. **setup()**: Initialize external connections
|
|
546
|
+
3. **createSideEffects()**: Synchronize data to external systems
|
|
547
|
+
4. **createAPIs()**: Expose custom endpoints
|
|
548
|
+
|
|
549
|
+
Choose the appropriate pattern based on your integration needs, and always prioritize event-driven design for state synchronization.
|
|
550
|
+
|
|
@@ -29,6 +29,6 @@
|
|
|
29
29
|
6. 最终产出的 interactions-design.json 仍然使用原文档里的 interaction 数据结构,但以流程为组来组织。
|
|
30
30
|
|
|
31
31
|
注意,在每一个步骤中,都要给出清晰的数据结构定义,用 json 来写。
|
|
32
|
-
整体用简洁的英语完成文档重写。注意原本文档中的在关键步骤 update
|
|
32
|
+
整体用简洁的英语完成文档重写。注意原本文档中的在关键步骤 update docs/{module}.status.json 仍然按照原文档的方式写。
|
|
33
33
|
|
|
34
34
|
|