@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
|
-
|
|
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
|
-
|
|
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 <
|
|
5131
|
-
await new Promise((resolve) => setTimeout(resolve,
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|