interaqt 0.8.3 → 0.8.5

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.
@@ -162,27 +162,41 @@ git commit -m "feat: Task 3.1.2 - Complete entity and relation implementation"
162
162
  - [ ] Ensure all payloads match the documented fields
163
163
  - [ ] **🔴 CRITICAL: For query interactions (action: GetAction):**
164
164
  - **MUST declare `data` field** - specify the Entity or Relation to query
165
- - **SHOULD declare `query` field** if there are predefined filters/fields (use Query.create with QueryItem)
166
- - Example:
165
+ - **SHOULD declare `dataPolicy` field** if there are predefined filters/fields or access restrictions
166
+ - **⚠️ IMPORTANT: Data Access Scope vs Business Rules**
167
+ - If `dataConstraints` express **data access scope restrictions** (e.g., "can only view own entities", "can only view specific fields"), use `dataPolicy` NOT `condition`
168
+ - `dataPolicy` controls what data can be accessed AFTER the operation is permitted
169
+ - `condition` controls WHETHER the operation can execute (permissions/business rules)
170
+ - Example of data policy: Restricting visible fields, filtering by ownership
171
+ - Example with dynamic data policy (user-based filtering):
172
+ ```typescript
173
+ const ViewMyOrders = Interaction.create({
174
+ name: 'ViewMyOrders',
175
+ action: GetAction,
176
+ data: Order,
177
+ dataPolicy: DataPolicy.create({
178
+ match: function(this: Controller, event: any) {
179
+ // Only show user's own orders
180
+ return MatchExp.atom({key: 'owner.id', value:['=', event.user.id]})
181
+ }
182
+ })
183
+ })
184
+ ```
185
+ - Example with combined data policy (filtering + field restrictions + pagination):
167
186
  ```typescript
168
187
  const ViewMyFollowers = Interaction.create({
169
- name: 'ViewUsers',
188
+ name: 'ViewMyFollowers',
170
189
  action: GetAction,
171
190
  data: User, // REQUIRED: specify what to query
172
- query: Query.create({ // OPTIONAL: predefined query config
173
- items: [
174
- QueryItem.create({
175
- name: 'attributeQuery',
176
- value: ['id', 'name', 'email']
177
- }),
178
- // Use function-based value to add conditional restrictions based on current context user
179
- QueryItem.create({
180
- name: 'match',
181
- value: function(this:Controller, event:any) {
182
- return MatchExp.atom({key: 'follow.id', value:['=', event.user.id]})
183
- }
184
- })
185
- ]
191
+ dataPolicy: DataPolicy.create({
192
+ // Dynamic filter: only users who follow the current user
193
+ match: function(this: Controller, event: any) {
194
+ return MatchExp.atom({key: 'following.id', value:['=', event.user.id]})
195
+ },
196
+ // Field restrictions: only expose specific fields
197
+ attributeQuery: ['id', 'name', 'email'],
198
+ // Default pagination
199
+ modifier: { limit: 20, orderBy: { name: 'asc' } }
186
200
  })
187
201
  })
188
202
  ```
@@ -1015,6 +1015,23 @@ const GetUserDonations = Interaction.create({
1015
1015
  ]
1016
1016
  })
1017
1017
  })
1018
+
1019
+ // Query interaction with dataPolicy for access control
1020
+ const GetMyDonations = Interaction.create({
1021
+ name: 'GetMyDonations',
1022
+ action: GetAction,
1023
+ data: Donation, // ✅ Entity reference
1024
+ dataPolicy: DataPolicy.create({
1025
+ // ✅ Dynamic filter: users can only see their own donations
1026
+ match: function(this: Controller, event: any) {
1027
+ return MatchExp.atom({key: 'donor.id', value: ['=', event.user.id]})
1028
+ },
1029
+ // ✅ Field restrictions: limit exposed fields
1030
+ attributeQuery: ['id', 'amount', 'createdAt', 'status'],
1031
+ // ✅ Default pagination
1032
+ modifier: { limit: 20, orderBy: { createdAt: 'desc' } }
1033
+ })
1034
+ })
1018
1035
  ```
1019
1036
 
1020
1037
  ### Pattern 5: Test Error Checking
@@ -205,6 +205,7 @@ npm run generate-frontend-api
205
205
  - **Framework**: React + TypeScript
206
206
  - **Build Tool**: Vite
207
207
  - **Styling**: Tailwind CSS
208
+ - **Routing**: React Router
208
209
  - **State Management**: React Context (for API client and global state)
209
210
 
210
211
  **Project Structure:**
@@ -231,7 +232,40 @@ npm run generate-frontend-api
231
232
  }
232
233
  ```
233
234
 
