@ductape/sdk 0.1.152 → 0.1.154
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/database/actions/output-contract.d.ts +46 -0
- package/dist/database/actions/output-contract.js +139 -0
- package/dist/database/actions/output-contract.js.map +1 -0
- package/dist/database/databases.service.d.ts +2 -2
- package/dist/database/databases.service.js.map +1 -1
- package/dist/database/index.d.ts +3 -1
- package/dist/database/index.js +5 -2
- package/dist/database/index.js.map +1 -1
- package/dist/database/types/action.interface.d.ts +26 -5
- package/dist/features/feature-executor.d.ts +1 -1
- package/dist/features/feature-executor.js +13 -1
- package/dist/features/feature-executor.js.map +1 -1
- package/dist/features/features.service.js +127 -10
- package/dist/features/features.service.js.map +1 -1
- package/dist/features/types/features.types.d.ts +67 -5
- package/dist/features/types/features.types.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +7 -3
- package/dist/index.js.map +1 -1
- package/dist/products/services/products.service.d.ts +1 -0
- package/dist/products/services/products.service.js +21 -2
- package/dist/products/services/products.service.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/productsBuilder.types.d.ts +16 -0
- package/dist/types/productsBuilder.types.js.map +1 -1
- package/package.json +1 -1
|
@@ -1666,7 +1666,7 @@ function createStepOutputProxy(pathPrefix = '') {
|
|
|
1666
1666
|
* - Default delimiter is "" so template literals like `receipts/${ctx.input.orderId}.txt` become receipts/ORD-001.txt not receipts/ ORD-001 .txt.
|
|
1667
1667
|
* - Recurses into plain objects and arrays; leaves other primitives unchanged.
|
|
1668
1668
|
*/
|
|
1669
|
-
const OPERATOR_REF_IN_STRING_REGEX = /(\$Input\{[^}]+\}|\$Sequence\{main\}\{[^}]+\}\{[^}]*\}|\$Now)/g;
|
|
1669
|
+
const OPERATOR_REF_IN_STRING_REGEX = /(\$Input\{[^}]+\}|\$Sequence\{main\}\{[^}]+\}\{[^}]*\}|\$Now|\$Uuid)/g;
|
|
1670
1670
|
function convertStringToConcatIfOperatorRefs(str, delimiter = '') {
|
|
1671
1671
|
OPERATOR_REF_IN_STRING_REGEX.lastIndex = 0;
|
|
1672
1672
|
const tokens = str.split(OPERATOR_REF_IN_STRING_REGEX);
|
|
@@ -1674,7 +1674,7 @@ function convertStringToConcatIfOperatorRefs(str, delimiter = '') {
|
|
|
1674
1674
|
for (const t of tokens) {
|
|
1675
1675
|
if (!t)
|
|
1676
1676
|
continue;
|
|
1677
|
-
const isOp = t.startsWith('$Input{') || t.startsWith('$Sequence{') || t === '$Now';
|
|
1677
|
+
const isOp = t.startsWith('$Input{') || t.startsWith('$Sequence{') || t === '$Now' || t === '$Uuid';
|
|
1678
1678
|
if (isOp) {
|
|
1679
1679
|
parts.push(t);
|
|
1680
1680
|
}
|
|
@@ -1717,6 +1717,9 @@ function convertToOperatorInput(value, inputProxy, schemaKeys) {
|
|
|
1717
1717
|
return out;
|
|
1718
1718
|
}
|
|
1719
1719
|
if (typeof value === 'string') {
|
|
1720
|
+
if (/^\$(?:Concat|Substring|Trim|Uppercase|Lowercase|Dateformat|Replace|Split|Join|Pick|Filter|Find|Size|Length|Add|Subtract)\s*[({]/.test(value)) {
|
|
1721
|
+
return value;
|
|
1722
|
+
}
|
|
1720
1723
|
return convertStringToConcatIfOperatorRefs(value);
|
|
1721
1724
|
}
|
|
1722
1725
|
return value;
|
|
@@ -1776,6 +1779,8 @@ class RecordingContext {
|
|
|
1776
1779
|
/** When set (from recordScenarios), every step we push gets this condition so it only runs when input matches. */
|
|
1777
1780
|
this._scenarioCondition = null;
|
|
1778
1781
|
this._recordInput = null;
|
|
1782
|
+
/** Explicit portable control-flow condition inherited by steps in a branch. */
|
|
1783
|
+
this._portableCondition = null;
|
|
1779
1784
|
this._featureTag = featureTag;
|
|
1780
1785
|
this._inputSchemaKeys = featureInputSchema ? Object.keys(featureInputSchema) : [];
|
|
1781
1786
|
this._stepResultOverrides = stepResultOverrides !== null && stepResultOverrides !== void 0 ? stepResultOverrides : {};
|
|
@@ -1827,10 +1832,68 @@ class RecordingContext {
|
|
|
1827
1832
|
get now() {
|
|
1828
1833
|
return '$Now';
|
|
1829
1834
|
}
|
|
1835
|
+
conditionOperand(value) {
|
|
1836
|
+
const converted = convertToOperatorInput(value, this._inputProxy, this._inputSchemaKeys);
|
|
1837
|
+
if (typeof converted === 'string' && converted.startsWith('$'))
|
|
1838
|
+
return converted;
|
|
1839
|
+
return JSON.stringify(converted);
|
|
1840
|
+
}
|
|
1841
|
+
comparison(left, operator, inverseOperator, right) {
|
|
1842
|
+
const lhs = this.conditionOperand(left);
|
|
1843
|
+
const rhs = this.conditionOperand(right);
|
|
1844
|
+
return { expression: `${lhs} ${operator} ${rhs}`, inverse: `${lhs} ${inverseOperator} ${rhs}` };
|
|
1845
|
+
}
|
|
1846
|
+
get when() {
|
|
1847
|
+
return {
|
|
1848
|
+
eq: (left, right) => this.comparison(left, '==', '!=', right),
|
|
1849
|
+
ne: (left, right) => this.comparison(left, '!=', '==', right),
|
|
1850
|
+
gt: (left, right) => this.comparison(left, '>', '<=', right),
|
|
1851
|
+
gte: (left, right) => this.comparison(left, '>=', '<', right),
|
|
1852
|
+
lt: (left, right) => this.comparison(left, '<', '>=', right),
|
|
1853
|
+
lte: (left, right) => this.comparison(left, '<=', '>', right),
|
|
1854
|
+
truthy: (value) => this.comparison(value, '==', '!=', true),
|
|
1855
|
+
falsy: (value) => this.comparison(value, '==', '!=', false),
|
|
1856
|
+
and: (...conditions) => ({
|
|
1857
|
+
expression: conditions.map((condition) => `(${condition.expression})`).join(' && '),
|
|
1858
|
+
inverse: conditions.map((condition) => `(${condition.inverse})`).join(' || '),
|
|
1859
|
+
}),
|
|
1860
|
+
or: (...conditions) => ({
|
|
1861
|
+
expression: conditions.map((condition) => `(${condition.expression})`).join(' || '),
|
|
1862
|
+
inverse: conditions.map((condition) => `(${condition.inverse})`).join(' && '),
|
|
1863
|
+
}),
|
|
1864
|
+
};
|
|
1865
|
+
}
|
|
1866
|
+
async branch(condition, handlers) {
|
|
1867
|
+
if (!(condition === null || condition === void 0 ? void 0 : condition.expression) || !(condition === null || condition === void 0 ? void 0 : condition.inverse)) {
|
|
1868
|
+
throw new FeatureCompilationError(this._featureTag, '(branch)', 'ctx.branch requires a condition created by ctx.when.');
|
|
1869
|
+
}
|
|
1870
|
+
const parent = this._portableCondition;
|
|
1871
|
+
const scoped = (expression) => parent ? `(${parent}) && (${expression})` : expression;
|
|
1872
|
+
try {
|
|
1873
|
+
this._portableCondition = scoped(condition.expression);
|
|
1874
|
+
await handlers.then();
|
|
1875
|
+
if (handlers.else) {
|
|
1876
|
+
this._portableCondition = scoped(condition.inverse);
|
|
1877
|
+
await handlers.else();
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
finally {
|
|
1881
|
+
this._portableCondition = parent;
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
async each(items, handler) {
|
|
1885
|
+
if (!Array.isArray(items)) {
|
|
1886
|
+
throw new FeatureCompilationError(this._featureTag, '(loop)', 'ctx.each requires a concrete ctx.sampleInput array during compilation. Use ctx.functions for runtime-sized collection logic.');
|
|
1887
|
+
}
|
|
1888
|
+
for (let index = 0; index < items.length; index += 1) {
|
|
1889
|
+
await handler(items[index], index);
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1830
1892
|
/**
|
|
1831
1893
|
* Record a step definition
|
|
1832
1894
|
*/
|
|
1833
1895
|
async step(tag, handler, rollback, options) {
|
|
1896
|
+
var _a;
|
|
1834
1897
|
this._currentStepTag = tag;
|
|
1835
1898
|
this._stepOrder++;
|
|
1836
1899
|
this._stepResultDependencies = new Set();
|
|
@@ -1889,6 +1952,16 @@ class RecordingContext {
|
|
|
1889
1952
|
const condParts = [];
|
|
1890
1953
|
if (this._scenarioCondition)
|
|
1891
1954
|
condParts.push(`(${this._scenarioCondition})`);
|
|
1955
|
+
if (this._portableCondition) {
|
|
1956
|
+
condParts.push(`(${this._portableCondition})`);
|
|
1957
|
+
const conditionDependencies = [
|
|
1958
|
+
...Array.from(this._portableCondition.matchAll(/\$Step\{([^}]+)\}/g), (match) => match[1]),
|
|
1959
|
+
...Array.from(this._portableCondition.matchAll(/\$Sequence\{main\}\{([^}]+)\}/g), (match) => match[1]),
|
|
1960
|
+
];
|
|
1961
|
+
if (conditionDependencies.length > 0) {
|
|
1962
|
+
step.depends_on = Array.from(new Set([...((_a = step.depends_on) !== null && _a !== void 0 ? _a : []), ...conditionDependencies]));
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1892
1965
|
if (this._pendingConditionFromOverride) {
|
|
1893
1966
|
condParts.push(`(${this._pendingConditionFromOverride})`);
|
|
1894
1967
|
this._pendingConditionFromOverride = null;
|
|
@@ -1960,21 +2033,42 @@ class RecordingContext {
|
|
|
1960
2033
|
insert: async (options) => {
|
|
1961
2034
|
this._updateCurrentStep(productsBuilder_types_1.FeatureStepType.DATABASE_ACTION, 'insert', {
|
|
1962
2035
|
database: options.database,
|
|
1963
|
-
input: { table: options.table, data: options.data },
|
|
2036
|
+
input: { table: options.table, data: options.data, returning: options.returning },
|
|
1964
2037
|
});
|
|
1965
2038
|
return createStepOutputProxy();
|
|
1966
2039
|
},
|
|
1967
2040
|
update: async (options) => {
|
|
1968
2041
|
this._updateCurrentStep(productsBuilder_types_1.FeatureStepType.DATABASE_ACTION, 'update', {
|
|
1969
2042
|
database: options.database,
|
|
1970
|
-
input: { table: options.table, data: options.data, where: options.where },
|
|
2043
|
+
input: { table: options.table, data: options.data, where: options.where, returning: options.returning },
|
|
1971
2044
|
});
|
|
1972
2045
|
return createStepOutputProxy();
|
|
1973
2046
|
},
|
|
1974
2047
|
delete: async (options) => {
|
|
1975
2048
|
this._updateCurrentStep(productsBuilder_types_1.FeatureStepType.DATABASE_ACTION, 'delete', {
|
|
1976
2049
|
database: options.database,
|
|
1977
|
-
input: { table: options.table, where: options.where },
|
|
2050
|
+
input: { table: options.table, where: options.where, returning: options.returning },
|
|
2051
|
+
});
|
|
2052
|
+
return createStepOutputProxy();
|
|
2053
|
+
},
|
|
2054
|
+
upsert: async (options) => {
|
|
2055
|
+
this._updateCurrentStep(productsBuilder_types_1.FeatureStepType.DATABASE_ACTION, 'upsert', {
|
|
2056
|
+
database: options.database,
|
|
2057
|
+
input: options,
|
|
2058
|
+
});
|
|
2059
|
+
return createStepOutputProxy();
|
|
2060
|
+
},
|
|
2061
|
+
aggregate: async (options) => {
|
|
2062
|
+
this._updateCurrentStep(productsBuilder_types_1.FeatureStepType.DATABASE_ACTION, 'aggregate', {
|
|
2063
|
+
database: options.database,
|
|
2064
|
+
input: options,
|
|
2065
|
+
});
|
|
2066
|
+
return createStepOutputProxy();
|
|
2067
|
+
},
|
|
2068
|
+
raw: async (options) => {
|
|
2069
|
+
this._updateCurrentStep(productsBuilder_types_1.FeatureStepType.DATABASE_ACTION, 'raw', {
|
|
2070
|
+
database: options.database,
|
|
2071
|
+
input: options,
|
|
1978
2072
|
});
|
|
1979
2073
|
return createStepOutputProxy();
|
|
1980
2074
|
},
|
|
@@ -2221,18 +2315,27 @@ class RecordingContext {
|
|
|
2221
2315
|
};
|
|
2222
2316
|
}
|
|
2223
2317
|
get transform() {
|
|
2318
|
+
const operator = (value) => {
|
|
2319
|
+
const converted = convertToOperatorInput(value, this._inputProxy, this._inputSchemaKeys);
|
|
2320
|
+
return typeof converted === 'string' && converted.startsWith('$') ? converted : null;
|
|
2321
|
+
};
|
|
2322
|
+
const operand = (value) => { var _a; return (_a = operator(value)) !== null && _a !== void 0 ? _a : JSON.stringify(value); };
|
|
2224
2323
|
return {
|
|
2324
|
+
concat: (...parts) => `$Concat([${parts.map(operand).join(', ')}], "")`,
|
|
2225
2325
|
size: (obj) => Object.keys(obj).length,
|
|
2226
2326
|
length: (arr) => arr.length,
|
|
2227
2327
|
parseJson: (str) => JSON.parse(str),
|
|
2228
2328
|
stringify: (obj) => JSON.stringify(obj),
|
|
2229
|
-
upper: (str) => str.toUpperCase(),
|
|
2230
|
-
lower: (str) => str.toLowerCase(),
|
|
2231
|
-
trim: (str) => str.trim(),
|
|
2329
|
+
upper: (str) => operator(str) ? `$Uppercase(${operand(str)})` : str.toUpperCase(),
|
|
2330
|
+
lower: (str) => operator(str) ? `$Lowercase(${operand(str)})` : str.toLowerCase(),
|
|
2331
|
+
trim: (str) => operator(str) ? `$Trim(${operand(str)})` : str.trim(),
|
|
2232
2332
|
split: (str, separator) => str.split(separator),
|
|
2233
2333
|
join: (arr, separator) => arr.join(separator),
|
|
2234
|
-
now: () =>
|
|
2235
|
-
|
|
2334
|
+
now: () => '$Now',
|
|
2335
|
+
uuid: () => '$Uuid',
|
|
2336
|
+
replace: (str, search, replacement) => operator(str) ? `$Replace(${operand(str)}, ${JSON.stringify(search)}, ${JSON.stringify(replacement)})` : str.split(search).join(replacement),
|
|
2337
|
+
substring: (str, start, end) => operator(str) ? `$Substring(${operand(str)}, ${start}, ${end})` : str.substring(start, end),
|
|
2338
|
+
formatDate: (date, format) => operator(date) ? `$Dateformat(${operand(date)}, ${JSON.stringify(format)})` : new Date(date).toISOString(),
|
|
2236
2339
|
};
|
|
2237
2340
|
}
|
|
2238
2341
|
// ==================== DATA REFERENCES ====================
|
|
@@ -2424,6 +2527,19 @@ class FeatureCompiler {
|
|
|
2424
2527
|
constructor(options) {
|
|
2425
2528
|
this.options = options;
|
|
2426
2529
|
}
|
|
2530
|
+
validatePortableControlFlow() {
|
|
2531
|
+
const source = this.options.handler.toString();
|
|
2532
|
+
if (/\bDate\s*\.\s*now\s*\(/.test(source) || /\bMath\s*\.\s*random\s*\(/.test(source)) {
|
|
2533
|
+
throw new FeatureCompilationError(this.options.tag, '(handler)', 'Date.now() and Math.random() run while the Feature is being compiled and would become frozen literals. ' +
|
|
2534
|
+
'Use ctx.transform.now() for the current runtime timestamp. Generate random or unique values through a portable function or reusable action.');
|
|
2535
|
+
}
|
|
2536
|
+
const containsNullishBranch = /\?\?/.test(source) ||
|
|
2537
|
+
/!==\s*null\s*&&[\s\S]{0,300}!==\s*void\s+0/.test(source);
|
|
2538
|
+
if (containsNullishBranch && !this.options.branchOverrides) {
|
|
2539
|
+
throw new FeatureCompilationError(this.options.tag, '(handler)', 'Nullish branching on a step result cannot be inferred safely. Provide branchOverrides for the step output that records this branch, ' +
|
|
2540
|
+
'or use an explicit portable branch primitive. The Feature was not persisted.');
|
|
2541
|
+
}
|
|
2542
|
+
}
|
|
2427
2543
|
/**
|
|
2428
2544
|
* Compile the feature definition to JSON schema
|
|
2429
2545
|
* Executes the handler with a recording context to capture step definitions
|
|
@@ -2478,6 +2594,7 @@ class FeatureCompiler {
|
|
|
2478
2594
|
*/
|
|
2479
2595
|
async compileAsync() {
|
|
2480
2596
|
var _a, _b, _c, _d, _e;
|
|
2597
|
+
this.validatePortableControlFlow();
|
|
2481
2598
|
const scenarios = this.options.recordScenarios && this.options.recordScenarios.length > 0
|
|
2482
2599
|
? this.options.recordScenarios
|
|
2483
2600
|
: [(_a = this.options.recordInput) !== null && _a !== void 0 ? _a : {}];
|