interaqt 0.8.4 → 0.8.6

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.
@@ -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:**
@@ -307,6 +308,22 @@ npm run generate-frontend-api
307
308
  - Test on different screen sizes
308
309
  - Ensure mobile-friendly layouts
309
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
+
310
327
  **🔴 CRITICAL: Completeness Check**
311
328
 
312
329
  Before marking Step 4 complete, verify:
@@ -302,6 +302,7 @@ interface IIntegration {
302
302
  setup?(controller: Controller): Promise<any> // Setup phase with controller access
303
303
  createSideEffects(): RecordMutationSideEffect[] // Listen to data mutations and create events
304
304
  createAPIs?(): APIs // Expose custom APIs (e.g., webhook endpoints)
305
+ createMiddlewares?(): MiddlewareHandler[] // Optional: Create HTTP middleware for request processing
305
306
  }
306
307
  ```
307
308
 
@@ -313,6 +314,63 @@ interface IIntegration {
313
314
  1. Webhook endpoints to receive external system callbacks
314
315
  2. Manual trigger/query APIs for status checks and retries
315
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
+ ```
316
374
 
317
375
  # Task 4: Integration Implementation
318
376
 
@@ -831,14 +889,26 @@ mkdir -p integrations/{integration-name}
831
889
 
832
890
  ### 4.4.2 Create External API Wrapper (if no SDK)
833
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
+
834
904
  Create `integrations/{integration-name}/externalApi.ts`:
835
905
 
836
906
  ```typescript
837
907
  /**
838
908
  * External API wrapper for {System Name}
839
909
  *
840
- * This module encapsulates all external API calls to keep the integration
841
- * logic clean and testable.
910
+ * CRITICAL: This file returns raw API responses with strict types.
911
+ * NO data transformation - integration file handles that.
842
912
  */
843
913
 
844
914
  export type ExternalApiConfig = {
@@ -846,16 +916,37 @@ export type ExternalApiConfig = {
846
916
  baseUrl?: string
847
917
  }
848
918
 
919
+ /**
920
+ * Request parameters - MUST match external API documentation exactly
921
+ */
849
922
  export type RequestParams = {
850
- // Define request parameters
923
+ // Define according to official API docs
924
+ param1: string
925
+ param2: number
926
+ // ... more parameters
851
927
  }
852
928
 
929
+ /**
930
+ * Response data - MUST match external API response exactly
931
+ */
853
932
  export type ResponseData = {
854
- // 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
+ }
855
944
  }
856
945
 
857
946
  /**
858
947
  * Call external API
948
+ *
949
+ * Returns raw API response - NO transformation
859
950
  */
860
951
  export async function callExternalApi(
861
952
  params: RequestParams,
@@ -882,7 +973,8 @@ export async function callExternalApi(
882
973
  throw new Error(`API call failed: ${response.statusText}`)
883
974
  }
884
975
 
885
- const data = await response.json()
976
+ // Return raw response - integration file will transform it
977
+ const data: ResponseData = await response.json()
886
978
  return data
887
979
  } catch (error: any) {
888
980
  console.error('[ExternalAPI] Call failed:', error.message)
@@ -892,12 +984,26 @@ export async function callExternalApi(
892
984
 
893
985
  /**
894
986
  * Query external status
987
+ *
988
+ * Returns raw status response - NO transformation
895
989
  */
896
990
  export async function queryExternalStatus(
897
991
  taskId: string,
898
992
  config?: ExternalApiConfig
899
993
  ): Promise<ResponseData> {
900
- // 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
901
1007
  }
902
1008
  ```
903
1009
 
@@ -912,8 +1018,9 @@ Create `integrations/{integration-name}/index.ts`:
912
1018
  * Purpose: {Brief description}
913
1019
  *
914
1020
  * Features:
915
- * - Listen to {Entity} mutations and trigger external API calls
916
- * - 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
917
1024
  * - Provide manual status refresh API
918
1025
  * - Factory function pattern for configuration flexibility
919
1026
  */
