@ductape/sdk 0.1.113 → 0.1.115
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.
- package/dist/api/services/processorApi.service.d.ts +2 -0
- package/dist/api/services/processorApi.service.js +51 -0
- package/dist/api/services/processorApi.service.js.map +1 -1
- package/dist/api/services/productsApi.service.d.ts +7 -1
- package/dist/api/services/productsApi.service.js.map +1 -1
- package/dist/api/urls.d.ts +1 -0
- package/dist/api/urls.js +5 -4
- package/dist/api/urls.js.map +1 -1
- package/dist/context/execution-context.d.ts +37 -0
- package/dist/context/execution-context.js +38 -0
- package/dist/context/execution-context.js.map +1 -0
- package/dist/context/index.d.ts +1 -0
- package/dist/context/index.js +18 -0
- package/dist/context/index.js.map +1 -0
- package/dist/database/adapters/mongodb.adapter.js +7 -7
- package/dist/database/adapters/mongodb.adapter.js.map +1 -1
- package/dist/database/databases.service.d.ts +4 -0
- package/dist/database/databases.service.js +7 -1
- package/dist/database/databases.service.js.map +1 -1
- package/dist/features/feature-executor.d.ts +10 -0
- package/dist/features/feature-executor.js +113 -35
- package/dist/features/feature-executor.js.map +1 -1
- package/dist/features/features.service.d.ts +16 -1
- package/dist/features/features.service.js +87 -26
- package/dist/features/features.service.js.map +1 -1
- package/dist/features/types/features.types.d.ts +8 -2
- package/dist/features/types/features.types.js.map +1 -1
- package/dist/functions/functions.runtime.js +25 -1
- package/dist/functions/functions.runtime.js.map +1 -1
- package/dist/functions/types.d.ts +6 -0
- package/dist/functions/types.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/products/bootstrap-cache.js +7 -2
- package/dist/products/bootstrap-cache.js.map +1 -1
- package/dist/products/services/products.service.d.ts +2 -2
- package/dist/products/services/products.service.js +36 -1
- package/dist/products/services/products.service.js.map +1 -1
- package/dist/products/utils/functions.utils.d.ts +1 -1
- package/dist/sessions/sessions.helper.js +13 -1
- package/dist/sessions/sessions.helper.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/processor.types.d.ts +15 -0
- package/dist/types/processor.types.js.map +1 -1
- package/package.json +2 -2
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
12
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
13
|
};
|
|
14
|
-
var _a, _b;
|
|
14
|
+
var _a, _b, _c, _d;
|
|
15
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
-
exports.FeatureExecutor = void 0;
|
|
16
|
+
exports.FeatureExecutor = exports.UnresolvedInputReferenceError = void 0;
|
|
17
17
|
const crypto_1 = require("crypto");
|
|
18
18
|
const processor_service_1 = __importDefault(require("../processor/services/processor.service"));
|
|
19
19
|
const products_service_1 = __importDefault(require("../products/services/products.service"));
|
|
@@ -33,12 +33,23 @@ const processor_utils_1 = require("../processor/utils/processor.utils");
|
|
|
33
33
|
const productsBuilder_types_1 = require("../types/productsBuilder.types");
|
|
34
34
|
const date_fns_1 = require("date-fns");
|
|
35
35
|
const functions_1 = require("../functions");
|
|
36
|
+
const context_1 = require("../context");
|
|
37
|
+
class UnresolvedInputReferenceError extends Error {
|
|
38
|
+
constructor(reference, required) {
|
|
39
|
+
super(`Runtime input reference ${reference} is absent${required ? ' but required by the feature contract' : ''}.`);
|
|
40
|
+
this.reference = reference;
|
|
41
|
+
this.required = required;
|
|
42
|
+
this.code = 'UNRESOLVED_INPUT_REFERENCE';
|
|
43
|
+
this.name = 'UnresolvedInputReferenceError';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
exports.UnresolvedInputReferenceError = UnresolvedInputReferenceError;
|
|
36
47
|
/** Only log when DUCTAPE_DEBUG is set to avoid sync I/O and serialization cost in hot path */
|
|
37
48
|
const debugLog = typeof process !== 'undefined' && (((_a = process.env) === null || _a === void 0 ? void 0 : _a.DUCTAPE_DEBUG) === 'true' || ((_b = process.env) === null || _b === void 0 ? void 0 : _b.DUCTAPE_DEBUG) === '1')
|
|
38
49
|
? (...args) => console.log(...args)
|
|
39
50
|
: () => { };
|
|
40
|
-
/**
|
|
41
|
-
const stepLog = typeof process !== 'undefined'
|
|
51
|
+
/** Detailed step logging is opt-in; synchronous stdout is expensive in server runtimes. */
|
|
52
|
+
const stepLog = typeof process !== 'undefined' && (((_c = process.env) === null || _c === void 0 ? void 0 : _c.DUCTAPE_DEBUG) === 'true' || ((_d = process.env) === null || _d === void 0 ? void 0 : _d.DUCTAPE_DEBUG) === '1')
|
|
42
53
|
? (msg, data) => {
|
|
43
54
|
if (data != null && Object.keys(data).length > 0) {
|
|
44
55
|
console.log(`[FeatureExecutor] ${msg}`, data);
|
|
@@ -150,6 +161,8 @@ class FeatureExecutor {
|
|
|
150
161
|
/** Pre-fetched bootstrap data per step tag (feature batch prefetch). */
|
|
151
162
|
this.stepBootstrapCache = new Map();
|
|
152
163
|
this.functionInvocations = new Map();
|
|
164
|
+
/** Step telemetry is accumulated and persisted with the terminal feature record in one round trip. */
|
|
165
|
+
this.pendingProcessorResults = [];
|
|
153
166
|
debugLog('[FeatureExecutor] constructor ENTRY', {
|
|
154
167
|
feature_tag: feature.tag,
|
|
155
168
|
product: options.product,
|
|
@@ -262,6 +275,7 @@ class FeatureExecutor {
|
|
|
262
275
|
env_type: this.config.env_type,
|
|
263
276
|
private_key: this._privateKey,
|
|
264
277
|
access_key: this.config.access_key,
|
|
278
|
+
redis_client: this.config.redis_client,
|
|
265
279
|
});
|
|
266
280
|
}
|
|
267
281
|
return this._graphService;
|
|
@@ -277,6 +291,8 @@ class FeatureExecutor {
|
|
|
277
291
|
env_type: this.config.env_type,
|
|
278
292
|
private_key: this._privateKey,
|
|
279
293
|
access_key: this.config.access_key,
|
|
294
|
+
redis_client: this.config.redis_client,
|
|
295
|
+
preInitializedProductBuilder: this.productBuilder,
|
|
280
296
|
});
|
|
281
297
|
}
|
|
282
298
|
return this._databaseService;
|
|
@@ -380,24 +396,9 @@ class FeatureExecutor {
|
|
|
380
396
|
const resultStr = stringifyProcessorValue(resultData);
|
|
381
397
|
const inputStr = stringifyProcessorValue(inputPayload);
|
|
382
398
|
const encKey = this.getProcessorResultEncryptionKey();
|
|
383
|
-
const processorResult = {
|
|
384
|
-
process_id: this.state.feature_id,
|
|
385
|
-
product_id: this.productId || null,
|
|
386
|
-
env: this.state.env,
|
|
387
|
-
component: types_1.LogEventTypes.FEATURE,
|
|
388
|
-
status,
|
|
389
|
-
start: this.state.started_at,
|
|
390
|
-
end: Date.now(),
|
|
391
|
-
retryable: true,
|
|
392
|
-
result: encKey ? (0, processor_utils_1.encrypt)(resultStr, encKey) : resultStr,
|
|
393
|
-
input: encKey ? (0, processor_utils_1.encrypt)(inputStr, encKey) : inputStr,
|
|
394
|
-
feature_id: this.state.feature_id,
|
|
395
|
-
feature_tag: this.state.feature_tag,
|
|
396
|
-
product_tag: this.state.product,
|
|
397
|
-
workspace_id: this.config.workspace_id,
|
|
398
|
-
};
|
|
399
|
+
const processorResult = Object.assign(Object.assign({}, (0, context_1.executionContextFields)()), { process_id: this.state.feature_id, product_id: this.productId || null, env: this.state.env, component: types_1.LogEventTypes.FEATURE, status, start: this.state.started_at, end: Date.now(), retryable: true, result: encKey ? (0, processor_utils_1.encrypt)(resultStr, encKey) : resultStr, input: encKey ? (0, processor_utils_1.encrypt)(inputStr, encKey) : inputStr, feature_id: this.state.feature_id, feature_tag: this.state.feature_tag, product_tag: this.state.product, workspace_id: this.config.workspace_id });
|
|
399
400
|
this.trace('execution.persistence.sent', { status, payload: resultData });
|
|
400
|
-
await this.processorApiService.
|
|
401
|
+
await this.processorApiService.saveResultsBatch([...this.pendingProcessorResults.splice(0), processorResult], this.getAuthPayload());
|
|
401
402
|
this.trace('execution.persistence.acknowledged', { status, process_id: this.state.feature_id });
|
|
402
403
|
debugLog('[FeatureExecutor] persistExecutionResult SUCCESS', { feature_id: this.state.feature_id, status });
|
|
403
404
|
}
|
|
@@ -455,8 +456,8 @@ class FeatureExecutor {
|
|
|
455
456
|
const resultStr = stringifyProcessorValue(value);
|
|
456
457
|
const inputStr = stringifyProcessorValue(resolvedInput);
|
|
457
458
|
const encKey = this.getProcessorResultEncryptionKey();
|
|
458
|
-
const stepResult = Object.assign({ process_id: stepProcessId, product_id: this.productId || null, env: this.state.env, component: types_1.LogEventTypes.FEATURE_STEP, // step results always feature_step
|
|
459
|
-
status, start: Date.now() - (duration || 0), end: Date.now(), retryable: true, result: encKey ? (0, processor_utils_1.encrypt)(resultStr, encKey) : resultStr, input: encKey ? (0, processor_utils_1.encrypt)(inputStr, encKey) : inputStr, feature_id: this.state.feature_id, feature_tag: this.state.feature_tag, product_tag: this.state.product, workspace_id: this.config.workspace_id, step_tag: step.tag, step_type: stepType, step_error: error, step_duration_ms: duration }, ((_a = this.functionInvocations.get(step.tag)) !== null && _a !== void 0 ? _a : {}));
|
|
459
|
+
const stepResult = Object.assign(Object.assign(Object.assign({}, (0, context_1.executionContextFields)()), { process_id: stepProcessId, product_id: this.productId || null, env: this.state.env, component: types_1.LogEventTypes.FEATURE_STEP, // step results always feature_step
|
|
460
|
+
status, start: Date.now() - (duration || 0), end: Date.now(), retryable: true, result: encKey ? (0, processor_utils_1.encrypt)(resultStr, encKey) : resultStr, input: encKey ? (0, processor_utils_1.encrypt)(inputStr, encKey) : inputStr, feature_id: this.state.feature_id, feature_tag: this.state.feature_tag, product_tag: this.state.product, workspace_id: this.config.workspace_id, step_tag: step.tag, step_type: stepType, step_error: error, step_duration_ms: duration }), ((_a = this.functionInvocations.get(step.tag)) !== null && _a !== void 0 ? _a : {}));
|
|
460
461
|
this.trace('step.persistence.sent', {
|
|
461
462
|
step_tag: step.tag,
|
|
462
463
|
step_type: stepType,
|
|
@@ -464,8 +465,8 @@ class FeatureExecutor {
|
|
|
464
465
|
input: resolvedInput,
|
|
465
466
|
output: value,
|
|
466
467
|
});
|
|
467
|
-
|
|
468
|
-
this.trace('step.persistence.
|
|
468
|
+
this.pendingProcessorResults.push(stepResult);
|
|
469
|
+
this.trace('step.persistence.queued', { step_tag: step.tag, status, process_id: stepProcessId });
|
|
469
470
|
debugLog('[FeatureExecutor] persistStepResult SUCCESS', { feature_id: this.state.feature_id, step_tag: step.tag });
|
|
470
471
|
}
|
|
471
472
|
catch (err) {
|
|
@@ -518,6 +519,25 @@ class FeatureExecutor {
|
|
|
518
519
|
* Execute the feature
|
|
519
520
|
*/
|
|
520
521
|
async execute() {
|
|
522
|
+
var _a;
|
|
523
|
+
const context = (0, context_1.createExecutionContext)({
|
|
524
|
+
kind: 'feature',
|
|
525
|
+
workspace_id: this.config.workspace_id,
|
|
526
|
+
product_id: (_a = this.productId) !== null && _a !== void 0 ? _a : undefined,
|
|
527
|
+
product_tag: this.state.product,
|
|
528
|
+
env: this.state.env,
|
|
529
|
+
feature_id: this.state.feature_id,
|
|
530
|
+
feature_tag: this.state.feature_tag,
|
|
531
|
+
feature_run_id: this.state.feature_id,
|
|
532
|
+
process_id: this.state.feature_id,
|
|
533
|
+
session: this.inheritedSession,
|
|
534
|
+
session_id: this.sessionLogFields.session_id,
|
|
535
|
+
session_tag: this.sessionLogFields.session_tag,
|
|
536
|
+
session_user_id: this.sessionLogFields.session_user_id,
|
|
537
|
+
});
|
|
538
|
+
return (0, context_1.runWithExecutionContext)(context, () => this.executeInContext());
|
|
539
|
+
}
|
|
540
|
+
async executeInContext() {
|
|
521
541
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
|
|
522
542
|
const startTime = Date.now();
|
|
523
543
|
debugLog('[FeatureExecutor] execute ENTRY', {
|
|
@@ -614,7 +634,7 @@ class FeatureExecutor {
|
|
|
614
634
|
else if (!result.success) {
|
|
615
635
|
stepReturnValue = undefined;
|
|
616
636
|
}
|
|
617
|
-
|
|
637
|
+
debugLog(`[FeatureExecutor] Step "${step.tag}" output:`, stepReturnValue);
|
|
618
638
|
// Persist step result to backend (use step return shape, not raw primitive)
|
|
619
639
|
await this.persistStepResult(step, result.success ? types_1.LogEventStatus.SUCCESS : types_1.LogEventStatus.FAIL, stepReturnValue, result.error, result.duration, result.input);
|
|
620
640
|
this.state.step_timings.push({
|
|
@@ -772,6 +792,19 @@ class FeatureExecutor {
|
|
|
772
792
|
* Execute a single step
|
|
773
793
|
*/
|
|
774
794
|
async executeStep(step) {
|
|
795
|
+
const parent = (0, context_1.currentExecutionContext)();
|
|
796
|
+
const stepRunId = (0, crypto_1.randomUUID)();
|
|
797
|
+
const context = (0, context_1.createExecutionContext)({
|
|
798
|
+
kind: 'step',
|
|
799
|
+
step_tag: step.tag,
|
|
800
|
+
step_run_id: stepRunId,
|
|
801
|
+
step_attempt: 1,
|
|
802
|
+
process_id: `${this.state.feature_id}:${step.tag}`,
|
|
803
|
+
parent_process_id: this.state.feature_id,
|
|
804
|
+
}, parent);
|
|
805
|
+
return (0, context_1.runWithExecutionContext)(context, () => this.executeStepInContext(step));
|
|
806
|
+
}
|
|
807
|
+
async executeStepInContext(step) {
|
|
775
808
|
const startTime = Date.now();
|
|
776
809
|
debugLog('[FeatureExecutor] executeStep ENTRY', {
|
|
777
810
|
feature_id: this.state.feature_id,
|
|
@@ -1183,16 +1216,17 @@ class FeatureExecutor {
|
|
|
1183
1216
|
}
|
|
1184
1217
|
/** Execute a portable application function through a local handler or signed HTTP transport. */
|
|
1185
1218
|
async executeFunctionStep(step, input) {
|
|
1219
|
+
var _a, _b, _c;
|
|
1186
1220
|
const reference = step.function_ref;
|
|
1187
1221
|
if (!reference) {
|
|
1188
1222
|
throw new Error(`Function step "${step.tag}" has no function_ref. Recompile the feature with ctx.functions.`);
|
|
1189
1223
|
}
|
|
1190
1224
|
const invocationId = (0, crypto_1.randomUUID)();
|
|
1191
1225
|
this.functionInvocations.set(step.tag, {
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1226
|
+
function_invocation_id: invocationId,
|
|
1227
|
+
function_namespace: reference.namespace,
|
|
1228
|
+
function_operation: reference.operation,
|
|
1229
|
+
function_version: reference.version,
|
|
1196
1230
|
});
|
|
1197
1231
|
const deadlineAt = reference.timeout_ms ? Date.now() + reference.timeout_ms : undefined;
|
|
1198
1232
|
this.trace('function.resolution.started', {
|
|
@@ -1208,9 +1242,16 @@ class FeatureExecutor {
|
|
|
1208
1242
|
workspace_id: this.config.workspace_id,
|
|
1209
1243
|
feature_id: this.state.feature_id,
|
|
1210
1244
|
feature_tag: this.state.feature_tag,
|
|
1245
|
+
feature_run_id: this.state.feature_id,
|
|
1211
1246
|
step_tag: step.tag,
|
|
1247
|
+
step_run_id: (_a = (0, context_1.currentExecutionContext)()) === null || _a === void 0 ? void 0 : _a.step_run_id,
|
|
1212
1248
|
invocation_id: invocationId,
|
|
1249
|
+
trace_id: (_b = (0, context_1.currentExecutionContext)()) === null || _b === void 0 ? void 0 : _b.trace_id,
|
|
1250
|
+
parent_span_id: (_c = (0, context_1.currentExecutionContext)()) === null || _c === void 0 ? void 0 : _c.span_id,
|
|
1213
1251
|
session: this.inheritedSession,
|
|
1252
|
+
session_id: this.sessionLogFields.session_id,
|
|
1253
|
+
session_tag: this.sessionLogFields.session_tag,
|
|
1254
|
+
session_user_id: this.sessionLogFields.session_user_id,
|
|
1214
1255
|
deadline_at: deadlineAt,
|
|
1215
1256
|
}, this.config.access_key);
|
|
1216
1257
|
this.trace('function.response.received', {
|
|
@@ -1680,7 +1721,10 @@ class FeatureExecutor {
|
|
|
1680
1721
|
const resolvedValues = await Promise.all(entries.map(([, value]) => this.resolveValue(value, stepOutput)));
|
|
1681
1722
|
const resolved = {};
|
|
1682
1723
|
entries.forEach(([key], i) => {
|
|
1683
|
-
|
|
1724
|
+
// An absent optional input is absence, not an unresolved operator string
|
|
1725
|
+
// and not an explicit null. Omitting it preserves defaulting semantics.
|
|
1726
|
+
if (resolvedValues[i] !== undefined)
|
|
1727
|
+
resolved[key] = resolvedValues[i];
|
|
1684
1728
|
});
|
|
1685
1729
|
return resolved;
|
|
1686
1730
|
}
|
|
@@ -1714,14 +1758,23 @@ class FeatureExecutor {
|
|
|
1714
1758
|
* $Uppercase, $Lowercase, $Dateformat, $Replace, $Filter, $Find, $Size, $Length
|
|
1715
1759
|
*/
|
|
1716
1760
|
async resolveStringValue(value, stepOutput) {
|
|
1717
|
-
var _a;
|
|
1761
|
+
var _a, _b;
|
|
1718
1762
|
const v = value.trim();
|
|
1719
1763
|
// --- Data references (exact match) ---
|
|
1720
1764
|
if (v === '$Now' || v === '$Now()')
|
|
1721
1765
|
return Date.now();
|
|
1722
1766
|
const inputMatch = v.match(/^\$Input\{(.+)\}$/);
|
|
1723
|
-
if (inputMatch)
|
|
1724
|
-
|
|
1767
|
+
if (inputMatch) {
|
|
1768
|
+
const path = inputMatch[1];
|
|
1769
|
+
const resolved = this.getNestedValue(this.state.input, path);
|
|
1770
|
+
if (resolved !== undefined)
|
|
1771
|
+
return resolved;
|
|
1772
|
+
const root = path.split('.')[0];
|
|
1773
|
+
const inputDefinition = (_a = this.feature.input) === null || _a === void 0 ? void 0 : _a[root];
|
|
1774
|
+
if (inputDefinition === null || inputDefinition === void 0 ? void 0 : inputDefinition.required)
|
|
1775
|
+
throw new UnresolvedInputReferenceError(v, true);
|
|
1776
|
+
return undefined;
|
|
1777
|
+
}
|
|
1725
1778
|
const sequenceMatch = v.match(/^\$Sequence\{main\}\{([^}]+)\}\{(.+)\}$/);
|
|
1726
1779
|
if (sequenceMatch) {
|
|
1727
1780
|
const stepData = this.state.steps[sequenceMatch[1]];
|
|
@@ -1782,6 +1835,10 @@ class FeatureExecutor {
|
|
|
1782
1835
|
const separator = concatMatch[2];
|
|
1783
1836
|
const parts = arrContent.split(',').map((s) => this.unquote(s.trim()));
|
|
1784
1837
|
const resolvedParts = await Promise.all(parts.map((p) => this.resolveValue(p, stepOutput)));
|
|
1838
|
+
const missingIndex = resolvedParts.findIndex((part) => part === undefined);
|
|
1839
|
+
if (missingIndex >= 0) {
|
|
1840
|
+
throw new UnresolvedInputReferenceError(parts[missingIndex], false);
|
|
1841
|
+
}
|
|
1785
1842
|
return resolvedParts.map((x) => (x == null ? '' : String(x))).join(separator);
|
|
1786
1843
|
}
|
|
1787
1844
|
// $Substring(str, start, end)
|
|
@@ -1836,7 +1893,7 @@ class FeatureExecutor {
|
|
|
1836
1893
|
const val = await this.resolveValue(this.unquote(pickMatch[1]), stepOutput);
|
|
1837
1894
|
const idx = Number(pickMatch[2]);
|
|
1838
1895
|
if (typeof val === 'string' || Array.isArray(val))
|
|
1839
|
-
return (
|
|
1896
|
+
return (_b = val[idx]) !== null && _b !== void 0 ? _b : '';
|
|
1840
1897
|
return '';
|
|
1841
1898
|
}
|
|
1842
1899
|
// $Filter(array, "op", value)
|
|
@@ -1983,6 +2040,24 @@ class FeatureExecutor {
|
|
|
1983
2040
|
storage_tag: step.storage,
|
|
1984
2041
|
});
|
|
1985
2042
|
}
|
|
2043
|
+
else if (step.type === types_1.FeatureStepType.DATABASE_ACTION && step.database) {
|
|
2044
|
+
workflowSteps.push({ step_tag: step.tag, type: 'database', database_tag: step.database });
|
|
2045
|
+
}
|
|
2046
|
+
else if ((step.type === types_1.FeatureStepType.PUBLISH || step.type === types_1.FeatureStepType.PRODUCE) && step.broker) {
|
|
2047
|
+
workflowSteps.push({ step_tag: step.tag, type: 'broker', broker_tag: step.broker, topic_tag: step.event });
|
|
2048
|
+
}
|
|
2049
|
+
else if (step.type === types_1.FeatureStepType.GRAPH && step.graph) {
|
|
2050
|
+
workflowSteps.push({ step_tag: step.tag, type: 'graph', graph_tag: step.graph });
|
|
2051
|
+
}
|
|
2052
|
+
else if (step.type === types_1.FeatureStepType.VECTOR && step.vector) {
|
|
2053
|
+
workflowSteps.push({ step_tag: step.tag, type: 'vector', vector_tag: step.vector });
|
|
2054
|
+
}
|
|
2055
|
+
else if (step.type === types_1.FeatureStepType.QUOTA && step.quota) {
|
|
2056
|
+
workflowSteps.push({ step_tag: step.tag, type: 'quota', component_tag: step.quota });
|
|
2057
|
+
}
|
|
2058
|
+
else if (step.type === types_1.FeatureStepType.FALLBACK && step.fallback) {
|
|
2059
|
+
workflowSteps.push({ step_tag: step.tag, type: 'fallback', component_tag: step.fallback });
|
|
2060
|
+
}
|
|
1986
2061
|
}
|
|
1987
2062
|
if (workflowSteps.length > 0) {
|
|
1988
2063
|
stepLog('Bootstrap feature API call (single request)', {
|
|
@@ -2007,6 +2082,9 @@ class FeatureExecutor {
|
|
|
2007
2082
|
}
|
|
2008
2083
|
}
|
|
2009
2084
|
}
|
|
2085
|
+
// All primitive metadata above is fetched by the single feature bootstrap call.
|
|
2086
|
+
return;
|
|
2087
|
+
// Legacy fallback retained below for older servers; unreachable with the unified bootstrap.
|
|
2010
2088
|
// Pre-fetch message brokers for produce steps so runBrokerPublish has broker/topic in cache
|
|
2011
2089
|
const brokerTags = new Set();
|
|
2012
2090
|
for (const step of steps) {
|