234
- 2. **Reference Existing Components for Patterns:**
235
+ 2. **Query Interactions Always Return Arrays:**
236
+ - Backend query-type interactions always return arrays in `response.data`
237
+ - To query a specific entity/relation, use the `match` field in the query options (2nd parameter)
238
+ - Do NOT put match criteria in the payload (1st parameter)
239
+
240
+ ```typescript
241
+ // ✅ Correct: Use match in query options
242
+ const response = await apiClient.ViewVideoGenerationStatus(
243
+ { videoGenerationRequestId: videoId }, // payload
244
+ {
245
+ attributeQuery: ['id', 'status', 'videoUrl'],
246
+ match: {
247
+ key: 'id',
248
+ value: ['=', videoId] // Match condition here
249
+ }
250
+ }
251
+ );
252
+ const item = response.data[0]; // Extract first item from array
253
+
254
+ // ❌ Wrong: Don't rely on payload for filtering
255
+ const response = await apiClient.ViewVideoGenerationStatus(
256
+ { videoGenerationRequestId: videoId } // This won't filter results
257
+ );
258
+ ```
259
+
260
+ 3. **Handling Asynchronous External System Tasks:**
261
+ - For asynchronous tasks that call external systems, backend typically does NOT implement polling unless explicitly specified in requirements
262
+ - Backend usually provides a separate API endpoint to trigger status updates
263
+ - Frontend can call this API to trigger backend status updates
264
+ - Frontend implementation options:
265
+ - **Manual trigger**: Add a button in the component for users to manually trigger the status update API
266
+ - **Automatic polling**: Implement polling in the component (on mount) until the task reaches a completion state
267
+
268
+ 4. **Reference Existing Components for Patterns:**
235
269
  - Look at `frontend/src/components/*.tsx` files
236
270
  - Follow the same patterns for API client usage
237
271
  - Check how error handling is implemented
@@ -274,6 +308,22 @@ npm run generate-frontend-api
274
308
  - Test on different screen sizes
275
309
  - Ensure mobile-friendly layouts
276
310
 
311
+ 7. **Modular Navigation:**
312
+ - **Module Entry Points**: Create a main navigation menu listing all modules
313
+ - **Route Organization**: Structure routes with module prefixes (e.g., `/donate/*`, `/livestream/*`)
314
+ - **Active Module Indicator**: Highlight current module in navigation menu
315
+ - **Breadcrumb Navigation**: Show module name → page hierarchy
316
+ - **Module Switching**: Enable seamless navigation between modules
317
+ - **Example Structure**:
318
+ ```typescript
319
+ // Main App Router
320
+ <Routes>
321
+ <Route path="/" element={<ModuleSelector />} />
322
+ <Route path="/donate/*" element={<DonateModule />} />
323
+ <Route path="/livestream/*" element={<LivestreamModule />} />
324
+ </Routes>
325
+ ```
326
+
277
327
  **🔴 CRITICAL: Completeness Check**
278
328
 
279
329
  Before marking Step 4 complete, verify:
