@aifabrix/builder 2.31.1 → 2.32.2

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.
Files changed (118) hide show
  1. package/README.md +9 -9
  2. package/integration/hubspot/README.md +2 -2
  3. package/integration/hubspot/hubspot-deploy-company.json +17 -14
  4. package/integration/hubspot/hubspot-deploy-contact.json +19 -16
  5. package/integration/hubspot/hubspot-deploy-deal.json +21 -18
  6. package/lib/api/types/datasources.types.js +31 -5
  7. package/lib/api/types/wizard.types.js +142 -0
  8. package/lib/api/wizard.api.js +177 -0
  9. package/lib/{app-config.js → app/config.js} +4 -4
  10. package/lib/{app-deploy.js → app/deploy.js} +8 -8
  11. package/lib/app/display.js +90 -0
  12. package/lib/{app-dockerfile.js → app/dockerfile.js} +4 -4
  13. package/lib/{app-down.js → app/down.js} +4 -4
  14. package/lib/app/helpers.js +218 -0
  15. package/lib/app/index.js +298 -0
  16. package/lib/{app-list.js → app/list.js} +6 -6
  17. package/lib/{app-push.js → app/push.js} +4 -4
  18. package/lib/{app-readme.js → app/readme.js} +34 -13
  19. package/lib/{app-register.js → app/register.js} +9 -9
  20. package/lib/{app-rotate-secret.js → app/rotate-secret.js} +10 -10
  21. package/lib/{app-run-helpers.js → app/run-helpers.js} +10 -10
  22. package/lib/{app-run.js → app/run.js} +6 -6
  23. package/lib/{build.js → build/index.js} +59 -32
  24. package/lib/build/package.json +7 -0
  25. package/lib/cli.js +245 -179
  26. package/lib/commands/app.js +3 -3
  27. package/lib/commands/datasource.js +4 -4
  28. package/lib/commands/login-credentials.js +209 -0
  29. package/lib/commands/login-device.js +254 -0
  30. package/lib/commands/login.js +67 -378
  31. package/lib/commands/logout.js +1 -1
  32. package/lib/commands/secrets-set.js +1 -1
  33. package/lib/commands/secure.js +2 -2
  34. package/lib/commands/wizard.js +498 -0
  35. package/lib/{audit-logger.js → core/audit-logger.js} +1 -1
  36. package/lib/{config.js → core/config.js} +28 -26
  37. package/lib/{diff.js → core/diff.js} +157 -72
  38. package/lib/{secrets.js → core/secrets.js} +86 -49
  39. package/lib/{templates.js → core/templates-env.js} +14 -222
  40. package/lib/core/templates.js +279 -0
  41. package/lib/{datasource-deploy.js → datasource/deploy.js} +6 -6
  42. package/lib/{datasource-diff.js → datasource/diff.js} +2 -2
  43. package/lib/datasource/list.js +223 -0
  44. package/lib/{datasource-validate.js → datasource/validate.js} +2 -2
  45. package/lib/{deployer.js → deployment/deployer.js} +48 -18
  46. package/lib/{environment-deploy.js → deployment/environment.js} +163 -84
  47. package/lib/{push.js → deployment/push.js} +1 -1
  48. package/lib/external-system/deploy-helpers.js +145 -0
  49. package/lib/{external-system-deploy.js → external-system/deploy.js} +156 -111
  50. package/lib/external-system/download-helpers.js +114 -0
  51. package/lib/{external-system-download.js → external-system/download.js} +92 -135
  52. package/lib/{external-system-generator.js → external-system/generator.js} +15 -11
  53. package/lib/external-system/test-auth.js +40 -0
  54. package/lib/external-system/test-execution.js +84 -0
  55. package/lib/external-system/test-helpers.js +109 -0
  56. package/lib/{external-system-test.js → external-system/test.js} +174 -192
  57. package/lib/{generator-builders.js → generator/builders.js} +87 -10
  58. package/lib/{generator-external.js → generator/external.js} +115 -52
  59. package/lib/{github-generator.js → generator/github.js} +116 -15
  60. package/lib/{generator-helpers.js → generator/helpers.js} +92 -42
  61. package/lib/{generator.js → generator/index.js} +49 -22
  62. package/lib/{generator-split.js → generator/split.js} +108 -55
  63. package/lib/generator/wizard-prompts.js +357 -0
  64. package/lib/generator/wizard.js +490 -0
  65. package/lib/{infra.js → infrastructure/index.js} +49 -22
  66. package/lib/schema/external-datasource.schema.json +158 -136
  67. package/lib/schema/external-system.schema.json +43 -1
  68. package/lib/utils/api.js +9 -5
  69. package/lib/utils/app-register-api.js +60 -32
  70. package/lib/utils/app-register-auth.js +172 -47
  71. package/lib/utils/app-register-config.js +130 -59
  72. package/lib/utils/app-run-containers.js +29 -8
  73. package/lib/utils/build-helpers.js +1 -1
  74. package/lib/utils/cli-utils.js +78 -30
  75. package/lib/utils/compose-generator.js +145 -65
  76. package/lib/utils/config-paths.js +2 -0
  77. package/lib/utils/deployment-errors.js +1 -1
  78. package/lib/utils/device-code.js +99 -41
  79. package/lib/utils/env-config-loader.js +1 -1
  80. package/lib/utils/env-copy.js +21 -18
  81. package/lib/utils/env-endpoints.js +115 -67
  82. package/lib/utils/env-map.js +13 -14
  83. package/lib/utils/env-ports.js +45 -25
  84. package/lib/utils/env-template.js +84 -42
  85. package/lib/utils/error-formatter.js +26 -9
  86. package/lib/utils/error-formatters/error-parser.js +90 -4
  87. package/lib/utils/error-formatters/http-status-errors.js +54 -17
  88. package/lib/utils/error-formatters/network-errors.js +103 -26
  89. package/lib/utils/external-system-display.js +184 -90
  90. package/lib/utils/external-system-validators.js +164 -42
  91. package/lib/utils/file-upload.js +109 -0
  92. package/lib/utils/health-check.js +199 -83
  93. package/lib/utils/infra-containers.js +1 -1
  94. package/lib/utils/infra-status.js +66 -15
  95. package/lib/utils/local-secrets.js +45 -25
  96. package/lib/utils/paths.js +45 -33
  97. package/lib/utils/schema-loader.js +42 -25
  98. package/lib/utils/schema-resolver.js +123 -74
  99. package/lib/utils/secrets-encryption.js +62 -25
  100. package/lib/utils/secrets-helpers.js +126 -63
  101. package/lib/utils/secrets-path.js +1 -1
  102. package/lib/utils/secrets-url.js +1 -1
  103. package/lib/utils/token-manager-refresh.js +181 -0
  104. package/lib/utils/token-manager.js +76 -123
  105. package/lib/utils/variable-transformer.js +154 -77
  106. package/lib/utils/yaml-preserve.js +41 -47
  107. package/lib/{template-validator.js → validation/template.js} +54 -23
  108. package/lib/{validate.js → validation/validate.js} +205 -125
  109. package/lib/{validator.js → validation/validator.js} +58 -39
  110. package/package.json +31 -2
  111. package/templates/external-system/deploy.ps1.hbs +34 -0
  112. package/templates/external-system/deploy.sh.hbs +34 -0
  113. package/templates/external-system/external-datasource.json.hbs +31 -12
  114. package/lib/app.js +0 -467
  115. package/lib/datasource-list.js +0 -141
  116. /package/lib/{app-prompts.js → app/prompts.js} +0 -0
  117. /package/lib/{env-reader.js → core/env-reader.js} +0 -0
  118. /package/lib/{key-generator.js → core/key-generator.js} +0 -0
