@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.
Files changed (46) hide show
  1. package/dist/api/services/processorApi.service.d.ts +2 -0
  2. package/dist/api/services/processorApi.service.js +51 -0
  3. package/dist/api/services/processorApi.service.js.map +1 -1
  4. package/dist/api/services/productsApi.service.d.ts +7 -1
  5. package/dist/api/services/productsApi.service.js.map +1 -1
  6. package/dist/api/urls.d.ts +1 -0
  7. package/dist/api/urls.js +5 -4
  8. package/dist/api/urls.js.map +1 -1
  9. package/dist/context/execution-context.d.ts +37 -0
  10. package/dist/context/execution-context.js +38 -0
  11. package/dist/context/execution-context.js.map +1 -0
  12. package/dist/context/index.d.ts +1 -0
  13. package/dist/context/index.js +18 -0
  14. package/dist/context/index.js.map +1 -0
  15. package/dist/database/adapters/mongodb.adapter.js +7 -7
  16. package/dist/database/adapters/mongodb.adapter.js.map +1 -1
  17. package/dist/database/databases.service.d.ts +4 -0
  18. package/dist/database/databases.service.js +7 -1
  19. package/dist/database/databases.service.js.map +1 -1
  20. package/dist/features/feature-executor.d.ts +10 -0
  21. package/dist/features/feature-executor.js +113 -35
  22. package/dist/features/feature-executor.js.map +1 -1
  23. package/dist/features/features.service.d.ts +16 -1
  24. package/dist/features/features.service.js +87 -26
  25. package/dist/features/features.service.js.map +1 -1
  26. package/dist/features/types/features.types.d.ts +8 -2
  27. package/dist/features/types/features.types.js.map +1 -1
  28. package/dist/functions/functions.runtime.js +25 -1
  29. package/dist/functions/functions.runtime.js.map +1 -1
  30. package/dist/functions/types.d.ts +6 -0
  31. package/dist/functions/types.js.map +1 -1
  32. package/dist/index.d.ts +2 -1
  33. package/dist/index.js +4 -0
  34. package/dist/index.js.map +1 -1
  35. package/dist/products/bootstrap-cache.js +7 -2
  36. package/dist/products/bootstrap-cache.js.map +1 -1
  37. package/dist/products/services/products.service.d.ts +2 -2
  38. package/dist/products/services/products.service.js +36 -1
  39. package/dist/products/services/products.service.js.map +1 -1
  40. package/dist/products/utils/functions.utils.d.ts +1 -1
  41. package/dist/sessions/sessions.helper.js +13 -1
  42. package/dist/sessions/sessions.helper.js.map +1 -1
  43. package/dist/tsconfig.tsbuildinfo +1 -1
  44. package/dist/types/processor.types.d.ts +15 -0
  45. package/dist/types/processor.types.js.map +1 -1
  46. 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
- /** Always-on step/summary logging for visibility in tests and support */
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.saveResult(processorResult, this.getAuthPayload());
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
- await this.processorApiService.saveResult(stepResult, this.getAuthPayload());
468
- this.trace('step.persistence.acknowledged', { step_tag: step.tag, status, process_id: stepProcessId });
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
- console.warn(`[FeatureExecutor] Step "${step.tag}" output:`, stepReturnValue);
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
- invocation_id: invocationId,
1193
- namespace: reference.namespace,
1194
- operation: reference.operation,
1195
- version: reference.version,
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
- resolved[key] = resolvedValues[i];
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
- return this.getNestedValue(this.state.input, inputMatch[1]);
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 (_a = val[idx]) !== null && _a !== void 0 ? _a : '';
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) {