@@ -45,6 +45,27 @@ This applies to BOTH async APIs (with task IDs) and sync APIs (immediate results
45
45
  - All integration documentation files MUST be prefixed with current module name from `.currentmodule`
46
46
  - Format: `docs/{module}.{integration-name}.integration-design.md`
47
47
 
48
+ **🔴 CRITICAL PRINCIPLE: Status Polling Strategy**
49
+
50
+ **Default Approach: Frontend Polling with Manual Query API**
51
+
52
+ Backend polling consumes significant server resources. Follow this priority order:
53
+
54
+ 1. **Default (ALWAYS implement)**: Provide manual query API for frontend
55
+ - Create API endpoint to query external status
56
+ - Frontend can poll this API at its own pace
57
+ - Even if polling is needed, frontend handles it unless explicitly stated otherwise
58
+
59
+ 2. **Backend Polling (ONLY if explicitly required)**: Implement server-side polling
60
+ - ONLY implement if user explicitly requests "backend polling" in requirements
61
+ - Use with caution due to resource consumption
62
+ - Example: volcjmeng integration (only because explicitly required)
63
+
64
+ 3. **Webhook (ONLY if both conditions met)**: Implement webhook endpoint
65
+ - ONLY if external service supports webhook registration
66
+ - AND user can register webhook themselves
67
+ - Requires exposing public endpoint for external callbacks
68
+
48
69
  # Core Concepts
49
70
 
50
71
  ## Interaqt Framework
@@ -281,6 +302,7 @@ interface IIntegration {
281
302
  setup?(controller: Controller): Promise<any> // Setup phase with controller access
282
303
  createSideEffects(): RecordMutationSideEffect[] // Listen to data mutations and create events
283
304
  createAPIs?(): APIs // Expose custom APIs (e.g., webhook endpoints)
305
+ createMiddlewares?(): MiddlewareHandler[] // Optional: Create HTTP middleware for request processing
284
306
  }
285
307
  ```
286
308
 
@@ -292,6 +314,63 @@ interface IIntegration {
292
314
  1. Webhook endpoints to receive external system callbacks
293
315
  2. Manual trigger/query APIs for status checks and retries
294
316
  3. Frontend support APIs (e.g., pre-signed URLs for uploads)
317
+ - **createMiddlewares()**: Optional method to create HTTP middleware for request processing (e.g., authentication, authorization, request validation)
318
+
319
+ **🔴 CRITICAL: Separation Between API Layer and Integration Layer**
320
+
321
+ **API File Responsibilities** (`integrations/{name}/externalApi.ts` or `integrations/{name}/volcApi.ts`):
322
+ - Construct HTTP requests according to external API documentation
323
+ - Call external APIs and return raw responses
324
+ - **NO data transformation** - return data as-is from external system
325
+ - Define **strict TypeScript types** based on official API documentation:
326
+ - Input parameter types (exactly matching API requirements)
327
+ - Output response types (exactly matching API responses)
328
+ - Handle only HTTP-level errors (network failures, status codes)
329
+
330
+ **Integration File Responsibilities** (`integrations/{name}/index.ts`):
331
+ - Call API file methods to interact with external system
332
+ - **Transform external API responses** into internal event format
333
+ - Map external data structures to business event entity fields
334
+ - Create integration events following unified sequence
335
+ - Handle business-level error scenarios
336
+
337
+ **Example:**
338
+ ```typescript
339
+ // ❌ WRONG: API file transforms data
340
+ // integrations/tts/externalApi.ts
341
+ export async function callTTSApi(params: TTSParams): Promise<{ audioUrl: string }> {
342
+ const response = await fetch(...)
343
+ const data = await response.json()
344
+ return { audioUrl: data.result.url } // ❌ Transformation in API file
345
+ }
346
+
347
+ // ✅ CORRECT: API file returns raw response
348
+ // integrations/tts/externalApi.ts
349
+ export type TTSApiResponse = {
350
+ taskId: string
351
+ status: string
352
+ result?: {
353
+ url: string
354
+ duration: number
355
+ }
356
+ }
357
+
358
+ export async function callTTSApi(params: TTSParams): Promise<TTSApiResponse> {
359
+ const response = await fetch(...)
360
+ return await response.json() // ✅ Raw response with strict types
361
+ }
362
+
363
+ // ✅ CORRECT: Integration file transforms data
364
+ // integrations/tts/index.ts
365
+ const apiResponse = await callTTSApi(requestParams)
366
+
367
+ // Transform to internal event format
368
+ await this.createIntegrationEvent(controller, apiCall.id, apiResponse.taskId, 'initialized', {
369
+ taskId: apiResponse.taskId, // Map to event fields
370
+ status: apiResponse.status,
371
+ audioUrl: apiResponse.result?.url // Extract what business needs
372
+ }, null)
373
+ ```
295
374
 
296
375
  # Task 4: Integration Implementation
297
376
 
@@ -810,14 +889,26 @@ mkdir -p integrations/{integration-name}
810
889
 
811
890
  ### 4.4.2 Create External API Wrapper (if no SDK)
812
891
 
892
+ **🔴 CRITICAL: API File Responsibilities**
893
+
894
+ This file MUST:
895
+ - Return raw API responses without transformation
896
+ - Define strict TypeScript types matching official API documentation
897
+ - Handle only HTTP-level errors
898
+
899
+ This file MUST NOT:
900
+ - Transform data to internal event format (that's integration file's job)
901
+ - Create any integration events
902
+ - Handle business logic
903
+
813
904
  Create `integrations/{integration-name}/externalApi.ts`:
814
905
 
815
906
  ```typescript
816
907
  /**
817
908
  * External API wrapper for {System Name}
818
909
  *
819
- * This module encapsulates all external API calls to keep the integration
820
- * logic clean and testable.
910
+ * CRITICAL: This file returns raw API responses with strict types.
911
+ * NO data transformation - integration file handles that.
821
912
  */
822
913
 
823
914
  export type ExternalApiConfig = {
@@ -825,16 +916,37 @@ export type ExternalApiConfig = {
825
916
  baseUrl?: string
826
917
  }
827
918
 
919
+ /**
920
+ * Request parameters - MUST match external API documentation exactly
921
+ */
828
922
  export type RequestParams = {
829
- // Define request parameters
923
+ // Define according to official API docs
924
+ param1: string
925
+ param2: number
926
+ // ... more parameters
830
927
  }
831
928
 
929
+ /**
930
+ * Response data - MUST match external API response exactly
931
+ */
832
932
  export type ResponseData = {
833
- // Define response structure
933
+ // Define according to official API docs
934
+ taskId: string
935
+ status: 'pending' | 'processing' | 'completed' | 'failed'
936
+ result?: {
937
+ // Define result structure from API docs
938
+ data: any
939
+ }
940
+ error?: {
941
+ code: string
942
+ message: string
943
+ }
834
944
  }
835
945
 
836
946
  /**
837
947
  * Call external API
948
+ *
949
+ * Returns raw API response - NO transformation
838
950
  */
839
951
  export async function callExternalApi(
840
952
  params: RequestParams,
@@ -861,7 +973,8 @@ export async function callExternalApi(
861
973
  throw new Error(`API call failed: ${response.statusText}`)
862
974
  }
863
975
 
864
- const data = await response.json()
976
+ // Return raw response - integration file will transform it
977
+ const data: ResponseData = await response.json()
865
978
  return data
866
979
  } catch (error: any) {
867
980
  console.error('[ExternalAPI] Call failed:', error.message)
@@ -871,12 +984,26 @@ export async function callExternalApi(
871
984
 
872
985
  /**
873
986
  * Query external status
987
+ *
988
+ * Returns raw status response - NO transformation
874
989
  */
875
990
  export async function queryExternalStatus(
876
991
  taskId: string,
877
992
  config?: ExternalApiConfig
878
993
  ): Promise<ResponseData> {
879
- // Similar implementation
994
+ // Similar implementation - return raw response
995
+ const apiKey = config?.apiKey
996
+ const baseUrl = config?.baseUrl
997
+
998
+ const response = await fetch(`${baseUrl}/v1/status/${taskId}`, {
999
+ method: 'GET',
1000
+ headers: {
1001
+ 'Authorization': `Bearer ${apiKey}`
1002
+ }
1003
+ })
1004
+
1005
+ const data: ResponseData = await response.json()
1006
+ return data // Raw response
880
1007
  }
881
1008
  ```
882
1009
 
@@ -891,8 +1018,9 @@ Create `integrations/{integration-name}/index.ts`:
891
1018
  * Purpose: {Brief description}
892
1019
  *
893
1020
  * Features:
894
- * - Listen to {Entity} mutations and trigger external API calls
895
- * - Convert external status updates to internal events
1021
+ * - Listen to APICall entity creation and trigger external API calls
1022
+ * - Transform external API responses to internal event format
1023
+ * - Create integration events following unified sequence
896
1024
  * - Provide manual status refresh API
897
1025
  * - Factory function pattern for configuration flexibility
898
1026
  */
@@ -1086,31 +1214,42 @@ export function create{IntegrationName}Integration(config: {IntegrationName}Conf
1086
1214
  requestParams
1087
1215
  })
1088
1216
 
1089
- // Step 2: Call external API and create unified event sequence
1217
+ // Step 2: Call external API (returns raw response with strict types)
1090
1218
  try {
1091
- const result = await callExternalApi(requestParams)
1219
+ const apiResponse = await callExternalApi(requestParams)
1220
+ // apiResponse has type ResponseData from API file
1092
1221
 
1222
+ // Step 3: Transform external response to internal event format
1093
1223
  // Determine externalId: use API's task ID or generate one
1094
- const externalId = result.taskId || result.id || crypto.randomUUID()
1224
+ const externalId = apiResponse.taskId
1095
1225
 
1096
1226
  console.log('[{IntegrationName}] External API called', {
1097
1227
  apiCallId: apiCall.id,
1098
1228
  externalId,
1099
- hasTaskId: !!(result.taskId || result.id)
1229
+ hasTaskId: !!(apiResponse.taskId)
1100
1230
  })
1101
1231
 
1232
+ // Step 4: Transform and create 'initialized' event
1233
+ // Map external fields to internal event format
1234
+ const eventData = {
1235
+ taskId: apiResponse.taskId,
1236
+ status: apiResponse.status,
1237
+ // Map other fields as needed for business logic
1238
+ rawResponse: apiResponse // Keep raw response if needed
1239
+ }
1240
+
1102
1241
  // ALWAYS create 'initialized' event with both entityId and externalId
1103
1242
  await self.createIntegrationEvent(
1104
1243
  this,
1105
1244
  apiCall.id, // entityId - APICall's id
1106
1245
  externalId, // externalId - task ID or generated UUID
1107
1246
  'initialized',
1108
- result,
1247
+ eventData, // Transformed data, not raw response
1109
1248
  null
1110
1249
  )
1111
1250
 
1112
1251
  // For sync APIs (no task ID): immediately create processing and completed events
1113
- if (!result.taskId && !result.id) {
1252
+ if (!apiResponse.taskId) {
1114
1253
  // Immediately create processing event
1115
1254
  await self.createIntegrationEvent(
1116
1255
  this,
@@ -1121,19 +1260,26 @@ export function create{IntegrationName}Integration(config: {IntegrationName}Conf
1121
1260
  null
1122
1261
  )
1123
1262
 
1124
- // Immediately create completed event
1263
+ // Transform completion data
1264
+ const completedData = {
1265
+ status: 'completed',
1266
+ result: apiResponse.result,
1267
+ // Map other completion fields
1268
+ }
1269
+
1270
+ // Immediately create completed event with transformed data
1125
1271
  await self.createIntegrationEvent(
1126
1272
  this,
1127
1273
  null,
1128
1274
  externalId,
1129
1275
  'completed',
1130
- result,
1276
+ completedData, // Transformed data
1131
1277
  null
1132
1278
  )
1133
1279
 
1134
1280
  console.log('[{IntegrationName}] Sync API completed with unified event sequence')
1135
1281
  }
1136
- // For async APIs: result will come later via webhook or polling
1282
+ // For async APIs: status will come later via webhook or polling
1137
1283
 
1138
1284
  } catch (error: any) {
1139
1285
  console.error('[{IntegrationName}] External API call failed', {
@@ -1191,10 +1337,11 @@ export function create{IntegrationName}Integration(config: {IntegrationName}Conf
1191
1337
  apiCallId: string
1192
1338
  }) {
1193
1339
  try {
1194
- await self.checkAndUpdateStatus(params.apiCallId)
1340
+ const apiResponse = await self.checkAndUpdateStatus(params.apiCallId)
1195
1341
  return {
1196
1342
  success: true,
1197
- message: 'Status check triggered, integration event created'
1343
+ message: 'Status check triggered, integration event created',
1344
+ response: apiResponse
1198
1345
  }
1199
1346
  } catch (error: any) {
1200
1347
  console.error('[{IntegrationName}] Failed to query status', {
@@ -1322,25 +1469,36 @@ export function create{IntegrationName}Integration(config: {IntegrationName}Conf
1322
1469
  throw new Error(`No external ID found for APICall: ${apiCallId}`)
1323
1470
  }
1324
1471
 
1325
- // Query external system
1326
- const result = await queryExternalStatus(externalId)
1472
+ // Query external system (returns raw response)
1473
+ const apiResponse = await queryExternalStatus(externalId)
1327
1474
 
1328
1475
  console.log('[{IntegrationName}] Status checked', {
1329
1476
  apiCallId,
1330
1477
  externalId,
1331
- status: result.status
1478
+ status: apiResponse.status
1332
1479
  })
1333
1480
 
1481
+ // Transform external response to internal event format
1482
+ const eventType = apiResponse.status // Map external status to event type
1483
+ const eventData = apiResponse.result ? {
1484
+ status: apiResponse.status,
1485
+ result: apiResponse.result,
1486
+ // Map other fields as needed
1487
+ } : null
1488
+ const errorMessage = apiResponse.error?.message || null
1489
+
1334
1490
  // Create integration event based on status
1335
1491
  // entityId is null because APICall already exists (not 'initialized' event)
1336
1492
  await this.createIntegrationEvent(
1337
1493
  this.controller,
1338
1494
  null, // entityId - not needed for status updates
1339
1495
  externalId, // externalId - to match with existing APICall
1340
- result.status, // eventType - 'processing' | 'completed' | 'failed'
1341
- result.data || null,
1342
- result.error || null
1496
+ eventType, // eventType - 'processing' | 'completed' | 'failed'
1497
+ eventData, // Transformed data, not raw response
1498
+ errorMessage // Extracted error message
1343
1499
  )
1500
+
1501
+ return apiResponse
1344
1502
  }
1345
1503
  }
1346
1504
  }
@@ -1666,6 +1824,101 @@ createAPIs() {
1666
1824
  }
1667
1825
  ```
1668
1826
 
1827
+ ## Integration Middleware
1828
+
1829
+ **🔴 CRITICAL: Middleware for Request Processing**
1830
+
1831
+ Integrations can provide HTTP middleware to handle cross-cutting concerns like authentication, authorization, request validation, logging, etc.
1832
+
1833
+ **When to use middleware:**
1834
+ - Authentication and authorization (e.g., JWT verification)
1835
+ - Request/response transformation
1836
+ - Logging and monitoring
1837
+ - Rate limiting
1838
+ - CORS handling
1839
+ - Custom header processing
1840
+
1841
+ **Middleware execution:**
1842
+ - Middleware runs BEFORE API handlers
1843
+ - Can access and modify request context
1844
+ - Can short-circuit request processing
1845
+ - Can inject context data for API handlers
1846
+
1847
+ **Example: Authentication Middleware**
1848
+
1849
+ ```typescript
1850
+ export function createAuthIntegration(config: AuthIntegrationConfig) {
1851
+ return class AuthIntegration implements IIntegration {
1852
+ createMiddlewares(): MiddlewareHandler[] {
1853
+ return [
1854
+ async (c, next) => {
1855
+ // Extract token from multiple sources
1856
+ let token: string | undefined
1857
+ const authToken = c.req.query('authToken')
1858
+ const authHeader = c.req.header('authorization')
1859
+ const cookieHeader = c.req.header('cookie')
1860
+
1861
+ if (authToken) {
1862
+ token = authToken
1863
+ } else if (authHeader?.startsWith('Bearer ')) {
1864
+ token = authHeader.substring(7)
1865
+ } else if (cookieHeader) {
1866
+ const allCookies: Record<string, string> = {}
1867
+ cookieHeader.split(';').forEach((cookie) => {
1868
+ const [name, value] = cookie.trim().split('=')
1869
+ if (name && value) {
1870
+ allCookies[name] = decodeURIComponent(value)
1871
+ }
1872
+ })
1873
+ token = allCookies.token
1874
+ }
1875
+
1876
+ // Verify token and inject userId into context
1877
+ if (token) {
1878
+ try {
1879
+ const decoded = await verify(token, jwtSecret) as any
1880
+ c.set('userId', decoded.userId)
1881
+ } catch (err) {
1882
+ console.log('Invalid token', err)
1883
+ }
1884
+ }
1885
+
1886
+ await next()
1887
+ }
1888
+ ]
1889
+ }
1890
+
1891
+ createAPIs() {
1892
+ return {
1893
+ login: createAPI(
1894
+ async function(context, params: { identifier: string; password: string }) {
1895
+ // Authenticate user
1896
+ // Generate JWT token
1897
+ // Return token to client
1898
+ },
1899
+ { allowAnonymous: true }
1900
+ )
1901
+ }
1902
+ }
1903
+ }
1904
+ }
1905
+ ```
1906
+
1907
+ **Key principles:**
1908
+ - **Middleware is optional**: Only use when needed for cross-cutting concerns
1909
+ - **Order matters**: Middleware executes in the order returned from createMiddlewares()
1910
+ - **Context injection**: Use `c.set()` to inject data into context for API handlers
1911
+ - **Non-blocking**: Always call `await next()` to continue the middleware chain
1912
+ - **Error handling**: Middleware can catch and handle errors before they reach API handlers
1913
+
1914
+ **Common use cases:**
1915
+ 1. **Authentication** (`integrations/auth/index.ts`): JWT verification, session management
1916
+ 2. **Authorization**: Role-based access control, permission checks
1917
+ 3. **Request validation**: Schema validation, sanitization
1918
+ 4. **Logging**: Request/response logging, performance monitoring
1919
+ 5. **Rate limiting**: Throttle requests per user/IP
1920
+ 6. **CORS**: Handle cross-origin requests
1921
+
1669
1922
  ## Example: Stripe Payment Integration
1670
1923
 
1671
1924
  ```typescript
@@ -553,6 +553,80 @@ git commit -m "feat: Task 1.2 - Complete functional requirements analysis"
553
553
 
554
554
  **External side-effects requiring third-party APIs must be identified separately.**
555
555
 
556
+ ### Special Case: Authentication Integration
557
+
558
+ **⚠️ CRITICAL: Authentication for Basic Module**
559
+
560
+ If the current module is "basic", authentication (login/registration) requirements must be handled as an integration named **"auth"**.
561
+
562
+ **Authentication vs Business Logic:**
563
+ 1. **Authentication (Integration)**:
564
+ - User login with credentials
565
+ - User registration/signup
566
+ - Password reset/recovery
567
+ - OAuth/social login
568
+ - These are NOT part of business logic - treat as integration "auth"
569
+
570
+ 2. **User Management (Business Logic)**:
571
+ - Administrator creates user accounts
572
+ - Administrator updates user information
573
+ - Administrator deactivates users
574
+ - These ARE business data operations - design as regular interactions
575
+
576
+ **Handling Authentication Requirements:**
577
+
578
+ **Case 1: User explicitly described login/registration requirements**
579
+ - Extract ALL authentication-related requirements into integration "auth"
580
+ - Do NOT create interactions for login/registration in Task 1.5
581
+ - Document the specific authentication flow in integration.json
582
+
583
+ **Case 2: User did NOT mention login/registration**
584
+ - For "basic" module, assume authentication is needed
585
+ - Design a simple password-based authentication integration
586
+ - Use default specification (see example below)
587
+
588
+ **Default Password Authentication Integration:**
589
+ ```json
590
+ {
591
+ "id": "INT_AUTH",
592
+ "name": "auth",
593
+ "type": "stateful-system",
594
+ "type_explanation": "Authentication system maintains session state and user credentials. Requires bidirectional communication for login, registration, and session management.",
595
+ "external_system": "Authentication Service",
596
+ "purpose": "Handle user authentication and registration for the system",
597
+ "related_requirements": ["User login", "User registration"],
598
+ "flow_description": "For registration: User provides username/email and password in current system. System sends registration request (username, password) to authentication service. Authentication service validates uniqueness, hashes password, creates auth record, and returns user credentials. Current system receives response and creates User entity with returned user ID. For login: User provides credentials in current system. System sends login request to authentication service. Authentication service validates credentials and returns session token. Current system stores session and grants access.",
599
+ "user_interactions": {
600
+ "in_current_system": [
601
+ "User enters username/email and password for registration",
602
+ "User enters credentials for login",
603
+ "User views login success/failure messages"
604
+ ],
605
+ "in_external_system": [
606
+ "Authentication service validates credential format",
607
+ "Authentication service checks username/email uniqueness",
608
+ "Authentication service hashes password securely",
609
+ "Authentication service generates session tokens"
610
+ ]
611
+ },
612
+ "current_system_data": [
613
+ {
614
+ "entity": "User",
615
+ "properties": ["id", "username", "email"],
616
+ "usage": "Created after successful registration using ID returned from authentication service. Read during login to fetch user profile."
617
+ }
618
+ ],
619
+ "notes": "This is a default password-based authentication. If user requirements specify OAuth, biometric, or other authentication methods, adjust accordingly."
620
+ }
621
+ ```
622
+
623
+ **Important Notes:**
624
+ - Authentication integration should ALWAYS be named "auth" for consistency
625
+ - Do NOT create "User" entity properties for password, passwordHash, or session tokens - these belong to the auth integration
626
+ - User entity should only contain business-related properties (username, email, profile info)
627
+ - In Task 1.4, do NOT create entities for authentication (no AuthSession, no Credentials entities)
628
+ - In Task 1.5, do NOT create login/registration interactions - these are handled by the auth integration
629
+
556
630
  ### What is NOT an Integration
557
631
 
558
632
  **⚠️ CRITICAL: Intra-Project Module Access**
@@ -1280,6 +1354,22 @@ git commit -m "feat: Task 1.4 - Complete data concept extraction"
1280
1354
  - Interaction IDs must be semantic names (e.g., "BorrowBook", "ViewAvailableBooks") not codes (e.g., "I001")
1281
1355
  - ❌ If a requirement has role="System", it was incorrectly created - SKIP it, do NOT create interaction
1282
1356
 
1357
+ **⚠️ IMPORTANT: Distinguishing Data Access Constraints**
1358
+
1359
+ For read-type interaction requirements with access restrictions, distinguish between:
1360
+
1361
+ 1. **Business Rules** - Constraints on whether the read operation can execute
1362
+ - Example: "Only administrators can view XXX entity"
1363
+ - Controls who can perform the action
1364
+ - Should be specified in the `conditions` field
1365
+
1366
+ 2. **Data Policy** - Constraints on the scope of data returned
1367
+ - Example: "Can only view own XXX entities" or "Can only view YYY fields of XXX entity"
1368
+ - Controls what data is accessible after the operation is permitted
1369
+ - Should be specified in the `dataConstraints` field
1370
+
1371
+ These must be separated as they are implemented differently in subsequent phases.
1372
+
1283
1373
  **⚠️ IMPORTANT: External Integration Interactions**
1284
1374
 
1285
1375
  If Task 1.4 includes API Call entities, design error handling interactions:
@@ -73,7 +73,9 @@
73
73
  "Bash(else echo \"VOLC_ACCESS_KEY_ID is set\")",
74
74
  "Bash(fi:*)",
75
75
  "Bash(if [ -z \"$VOLC_SECRET_ACCESS_KEY\" ])",
76
- "Bash(then echo \"VOLC_SECRET_ACCESS_KEY is empty or not set\")"
76
+ "Bash(then echo \"VOLC_SECRET_ACCESS_KEY is empty or not set\")",
77
+ "Bash(npm run)",
78
+ "Bash(chmod:*)"
77
79
  ],
78
80
  "deny": [],
79
81
  "ask": []
package/package.json CHANGED
@@ -39,7 +39,7 @@
39
39
  "sync:agent": "tsx scripts/sync-agent-files.ts",
40
40
  "release": "release-it"
41
41
  },
42
- "version": "0.8.3",
42
+ "version": "0.8.5",
43
43
  "main": "dist/index.js",
44
44
  "files": [
45
45
  "dist",
@@ -1,19 +0,0 @@
1
- 我们的项目所使用的框架是一个叫做 Interaqt 的后端响应式数据框架,它会自动根据应用中的数据变化及响应式数据的定义执行相应的数据变化。它只负责处理一般的业务逻辑中表达的数据逻辑,例如一般业务逻辑中会用到 平均/综合 等计算,还有常见的基于状态机等业务逻辑表达等。对于一些非一般的能力需求,例如 大模型生图、大模型生视频、tts、发送邮件、发送信息、完整支付系统等。它需要借助外部系统/api 来完成。
2
- 我们设计了一个叫做 integration 的概念,专门用来对接当前系统和外部的api/系统。它通过 interaqt 框架提供的数据观察机制,来观察数据变化,根据数据变化来决定如何调用外部的 api。同时通过 webhook 等机制来接受外部的事件,将外部事件同步回系统中,触发响应式的业务逻辑。
3
-
4
- 我们将 integration 需要集成的功能分成了三种类别:
5
- 1. 调用外部的 api,为了获得一个具体的返回。例如 tts,大模型生图等。
6
- 2. 执行某种副作用,例如发送邮件、发送 im 消息等。
7
- 3. 对接其他有状态的系统,例如支付系统等。
8
-
9
- 现在我们需要指导 claude code 的 sub agent 合理地识别需要的外部服务以及如何自己实现 integration。
10
- 1. 指导 `.claude/agents/requirements-analysis-handler.md` 在需求分析阶段,正确分析出 integration 的类型。并在相应的输出的文档中,设计一个字段来表达 integration 的类型。
11
- 2. 指导 `.claude/agents/implement-design-handler.md` 在设计数据的时候,根据如下原则进行设计:
12
- 2.1. 不管是哪种类型,都会涉及到对外部 api 的调用,例如执行副作用,也会有副作用 api 的调用。所以我们应该对每一个 api 的调用都设计一个 `{xxx}APICall` 的 entity,它负责记录这次 api 调用的参数、状态、返回值、调用时间等。
13
- 2.2. 同时设计一个相应的 integraion event entity,当我们通过 webhook 或者自己通过接口查询到 api 调用状态的变化时,在系统内创建相应的 api call result event 事件。并且将上一步创建的 `{xxx}APICall` entity 的 status 和 data 字段写成基于 integration event entity 的 computation,这样就完整符合了框架的响应式范式。也记录了所有应该记录的数据,增强了系统的健壮性。
14
- 2.3. 如果当前场景是希望基于这个 integration 获得具体的返回值,那么意味着我们系统内的业务数据对这个 `{xxx}APICall` 的 entity 是有依赖的,应该写成基于 `{xxx}APICall` 的 computation。例如我们的有一个 `Greeting` entity,其中有个 `voiceUrl` property 是需要利用外部 tts 能力将文本转化为语音。那么 `Greeting.voiceUrl` 就应该表达为基于 `{ttsAPICall}` entity 的 computation。如果是纯副作用类型等的调用,就不需要了。注意,这种情况下,还需要建立相应的 entity 和 api call entity 之间的关系,才能查找到正确的数据。
15
- 2.4. `.claude/agents/implement-design-handler.md` 在做 data-design 的时候,应该明确表达出来:1. 设计的哪些实体是 api call 类型的 entity,哪些实体是 api call result event 实体。2. 系统内的业务数据如果需要 api 的返回结果,那么应该依赖正确的 api call entity。
16
- 3. 指导 `.claude/agents/code-generation-handler.md` 在实现阶段,在写测试用例时,完全可以通过创建正确的 api call result event 来模拟 api 的调用,完整验证系统的内部逻辑的正确性。不需要等到 integration 的真实实现。
17
- 4. 指导 `.claude/agents/error-check-handler.md` 在合适的阶段对 integration 相关的设计做错误检查。
18
-
19
- 你充分理解上面的所有思路,并且修改相应的 sub agent 文件来达成目标。
@@ -1,34 +0,0 @@
1
- 在需求分析的过程中,我们已经在 `.claude/agents/requirements-analysis-handle.md` 中提出了一种以最终"读需求"为起点,反向衍生出 创建/修改/删除 需求的方法。
2
- 在实际的探索中发现:用户可能会在输入阶段就按照自己的真实需要描述了一部分流程了,这个流程里面包含了
3
-
4
- - 进行增删改查交互的步骤,通常是有依赖顺序的
5
- - 进行交互的角色
6
- - 交互对数据产生的具体增删改变化
7
- 例子:在 `requirements/requirements.md` 中,用户直接在描述中叙述如何创建版本 rollback 时数据应该如何变化。这里面就已经包含了:"创建版本-回退版本"的具体流程。
8
-
9
- 这个流程本身也是用户的一种需求,我们需要严格遵照他的指示实现。
10
- 现在,你来将 `.claude/agents/requirements-analysis-handle.md` 完全重写。要求:
11
- 1. 先将 `.claude/agents/requirements-analysis-handle.md` 关于目标补充、数据分析、交互动作定义的步骤、设计的数据结构完全提取出来,这些部分已经很稳定,可以留作复用。
12
-
13
- 用下面的这些步骤作为重写的分析步骤:
14
- 1. 对用户的输入进行分析,识别出其中的:
15
- - 目标。通常是和具体软件功能无关,和现实中真实目标相关的描述。例如“管理图书”,“管理员工”,“和好友实时交流”等。
16
- - 流程。例如 "发出好友申请-收到申请后同意/收到申请后拒绝"。
17
- - 数据概念。(使用原文档里数据概念)。
18
- 2. 进行常见的目标补充(使用原文档里的方法)。
19
- 3. 进行完整的数据概念设计(复用原文档中的方法)。
20
- 4. 进行流程和交互动作设计。
21
- 3.1. 优先整合用户描述中的流程。并且看流程达到了哪些目标。
22
- 3.2. 为没有达到的目标设计流程。
23
- 3.2.1. 流程是一系列有顺序的交互的总和,其中可以包含可能的分支或者循环。交互的具体定义仍然采用原文档中的定义。
24
- 3.2.2. 流程的最后仍然应该是一个"读需求"作为结尾,来真正满足目标。但前面需要补充常见的创建等逻辑。
25
- 3.3. 注意,流程中的每一步应该都是一个交互动作,交互动作要使用原文档中 interactions-design.json 中的数据结构表示。要包含完整 `data.creates`/`data.updates`/`data.deletes`。从用户的输入中直接提取的出来的流程也要完善这些信息。
26
- 5. 设计完流程后。从数据的角度来补充用户可能需要的其他增删改查需求:
27
- 4.1. 为每一个修改和删除的交互考虑用户作为人类,是否需要经过查看/搜索再进行决策的需求。
28
- 4.2. 为每一个创建的交互动作考虑,数据在现实中是否允许修改/删除。如果允许就应该增加相应的需求。
29
- 6. 最终产出的 interactions-design.json 仍然使用原文档里的 interaction 数据结构,但以流程为组来组织。
30
-
31
- 注意,在每一个步骤中,都要给出清晰的数据结构定义,用 json 来写。
32
- 整体用简洁的英语完成文档重写。注意原本文档中的在关键步骤 update docs/{module}.status.json 仍然按照原文档的方式写。
33
-
34
-