@@ -2,20 +2,20 @@
2
2
  "$schema":"https://json-schema.org/draft/2020-12/schema",
3
3
  "$id":"https://raw.githubusercontent.com/esystemsdev/aifabrix-builder/refs/heads/main/lib/schema/external-datasource.schema.json",
4
4
  "title":"External Data Source",
5
- "description":"Configuration for AI Fabrix ExternalDataSource entities. Includes metadata schema, ABAC access fields, transformation mappings, OpenAPI/MCP exposure, execution logic, and sync behavior.",
5
+ "description":"Configuration for AI Fabrix ExternalDataSource entities. Includes metadata schema, data dimensions, transformation mappings, OpenAPI/MCP exposure, execution logic, and sync behavior.",
6
6
  "metadata":{
7
7
  "key":"external-datasource-schema",
8
8
  "name":"External Data Source Configuration Schema",
9
9
  "description":"JSON schema for validating ExternalDataSource configuration files",
10
- "version":"1.1.0",
10
+ "version":"2.0.0",
11
11
  "type":"schema",
12
12
  "category":"integration",
13
13
  "author":"AI Fabrix Team",
14
14
  "createdAt":"2024-01-01T00:00:00Z",
15
- "updatedAt":"2025-01-01T00:00:00Z",
15
+ "updatedAt":"2026-01-18T00:00:00Z",
16
16
  "compatibility":{
17
17
  "minVersion":"1.0.0",
18
- "maxVersion":"2.0.0",
18
+ "maxVersion":"3.0.0",
19
19
  "deprecated":false
20
20
  },
21
21
  "tags":[
@@ -57,6 +57,29 @@
57
57
  "Added data quality rules, indexing, and AI context hints"
58
58
  ],
59
59
  "breaking":false
60
+ },
61
+ {
62
+ "version":"1.2.0",
63
+ "date":"2026-01-07T00:00:00Z",
64
+ "changes":[
65
+ "Added config.abac section with crossSystemSql and crossSystemJson properties",
66
+ "Added support for unified filter model (JSON and SQL formats) in ABAC cross-system configuration",
67
+ "Updated CIP filter expression to support both SQL and JSON formats"
68
+ ],
69
+ "breaking":false
70
+ },
71
+ {
72
+ "version":"2.0.0",
73
+ "date":"2026-01-18T00:00:00Z",
74
+ "changes":[
75
+ "BREAKING: Renamed fieldMappings.accessFields (array) to fieldMappings.dimensions (object)",
76
+ "BREAKING: Renamed fieldMappings.fields to fieldMappings.attributes",
77
+ "BREAKING: Renamed entityKey to entityType",
78
+ "BREAKING: Renamed exposed.fields to exposed.attributes",
79
+ "Added indexed property to attribute definitions for database index creation",
80
+ "Updated descriptions to reflect dimension-first data model approach"
81
+ ],
82
+ "breaking":true
60
83
  }
