@ductape/sdk 0.1.117 → 0.1.120

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.
@@ -5120,16 +5120,47 @@ class ProductsBuilderService {
5120
5120
  }
5121
5121
  }
5122
5122
  }
5123
- await this.productApi.updateProduct(this.product_id, Object.assign(Object.assign({}, data), { component: enums_1.ProductComponents.FEATURE, action: enums_1.RequestAction.CREATE }), this.getUserAccess());
5123
+ try {
5124
+ await this.productApi.updateProduct(this.product_id, Object.assign(Object.assign({}, data), { component: enums_1.ProductComponents.FEATURE, action: enums_1.RequestAction.CREATE }), this.getUserAccess());
5125
+ }
5126
+ catch (createErr) {
5127
+ // The by-tag existence check above (fetchFeature) is known to false-negative
5128
+ // under backend read lag -- confirmed by tags that unambiguously already exist
5129
+ // (verified via the always-consistent list-all endpoint) still failing this
5130
+ // check, especially under the ~30-way concurrent defineFeature() calls this SDK
5131
+ // makes on a typical app boot. If the backend now rejects this as a duplicate,
5132
+ // the feature was there all along and this is that false negative resolving
5133
+ // itself -- fall through to the read-back below instead of failing the caller.
5134
+ const isDuplicate = createErr instanceof Error && /already exists|duplicate|in use/i.test(createErr.message);
5135
+ if (!isDuplicate)
5136
+ throw createErr;
5137
+ }
5124
5138
  // A successful mutation response is insufficient: execution and Workbench
5125
5139
  // use the catalogue read API. Confirm that exact path can observe the write.
5126
- for (let attempt = 0; attempt < 5; attempt += 1) {
5140
+ // Widened from the original 5x50-200ms budget (~500ms total): that was proven
5141
+ // too tight against real backend read lag, which is on the order of seconds
5142
+ // under concurrent boot-time load, not milliseconds.
5143
+ let delay = 100;
5144
+ for (let attempt = 0; attempt < 8; attempt += 1) {
5127
5145
  const persisted = await this.fetchFeature(data.tag);
5128
5146
  if (persisted)
5129
5147
  return persisted;
5130
- if (attempt < 4)
5131
- await new Promise((resolve) => setTimeout(resolve, 50 * (attempt + 1)));
5148
+ if (attempt < 7) {
5149
+ await new Promise((resolve) => setTimeout(resolve, delay));
5150
+ delay = Math.min(delay * 2, 3000);
5151
+ }
5132
5152
  }
5153
+ // The by-tag read path is still lagging. Fall back to the list-all endpoint,
5154
+ // which every observed case shows is immediately consistent -- if the feature
5155
+ // shows up there, the write is real and the by-tag path is just slow, so don't
5156
+ // fail the caller (e.g. a boot-time feature registration) over that.
5157
+ const all = await this.fetchFeatures();
5158
+ const viaList = all.find((feature) => feature.tag === data.tag);
5159
+ if (viaList)
5160
+ return viaList;
5161
+ // Genuinely unconfirmed on every read path we have, including the always-reliable
5162
+ // list-all endpoint -- this is no longer just a lagging read, so fail loudly
5163
+ // instead of pretending the write succeeded.
5133
5164
  throw new Error(`Feature catalogue write was acknowledged but ${data.tag} could not be read back`);
5134
5165
  }
5135
5166
  else {
@@ -5169,11 +5200,35 @@ class ProductsBuilderService {
5169
5200
  }
5170
5201
  async fetchFeatures() {
5171
5202
  const components = await this.productApi.fetchProductComponents(this.product_id, 'feature', this.getUserAccess());
5172
- return components;
5203
+ if (Array.isArray(components) && components.length > 0) {
5204
+ return components;
5205
+ }
5206
+ // Compatibility path for rolling backend deployments. The full-product
5207
+ // endpoint and Workbench read the canonical embedded `features` array,
5208
+ // while older /components/:id/feature handlers can incorrectly return an
5209
+ // empty list. Always perform a fresh product read before concluding that a
5210
+ // product has no features.
5211
+ const product = await this.productApi.fetchProduct(this.product_id, this.getUserAccess());
5212
+ const rawProduct = product;
5213
+ const byTag = new Map();
5214
+ for (const feature of [
5215
+ ...(rawProduct.features || []),
5216
+ ...(rawProduct.workflows || []),
5217
+ ]) {
5218
+ if ((feature === null || feature === void 0 ? void 0 : feature.tag) && feature.deleted !== true && !byTag.has(feature.tag)) {
5219
+ byTag.set(feature.tag, feature);
5220
+ }
5221
+ }
5222
+ return Array.from(byTag.values());
5173
5223
  }
5174
5224
  async fetchFeature(tag) {
5175
5225
  const component = await this.productApi.fetchProductComponentByTag(this.product_id, 'feature', tag, this.getUserAccess());
5176
- return component;
5226
+ if (component)
5227
+ return component;
5228
+ // Keep tag lookup consistent with fetchFeatures()/Workbench when an older
5229
+ // feature-component endpoint is still serving traffic.
5230
+ const features = await this.fetchFeatures();
5231
+ return features.find((feature) => feature.tag === tag) || null;
5177
5232
  }
5178
5233
  async deleteFeature(tag) {
5179
5234
  try {