@@ -1107,31 +1214,42 @@ export function create{IntegrationName}Integration(config: {IntegrationName}Conf
1107
1214
  requestParams
1108
1215
  })
1109
1216
 
1110
- // Step 2: Call external API and create unified event sequence
1217
+ // Step 2: Call external API (returns raw response with strict types)
1111
1218
  try {
1112
- const result = await callExternalApi(requestParams)
1219
+ const apiResponse = await callExternalApi(requestParams)
1220
+ // apiResponse has type ResponseData from API file
1113
1221
 
1222
+ // Step 3: Transform external response to internal event format
1114
1223
  // Determine externalId: use API's task ID or generate one
1115
- const externalId = result.taskId || result.id || crypto.randomUUID()
1224
+ const externalId = apiResponse.taskId
1116
1225
 
1117
1226
  console.log('[{IntegrationName}] External API called', {
1118
1227
  apiCallId: apiCall.id,
1119
1228
  externalId,
1120
- hasTaskId: !!(result.taskId || result.id)
1229
+ hasTaskId: !!(apiResponse.taskId)
1121
1230
  })
1122
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
+
1123
1241
  // ALWAYS create 'initialized' event with both entityId and externalId
1124
1242
  await self.createIntegrationEvent(
1125
1243
  this,
1126
1244
  apiCall.id, // entityId - APICall's id
1127
1245
  externalId, // externalId - task ID or generated UUID
1128
1246
  'initialized',
1129
- result,
1247
+ eventData, // Transformed data, not raw response
1130
1248
  null
1131
1249
  )
1132
1250
 
1133
1251
  // For sync APIs (no task ID): immediately create processing and completed events
1134
- if (!result.taskId && !result.id) {
1252
+ if (!apiResponse.taskId) {
1135
1253
  // Immediately create processing event
1136
1254
  await self.createIntegrationEvent(
1137
1255
  this,
@@ -1142,19 +1260,26 @@ export function create{IntegrationName}Integration(config: {IntegrationName}Conf
1142
1260
  null
1143
1261
  )
1144
1262
 
1145
- // 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
1146
1271
  await self.createIntegrationEvent(
1147
1272
  this,
1148
1273
  null,
1149
1274
  externalId,
1150
1275
  'completed',
1151
- result,
1276
+ completedData, // Transformed data
1152
1277
  null
1153
1278
  )
1154
1279
 
1155
1280
  console.log('[{IntegrationName}] Sync API completed with unified event sequence')
1156
1281
  }
1157
- // For async APIs: result will come later via webhook or polling
1282
+ // For async APIs: status will come later via webhook or polling
1158
1283
 
1159
1284
  } catch (error: any) {
1160
1285
  console.error('[{IntegrationName}] External API call failed', {
@@ -1212,10 +1337,11 @@ export function create{IntegrationName}Integration(config: {IntegrationName}Conf
1212
1337
  apiCallId: string
1213
1338
  }) {
1214
1339
  try {
1215
- await self.checkAndUpdateStatus(params.apiCallId)
1340
+ const apiResponse = await self.checkAndUpdateStatus(params.apiCallId)
1216
1341
  return {
1217
1342
  success: true,
1218
- message: 'Status check triggered, integration event created'
1343
+ message: 'Status check triggered, integration event created',
1344
+ response: apiResponse
1219
1345
  }
1220
1346
  } catch (error: any) {
1221
1347
  console.error('[{IntegrationName}] Failed to query status', {
@@ -1343,25 +1469,36 @@ export function create{IntegrationName}Integration(config: {IntegrationName}Conf
1343
1469
  throw new Error(`No external ID found for APICall: ${apiCallId}`)
1344
1470
  }
1345
1471
 
1346
- // Query external system
1347
- const result = await queryExternalStatus(externalId)
1472
+ // Query external system (returns raw response)
1473
+ const apiResponse = await queryExternalStatus(externalId)
1348
1474
 
1349
1475
  console.log('[{IntegrationName}] Status checked', {
1350
1476
  apiCallId,
1351
1477
  externalId,
1352
- status: result.status
1478
+ status: apiResponse.status
1353
1479
  })
1354
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
+
1355
1490
  // Create integration event based on status
1356
1491
  // entityId is null because APICall already exists (not 'initialized' event)
1357
1492
  await this.createIntegrationEvent(
1358
1493
  this.controller,
1359
1494
  null, // entityId - not needed for status updates
1360
1495
  externalId, // externalId - to match with existing APICall
1361
- result.status, // eventType - 'processing' | 'completed' | 'failed'
1362
- result.data || null,
1363
- result.error || null
1496
+ eventType, // eventType - 'processing' | 'completed' | 'failed'
1497
+ eventData, // Transformed data, not raw response
1498
+ errorMessage // Extracted error message
1364
1499
  )
1500
+
1501
+ return apiResponse
1365
1502
  }
1366
1503
  }
1367
1504
  }
@@ -1687,6 +1824,101 @@ createAPIs() {
1687
1824
  }
1688
1825
  ```
1689
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
+
1690
1922
  ## Example: Stripe Payment Integration
1691
1923
 
1692
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**
@@ -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/dist/index.js CHANGED
@@ -93,7 +93,7 @@ const Ie = /^[a-zA-Z0-9_]+$/;
93
93
  class I {
94
94
  // for Merged Entity
95
95
  constructor(t, e) {
96
- this._type = "Entity", this._options = e, this.uuid = k(e), this.name = t.name, this.properties = t.properties || [], this.computation = t.computation, this.baseEntity = t.baseEntity, this.matchExpression = t.matchExpression, this.inputEntities = t.inputEntities;
96
+ this._type = "Entity", this._options = e, this.uuid = k(e), this.name = t.name, this.properties = t.properties || [], this.computation = t.computation, this.baseEntity = t.baseEntity, this.matchExpression = t.matchExpression, this.inputEntities = t.inputEntities, this.commonProperties = t.commonProperties;
97
97
  }
98
98
  static {
99
99
  this.isKlass = !0;
@@ -122,6 +122,15 @@ class I {
122
122
  },
123
123
  defaultValue: () => []
124
124
  },
125
+ commonProperties: {
126
+ type: "Property",
127
+ collection: !0,
128
+ required: !1,
129
+ constraints: {
130
+ eachNameUnique: ({ properties: t }) => new Set(t.map((i) => i.name)).size === t.length
131
+ },
132
+ defaultValue: () => []
133
+ },
125
134
  computation: {
126
135
  type: [],
127
136
  collection: !1,
@@ -390,7 +399,7 @@ class P {
390
399
  if (s.target !== i.target)
391
400
  throw new Error("All inputRelations must have the same target");
392
401
  }
393
- this.inputRelations = t.inputRelations, this.source = i.source, this.target = i.target, this.sourceProperty = t.sourceProperty, this.targetProperty = t.targetProperty, this.type = i.type, this.isTargetReliance = i.isTargetReliance, this._name = t.name;
402
+ this.inputRelations = t.inputRelations, this.source = i.source, this.target = i.target, this.sourceProperty = t.sourceProperty, this.targetProperty = t.targetProperty, this.type = i.type, this.isTargetReliance = i.isTargetReliance, this._name = t.name, this.commonProperties = t.commonProperties;
394
403
  } else if (t.baseRelation) {
395
404
  if (!t.sourceProperty || !t.targetProperty)
396
405
  throw new Error("Filtered relation must have sourceProperty and targetProperty");
@@ -5536,6 +5545,11 @@ function He(u, t, e, i) {
5536
5545
  );
5537
5546
  t.replace(n, u), c !== n && t.add(c);
5538
5547
  const l = pt(u);
5548
+ if (u.commonProperties) {
5549
+ const d = l.filter((h) => u.commonProperties.some((p) => !h.properties.some((y) => y.name === p.name && y.type === p.type)));
5550
+ if (d.length > 0)
5551
+ throw new Error(`Merged ${e} ${u.name} defined commonProperties, but these ${e}s do not have commonProperties: ${d.map((h) => h.name).join(", ")}`);
5552
+ }
5539
5553
  if (l)
5540
5554
  for (const d of l)
5541
5555
  je(