61
84
  ]
62
85
  },
@@ -65,7 +88,8 @@
65
88
  "key",
66
89
  "displayName",
67
90
  "systemKey",
68
- "entityKey",
91
+ "entityType",
92
+ "resourceType",
69
93
  "fieldMappings"
70
94
  ],
71
95
  "properties":{
@@ -91,21 +115,16 @@
91
115
  "pattern":"^[a-z0-9-]+$",
92
116
  "description":"Must match ExternalSystem.key"
93
117
  },
94
- "entityKey":{
118
+ "entityType":{
95
119
  "type":"string",
96
- "pattern":"^[a-z0-9-]+$"
120
+ "enum":["document-storage","documentStorage","vector-store","vectorStore","record-storage","recordStorage","message-service","messageService","none"],
121
+ "description":"Entity type identifier. Defines storage semantics and behavior: 'document-storage' or 'documentStorage' (creates document storage service with vector-storage), 'vector-store' or 'vectorStore' (external vector storage system), 'record-storage' or 'recordStorage' (creates record-based system with metadata sync and access rights), 'message-service' or 'messageService' (message service for notifications - Slack, Teams, Email), 'none' (uses external system data directly or connects other data sources). Required field. Supports both camelCase and kebab-case formats for backward compatibility."
97
122
  },
98
123
  "resourceType":{
99
124
  "type":"string",
100
- "enum":[
101
- "customer",
102
- "contact",
103
- "person",
104
- "document",
105
- "deal"
106
- ],
125
+ "pattern":"^[a-z0-9-]+$",
107
126
  "default":"document",
108
- "description":"Resource type for the datasource. Maps to database ResourceType enum."
127
+ "description":"Resource type for the datasource. Represents business entity classification. Common values: customer, contact, person, document, deal, record. Any lowercase alphanumeric string with hyphens is allowed."
109
128
  },
110
129
  "version":{
111
130
  "type":"string",
@@ -119,25 +138,24 @@
119
138
  },
120
139
  "fieldMappings":{
121
140
  "type":"object",
122
- "description":"Transformation rules and ABAC accessFields. Replaces older flat mapping schema.",
141
+ "description":"Transformation rules and data dimensions. Maps canonical dimensions to system attributes.",
123
142
  "required":[
124
- "accessFields",
125
- "fields"
143
+ "dimensions",
144
+ "attributes"
126
145
  ],
127
146
  "properties":{
128
- "accessFields":{
129
- "type":"array",
130
- "description":"Normalized fields used in ABAC engine (must exist in 'fields').",
131
- "items":{
147
+ "dimensions":{
148
+ "type":"object",
149
+ "description":"Data dimensions mapping. Key = dimension key (from Dimension Catalog), Value = attribute path (e.g., 'metadata.department').",
150
+ "additionalProperties":{
132
151
  "type":"string",
133
- "pattern":"^[a-zA-Z0-9_]+$"
152
+ "pattern":"^[a-zA-Z0-9_.]+$"
134
153
  },
135
- "minItems":1,
136
- "uniqueItems":true
154
+ "minProperties":0
137
155
  },
138
- "fields":{
156
+ "attributes":{
139
157
  "type":"object",
140
- "description":"Key = normalized field name. Value = transformation expression + type.",
158
+ "description":"Key = normalized attribute name. Value = transformation expression + type.",
141
159
  "additionalProperties":{
142
160
  "type":"object",
143
161
  "required":[
@@ -161,6 +179,11 @@
161
179
  "object"
162
180
  ]
163
181
  },
182
+ "indexed":{
183
+ "type":"boolean",
184
+ "default":false,
185
+ "description":"If true, creates database index for fast search/filtering. Dimensions are automatically indexed."
186
+ },
164
187
  "description":{
165
188
  "type":"string",
166
189
  "description":"Technical description of the normalized field."
@@ -210,9 +233,9 @@
210
233
  "type":"object",
211
234
  "description":"Defines which normalized fields are exposed through MCP/OpenAPI. Ensures ISO27001-safe output and predictable AI model behavior.",
212
235
  "properties":{
213
- "fields":{
236
+ "attributes":{
214
237
  "type":"array",
215
- "description":"List of normalized fields (from fieldMappings.fields keys) that the MCP/OpenAPI layers will return.",
238
+ "description":"List of normalized attributes (from fieldMappings.attributes keys) that the MCP/OpenAPI layers will return.",
216
239
  "items":{
217
240
  "type":"string",
218
241
  "pattern":"^[a-zA-Z0-9_]+$"
@@ -270,7 +293,7 @@
270
293
  }
271
294
  },
272
295
  "required":[
273
- "fields"
296
+ "attributes"
274
297
  ],
275
298
  "additionalProperties":false
276
299
  },
@@ -584,112 +607,42 @@
584
607
  },
585
608
  "documentStorage":{
586
609
  "type":"object",
587
- "description":"Document storage configuration (optional, enables vector storage)",
610
+ "description":"Document storage configuration (optional, enables vector storage). Validated against type/document-storage.json schema.",
588
611
  "properties":{
589
612
  "enabled":{
590
613
  "type":"boolean",
591
614
  "default":true
592
615
  },
593
- "flowiseInstanceId":{
594
- "type":"string"
595
- },
596
616
  "twoPhaseSync":{
597
617
  "type":"boolean",
598
- "default":true
618
+ "default":true,
619
+ "description":"Enable two-phase sync pattern. When true: validates metadata first (quality rules, comparison with DocumentRecords), then fetches binaries via CIP for changed/new documents. When false: fetches binaries directly without metadata validation phase (single-phase sync). Note: Files are never synced back to external systems (one-way sync only: external → dataplane)."
620
+ },
621
+ "binaryOperationRef":{
622
+ "type":"string",
623
+ "default":"get",
624
+ "description":"CIP operation name for binary document retrieval. Must exist in execution.cip.operations. Defaults to 'get' operation."
599
625
  },
600
- "binaryOperation":{
626
+ "responseType":{
627
+ "type":"string",
628
+ "enum":[
629
+ "binary",
630
+ "base64",
631
+ "json"
632
+ ],
633
+ "default":"binary",
634
+ "description":"Expected response type from CIP operation. 'binary' for raw binary data, 'base64' for base64-encoded data, 'json' for JSON response with binary field."
635
+ },
636
+ "binaryField":{
637
+ "type":"string",
638
+ "description":"Field name containing binary data if responseType is 'json' or 'base64'. Required when responseType is not 'binary'."
639
+ },
640
+ "parameterMapping":{
601
641
  "type":"object",
602
- "description":"Configuration for binary document retrieval. Supports CIP operations or direct HTTP/OpenAPI.",
603
- "oneOf":[
604
- {
605
- "required":[
606
- "cipOperationRef",
607
- "responseType"
608
- ],
609
- "properties":{
610
- "cipOperationRef":{
611
- "type":"string",
612
- "description":"Reference to CIP operation (e.g., 'get' or custom operation name)"
613
- },
614
- "responseType":{
615
- "type":"string",
616
- "enum":[
617
- "binary",
618
- "base64",
619
- "json"
620
- ],
621
- "default":"binary"
622
- },
623
- "binaryField":{
624
- "type":"string"
625
- },
626
- "parameterMapping":{
627
- "type":"object",
628
- "additionalProperties":{
629
- "type":"string"
630
- }
631
- }
632
- },
633
- "additionalProperties":false
634
- },
635
- {
636
- "required":[
637
- "path",
638
- "responseType"
639
- ],
640
- "properties":{
641
- "operationId":{
642
- "type":"string"
643
- },
644
- "method":{
645
- "type":"string",
646
- "enum":[
647
- "GET",
648
- "POST",
649
- "PUT"
650
- ],
651
- "default":"GET"
652
- },
653
- "path":{
654
- "type":"string"
655
- },
656
- "queryParameters":{
657
- "type":"object",
658
- "additionalProperties":{
659
- "type":"string"
660
- }
661
- },
662
- "headers":{
663
- "type":"object",
664
- "additionalProperties":{
665
- "type":"string"
666
- }
667
- },
668
- "requestBody":{
669
- "type":"object"
670
- },
671
- "responseType":{
672
- "type":"string",
673
- "enum":[
674
- "binary",
675
- "base64",
676
- "json"
677
- ],
678
- "default":"binary"
679
- },
680
- "binaryField":{
681
- "type":"string"
682
- },
683
- "parameterMapping":{
684
- "type":"object",
685
- "additionalProperties":{
686
- "type":"string"
687
- }
688
- }
689
- },
690
- "additionalProperties":false
691
- }
692
- ]
642
+ "additionalProperties":{
643
+ "type":"string"
644
+ },
645
+ "description":"Map metadata record fields to CIP operation parameters. Example: {\"fileId\": \"{{key}}\", \"downloadUrl\": \"{{metadata.downloadUrl}}\"}"
693
646
  },
694
647
  "processing":{
695
648
  "type":"object",
@@ -715,8 +668,7 @@
715
668
  }
716
669
  },
717
670
  "required":[
718
- "flowiseInstanceId",
719
- "binaryOperation"
671
+ "enabled"
720
672
  ],
721
673
  "additionalProperties":false
722
674
  },
@@ -847,6 +799,53 @@
847
799
  }
848
800
  },
849
801
  "additionalProperties":false
802
+ },
803
+ "config":{
804
+ "type":"object",
805
+ "description":"Additional configuration for this datasource, including ABAC settings, MCP contracts, and other metadata.",
806
+ "properties":{
807
+ "abac":{
808
+ "type":"object",
809
+ "description":"Attribute-Based Access Control (ABAC) configuration for this datasource.",
810
+ "properties":{
811
+ "dimensions":{
812
+ "type":"object",
813
+ "description":"Data dimensions mapping. Key = dimension key (from Dimension Catalog), Value = attribute path. Overrides fieldMappings.dimensions if specified.",
814
+ "additionalProperties":{
815
+ "type":"string",
816
+ "pattern":"^[a-zA-Z0-9_.]+$"
817
+ }
818
+ },
819
+ "crossSystemSql":{
820
+ "type":"string",
821
+ "description":"Cross-system ABAC filter expression in SQL format (advanced, for developers). Example: 'hubspot-companies.country = user.country AND hubspot-companies.revenue >= 1000000'"
822
+ },
823
+ "crossSystemJson":{
824
+ "type":"object",
825
+ "description":"Cross-system ABAC filter expression in JSON format (simple, for UI). Example: {'hubspot-companies.country': {'eq': 'user.country'}, 'hubspot-companies.revenue': {'gte': 1000000}}",
826
+ "additionalProperties":{
827
+ "type":"object",
828
+ "properties":{
829
+ "eq":{"type":["string","number","boolean"]},
830
+ "ne":{"type":["string","number","boolean"]},
831
+ "gt":{"type":["string","number"]},
832
+ "lt":{"type":["string","number"]},
833
+ "gte":{"type":["string","number"]},
834
+ "lte":{"type":["string","number"]},
835
+ "in":{"type":"array"},
836
+ "nin":{"type":"array"},
837
+ "contains":{"type":"string"},
838
+ "like":{"type":"string"},
839
+ "isNull":{"type":"null"},
840
+ "isNotNull":{"type":"null"}
841
+ }
842
+ }
843
+ }
844
+ },
845
+ "additionalProperties":false
846
+ }
847
+ },
848
+ "additionalProperties":true
850
849
  }
851
850
  },
852
851
  "$defs":{
@@ -1025,7 +1024,10 @@
1025
1024
  "type":"string",
1026
1025
  "enum":[
1027
1026
  "GET",
1028
- "POST"
1027
+ "POST",
1028
+ "PUT",
1029
+ "PATCH",
1030
+ "DELETE"
1029
1031
  ],
1030
1032
  "description":"Explicit HTTP method when source='http'."
1031
1033
  },
@@ -1046,8 +1048,15 @@
1046
1048
  }
1047
1049
  },
1048
1050
  "bodyTemplate":{
1049
- "description":"Optional static request body or template for POST/PUT fetches.",
1051
+ "description":"Optional static request body or template for POST/PUT/PATCH fetches.",
1050
1052
  "type":["object","string","null"]
1053
+ },
1054
+ "headers":{
1055
+ "type":"object",
1056
+ "description":"Optional HTTP headers for the request.",
1057
+ "additionalProperties":{
1058
+ "type":"string"
1059
+ }
1051
1060
  }
1052
1061
  },
1053
1062
  "allOf":[
@@ -1173,7 +1182,7 @@
1173
1182
  "useFieldMappings":{
1174
1183
  "type":"boolean",
1175
1184
  "default":true,
1176
- "description":"If true, apply the datasource.fieldMappings.fields expressions; no inline mapping needed."
1185
+ "description":"If true, apply the datasource.fieldMappings.attributes expressions; no inline mapping needed."
1177
1186
  },
1178
1187
  "inline":{
1179
1188
  "type":"object",
@@ -1200,15 +1209,27 @@
1200
1209
  "properties":{
1201
1210
  "filter":{
1202
1211
  "type":"object",
1203
- "description":"Filter mapped records before output. ABAC is applied automatically based on fieldMappings.accessFields.",
1212
+ "description":"Filter mapped records before output. Dimension enforcement is applied automatically based on fieldMappings.dimensions.",
1204
1213
  "properties":{
1205
1214
  "enforceAbac":{
1206
1215
  "type":"boolean",
1207
1216
  "default":true
1208
1217
  },
1209
1218
  "expression":{
1210
- "type":"string",
1211
- "description":"Optional additional filter expression (e.g. '@.status == \"active\"')."
1219
+ "oneOf":[
1220
+ {
1221
+ "type":"string",
1222
+ "description":"SQL filter expression (e.g., 'status = \"active\" AND revenue >= 1000000')"
1223
+ },
1224
+ {
1225
+ "type":"object",
1226
+ "description":"JSON filter expression (e.g., {'status': {'eq': 'active'}, 'revenue': {'gte': 1000000}})",
1227
+ "additionalProperties":{
1228
+ "type":"object"
1229
+ }
1230
+ }
1231
+ ],
1232
+ "description":"Optional additional filter expression. Supports both SQL and JSON formats."
1212
1233
  }
1213
1234
  },
1214
1235
  "additionalProperties":false
@@ -1283,4 +1304,5 @@
1283
1304
  }
1284
1305
  },
1285
1306
  "additionalProperties":false
1286
- }
1307
+ }
1308
+
@@ -12,7 +12,7 @@
12
12
  "category": "integration",
13
13
  "author": "AI Fabrix Team",
14
14
  "createdAt": "2024-01-01T00:00:00Z",
15
- "updatedAt": "2024-01-01T00:00:00Z",
15
+ "updatedAt": "2025-12-01T00:00:00Z",
16
16
  "compatibility": {
17
17
  "minVersion": "1.0.0",
18
18
  "maxVersion": "2.0.0",
@@ -365,6 +365,48 @@
365
365
  },
366
366
  "additionalProperties": false
367
367
  }
368
+ },
369
+ "endpoints": {
370
+ "type": "array",
371
+ "description": "API endpoint configurations for this external system. Used for dynamic endpoint registration at application startup.",
372
+ "items": {
373
+ "type": "object",
374
+ "required": [
375
+ "type",
376
+ "path"
377
+ ],
378
+ "properties": {
379
+ "type": {
380
+ "type": "string",
381
+ "description": "Endpoint type identifier (e.g., 'commands', 'events', 'notifications')",
382
+ "pattern": "^[a-z0-9-]+$"
383
+ },
384
+ "path": {
385
+ "type": "string",
386
+ "description": "URL path for this endpoint (e.g., '/commands', '/events')",
387
+ "pattern": "^/[a-z0-9/-]*$"
388
+ },
389
+ "description": {
390
+ "type": "string",
391
+ "description": "Human-readable description of the endpoint"
392
+ },
393
+ "active": {
394
+ "type": "boolean",
395
+ "description": "Whether this endpoint should be registered (defaults to true)",
396
+ "default": true
397
+ },
398
+ "router": {
399
+ "type": "string",
400
+ "description": "Custom router module path override (defaults to auto-discovery based on naming convention)"
401
+ }
402
+ },
403
+ "additionalProperties": false
404
+ }
405
+ },
406
+ "endpointsActive": {
407
+ "type": "boolean",
408
+ "description": "Master switch for all endpoints in this system. If false, no endpoints are registered regardless of individual endpoint active flags.",
409
+ "default": true
368
410
  }
