@ductape/sdk 0.1.12 → 0.1.14

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 (109) hide show
  1. package/dist/api/services/cloudApi.service.d.ts +3 -1
  2. package/dist/api/services/cloudApi.service.js +11 -2
  3. package/dist/api/services/cloudApi.service.js.map +1 -1
  4. package/dist/api/services/webhooksApi.service.js +6 -4
  5. package/dist/api/services/webhooksApi.service.js.map +1 -1
  6. package/dist/apps/services/app.service.d.ts +4 -0
  7. package/dist/apps/services/app.service.js +37 -0
  8. package/dist/apps/services/app.service.js.map +1 -1
  9. package/dist/brokers/brokers.service.js +23 -1
  10. package/dist/brokers/brokers.service.js.map +1 -1
  11. package/dist/brokers/utils/broker.util.js +4 -1
  12. package/dist/brokers/utils/broker.util.js.map +1 -1
  13. package/dist/brokers/utils/providers/azure-servicebus.service.d.ts +17 -0
  14. package/dist/brokers/utils/providers/azure-servicebus.service.js +51 -0
  15. package/dist/brokers/utils/providers/azure-servicebus.service.js.map +1 -0
  16. package/dist/cloud/cloud-api.util.d.ts +1 -1
  17. package/dist/cloud/cloud-api.util.js +21 -3
  18. package/dist/cloud/cloud-api.util.js.map +1 -1
  19. package/dist/cloud/cloud-broker-link.util.js +32 -11
  20. package/dist/cloud/cloud-broker-link.util.js.map +1 -1
  21. package/dist/cloud/cloud-database-link.util.js +8 -11
  22. package/dist/cloud/cloud-database-link.util.js.map +1 -1
  23. package/dist/cloud/cloud-graph-link.util.d.ts +12 -0
  24. package/dist/cloud/cloud-graph-link.util.js +61 -0
  25. package/dist/cloud/cloud-graph-link.util.js.map +1 -0
  26. package/dist/cloud/cloud-runtime.util.js +8 -1
  27. package/dist/cloud/cloud-runtime.util.js.map +1 -1
  28. package/dist/cloud/cloud-service-map.util.d.ts +3 -0
  29. package/dist/cloud/cloud-service-map.util.js +52 -0
  30. package/dist/cloud/cloud-service-map.util.js.map +1 -0
  31. package/dist/cloud/cloud-vector-link.util.d.ts +12 -0
  32. package/dist/cloud/cloud-vector-link.util.js +60 -0
  33. package/dist/cloud/cloud-vector-link.util.js.map +1 -0
  34. package/dist/cloud/cloud.service.d.ts +2 -1
  35. package/dist/cloud/cloud.service.js +3 -0
  36. package/dist/cloud/cloud.service.js.map +1 -1
  37. package/dist/cloud/index.d.ts +3 -0
  38. package/dist/cloud/index.js +8 -1
  39. package/dist/cloud/index.js.map +1 -1
  40. package/dist/cloud/types/cloud.types.d.ts +13 -3
  41. package/dist/database/databases.service.js +27 -10
  42. package/dist/database/databases.service.js.map +1 -1
  43. package/dist/database/types/connection.interface.d.ts +3 -0
  44. package/dist/graph/adapters/adapter.factory.js +4 -0
  45. package/dist/graph/adapters/adapter.factory.js.map +1 -1
  46. package/dist/graph/adapters/cosmos-gremlin.adapter.d.ts +12 -0
  47. package/dist/graph/adapters/cosmos-gremlin.adapter.js +125 -0
  48. package/dist/graph/adapters/cosmos-gremlin.adapter.js.map +1 -0
  49. package/dist/graph/adapters/index.d.ts +2 -0
  50. package/dist/graph/adapters/index.js +5 -1
  51. package/dist/graph/adapters/index.js.map +1 -1
  52. package/dist/graph/adapters/neptune.adapter.d.ts +4 -4
  53. package/dist/graph/adapters/neptune.adapter.js.map +1 -1
  54. package/dist/graph/adapters/spanner-graph.adapter.d.ts +68 -0
  55. package/dist/graph/adapters/spanner-graph.adapter.js +220 -0
  56. package/dist/graph/adapters/spanner-graph.adapter.js.map +1 -0
  57. package/dist/graph/graphs.service.js +36 -20
  58. package/dist/graph/graphs.service.js.map +1 -1
  59. package/dist/graph/types/connection.interface.d.ts +4 -0
  60. package/dist/graph/types/enums.d.ts +2 -0
  61. package/dist/graph/types/enums.js +2 -0
  62. package/dist/graph/types/enums.js.map +1 -1
  63. package/dist/index.d.ts +17 -0
  64. package/dist/index.js +26 -0
  65. package/dist/index.js.map +1 -1
  66. package/dist/logs/logs.types.d.ts +5 -1
  67. package/dist/logs/logs.types.js +2 -0
  68. package/dist/logs/logs.types.js.map +1 -1
  69. package/dist/products/services/products.service.d.ts +13 -0
  70. package/dist/products/services/products.service.js +201 -104
  71. package/dist/products/services/products.service.js.map +1 -1
  72. package/dist/products/validators/joi-validators/create.productGraph.validator.js +8 -0
  73. package/dist/products/validators/joi-validators/create.productGraph.validator.js.map +1 -1
  74. package/dist/products/validators/joi-validators/create.productMessageBrokers.validator.js +28 -5
  75. package/dist/products/validators/joi-validators/create.productMessageBrokers.validator.js.map +1 -1
  76. package/dist/products/validators/joi-validators/create.productVector.validator.js +8 -0
  77. package/dist/products/validators/joi-validators/create.productVector.validator.js.map +1 -1
  78. package/dist/resilience/fallback.service.js +11 -6
  79. package/dist/resilience/fallback.service.js.map +1 -1
  80. package/dist/resilience/quota.service.d.ts +1 -1
  81. package/dist/resilience/quota.service.js +48 -10
  82. package/dist/resilience/quota.service.js.map +1 -1
  83. package/dist/resilience/resilience-mapping.utils.d.ts +5 -0
  84. package/dist/resilience/resilience-mapping.utils.js +53 -0
  85. package/dist/resilience/resilience-mapping.utils.js.map +1 -0
  86. package/dist/types/productsBuilder.types.d.ts +12 -0
  87. package/dist/types/productsBuilder.types.js +6 -0
  88. package/dist/types/productsBuilder.types.js.map +1 -1
  89. package/dist/vector/adapters/azure-search.adapter.d.ts +25 -0
  90. package/dist/vector/adapters/azure-search.adapter.js +217 -0
  91. package/dist/vector/adapters/azure-search.adapter.js.map +1 -0
  92. package/dist/vector/adapters/index.d.ts +3 -0
  93. package/dist/vector/adapters/index.js +7 -1
  94. package/dist/vector/adapters/index.js.map +1 -1
  95. package/dist/vector/adapters/opensearch.adapter.d.ts +25 -0
  96. package/dist/vector/adapters/opensearch.adapter.js +224 -0
  97. package/dist/vector/adapters/opensearch.adapter.js.map +1 -0
  98. package/dist/vector/adapters/vertex-vector-search.adapter.d.ts +29 -0
  99. package/dist/vector/adapters/vertex-vector-search.adapter.js +178 -0
  100. package/dist/vector/adapters/vertex-vector-search.adapter.js.map +1 -0
  101. package/dist/vector/types/enums.d.ts +6 -0
  102. package/dist/vector/types/enums.js +6 -0
  103. package/dist/vector/types/enums.js.map +1 -1
  104. package/dist/vector/vector-database.service.d.ts +6 -0
  105. package/dist/vector/vector-database.service.js +30 -16
  106. package/dist/vector/vector-database.service.js.map +1 -1
  107. package/dist/vector/vector.service.js +6 -0
  108. package/dist/vector/vector.service.js.map +1 -1
  109. package/package.json +8 -2