369
411
  },
370
412
  "additionalProperties": false
package/lib/utils/api.js CHANGED
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  const { parseErrorResponse } = require('./api-error-handler');
14
- const auditLogger = require('../audit-logger');
14
+ const auditLogger = require('../core/audit-logger');
15
15
 
16
16
  /**
17
17
  * Logs API request performance metrics and errors to audit log
@@ -27,14 +27,18 @@ async function logApiPerformance(params) {
27
27
  // Log all API calls (both success and failure) to audit log for troubleshooting
28
28
  // This helps track what API calls were made when errors occur
29
29
  try {
30
- await auditLogger.logApiCall({
30
+ const logData = {
31
31
  url: params.url,
32
32
  options: params.options,
33
33
  statusCode: params.statusCode,
34
34
  duration: params.duration,
35
- success: params.success,
36
- errorInfo: params.errorInfo || {}
37
- });
35
+ success: params.success
36
+ };
37
+ // Only include errorInfo if there's actual error information
38
+ if (params.errorInfo) {
39
+ logData.errorInfo = params.errorInfo;
40
+ }
41
+ await auditLogger.logApiCall(logData);
38
42
  } catch (logError) {
39
43
  // Don't fail the API call if audit logging fails
40
44
  // Silently continue - audit logging should never break functionality
@@ -22,6 +22,63 @@ const { formatApiError } = require('./api-error-handler');
22
22
  * @param {Object} registrationData - Registration data
23
23
  * @returns {Promise<Object>} API response
24
24
  */