@@ -172,6 +172,13 @@ export default class ProductsBuilderService implements IProductsBuilderService {
172
172
  fetchHealthcheck(access_tag: string, tag: string, throwError?: boolean): Promise<IProductAppHealth | null>;
173
173
  fetchHealthchecks(access_tag: string, throwError?: boolean): Promise<Array<IProductAppHealth>>;
174
174
  fetchProductHealthchecks(): Promise<Array<IProductAppHealth>>;
175
+ /** Validate quota/fallback provider references a registered product healthcheck component. */
176
+ validateProviderHealthcheckTag(option: {
177
+ healthcheck?: string;
178
+ check_interval?: number;
179
+ }): Promise<void>;
180
+ /** Keep in-memory product component arrays in sync after component CRUD (components API can lag). */
181
+ private mergeProductComponent;
175
182
  /**
176
183
  * Create a healthcheck from a schema (e.g. from resilience health.define/create).
177
184
  * Persists as product component so processor healthcheck workers and health.list/fetch see it.
@@ -471,6 +478,12 @@ export default class ProductsBuilderService implements IProductsBuilderService {
471
478
  fetchPriorSequence(meta: IParseInputStringMetaData, stage: string): IFeatureSequence;
472
479
  validateActionKeyPlacement(data: IParseActionEventInput): IParsedSample;
473
480
  fetchThirdPartyAppByAccessTag(access_tag: string): Promise<IApp & IVersion>;
481
+ /**
482
+ * When workspace access-tag lookup fails (common for product-scoped tags like ductape:paystack:ductape),
483
+ * resolve the app via product-linked components or fetchAppByTag.
484
+ */
485
+ private fetchThirdPartyAppByAppTagFallback;
486
+ private resolveAppTagFromAccessReference;
474
487
  fetchThirdPartyAppActionByTag(actions: Array<IAppAction>, action_tag: string): IAppAction;
475
488
  createFeature(data: Partial<IProductFeature>, throwErrorIfExists?: boolean): Promise<void>;
476
489
  updateFeature(tag: string, data: Partial<IProductFeature>): Promise<void>;
@@ -25,6 +25,8 @@ const cloudApi_service_1 = require("../../api/services/cloudApi.service");
25
25
  const storage_cloud_link_util_1 = require("../../storage/storage-cloud-link.util");
26
26
  const cloud_broker_link_util_1 = require("../../cloud/cloud-broker-link.util");
27
27
  const cloud_database_link_util_1 = require("../../cloud/cloud-database-link.util");
28
+ const cloud_graph_link_util_1 = require("../../cloud/cloud-graph-link.util");
29
+ const cloud_vector_link_util_1 = require("../../cloud/cloud-vector-link.util");
28
30
  const productsBuilder_types_1 = require("../../types/productsBuilder.types");
29
31
  const validators_1 = require("../validators");
30
32
  const objects_utils_1 = require("../utils/objects.utils");
@@ -220,6 +222,34 @@ class ProductsBuilderService {
220
222
  const healthchecks = await this.productApi.fetchProductComponents(this.product_id, 'healthcheck', this.getUserAccess());
221
223
  return healthchecks;
222
224
  }
225
+ /** Validate quota/fallback provider references a registered product healthcheck component. */
226
+ async validateProviderHealthcheckTag(option) {
227
+ if (!option.healthcheck)
228
+ return;
229
+ const healthchecks = await this.fetchProductHealthchecks();
230
+ const found = healthchecks.some((h) => h.tag === option.healthcheck);
231
+ if (!found) {
232
+ throw new Error(`Product healthcheck "${option.healthcheck}" not found. Register it first (e.g. npm run test:healthchecks:populate).`);
233
+ }
234
+ if (!option.check_interval) {
235
+ option.check_interval = 10000;
236
+ }
237
+ }
238
+ /** Keep in-memory product component arrays in sync after component CRUD (components API can lag). */
239
+ mergeProductComponent(kind, item) {
240
+ if (!this.product || !(item === null || item === void 0 ? void 0 : item.tag))
241
+ return;
242
+ const key = kind === 'healthcheck' ? 'healthchecks' : kind === 'quota' ? 'quota' : 'fallbacks';
243
+ const list = Array.isArray(this.product[key])
244
+ ? [...this.product[key]]
245
+ : [];
246
+ const idx = list.findIndex((c) => c.tag === item.tag);
247
+ if (idx >= 0)
248
+ list[idx] = Object.assign(Object.assign({}, list[idx]), item);
249
+ else
250
+ list.push(item);
251
+ this.product[key] = list;
252
+ }
223
253
  /**
224
254
  * Create a healthcheck from a schema (e.g. from resilience health.define/create).
225
255
  * Persists as product component so processor healthcheck workers and health.list/fetch see it.
@@ -1053,19 +1083,7 @@ class ProductsBuilderService {
1053
1083
  throw new Error(`Cannot find action ${d.event} on app ${app.tag} version ${app.version}`);
1054
1084
  }
1055
1085
  if (d.healthcheck) {
1056
- const action = version.actions.find((action) => action.tag === d.healthcheck);
1057
- if (!d.check_interval) {
1058
- d.check_interval = 10000;
1059
- }
1060
- if (!action) {
1061
- throw new Error(`Cannot find healthcheck action ${d.healthcheck} on app ${app.tag} version ${app.version}`);
1062
- }
1063
- if (action.headers.data.length > 0 ||
1064
- action.body.data.length > 0 ||
1065
- action.params.data.length > 0 ||
1066
- action.query.data.length > 0) {
1067
- throw new Error('Healthcheck action is expected to have no headers, body, params or query');
1068
- }
1086
+ await this.validateProviderHealthcheckTag(d);
1069
1087
  }
1070
1088
  }
1071
1089
  d.last_checked = new Date();
@@ -1073,6 +1091,7 @@ class ProductsBuilderService {
1073
1091
  d.last_available = true;
1074
1092
  }));
1075
1093
  await this.productApi.updateProduct(this.product_id, Object.assign(Object.assign({}, data), { component: enums_1.ProductComponents.QUOTA, action: enums_1.RequestAction.CREATE }), this.getUserAccess());
1094
+ this.mergeProductComponent('quota', data);
1076
1095
  }
1077
1096
  }
1078
1097
  catch (e) {
@@ -1102,19 +1121,7 @@ class ProductsBuilderService {
1102
1121
  throw new Error(`Cannot find action ${d.event} on app ${app.tag} version ${app.version}`);
1103
1122
  }
1104
1123
  if (d.healthcheck) {
1105
- const action = version.actions.find((action) => action.tag === d.healthcheck);
1106
- if (!d.check_interval) {
1107
- d.check_interval = 10000;
1108
- }
1109
- if (!action) {
1110
- throw new Error(`Cannot find healthcheck action ${d.healthcheck} on app ${app.tag} version ${app.version}`);
1111
- }
1112
- if (action.headers.data.length > 0 ||
1113
- action.body.data.length > 0 ||
1114
- action.params.data.length > 0 ||
1115
- action.query.data.length > 0) {
1116
- throw new Error('Healthcheck action is expected to have no headers, body, params or query');
1117
- }
1124
+ await this.validateProviderHealthcheckTag(d);
1118
1125
  }
1119
1126
  }
1120
1127
  d.last_checked = new Date();
@@ -1133,12 +1140,19 @@ class ProductsBuilderService {
1133
1140
  }
1134
1141
  }
1135
1142
  async fetchQuotas(version) {
1143
+ var _a;
1136
1144
  const components = await this.productApi.fetchProductComponents(this.product_id, 'quota', this.getUserAccess());
1137
- return components;
1145
+ if (Array.isArray(components) && components.length > 0) {
1146
+ return components;
1147
+ }
1148
+ return Array.isArray((_a = this.product) === null || _a === void 0 ? void 0 : _a.quota) ? this.product.quota : [];
1138
1149
  }
1139
1150
  async fetchQuota(tag, version) {
1151
+ var _a, _b, _c;
1140
1152
  const component = await this.productApi.fetchProductComponentByTag(this.product_id, 'quota', tag, this.getUserAccess());
1141
- return component;
1153
+ if (component)
1154
+ return component;
1155
+ return (_c = ((_b = (_a = this.product) === null || _a === void 0 ? void 0 : _a.quota) !== null && _b !== void 0 ? _b : []).find((q) => q.tag === tag)) !== null && _c !== void 0 ? _c : null;
1142
1156
  }
1143
1157
  async deleteQuota(tag) {
1144
1158
  try {
@@ -1176,19 +1190,7 @@ class ProductsBuilderService {
1176
1190
  throw new Error(`Cannot find action ${d.event} on app ${app.tag} version ${app.version}`);
1177
1191
  }
1178
1192
  if (d.healthcheck) {
1179
- const action = version.actions.find((action) => action.tag === d.healthcheck);
1180
- if (!d.check_interval) {
1181
- d.check_interval = 10000;
1182
- }
1183
- if (!action) {
1184
- throw new Error(`Cannot find healthcheck action ${d.healthcheck} on app ${app.tag} version ${app.version}`);
1185
- }
1186
- if (action.headers.data.length > 0 ||
1187
- action.body.data.length > 0 ||
1188
- action.params.data.length > 0 ||
1189
- action.query.data.length > 0) {
1190
- throw new Error('Healthcheck action is expected to have no headers, body, params or query');
1191
- }
1193
+ await this.validateProviderHealthcheckTag(d);
1192
1194
  }
1193
1195
  }
1194
1196
  d.last_checked = new Date();
@@ -1196,6 +1198,7 @@ class ProductsBuilderService {
1196
1198
  d.last_available = true;
1197
1199
  }));
1198
1200
  await this.productApi.updateProduct(this.product_id, Object.assign(Object.assign({}, data), { component: enums_1.ProductComponents.FALLBACK, action: enums_1.RequestAction.CREATE }), this.getUserAccess());
1201
+ this.mergeProductComponent('fallback', data);
1199
1202
  }
1200
1203
  }
1201
1204
  catch (e) {
@@ -1222,19 +1225,7 @@ class ProductsBuilderService {
1222
1225
  throw new Error(`Cannot find action ${d.event} on app ${app.tag} version ${app.version}`);
1223
1226
  }
1224
1227
  if (d.healthcheck) {
1225
- const action = version.actions.find((action) => action.tag === d.healthcheck);
1226
- if (!d.check_interval) {
1227
- d.check_interval = 10000;
1228
- }
1229
- if (!action) {
1230
- throw new Error(`Cannot find healthcheck action ${d.healthcheck} on app ${app.tag} version ${app.version}`);
1231
- }
1232
- if (action.headers.data.length > 0 ||
1233
- action.body.data.length > 0 ||
1234
- action.params.data.length > 0 ||
1235
- action.query.data.length > 0) {
1236
- throw new Error('Healthcheck action is expected to have no headers, body, params or query');
1237
- }
1228
+ await this.validateProviderHealthcheckTag(d);
1238
1229
  }
1239
1230
  }
1240
1231
  d.last_checked = new Date();
@@ -1253,12 +1244,19 @@ class ProductsBuilderService {
1253
1244
  }
1254
1245
  }
1255
1246
  async fetchFallbacks(version) {
1247
+ var _a;
1256
1248
  const components = await this.productApi.fetchProductComponents(this.product_id, 'fallback', this.getUserAccess());
1257
- return components;
1249
+ if (Array.isArray(components) && components.length > 0) {
1250
+ return components;
1251
+ }
1252
+ return Array.isArray((_a = this.product) === null || _a === void 0 ? void 0 : _a.fallback) ? this.product.fallback : [];
1258
1253
  }
1259
1254
  async fetchFallback(tag, version) {
1255
+ var _a, _b, _c;
1260
1256
  const component = await this.productApi.fetchProductComponentByTag(this.product_id, 'fallback', tag, this.getUserAccess());
1261
- return component;
1257
+ if (component)
1258
+ return component;
1259
+ return (_c = ((_b = (_a = this.product) === null || _a === void 0 ? void 0 : _a.fallback) !== null && _b !== void 0 ? _b : []).find((f) => f.tag === tag)) !== null && _c !== void 0 ? _c : null;
1262
1260
  }
1263
1261
  async deleteFallback(tag) {
1264
1262
  try {
@@ -1778,6 +1776,19 @@ class ProductsBuilderService {
1778
1776
  productTag: (_b = this.product) === null || _b === void 0 ? void 0 : _b.tag,
1779
1777
  });
1780
1778
  if (!(await this.fetchVector(data.tag))) {
1779
+ const auth = this.getUserAccess();
1780
+ const cloudApi = new cloudApi_service_1.CloudApiService(this.env_type);
1781
+ const productTag = this.product.tag;
1782
+ const materializedEnvs = [];
1783
+ for (const env of data.envs || []) {
1784
+ materializedEnvs.push(await (0, cloud_vector_link_util_1.materializeCloudLinkedVectorEnv)(env, {
1785
+ cloudApi,
1786
+ auth,
1787
+ productTag,
1788
+ vectorTag: data.tag,
1789
+ }));
1790
+ }
1791
+ data.envs = materializedEnvs;
1781
1792
  await validators_1.CreateProductVectorSchema.validateAsync(data);
1782
1793
  const processedEnvs = [];
1783
1794
  for (const env of data.envs) {
@@ -1863,43 +1874,52 @@ class ProductsBuilderService {
1863
1874
  }
1864
1875
  if (data.envs) {
1865
1876
  console.log('[ProductsService.updateVector] Processing envs update');
1877
+ const auth = this.getUserAccess();
1878
+ const cloudApi = new cloudApi_service_1.CloudApiService(this.env_type);
1879
+ const productTag = this.product.tag;
1866
1880
  data.envs = await Promise.all(data.envs.map(async (env) => {
1867
1881
  var _a, _b;
1882
+ const materialized = await (0, cloud_vector_link_util_1.materializeCloudLinkedVectorEnv)(env, {
1883
+ cloudApi,
1884
+ auth,
1885
+ productTag,
1886
+ vectorTag: data.tag || tag,
1887
+ });
1868
1888
  console.log('[ProductsService.updateVector] Processing env:', {
1869
1889
  slug: env.slug,
1870
1890
  hasApiKey: !!env.apiKey,
1871
1891
  hasEndpoint: !!env.endpoint,
1872
1892
  });
1873
- const exists = await this.fetchEnv(env.slug);
1893
+ const exists = await this.fetchEnv(materialized.slug);
1874
1894
  if (!exists) {
1875
- throw new Error(`Env ${env.slug} does not exist`);
1895
+ throw new Error(`Env ${materialized.slug} does not exist`);
1876
1896
  }
1877
1897
  // Store API key as secret and encrypt
1878
- if (env.apiKey) {
1879
- console.log('[ProductsService.updateVector] Converting API key to secret for env:', env.slug);
1880
- const originalApiKey = env.apiKey;
1881
- env.apiKey = await this.storeVectorApiKeyAsSecret(env.apiKey, data.tag || tag, env.slug);
1898
+ if (materialized.apiKey) {
1899
+ console.log('[ProductsService.updateVector] Converting API key to secret for env:', materialized.slug);
1900
+ const originalApiKey = materialized.apiKey;
1901
+ materialized.apiKey = await this.storeVectorApiKeyAsSecret(materialized.apiKey, data.tag || tag, materialized.slug);
1882
1902
  console.log('[ProductsService.updateVector] API key after secret conversion:', {
1883
- wasConverted: originalApiKey !== env.apiKey,
1884
- isSecretRef: (_a = env.apiKey) === null || _a === void 0 ? void 0 : _a.startsWith('$Secret{'),
1903
+ wasConverted: originalApiKey !== materialized.apiKey,
1904
+ isSecretRef: (_a = materialized.apiKey) === null || _a === void 0 ? void 0 : _a.startsWith('$Secret{'),
1885
1905
  });
1886
- env.apiKey = (0, processor_utils_1.encrypt)(env.apiKey, this.product.private_key);
1906
+ materialized.apiKey = (0, processor_utils_1.encrypt)(materialized.apiKey, this.product.private_key);
1887
1907
  console.log('[ProductsService.updateVector] API key encrypted');
1888
1908
  }
1889
1909
  // Store endpoint as secret and encrypt
1890
- if (env.endpoint) {
1891
- console.log('[ProductsService.updateVector] Converting endpoint to secret for env:', env.slug);
1892
- const originalEndpoint = env.endpoint;
1893
- env.endpoint = await this.storeVectorEndpointAsSecret(env.endpoint, data.tag || tag, env.slug);
1910
+ if (materialized.endpoint) {
1911
+ console.log('[ProductsService.updateVector] Converting endpoint to secret for env:', materialized.slug);
1912
+ const originalEndpoint = materialized.endpoint;
1913
+ materialized.endpoint = await this.storeVectorEndpointAsSecret(materialized.endpoint, data.tag || tag, materialized.slug);
1894
1914
  console.log('[ProductsService.updateVector] Endpoint after secret conversion:', {
1895
- wasConverted: originalEndpoint !== env.endpoint,
1896
- isSecretRef: (_b = env.endpoint) === null || _b === void 0 ? void 0 : _b.startsWith('$Secret{'),
1915
+ wasConverted: originalEndpoint !== materialized.endpoint,
1916
+ isSecretRef: (_b = materialized.endpoint) === null || _b === void 0 ? void 0 : _b.startsWith('$Secret{'),
1897
1917
  });
1898
- env.endpoint = (0, processor_utils_1.encrypt)(env.endpoint, this.product.private_key);
1918
+ materialized.endpoint = (0, processor_utils_1.encrypt)(materialized.endpoint, this.product.private_key);
1899
1919
  console.log('[ProductsService.updateVector] Endpoint encrypted');
1900
1920
  }
1901
- console.log('[ProductsService.updateVector] Env processed successfully:', env.slug);
1902
- return env;
1921
+ console.log('[ProductsService.updateVector] Env processed successfully:', materialized.slug);
1922
+ return materialized;
1903
1923
  }));
1904
1924
  const overwrite = [];
1905
1925
  const newEnvs = [];
@@ -2580,18 +2600,26 @@ class ProductsBuilderService {
2580
2600
  }
2581
2601
  }
2582
2602
  async fetchAppWebhooks(access_tag) {
2603
+ var _a;
2583
2604
  try {
2584
2605
  const app = await this.fetchThirdPartyAppByAccessTag(access_tag);
2585
2606
  if (!app) {
2586
2607
  throw new Error(`App with access tag ${access_tag} not found`);
2587
2608
  }
2588
2609
  const version = app.versions.find((version) => version.tag === app.version);
2589
- //const status = await this.webhooksApi.fetchWebhooks({access_tag, app_tag: app.tag, version: app.version}, this.getUserAccess())
2590
- console.log(status);
2591
2610
  if (!version) {
2592
2611
  throw new Error(`Required app version not found`);
2593
2612
  }
2594
- return version.webhooks;
2613
+ try {
2614
+ const withConfig = await this.webhooksApi.fetchWebhooks({ access_tag, app_tag: app.tag, version: app.version }, this.getUserAccess());
2615
+ if (Array.isArray(withConfig) && withConfig.length > 0) {
2616
+ return withConfig;
2617
+ }
2618
+ }
2619
+ catch (_b) {
2620
+ // Fall back to app definition webhooks when runtime service is unavailable
2621
+ }
2622
+ return (_a = version.webhooks) !== null && _a !== void 0 ? _a : [];
2595
2623
  }
2596
2624
  catch (e) {
2597
2625
  throw e;
@@ -4070,11 +4098,11 @@ class ProductsBuilderService {
4070
4098
  return datapoint;
4071
4099
  }
4072
4100
  async fetchThirdPartyAppByAccessTag(access_tag) {
4101
+ const cached = this.thirdPartyApps.find((item) => access_tag === item.access_tag);
4102
+ if (cached) {
4103
+ return Object.assign(Object.assign({}, cached.app), { version: cached.version });
4104
+ }
4073
4105
  try {
4074
- const app = this.thirdPartyApps.find((item) => access_tag === item.access_tag);
4075
- if (app) {
4076
- return Object.assign(Object.assign({}, app.app), { version: app.version });
4077
- }
4078
4106
  const access = await this.appApi.fetchAccessByTag(access_tag, this.getUserAccess());
4079
4107
  if (!access) {
4080
4108
  throw new Error(`Access to app ${access_tag} not found`);
@@ -4083,10 +4111,57 @@ class ProductsBuilderService {
4083
4111
  this.thirdPartyApps.push({ access_tag, app: appData, version: access.version });
4084
4112
  return Object.assign(Object.assign({}, appData), { version: access.version });
4085
4113
  }
4086
- catch (e) {
4087
- throw e;
4114
+ catch (primaryError) {
4115
+ const resolved = await this.fetchThirdPartyAppByAppTagFallback(access_tag);
4116
+ if (resolved) {
4117
+ this.thirdPartyApps.push({
4118
+ access_tag,
4119
+ app: resolved,
4120
+ version: resolved.version,
4121
+ });
4122
+ return resolved;
4123
+ }
4124
+ throw primaryError;
4088
4125
  }
4089
4126
  }
4127
+ /**
4128
+ * When workspace access-tag lookup fails (common for product-scoped tags like ductape:paystack:ductape),
4129
+ * resolve the app via product-linked components or fetchAppByTag.
4130
+ */
4131
+ async fetchThirdPartyAppByAppTagFallback(access_tag) {
4132
+ var _a, _b, _c, _d, _e, _f, _g;
4133
+ const appTag = await this.resolveAppTagFromAccessReference(access_tag);
4134
+ if (!appTag)
4135
+ return null;
4136
+ const appData = await this.appApi.fetchAppByTag(appTag, this.getUserAccess());
4137
+ if (!appData)
4138
+ return null;
4139
+ const version = (_g = (_d = (_a = (typeof appData.version === 'string'
4140
+ ? appData.version
4141
+ : undefined)) !== null && _a !== void 0 ? _a : (_c = (_b = appData.versions) === null || _b === void 0 ? void 0 : _b.find((v) => v.tag === 'ductape')) === null || _c === void 0 ? void 0 : _c.tag) !== null && _d !== void 0 ? _d : (_f = (_e = appData.versions) === null || _e === void 0 ? void 0 : _e[0]) === null || _f === void 0 ? void 0 : _f.tag) !== null && _g !== void 0 ? _g : 'ductape';
4142
+ return Object.assign(Object.assign({}, appData), { version });
4143
+ }
4144
+ async resolveAppTagFromAccessReference(access_tag) {
4145
+ if (this.product_id) {
4146
+ const productApps = (await this.productApi.fetchProductComponents(this.product_id, 'app', this.getUserAccess()));
4147
+ const linked = productApps.find((a) => {
4148
+ var _a;
4149
+ return a.access_tag === access_tag ||
4150
+ a.app_tag === access_tag ||
4151
+ ((_a = a.access_tag) === null || _a === void 0 ? void 0 : _a.startsWith(`${access_tag}:`));
4152
+ });
4153
+ if (linked === null || linked === void 0 ? void 0 : linked.app_tag)
4154
+ return linked.app_tag;
4155
+ }
4156
+ // Heuristic: product access tags are often `{app_tag}:{workspace}` — try app tag prefix
4157
+ if (access_tag.includes(':')) {
4158
+ const parts = access_tag.split(':');
4159
+ if (parts.length >= 2) {
4160
+ return `${parts[0]}:${parts[1]}`;
4161
+ }
4162
+ }
4163
+ return access_tag;
4164
+ }
4090
4165
  fetchThirdPartyAppActionByTag(actions, action_tag) {
4091
4166
  const action = actions.find((action) => action.tag === action_tag);
4092
4167
  if (!action) {
@@ -4322,6 +4397,19 @@ class ProductsBuilderService {
4322
4397
  });
4323
4398
  try {
4324
4399
  if (!(await this.fetchGraph(data.tag))) {
4400
+ const auth = this.getUserAccess();
4401
+ const cloudApi = new cloudApi_service_1.CloudApiService(this.env_type);
4402
+ const productTag = this.product.tag;
4403
+ const materializedEnvs = [];
4404
+ for (const env of data.envs || []) {
4405
+ materializedEnvs.push(await (0, cloud_graph_link_util_1.materializeCloudLinkedGraphEnv)(env, {
4406
+ cloudApi,
4407
+ auth,
4408
+ productTag,
4409
+ graphTag: data.tag,
4410
+ }));
4411
+ }
4412
+ data.envs = materializedEnvs;
4325
4413
  await validators_1.CreateProductGraphSchema.validateAsync(data);
4326
4414
  const processedEnvs = [];
4327
4415
  for (const env of data.envs) {
@@ -4402,41 +4490,50 @@ class ProductsBuilderService {
4402
4490
  throw new Error(`tag ${tag} is in use`);
4403
4491
  }
4404
4492
  console.log('[ProductsService.updateGraph] Processing envs update');
4493
+ const auth = this.getUserAccess();
4494
+ const cloudApi = new cloudApi_service_1.CloudApiService(this.env_type);
4495
+ const productTag = this.product.tag;
4405
4496
  data.envs = await Promise.all(data.envs.map(async (env) => {
4406
4497
  var _a;
4498
+ const materialized = await (0, cloud_graph_link_util_1.materializeCloudLinkedGraphEnv)(env, {
4499
+ cloudApi,
4500
+ auth,
4501
+ productTag,
4502
+ graphTag: data.tag || tag,
4503
+ });
4407
4504
  console.log('[ProductsService.updateGraph] Processing env:', {
4408
- slug: env.slug,
4409
- hasConnectionUrl: !!env.connection_url,
4410
- hasUsername: !!env.username,
4411
- hasPassword: !!env.password,
4505
+ slug: materialized.slug,
4506
+ hasConnectionUrl: !!materialized.connection_url,
4507
+ hasUsername: !!materialized.username,
4508
+ hasPassword: !!materialized.password,
4412
4509
  });
4413
- const exists = await this.fetchEnv(env.slug);
4510
+ const exists = await this.fetchEnv(materialized.slug);
4414
4511
  if (!exists) {
4415
- throw new Error(`Env ${env.slug} does not exist`);
4512
+ throw new Error(`Env ${materialized.slug} does not exist`);
4416
4513
  }
4417
- if (env.connection_url) {
4514
+ if (materialized.connection_url) {
4418
4515
  // Store connection URL as secret
4419
- console.log('[ProductsService.updateGraph] Converting connection URL to secret for env:', env.slug);
4420
- const originalUrl = env.connection_url;
4421
- env.connection_url = await this.storeGraphConnectionUrlAsSecret(env.connection_url, data.tag || tag, env.slug);
4516
+ console.log('[ProductsService.updateGraph] Converting connection URL to secret for env:', materialized.slug);
4517
+ const originalUrl = materialized.connection_url;
4518
+ materialized.connection_url = await this.storeGraphConnectionUrlAsSecret(materialized.connection_url, data.tag || tag, materialized.slug);
4422
4519
  console.log('[ProductsService.updateGraph] Connection URL after secret conversion:', {
4423
- wasConverted: originalUrl !== env.connection_url,
4424
- isSecretRef: (_a = env.connection_url) === null || _a === void 0 ? void 0 : _a.startsWith('$Secret{'),
4520
+ wasConverted: originalUrl !== materialized.connection_url,
4521
+ isSecretRef: (_a = materialized.connection_url) === null || _a === void 0 ? void 0 : _a.startsWith('$Secret{'),
4425
4522
  });
4426
- env.connection_url = (0, processor_utils_1.encrypt)(env.connection_url, this.product.private_key);
4523
+ materialized.connection_url = (0, processor_utils_1.encrypt)(materialized.connection_url, this.product.private_key);
4427
4524
  console.log('[ProductsService.updateGraph] Connection URL encrypted');
4428
4525
  }
4429
4526
  // Encrypt username and password if provided
4430
- if (env.username) {
4431
- console.log('[ProductsService.updateGraph] Encrypting username for env:', env.slug);
4432
- env.username = (0, processor_utils_1.encrypt)(env.username, this.product.private_key);
4527
+ if (materialized.username) {
4528
+ console.log('[ProductsService.updateGraph] Encrypting username for env:', materialized.slug);
4529
+ materialized.username = (0, processor_utils_1.encrypt)(materialized.username, this.product.private_key);
4433
4530
  }
4434
- if (env.password) {
4435
- console.log('[ProductsService.updateGraph] Encrypting password for env:', env.slug);
4436
- env.password = (0, processor_utils_1.encrypt)(env.password, this.product.private_key);
4531
+ if (materialized.password) {
4532
+ console.log('[ProductsService.updateGraph] Encrypting password for env:', materialized.slug);
4533
+ materialized.password = (0, processor_utils_1.encrypt)(materialized.password, this.product.private_key);
4437
4534
  }
4438
- console.log('[ProductsService.updateGraph] Env processed successfully:', env.slug);
4439
- return env;
4535
+ console.log('[ProductsService.updateGraph] Env processed successfully:', materialized.slug);
4536
+ return materialized;
4440
4537
  }));
4441
4538
  const overwrite = [];
4442
4539
  const newEnvs = [];