25
+ /**
26
+ * Handles registration API error response
27
+ * @function handleRegistrationError
28
+ * @param {Object} response - API response
29
+ * @param {string} apiUrl - Controller URL
30
+ * @param {Object} registrationData - Registration data
31
+ */
32
+ function handleRegistrationError(response, apiUrl, registrationData) {
33
+ const formattedError = response.formattedError || formatApiError(response, apiUrl);
34
+ logger.error(formattedError);
35
+ logger.error(chalk.gray(`\nController URL: ${apiUrl}`));
36
+
37
+ // For validation errors (400, 422), show the request payload for debugging
38
+ if (response.status === 400 || response.status === 422) {
39
+ logger.error(chalk.gray('\nRequest payload:'));
40
+ logger.error(chalk.gray(JSON.stringify(registrationData, null, 2)));
41
+ logger.error('');
42
+ logger.error(chalk.gray('Check your variables.yaml file and ensure all required fields are correctly set.'));
43
+ }
44
+
45
+ process.exit(1);
46
+ }
47
+
48
+ /**
49
+ * Extracts application data from API response
50
+ * @function extractApplicationData
51
+ * @param {Object} response - API response
52
+ * @param {string} apiUrl - Controller URL
53
+ * @returns {Object} Application data
54
+ * @throws {Error} If response format is invalid
55
+ */
56
+ function extractApplicationData(response, apiUrl) {
57
+ const apiResponse = response.data;
58
+
59
+ // Handle API response structure:
60
+ // registerApplication returns: { success: true, data: <API response> }
61
+ // API response can be:
62
+ // 1. Direct format: { application: {...}, credentials: {...} }
63
+ // 2. Wrapped format: { success: true, data: { application: {...}, credentials: {...} } }
64
+ if (apiResponse && apiResponse.data && apiResponse.data.application) {
65
+ // Wrapped format: use apiResponse.data
66
+ return apiResponse.data;
67
+ }
68
+
69
+ if (apiResponse && apiResponse.application) {
70
+ // Direct format: use apiResponse directly
71
+ return apiResponse;
72
+ }
73
+
74
+ // Fallback: return apiResponse as-is (shouldn't happen, but handle gracefully)
75
+ logger.error(chalk.red('❌ Invalid response: missing application data'));
76
+ logger.error(chalk.gray(`\nController URL: ${apiUrl}`));
77
+ logger.error(chalk.gray('\nFull response for debugging:'));
78
+ logger.error(chalk.gray(JSON.stringify(response, null, 2)));
79
+ process.exit(1);
80
+ }
81
+
25
82
  async function callRegisterApi(apiUrl, token, environment, registrationData) {
26
83
  // Use centralized API client
27
84
  const authConfig = { type: 'bearer', token: token };
@@ -29,40 +86,11 @@ async function callRegisterApi(apiUrl, token, environment, registrationData) {
29
86
  const response = await registerApplication(apiUrl, environment, authConfig, registrationData);
30
87
 
31
88
  if (!response.success) {
32
- const formattedError = response.formattedError || formatApiError(response, apiUrl);
33
- logger.error(formattedError);
34
- logger.error(chalk.gray(`\nController URL: ${apiUrl}`));
35
-
36
- // For validation errors (400, 422), show the request payload for debugging
37
- if (response.status === 400 || response.status === 422) {
38
- logger.error(chalk.gray('\nRequest payload:'));
39
- logger.error(chalk.gray(JSON.stringify(registrationData, null, 2)));
40
- logger.error('');
41
- logger.error(chalk.gray('Check your variables.yaml file and ensure all required fields are correctly set.'));
42
- }
43
-
44
- process.exit(1);
89
+ handleRegistrationError(response, apiUrl, registrationData);
90
+ return; // Never reached, but satisfies linter
45
91
  }
46
92
 
47
- // Handle API response structure:
48
- // registerApplication returns: { success: true, data: <API response> }
49
- // API response can be:
50
- // 1. Direct format: { application: {...}, credentials: {...} }
51
- // 2. Wrapped format: { success: true, data: { application: {...}, credentials: {...} } }
52
- const apiResponse = response.data;
53
- if (apiResponse && apiResponse.data && apiResponse.data.application) {
54
- // Wrapped format: use apiResponse.data
55
- return apiResponse.data;
56
- } else if (apiResponse && apiResponse.application) {
57
- // Direct format: use apiResponse directly
58
- return apiResponse;
59
- }
60
- // Fallback: return apiResponse as-is (shouldn't happen, but handle gracefully)
61
- logger.error(chalk.red('❌ Invalid response: missing application data'));
62
- logger.error(chalk.gray(`\nController URL: ${apiUrl}`));
63
- logger.error(chalk.gray('\nFull response for debugging:'));
64
- logger.error(chalk.gray(JSON.stringify(response, null, 2)));
65
- process.exit(1);
93
+ return extractApplicationData(response, apiUrl);
66
94
  } catch (error) {
67
95
  // Include controller URL in error context
68
96
  logger.error(chalk.red('❌ Registration API call failed'));