@haibun/core 3.0.3 → 3.1.0
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/build/currentVersion.d.ts +1 -1
- package/build/currentVersion.js +1 -2
- package/build/lib/EventLogger.d.ts +22 -0
- package/build/lib/EventLogger.d.ts.map +1 -0
- package/build/lib/EventLogger.js +95 -0
- package/build/lib/EventLogger.js.map +1 -0
- package/build/lib/Logger.d.ts.map +1 -1
- package/build/lib/Logger.js +2 -1
- package/build/lib/Logger.js.map +1 -1
- package/build/lib/astepper.d.ts +11 -1
- package/build/lib/astepper.d.ts.map +1 -1
- package/build/lib/astepper.js +8 -0
- package/build/lib/astepper.js.map +1 -1
- package/build/lib/core/flow-runner.d.ts +25 -0
- package/build/lib/core/flow-runner.d.ts.map +1 -0
- package/build/lib/core/flow-runner.js +128 -0
- package/build/lib/core/flow-runner.js.map +1 -0
- package/build/lib/core/protocol.d.ts +58 -0
- package/build/lib/core/protocol.d.ts.map +1 -0
- package/build/lib/core/protocol.js +18 -0
- package/build/lib/core/protocol.js.map +1 -0
- package/build/lib/core-domains.d.ts +2 -16
- package/build/lib/core-domains.d.ts.map +1 -1
- package/build/lib/core-domains.js +46 -27
- package/build/lib/core-domains.js.map +1 -1
- package/build/lib/defs.d.ts +42 -11
- package/build/lib/defs.d.ts.map +1 -1
- package/build/lib/defs.js +6 -1
- package/build/lib/defs.js.map +1 -1
- package/build/lib/domain-types.d.ts +12 -1
- package/build/lib/domain-types.d.ts.map +1 -1
- package/build/lib/domain-types.js +63 -3
- package/build/lib/domain-types.js.map +1 -1
- package/build/lib/event-bridge.d.ts +28 -0
- package/build/lib/event-bridge.d.ts.map +1 -0
- package/build/lib/event-bridge.js +114 -0
- package/build/lib/event-bridge.js.map +1 -0
- package/build/lib/feature-variables.d.ts +14 -0
- package/build/lib/feature-variables.d.ts.map +1 -1
- package/build/lib/feature-variables.js +128 -6
- package/build/lib/feature-variables.js.map +1 -1
- package/build/lib/interfaces/logger.d.ts +19 -3
- package/build/lib/interfaces/logger.d.ts.map +1 -1
- package/build/lib/interfaces/logger.js +11 -1
- package/build/lib/interfaces/logger.js.map +1 -1
- package/build/lib/namedVars.d.ts.map +1 -1
- package/build/lib/namedVars.js +18 -10
- package/build/lib/namedVars.js.map +1 -1
- package/build/lib/populateActionArgs.d.ts +1 -1
- package/build/lib/populateActionArgs.d.ts.map +1 -1
- package/build/lib/populateActionArgs.js +11 -49
- package/build/lib/populateActionArgs.js.map +1 -1
- package/build/lib/test/TestSteps.d.ts +1 -0
- package/build/lib/test/TestSteps.d.ts.map +1 -1
- package/build/lib/test/TestStepsWithOptions.d.ts +1 -0
- package/build/lib/test/TestStepsWithOptions.d.ts.map +1 -1
- package/build/lib/test/lib.d.ts.map +1 -1
- package/build/lib/test/lib.js +2 -0
- package/build/lib/test/lib.js.map +1 -1
- package/build/lib/util/actualURI.js +1 -1
- package/build/lib/util/actualURI.js.map +1 -1
- package/build/lib/util/index.d.ts +11 -2
- package/build/lib/util/index.d.ts.map +1 -1
- package/build/lib/util/index.js +46 -2
- package/build/lib/util/index.js.map +1 -1
- package/build/lib/util/variables.d.ts +9 -0
- package/build/lib/util/variables.d.ts.map +1 -0
- package/build/lib/util/variables.js +40 -0
- package/build/lib/util/variables.js.map +1 -0
- package/build/lib/util/workspace-lib.d.ts.map +1 -1
- package/build/lib/util/workspace-lib.js +30 -3
- package/build/lib/util/workspace-lib.js.map +1 -1
- package/build/monitor/browser-stubs.d.ts +12 -0
- package/build/monitor/browser-stubs.d.ts.map +1 -0
- package/build/monitor/browser-stubs.js +20 -0
- package/build/monitor/browser-stubs.js.map +1 -0
- package/build/monitor/constants.d.ts +9 -0
- package/build/monitor/constants.d.ts.map +1 -0
- package/build/monitor/constants.js +9 -0
- package/build/monitor/constants.js.map +1 -0
- package/build/monitor/event-view.d.ts +40 -0
- package/build/monitor/event-view.d.ts.map +1 -0
- package/build/monitor/event-view.js +104 -0
- package/build/monitor/event-view.js.map +1 -0
- package/build/monitor/filters.d.ts +17 -0
- package/build/monitor/filters.d.ts.map +1 -0
- package/build/monitor/filters.js +32 -0
- package/build/monitor/filters.js.map +1 -0
- package/build/monitor/formatters.d.ts +27 -0
- package/build/monitor/formatters.d.ts.map +1 -0
- package/build/monitor/formatters.js +89 -0
- package/build/monitor/formatters.js.map +1 -0
- package/build/monitor/index.d.ts +11 -0
- package/build/monitor/index.d.ts.map +1 -0
- package/build/monitor/index.js +15 -0
- package/build/monitor/index.js.map +1 -0
- package/build/monitor/jit-serialization.d.ts +9 -0
- package/build/monitor/jit-serialization.d.ts.map +1 -0
- package/build/monitor/jit-serialization.js +73 -0
- package/build/monitor/jit-serialization.js.map +1 -0
- package/build/monitor/monitor-types.d.ts +119 -0
- package/build/monitor/monitor-types.d.ts.map +1 -0
- package/build/monitor/monitor-types.js +43 -0
- package/build/monitor/monitor-types.js.map +1 -0
- package/build/monitor/speculative-tracker.d.ts +20 -0
- package/build/monitor/speculative-tracker.d.ts.map +1 -0
- package/build/monitor/speculative-tracker.js +38 -0
- package/build/monitor/speculative-tracker.js.map +1 -0
- package/build/monitor/state.d.ts +31 -0
- package/build/monitor/state.d.ts.map +1 -0
- package/build/monitor/state.js +110 -0
- package/build/monitor/state.js.map +1 -0
- package/build/monitor/timer.d.ts +11 -0
- package/build/monitor/timer.d.ts.map +1 -0
- package/build/monitor/timer.js +13 -0
- package/build/monitor/timer.js.map +1 -0
- package/build/monitor/tree-builder.d.ts +25 -0
- package/build/monitor/tree-builder.d.ts.map +1 -0
- package/build/monitor/tree-builder.js +36 -0
- package/build/monitor/tree-builder.js.map +1 -0
- package/build/phases/Executor.d.ts.map +1 -1
- package/build/phases/Executor.js +102 -43
- package/build/phases/Executor.js.map +1 -1
- package/build/phases/Resolver.d.ts +2 -1
- package/build/phases/Resolver.d.ts.map +1 -1
- package/build/phases/Resolver.js +64 -1
- package/build/phases/Resolver.js.map +1 -1
- package/build/runner.d.ts.map +1 -1
- package/build/runner.js +5 -0
- package/build/runner.js.map +1 -1
- package/build/schema/events.d.ts +174 -0
- package/build/schema/events.d.ts.map +1 -0
- package/build/schema/events.js +55 -0
- package/build/schema/events.js.map +1 -0
- package/build/steps/activities-stepper.d.ts +55 -20
- package/build/steps/activities-stepper.d.ts.map +1 -1
- package/build/steps/activities-stepper.js +342 -207
- package/build/steps/activities-stepper.js.map +1 -1
- package/build/steps/conformance.d.ts +1 -0
- package/build/steps/conformance.d.ts.map +1 -1
- package/build/steps/console-monitor-stepper.d.ts +44 -0
- package/build/steps/console-monitor-stepper.d.ts.map +1 -0
- package/build/steps/console-monitor-stepper.js +106 -0
- package/build/steps/console-monitor-stepper.js.map +1 -0
- package/build/steps/debugger-stepper.d.ts +5 -3
- package/build/steps/debugger-stepper.d.ts.map +1 -1
- package/build/steps/debugger-stepper.js +35 -11
- package/build/steps/debugger-stepper.js.map +1 -1
- package/build/steps/haibun.d.ts +11 -18
- package/build/steps/haibun.d.ts.map +1 -1
- package/build/steps/haibun.js +51 -55
- package/build/steps/haibun.js.map +1 -1
- package/build/steps/lib/tts.d.ts.map +1 -1
- package/build/steps/lib/tts.js +2 -1
- package/build/steps/lib/tts.js.map +1 -1
- package/build/steps/logic-stepper.d.ts +42 -0
- package/build/steps/logic-stepper.d.ts.map +1 -0
- package/build/steps/logic-stepper.js +143 -0
- package/build/steps/logic-stepper.js.map +1 -0
- package/build/steps/narrator.d.ts.map +1 -1
- package/build/steps/narrator.js +1 -1
- package/build/steps/narrator.js.map +1 -1
- package/build/steps/parse.d.ts +1 -0
- package/build/steps/parse.d.ts.map +1 -1
- package/build/steps/variables-stepper.d.ts +11 -71
- package/build/steps/variables-stepper.d.ts.map +1 -1
- package/build/steps/variables-stepper.js +534 -84
- package/build/steps/variables-stepper.js.map +1 -1
- package/package.json +16 -3
- package/build/applyEffectFeatures.d.ts +0 -4
- package/build/applyEffectFeatures.d.ts.map +0 -1
- package/build/applyEffectFeatures.js +0 -31
- package/build/applyEffectFeatures.js.map +0 -1
- package/build/jsprolog/converter.d.ts +0 -9
- package/build/jsprolog/converter.d.ts.map +0 -1
- package/build/jsprolog/converter.js +0 -42
- package/build/jsprolog/converter.js.map +0 -1
- package/build/jsprolog/stepper-utils.d.ts +0 -8
- package/build/jsprolog/stepper-utils.d.ts.map +0 -1
- package/build/jsprolog/stepper-utils.js +0 -8
- package/build/jsprolog/stepper-utils.js.map +0 -1
- package/build/jsprolog/test.jsprolog.d.ts +0 -4
- package/build/jsprolog/test.jsprolog.d.ts.map +0 -1
- package/build/jsprolog/test.jsprolog.js +0 -19
- package/build/jsprolog/test.jsprolog.js.map +0 -1
- package/build/jsprolog/test.kireji.d.ts +0 -4
- package/build/jsprolog/test.kireji.d.ts.map +0 -1
- package/build/jsprolog/test.kireji.js +0 -19
- package/build/jsprolog/test.kireji.js.map +0 -1
- package/build/jsprolog/withAction.d.ts +0 -32
- package/build/jsprolog/withAction.d.ts.map +0 -1
- package/build/jsprolog/withAction.js +0 -65
- package/build/jsprolog/withAction.js.map +0 -1
- package/build/kireji/test.kireji.d.ts +0 -4
- package/build/kireji/test.kireji.d.ts.map +0 -1
- package/build/kireji/test.kireji.js +0 -19
- package/build/kireji/test.kireji.js.map +0 -1
- package/build/lib/errors.d.ts +0 -13
- package/build/lib/errors.d.ts.map +0 -1
- package/build/lib/errors.js +0 -18
- package/build/lib/errors.js.map +0 -1
- package/build/lib/util/featureStep-executor.d.ts +0 -7
- package/build/lib/util/featureStep-executor.d.ts.map +0 -1
- package/build/lib/util/featureStep-executor.js +0 -63
- package/build/lib/util/featureStep-executor.js.map +0 -1
- package/build/steps/conditions-stepper.d.ts +0 -27
- package/build/steps/conditions-stepper.d.ts.map +0 -1
- package/build/steps/conditions-stepper.js +0 -99
- package/build/steps/conditions-stepper.js.map +0 -1
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
1
2
|
import { OK, Origin } from '../lib/defs.js';
|
|
2
3
|
import { AStepper } from '../lib/astepper.js';
|
|
3
|
-
import { actionNotOK,
|
|
4
|
+
import { actionOK, actionNotOK, getStepTerm, isLiteralValue } from '../lib/util/index.js';
|
|
4
5
|
import { FeatureVariables } from '../lib/feature-variables.js';
|
|
5
|
-
import { DOMAIN_STRING } from '../lib/domain-types.js';
|
|
6
|
+
import { DOMAIN_STATEMENT, DOMAIN_STRING, normalizeDomainKey, createEnumDomainDefinition, registerDomains } from '../lib/domain-types.js';
|
|
6
7
|
import { EExecutionMessageType } from '../lib/interfaces/logger.js';
|
|
7
8
|
const clearVars = (vars) => () => {
|
|
8
9
|
vars.getWorld().shared.clear();
|
|
@@ -10,8 +11,8 @@ const clearVars = (vars) => () => {
|
|
|
10
11
|
};
|
|
11
12
|
const cycles = (variablesStepper) => ({
|
|
12
13
|
startFeature: clearVars(variablesStepper),
|
|
13
|
-
startScenario: ({
|
|
14
|
-
variablesStepper.getWorld().shared = new FeatureVariables(variablesStepper.getWorld(), { ...
|
|
14
|
+
startScenario: ({ scopedVars }) => {
|
|
15
|
+
variablesStepper.getWorld().shared = new FeatureVariables(variablesStepper.getWorld(), { ...scopedVars.all() });
|
|
15
16
|
return Promise.resolve();
|
|
16
17
|
},
|
|
17
18
|
});
|
|
@@ -23,65 +24,107 @@ class VariablesStepper extends AStepper {
|
|
|
23
24
|
this.steppers = steppers;
|
|
24
25
|
await Promise.resolve();
|
|
25
26
|
}
|
|
26
|
-
checkIsSet(what) {
|
|
27
|
-
return this.getVarValue(what) !== undefined;
|
|
28
|
-
}
|
|
29
|
-
// FIXME provide explicit mapping to more carefully handle env, etc.
|
|
30
|
-
getVarValue(what) {
|
|
31
|
-
const envVal = this.getWorld().options.envVariables[what];
|
|
32
|
-
if (envVal !== undefined) {
|
|
33
|
-
return envVal;
|
|
34
|
-
}
|
|
35
|
-
return this.getWorld().shared.get(what);
|
|
36
|
-
}
|
|
37
|
-
isSet(what) {
|
|
38
|
-
if (this.checkIsSet(what)) {
|
|
39
|
-
return OK;
|
|
40
|
-
}
|
|
41
|
-
return actionNotOK(`${what} not set`);
|
|
42
|
-
}
|
|
43
27
|
steps = {
|
|
44
|
-
|
|
45
|
-
gwta:
|
|
46
|
-
|
|
47
|
-
action: ({
|
|
28
|
+
defineOpenSet: {
|
|
29
|
+
gwta: `set of {domain: string} as {superdomains: ${DOMAIN_STATEMENT}}`,
|
|
30
|
+
handlesUndefined: ['domain'],
|
|
31
|
+
action: ({ domain, superdomains }, featureStep) => this.registerSubdomainFromStatement(domain, superdomains, featureStep)
|
|
32
|
+
},
|
|
33
|
+
defineOrderedSet: {
|
|
34
|
+
precludes: [`${VariablesStepper.name}.defineValuesSet`, `${VariablesStepper.name}.defineSet`],
|
|
35
|
+
handlesUndefined: ['domain'],
|
|
36
|
+
gwta: `ordered set of {domain: string} is {values:${DOMAIN_STATEMENT}}`,
|
|
37
|
+
action: ({ domain, values }, featureStep) => this.registerValuesDomainFromStatement(domain, values, featureStep, { ordered: true, label: 'ordered set' })
|
|
38
|
+
},
|
|
39
|
+
defineValuesSet: {
|
|
40
|
+
gwta: `set of {domain: string} is {values:${DOMAIN_STATEMENT}}`,
|
|
41
|
+
handlesUndefined: ['domain'],
|
|
42
|
+
action: ({ domain, values }, featureStep) => this.registerValuesDomainFromStatement(domain, values, featureStep, { ordered: false, label: 'set' })
|
|
43
|
+
},
|
|
44
|
+
statementSetValues: {
|
|
45
|
+
expose: false,
|
|
46
|
+
gwta: '\\[{items: string}\\]',
|
|
47
|
+
action: () => OK,
|
|
48
|
+
},
|
|
49
|
+
composeAs: {
|
|
50
|
+
gwta: 'compose {what} as {domain} with {template}',
|
|
51
|
+
handlesUndefined: ['what', 'template'],
|
|
52
|
+
precludes: [`${VariablesStepper.name}.compose`],
|
|
53
|
+
action: ({ domain }, featureStep) => {
|
|
48
54
|
const { term } = featureStep.action.stepValuesMap.what;
|
|
49
|
-
|
|
50
|
-
|
|
55
|
+
const templateVal = featureStep.action.stepValuesMap.template;
|
|
56
|
+
if (!templateVal?.term)
|
|
57
|
+
return actionNotOK('template not provided');
|
|
58
|
+
const result = this.interpolateTemplate(templateVal.term, featureStep);
|
|
59
|
+
if (result.error)
|
|
60
|
+
return actionNotOK(result.error);
|
|
61
|
+
return trySetVariable(this.getWorld().shared, { term: String(term), value: result.value, domain, origin: Origin.var }, provenanceFromFeatureStep(featureStep));
|
|
51
62
|
}
|
|
52
63
|
},
|
|
53
|
-
|
|
54
|
-
gwta: '
|
|
55
|
-
|
|
64
|
+
compose: {
|
|
65
|
+
gwta: 'compose {what} with {template}',
|
|
66
|
+
handlesUndefined: ['what', 'template'],
|
|
67
|
+
action: (_, featureStep) => {
|
|
56
68
|
const { term } = featureStep.action.stepValuesMap.what;
|
|
57
|
-
|
|
58
|
-
|
|
69
|
+
const templateVal = featureStep.action.stepValuesMap.template;
|
|
70
|
+
if (!templateVal?.term)
|
|
71
|
+
return actionNotOK('template not provided');
|
|
72
|
+
const result = this.interpolateTemplate(templateVal.term, featureStep);
|
|
73
|
+
if (result.error)
|
|
74
|
+
return actionNotOK(result.error);
|
|
75
|
+
return trySetVariable(this.getWorld().shared, { term: String(term), value: result.value, domain: DOMAIN_STRING, origin: Origin.var }, provenanceFromFeatureStep(featureStep));
|
|
59
76
|
}
|
|
60
77
|
},
|
|
61
78
|
increment: {
|
|
62
79
|
gwta: 'increment {what}',
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const { term, domain } = featureStep.action.stepValuesMap.what;
|
|
66
|
-
const
|
|
67
|
-
|
|
80
|
+
handlesUndefined: ['what'],
|
|
81
|
+
action: (_, featureStep) => {
|
|
82
|
+
const { term: rawTerm, domain } = featureStep.action.stepValuesMap.what;
|
|
83
|
+
const interpolated = this.interpolateTemplate(rawTerm, featureStep);
|
|
84
|
+
if (interpolated.error)
|
|
85
|
+
return actionNotOK(interpolated.error);
|
|
86
|
+
const term = interpolated.value;
|
|
87
|
+
const resolved = this.getWorld().shared.resolveVariable({ term, origin: Origin.var, domain }, featureStep);
|
|
88
|
+
const presentVal = resolved.value;
|
|
89
|
+
const effectiveDomain = resolved.domain;
|
|
68
90
|
if (presentVal === undefined) {
|
|
69
|
-
|
|
91
|
+
return actionNotOK(`${term} not set`);
|
|
70
92
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
93
|
+
// If domain is an ordered enum, advance to the next enum value
|
|
94
|
+
const domainKey = effectiveDomain ? normalizeDomainKey(effectiveDomain) : undefined;
|
|
95
|
+
const registered = domainKey ? this.getWorld().domains[domainKey] : undefined;
|
|
96
|
+
if (registered?.comparator && Array.isArray(registered.values) && registered.values.length) {
|
|
97
|
+
const enumValues = registered.values;
|
|
98
|
+
const idx = enumValues.indexOf(String(presentVal));
|
|
99
|
+
if (idx === -1) {
|
|
100
|
+
return actionNotOK(`${term} has value "${presentVal}" which is not in domain values`);
|
|
101
|
+
}
|
|
102
|
+
const nextIdx = Math.min(enumValues.length - 1, idx + 1);
|
|
103
|
+
const nextVal = enumValues[nextIdx];
|
|
104
|
+
if (nextVal === presentVal) {
|
|
105
|
+
return OK;
|
|
75
106
|
}
|
|
76
|
-
|
|
107
|
+
this.getWorld().shared.set({ term: String(term), value: nextVal, domain: effectiveDomain, origin: Origin.var }, provenanceFromFeatureStep(featureStep));
|
|
108
|
+
return OK;
|
|
109
|
+
}
|
|
110
|
+
// Fallback: numeric increment
|
|
111
|
+
const numVal = Number(presentVal);
|
|
112
|
+
if (isNaN(numVal)) {
|
|
113
|
+
return actionNotOK(`cannot increment non-numeric variable ${term} with value "${presentVal}"`);
|
|
77
114
|
}
|
|
78
|
-
|
|
79
|
-
|
|
115
|
+
const newNum = numVal + 1;
|
|
116
|
+
this.getWorld().shared.set({ term: String(term), value: String(newNum), domain: effectiveDomain, origin: Origin.var }, provenanceFromFeatureStep(featureStep));
|
|
117
|
+
this.getWorld().logger.info(`incremented ${term} to ${newNum}`, {
|
|
80
118
|
incident: EExecutionMessageType.ACTION,
|
|
81
|
-
incidentDetails: { json: { incremented: { [term]:
|
|
82
|
-
};
|
|
83
|
-
this.getWorld().
|
|
84
|
-
|
|
119
|
+
incidentDetails: { json: { incremented: { [term]: newNum } } },
|
|
120
|
+
});
|
|
121
|
+
this.getWorld().eventLogger.log(featureStep, 'info', `incremented ${term} to ${newNum}`, {
|
|
122
|
+
variable: term,
|
|
123
|
+
oldValue: presentVal,
|
|
124
|
+
newValue: newNum,
|
|
125
|
+
operation: 'increment'
|
|
126
|
+
});
|
|
127
|
+
return OK;
|
|
85
128
|
}
|
|
86
129
|
},
|
|
87
130
|
showEnv: {
|
|
@@ -96,91 +139,417 @@ class VariablesStepper extends AStepper {
|
|
|
96
139
|
showVars: {
|
|
97
140
|
gwta: 'show vars',
|
|
98
141
|
action: () => {
|
|
99
|
-
|
|
100
|
-
|
|
142
|
+
const displayVars = Object.fromEntries(Object.entries(this.getWorld().shared.all()).map(([k, v]) => [k, v.value]));
|
|
143
|
+
console.info('vars', displayVars);
|
|
144
|
+
return actionOK({ artifact: { artifactType: 'json', json: { vars: displayVars } } });
|
|
101
145
|
},
|
|
102
146
|
},
|
|
103
147
|
set: {
|
|
104
148
|
gwta: 'set( empty)? {what: string} to {value: string}',
|
|
149
|
+
handlesUndefined: ['what', 'value'],
|
|
105
150
|
precludes: ['Haibun.prose'],
|
|
106
151
|
action: (args, featureStep) => {
|
|
107
|
-
const
|
|
108
|
-
const
|
|
109
|
-
if (
|
|
110
|
-
return
|
|
152
|
+
const { term: rawTerm, domain, origin } = featureStep.action.stepValuesMap.what;
|
|
153
|
+
const parsedValue = this.getWorld().shared.resolveVariable(featureStep.action.stepValuesMap.value, featureStep);
|
|
154
|
+
if (parsedValue.value === undefined)
|
|
155
|
+
return actionNotOK(`Variable ${featureStep.action.stepValuesMap.value.term} not found`);
|
|
156
|
+
const resolved = { value: String(parsedValue.value) };
|
|
157
|
+
const interpolated = this.interpolateTemplate(rawTerm, featureStep);
|
|
158
|
+
if (interpolated.error)
|
|
159
|
+
return actionNotOK(interpolated.error);
|
|
160
|
+
const term = interpolated.value;
|
|
161
|
+
const skip = shouldSkipEmpty(featureStep, term, this.getWorld().shared);
|
|
162
|
+
if (skip)
|
|
163
|
+
return skip;
|
|
164
|
+
// Inherit domain from existing variable if not explicitly specified
|
|
165
|
+
const existing = this.getWorld().shared.resolveVariable({ term, origin: Origin.var }, featureStep);
|
|
166
|
+
const effectiveDomain = (domain === DOMAIN_STRING && existing?.domain) ? existing.domain : (domain || DOMAIN_STRING);
|
|
167
|
+
const result = trySetVariable(this.getWorld().shared, { term, value: resolved.value, domain: effectiveDomain, origin }, provenanceFromFeatureStep(featureStep));
|
|
168
|
+
if (result.ok) {
|
|
169
|
+
this.getWorld().eventLogger.log(featureStep, 'info', `set ${term} to ${resolved.value}`, {
|
|
170
|
+
variable: term,
|
|
171
|
+
newValue: resolved.value,
|
|
172
|
+
domain: effectiveDomain,
|
|
173
|
+
operation: 'set'
|
|
174
|
+
});
|
|
111
175
|
}
|
|
112
|
-
|
|
113
|
-
return Promise.resolve(OK);
|
|
176
|
+
return result;
|
|
114
177
|
}
|
|
115
178
|
},
|
|
116
179
|
setAs: {
|
|
117
|
-
gwta: 'set( empty)? {what
|
|
180
|
+
gwta: 'set( empty)? {what} as {domain} to {value}',
|
|
181
|
+
handlesUndefined: ['what', 'domain', 'value'],
|
|
118
182
|
precludes: [`${VariablesStepper.name}.set`],
|
|
119
183
|
action: ({ value, domain }, featureStep) => {
|
|
120
|
-
const
|
|
121
|
-
const { term, origin } = featureStep.action.stepValuesMap.what;
|
|
122
|
-
|
|
123
|
-
|
|
184
|
+
const readonly = !!featureStep.in.match(/ as read-only /);
|
|
185
|
+
const { term: rawTerm, origin } = featureStep.action.stepValuesMap.what;
|
|
186
|
+
const parsedValue = this.getWorld().shared.resolveVariable(featureStep.action.stepValuesMap.value, featureStep);
|
|
187
|
+
if (parsedValue.value === undefined)
|
|
188
|
+
return actionNotOK(`Variable ${featureStep.action.stepValuesMap.value.term} not found`);
|
|
189
|
+
const resolved = { value: String(parsedValue.value) };
|
|
190
|
+
const interpolated = this.interpolateTemplate(rawTerm, featureStep);
|
|
191
|
+
if (interpolated.error)
|
|
192
|
+
return actionNotOK(interpolated.error);
|
|
193
|
+
const term = interpolated.value;
|
|
194
|
+
const skip = shouldSkipEmpty(featureStep, term, this.getWorld().shared);
|
|
195
|
+
if (skip)
|
|
196
|
+
return skip;
|
|
197
|
+
// Fallback for unquoted domain names (e.g. 'as number') that resolve to undefined
|
|
198
|
+
let effectiveDomain = domain ?? getStepTerm(featureStep, 'domain');
|
|
199
|
+
if (effectiveDomain) {
|
|
200
|
+
if (effectiveDomain.startsWith('read-only ')) {
|
|
201
|
+
effectiveDomain = effectiveDomain.replace('read-only ', '');
|
|
202
|
+
}
|
|
203
|
+
if (effectiveDomain.startsWith('"') && effectiveDomain.endsWith('"')) {
|
|
204
|
+
effectiveDomain = effectiveDomain.slice(1, -1);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
let finalValue = resolved.value;
|
|
208
|
+
if (typeof finalValue === 'string' && finalValue.startsWith('"') && finalValue.endsWith('"')) {
|
|
209
|
+
finalValue = finalValue.slice(1, -1);
|
|
124
210
|
}
|
|
125
|
-
this.getWorld().shared
|
|
211
|
+
return trySetVariable(this.getWorld().shared, { term, value: finalValue, domain: effectiveDomain, origin, readonly }, provenanceFromFeatureStep(featureStep));
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
unset: {
|
|
215
|
+
gwta: 'unset {what: string}',
|
|
216
|
+
action: ({ what }, featureStep) => {
|
|
217
|
+
const { term } = featureStep.action.stepValuesMap.what;
|
|
218
|
+
this.getWorld().shared.unset(term);
|
|
126
219
|
return Promise.resolve(OK);
|
|
127
220
|
}
|
|
128
221
|
},
|
|
129
222
|
setRandom: {
|
|
130
223
|
precludes: [`${VariablesStepper.name}.set`],
|
|
131
224
|
gwta: `set( empty)? {what: string} to {length: number} random characters`,
|
|
225
|
+
handlesUndefined: ['what'],
|
|
132
226
|
action: ({ length }, featureStep) => {
|
|
133
|
-
const emptyOnly = !!featureStep.in.match(/set empty /);
|
|
134
227
|
const { term } = featureStep.action.stepValuesMap.what;
|
|
135
228
|
if (length < 1 || length > 100) {
|
|
136
229
|
return actionNotOK(`length ${length} must be between 1 and 100`);
|
|
137
230
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
231
|
+
const skip = shouldSkipEmpty(featureStep, term, this.getWorld().shared);
|
|
232
|
+
if (skip)
|
|
233
|
+
return skip;
|
|
141
234
|
let rand = '';
|
|
142
235
|
while (rand.length < length) {
|
|
143
236
|
rand += Math.random().toString(36).substring(2, 2 + length);
|
|
144
237
|
}
|
|
145
238
|
rand = rand.substring(0, length);
|
|
146
|
-
this.getWorld().shared
|
|
147
|
-
return Promise.resolve(OK);
|
|
239
|
+
return trySetVariable(this.getWorld().shared, { term, value: rand, domain: DOMAIN_STRING, origin: Origin.var }, provenanceFromFeatureStep(featureStep));
|
|
148
240
|
}
|
|
149
241
|
},
|
|
150
242
|
is: {
|
|
151
243
|
gwta: 'variable {what} is {value}',
|
|
244
|
+
handlesUndefined: ['what', 'value'],
|
|
245
|
+
action: (args, featureStep) => {
|
|
246
|
+
const { term: rawTerm } = featureStep.action.stepValuesMap.what;
|
|
247
|
+
const interpolated = this.interpolateTemplate(rawTerm, featureStep);
|
|
248
|
+
if (interpolated.error)
|
|
249
|
+
return actionNotOK(interpolated.error);
|
|
250
|
+
const term = interpolated.value;
|
|
251
|
+
const resolved = this.getWorld().shared.resolveVariable({ term, origin: Origin.defined }, featureStep);
|
|
252
|
+
if (resolved.value === undefined) {
|
|
253
|
+
return actionNotOK(`${term} is not set`);
|
|
254
|
+
}
|
|
255
|
+
const storedVal = resolved.value;
|
|
256
|
+
const parsedValue = this.getWorld().shared.resolveVariable(featureStep.action.stepValuesMap.value, featureStep);
|
|
257
|
+
if (parsedValue.value === undefined)
|
|
258
|
+
return actionNotOK(`Variable ${featureStep.action.stepValuesMap.value.term} not found`);
|
|
259
|
+
const value = String(parsedValue.value);
|
|
260
|
+
const domainKey = normalizeDomainKey(resolved.domain);
|
|
261
|
+
const compareVal = this.getWorld().domains[domainKey].coerce({ term: '_cmp', value, domain: domainKey, origin: Origin.quoted }, featureStep, this.steppers);
|
|
262
|
+
return JSON.stringify(storedVal) === JSON.stringify(compareVal) ? OK : actionNotOK(`${term} is ${JSON.stringify(storedVal)}, not ${JSON.stringify(compareVal)}`);
|
|
263
|
+
}
|
|
264
|
+
},
|
|
265
|
+
isLessThan: {
|
|
266
|
+
gwta: 'variable {what} is less than {value}',
|
|
267
|
+
handlesUndefined: ['what', 'value'],
|
|
268
|
+
precludes: ['VariablesStepper.is'],
|
|
152
269
|
action: ({ what, value }, featureStep) => {
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const val = this.getVarValue(term);
|
|
156
|
-
const asDomain = this.getWorld().domains[domain].coerce({ domain, value, term, origin: 'quoted' });
|
|
157
|
-
return JSON.stringify(val) === JSON.stringify(asDomain) ? OK : actionNotOK(`${term} is ${JSON.stringify(val)}, not ${JSON.stringify(value)}`);
|
|
270
|
+
const term = getStepTerm(featureStep, 'what') ?? what;
|
|
271
|
+
return this.compareValues(featureStep, term, value, '<');
|
|
158
272
|
}
|
|
159
273
|
},
|
|
160
|
-
|
|
274
|
+
isMoreThan: {
|
|
275
|
+
gwta: 'variable {what} is more than {value}',
|
|
276
|
+
handlesUndefined: ['what', 'value'],
|
|
161
277
|
precludes: ['VariablesStepper.is'],
|
|
162
|
-
|
|
278
|
+
action: ({ what, value }, featureStep) => {
|
|
279
|
+
const term = getStepTerm(featureStep, 'what') ?? what;
|
|
280
|
+
return this.compareValues(featureStep, term, value, '>');
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
exists: {
|
|
284
|
+
gwta: 'variable {what} exists',
|
|
285
|
+
handlesUndefined: ['what'],
|
|
163
286
|
action: ({ what }, featureStep) => {
|
|
164
|
-
|
|
165
|
-
const
|
|
166
|
-
return
|
|
287
|
+
const term = (getStepTerm(featureStep, 'what') ?? what);
|
|
288
|
+
const resolved = this.getWorld().shared.resolveVariable({ term, origin: Origin.defined }, featureStep);
|
|
289
|
+
return resolved && (resolved.origin === Origin.var || resolved.origin === Origin.env) ? OK : actionNotOK(`${what} not set`);
|
|
167
290
|
}
|
|
168
291
|
},
|
|
169
292
|
showVar: {
|
|
170
293
|
gwta: 'show var {what}',
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const
|
|
174
|
-
if (
|
|
294
|
+
handlesUndefined: ['what'],
|
|
295
|
+
action: (_, featureStep) => {
|
|
296
|
+
const rawTerm = getStepTerm(featureStep, 'what');
|
|
297
|
+
if (rawTerm === undefined)
|
|
298
|
+
return actionNotOK('variable not provided');
|
|
299
|
+
const interpolated = this.interpolateTemplate(rawTerm, featureStep);
|
|
300
|
+
if (interpolated.error)
|
|
301
|
+
return actionNotOK(interpolated.error);
|
|
302
|
+
const term = interpolated.value;
|
|
303
|
+
const stepValue = this.getWorld().shared.resolveVariable({ term, origin: Origin.defined }, featureStep);
|
|
304
|
+
if (stepValue.value === undefined) {
|
|
175
305
|
this.getWorld().logger.info(`is undefined`);
|
|
176
306
|
}
|
|
177
307
|
else {
|
|
178
|
-
|
|
308
|
+
const provenance = featureStep.action.stepValuesMap.what.provenance?.map((p, i) => ({ [i]: { in: p.in, seq: p.seq.join(','), when: p.when } }));
|
|
309
|
+
this.getWorld().logger.info(`is ${JSON.stringify({ ...stepValue, provenance }, null, 2)}`);
|
|
179
310
|
}
|
|
180
311
|
return actionOK({ artifact: { artifactType: 'json', json: { json: stepValue } } });
|
|
181
312
|
}
|
|
182
313
|
},
|
|
314
|
+
showDomains: {
|
|
315
|
+
gwta: 'show domains',
|
|
316
|
+
action: () => {
|
|
317
|
+
const domains = this.getWorld().domains;
|
|
318
|
+
const allVars = this.getWorld().shared.all();
|
|
319
|
+
const summary = {};
|
|
320
|
+
for (const [name, def] of Object.entries(domains)) {
|
|
321
|
+
// Count variables in this domain
|
|
322
|
+
let members = 0;
|
|
323
|
+
for (const variable of Object.values(allVars)) {
|
|
324
|
+
if (variable.domain && normalizeDomainKey(variable.domain) === name) {
|
|
325
|
+
members++;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
// Determine type from schema or values
|
|
329
|
+
let type = 'schema';
|
|
330
|
+
if (def.values) {
|
|
331
|
+
type = def.values;
|
|
332
|
+
}
|
|
333
|
+
else if (def.description) {
|
|
334
|
+
type = def.description;
|
|
335
|
+
}
|
|
336
|
+
summary[name] = {
|
|
337
|
+
type,
|
|
338
|
+
members,
|
|
339
|
+
ordered: !!def.comparator
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
this.getWorld().logger.info(`Domains: ${JSON.stringify(summary, null, 2)}`);
|
|
343
|
+
return OK;
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
showDomain: {
|
|
347
|
+
gwta: 'show domain {name}',
|
|
348
|
+
handlesUndefined: ['name'],
|
|
349
|
+
action: (_, featureStep) => {
|
|
350
|
+
const name = getStepTerm(featureStep, 'name');
|
|
351
|
+
const domain = this.getWorld().domains[name];
|
|
352
|
+
if (!domain) {
|
|
353
|
+
return actionNotOK(`Domain "${name}" not found`);
|
|
354
|
+
}
|
|
355
|
+
const allVars = this.getWorld().shared.all();
|
|
356
|
+
const members = {};
|
|
357
|
+
for (const [key, variable] of Object.entries(allVars)) {
|
|
358
|
+
if (variable.domain && normalizeDomainKey(variable.domain) === name) {
|
|
359
|
+
members[key] = variable.value;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
this.getWorld().logger.info(`Domain "${name}": ${JSON.stringify({ ...domain, members }, null, 2)}`);
|
|
363
|
+
return OK;
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
// Membership check: value is in domain (enum or member values)
|
|
367
|
+
// Handles quoted ("value"), braced ({var}), or bare (value) forms
|
|
368
|
+
// fallback: true lets quantifiers take precedence when both match
|
|
369
|
+
isIn: {
|
|
370
|
+
match: /^(.+) is in ([a-zA-Z][a-zA-Z0-9 ]*)$/,
|
|
371
|
+
fallback: true,
|
|
372
|
+
action: (_, featureStep) => {
|
|
373
|
+
const matchResult = featureStep.in.match(/^(.+) is in ([a-zA-Z][a-zA-Z0-9 ]*)$/);
|
|
374
|
+
if (!matchResult) {
|
|
375
|
+
return actionNotOK('Invalid "is in" syntax');
|
|
376
|
+
}
|
|
377
|
+
let valueTerm = matchResult[1].trim();
|
|
378
|
+
// Strip quotes if present
|
|
379
|
+
if ((valueTerm.startsWith('"') && valueTerm.endsWith('"')) ||
|
|
380
|
+
(valueTerm.startsWith('`') && valueTerm.endsWith('`'))) {
|
|
381
|
+
valueTerm = valueTerm.slice(1, -1);
|
|
382
|
+
}
|
|
383
|
+
// Strip braces if present and resolve variable
|
|
384
|
+
if (valueTerm.startsWith('{') && valueTerm.endsWith('}')) {
|
|
385
|
+
valueTerm = valueTerm.slice(1, -1);
|
|
386
|
+
}
|
|
387
|
+
// Try to resolve as variable, fall back to literal
|
|
388
|
+
const resolved = this.getWorld().shared.resolveVariable({ term: valueTerm, origin: Origin.defined });
|
|
389
|
+
const actualValue = resolved?.value !== undefined ? String(resolved.value) : valueTerm;
|
|
390
|
+
const domainName = matchResult[2].trim();
|
|
391
|
+
const domainKey = normalizeDomainKey(domainName);
|
|
392
|
+
const domainDef = this.getWorld().domains[domainKey];
|
|
393
|
+
if (!domainDef) {
|
|
394
|
+
return actionNotOK(`Domain "${domainName}" is not defined`);
|
|
395
|
+
}
|
|
396
|
+
// Check enum values first
|
|
397
|
+
if (Array.isArray(domainDef.values) && domainDef.values.includes(actualValue)) {
|
|
398
|
+
return OK;
|
|
399
|
+
}
|
|
400
|
+
// Check member values
|
|
401
|
+
const allVars = this.getWorld().shared.all();
|
|
402
|
+
const memberValues = Object.values(allVars)
|
|
403
|
+
.filter(v => v.domain && normalizeDomainKey(v.domain) === domainKey)
|
|
404
|
+
.map(v => String(v.value));
|
|
405
|
+
return memberValues.includes(actualValue)
|
|
406
|
+
? OK
|
|
407
|
+
: actionNotOK(`"${actualValue}" is not in ${domainName}`);
|
|
408
|
+
}
|
|
409
|
+
},
|
|
410
|
+
// Starts with: predicate for prefix checking
|
|
411
|
+
// Starts with literal 'that' for unambiguous parsing in compositions
|
|
412
|
+
// Usage: that {page} starts with {prefix}
|
|
413
|
+
startsWith: {
|
|
414
|
+
gwta: 'that {value} starts with {prefix}',
|
|
415
|
+
action: ({ value, prefix }) => {
|
|
416
|
+
const actualValue = String(value);
|
|
417
|
+
const actualPrefix = String(prefix);
|
|
418
|
+
return actualValue.startsWith(actualPrefix)
|
|
419
|
+
? OK
|
|
420
|
+
: actionNotOK(`"${actualValue}" does not start with "${actualPrefix}"`);
|
|
421
|
+
}
|
|
422
|
+
},
|
|
183
423
|
};
|
|
424
|
+
compareValues(featureStep, rawTerm, value, operator) {
|
|
425
|
+
const interpolated = this.interpolateTemplate(rawTerm, featureStep);
|
|
426
|
+
if (interpolated.error)
|
|
427
|
+
return actionNotOK(interpolated.error);
|
|
428
|
+
const term = interpolated.value;
|
|
429
|
+
const stored = this.getWorld().shared.resolveVariable({ term, origin: Origin.var }, featureStep);
|
|
430
|
+
if (!stored) {
|
|
431
|
+
return actionNotOK(`${term} is not set`);
|
|
432
|
+
}
|
|
433
|
+
const domainKey = normalizeDomainKey(stored.domain);
|
|
434
|
+
const domainEntry = this.getWorld().domains[domainKey];
|
|
435
|
+
if (!domainEntry) {
|
|
436
|
+
throw new Error(`No domain coercer found for domain "${domainKey}"`);
|
|
437
|
+
}
|
|
438
|
+
const left = domainEntry.coerce({ ...stored, domain: domainKey }, featureStep, this.steppers);
|
|
439
|
+
let rightValue = value;
|
|
440
|
+
if (rightValue === undefined) {
|
|
441
|
+
const parsed = this.getWorld().shared.resolveVariable(featureStep.action.stepValuesMap.value, featureStep);
|
|
442
|
+
if (parsed.value === undefined)
|
|
443
|
+
return actionNotOK(`Variable ${featureStep.action.stepValuesMap.value.term} not found`);
|
|
444
|
+
rightValue = String(parsed.value);
|
|
445
|
+
}
|
|
446
|
+
if (typeof rightValue === 'string' && rightValue.startsWith('"') && rightValue.endsWith('"')) {
|
|
447
|
+
rightValue = rightValue.slice(1, -1);
|
|
448
|
+
}
|
|
449
|
+
const right = domainEntry.coerce({ term: `${term}__comparison`, value: rightValue, domain: domainKey, origin: Origin.quoted }, featureStep, this.steppers);
|
|
450
|
+
const comparison = compareDomainValues(domainEntry, left, right, stored.domain);
|
|
451
|
+
if (operator === '>') {
|
|
452
|
+
return comparison > 0 ? OK : actionNotOK(`${term} is ${JSON.stringify(left)}, not ${JSON.stringify(right)}`);
|
|
453
|
+
}
|
|
454
|
+
if (operator === '<') {
|
|
455
|
+
return comparison < 0 ? OK : actionNotOK(`${term} is ${JSON.stringify(left)}, not ${JSON.stringify(right)}`);
|
|
456
|
+
}
|
|
457
|
+
return actionNotOK(`Unsupported operator: ${operator}`);
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Interpolates a template string by replacing {varName} placeholders with variable values.
|
|
461
|
+
* Returns the interpolated string or an error if a variable is not found.
|
|
462
|
+
*/
|
|
463
|
+
interpolateTemplate(template, featureStep) {
|
|
464
|
+
const placeholderRegex = /\{([^}]+)\}/g;
|
|
465
|
+
let result = template;
|
|
466
|
+
let match;
|
|
467
|
+
while ((match = placeholderRegex.exec(template)) !== null) {
|
|
468
|
+
const varName = match[1];
|
|
469
|
+
// Origin.defined checks runtimeArgs -> env -> literal fallback?
|
|
470
|
+
// We want strict. If resolves to literal, that's fine if user quoted it?
|
|
471
|
+
// But {unknown} resolves to undefined if not found?
|
|
472
|
+
// In feature-variables.ts, resolveVariable defaults to input.term if isLiteralValue.
|
|
473
|
+
// {unknown} is NOT literal value (braces).
|
|
474
|
+
// So resolveVariable returns undefined.
|
|
475
|
+
const resolved = this.getWorld().shared.resolveVariable({ term: varName, origin: Origin.defined }, featureStep);
|
|
476
|
+
if (resolved.value === undefined) {
|
|
477
|
+
return { error: `Variable ${varName} not found` };
|
|
478
|
+
}
|
|
479
|
+
result = result.replace(match[0], String(resolved.value));
|
|
480
|
+
}
|
|
481
|
+
return { value: result };
|
|
482
|
+
}
|
|
483
|
+
registerSubdomainFromStatement(domain, superdomains, featureStep) {
|
|
484
|
+
try {
|
|
485
|
+
const fallback = getStepTerm(featureStep, 'superdomains') ?? featureStep.in;
|
|
486
|
+
const superdomainNames = extractValuesFromFragments(superdomains, fallback);
|
|
487
|
+
if (!superdomainNames.length) {
|
|
488
|
+
throw new Error('Superdomain set must specify at least one superdomain');
|
|
489
|
+
}
|
|
490
|
+
const uniqueNames = Array.from(new Set(superdomainNames));
|
|
491
|
+
const effectiveDomain = domain ?? getStepTerm(featureStep, 'domain');
|
|
492
|
+
if (!effectiveDomain)
|
|
493
|
+
return actionNotOK('Domain name must be provided');
|
|
494
|
+
const domainKey = normalizeDomainKey(effectiveDomain);
|
|
495
|
+
if (this.getWorld().domains[domainKey]) {
|
|
496
|
+
return actionNotOK(`Domain "${domainKey}" already exists`);
|
|
497
|
+
}
|
|
498
|
+
const superdomainDefs = uniqueNames.map((name) => {
|
|
499
|
+
const normalized = normalizeDomainKey(name);
|
|
500
|
+
const registered = this.getWorld().domains[normalized];
|
|
501
|
+
if (!registered) {
|
|
502
|
+
throw new Error(`Superdomain "${name}" not registered`);
|
|
503
|
+
}
|
|
504
|
+
return registered;
|
|
505
|
+
});
|
|
506
|
+
const enumSources = superdomainDefs.filter((entry) => Array.isArray(entry.values) && entry.values.length);
|
|
507
|
+
const uniqueValues = Array.from(new Set(enumSources.flatMap((entry) => entry.values)));
|
|
508
|
+
const description = `Values inherited from ${uniqueNames.join(', ')}`;
|
|
509
|
+
if (enumSources.length === superdomainDefs.length && uniqueValues.length) {
|
|
510
|
+
const definition = createEnumDomainDefinition({ name: domainKey, values: uniqueValues, description });
|
|
511
|
+
registerDomains(this.getWorld(), [[definition]]);
|
|
512
|
+
return OK;
|
|
513
|
+
}
|
|
514
|
+
const schemaList = superdomainDefs.map((entry) => entry.schema);
|
|
515
|
+
if (!schemaList.length) {
|
|
516
|
+
throw new Error('Superdomains did not expose any schema to derive from');
|
|
517
|
+
}
|
|
518
|
+
let mergedSchema = schemaList[0];
|
|
519
|
+
for (let i = 1; i < schemaList.length; i++) {
|
|
520
|
+
mergedSchema = z.union([mergedSchema, schemaList[i]]);
|
|
521
|
+
}
|
|
522
|
+
const definition = {
|
|
523
|
+
selectors: [domainKey],
|
|
524
|
+
schema: mergedSchema,
|
|
525
|
+
coerce: (proto) => mergedSchema.parse(proto.value),
|
|
526
|
+
description,
|
|
527
|
+
};
|
|
528
|
+
registerDomains(this.getWorld(), [[definition]]);
|
|
529
|
+
return OK;
|
|
530
|
+
}
|
|
531
|
+
catch (error) {
|
|
532
|
+
return actionNotOK(error instanceof Error ? error.message : String(error));
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
registerValuesDomainFromStatement(domain, valueFragments, featureStep, options) {
|
|
536
|
+
try {
|
|
537
|
+
const values = extractValuesFromFragments(valueFragments, getStepTerm(featureStep, 'values') ?? featureStep.in);
|
|
538
|
+
const effectiveDomain = domain ?? getStepTerm(featureStep, 'domain');
|
|
539
|
+
if (!effectiveDomain)
|
|
540
|
+
return actionNotOK('Domain name must be provided');
|
|
541
|
+
const domainKey = normalizeDomainKey(effectiveDomain);
|
|
542
|
+
if (this.getWorld().domains[domainKey]) {
|
|
543
|
+
return actionNotOK(`Domain "${domainKey}" already exists`);
|
|
544
|
+
}
|
|
545
|
+
const definition = createEnumDomainDefinition({ name: domainKey, values, description: options?.description, ordered: options?.ordered });
|
|
546
|
+
registerDomains(this.getWorld(), [[definition]]);
|
|
547
|
+
return OK;
|
|
548
|
+
}
|
|
549
|
+
catch (error) {
|
|
550
|
+
return actionNotOK(error instanceof Error ? error.message : String(error));
|
|
551
|
+
}
|
|
552
|
+
}
|
|
184
553
|
}
|
|
185
554
|
export default VariablesStepper;
|
|
186
555
|
export const didNotOverwrite = (what, present, value) => ({
|
|
@@ -193,4 +562,85 @@ export function provenanceFromFeatureStep(featureStep) {
|
|
|
193
562
|
when: `${featureStep.action.stepperName}.steps.${featureStep.action.actionName}`
|
|
194
563
|
};
|
|
195
564
|
}
|
|
565
|
+
const QUOTED_STRING = /"([^"]+)"/g;
|
|
566
|
+
const extractValuesFromFragments = (valueFragments, fallback) => {
|
|
567
|
+
if (valueFragments?.length) {
|
|
568
|
+
const innerChunks = valueFragments.map(fragment => {
|
|
569
|
+
const raw = fragment.in ?? fragment.action?.stepValuesMap?.items?.term ?? '';
|
|
570
|
+
const trimmed = raw.trim();
|
|
571
|
+
if (trimmed.startsWith('[') && trimmed.endsWith(']')) {
|
|
572
|
+
return trimmed.slice(1, -1).trim();
|
|
573
|
+
}
|
|
574
|
+
return trimmed;
|
|
575
|
+
}).filter(Boolean);
|
|
576
|
+
const inner = innerChunks.join(' ').trim();
|
|
577
|
+
if (!inner) {
|
|
578
|
+
throw new Error('Set values cannot be empty');
|
|
579
|
+
}
|
|
580
|
+
return parseQuotedOrWordList(inner);
|
|
581
|
+
}
|
|
582
|
+
if (fallback) {
|
|
583
|
+
return parseBracketedValues(fallback);
|
|
584
|
+
}
|
|
585
|
+
throw new Error('Set statement missing values');
|
|
586
|
+
};
|
|
587
|
+
const parseBracketedValues = (raw) => {
|
|
588
|
+
const trimmed = raw.trim();
|
|
589
|
+
const start = trimmed.indexOf('[');
|
|
590
|
+
const end = trimmed.lastIndexOf(']');
|
|
591
|
+
if (start === -1 || end === -1 || end <= start) {
|
|
592
|
+
throw new Error('Set values must include [ ]');
|
|
593
|
+
}
|
|
594
|
+
const inner = trimmed.substring(start + 1, end).trim();
|
|
595
|
+
return parseQuotedOrWordList(inner);
|
|
596
|
+
};
|
|
597
|
+
const parseQuotedOrWordList = (value) => {
|
|
598
|
+
const quoted = [...value.matchAll(QUOTED_STRING)].map(match => match[1].trim()).filter(Boolean);
|
|
599
|
+
if (quoted.length) {
|
|
600
|
+
return quoted;
|
|
601
|
+
}
|
|
602
|
+
return value.split(/[\s,]+/).map(token => token.trim()).filter(Boolean);
|
|
603
|
+
};
|
|
604
|
+
const compareDomainValues = (domain, left, right, domainName) => {
|
|
605
|
+
if (domain.comparator) {
|
|
606
|
+
return domain.comparator(left, right);
|
|
607
|
+
}
|
|
608
|
+
if (typeof left === 'number' && typeof right === 'number') {
|
|
609
|
+
return left - right;
|
|
610
|
+
}
|
|
611
|
+
if (left instanceof Date && right instanceof Date) {
|
|
612
|
+
return left.getTime() - right.getTime();
|
|
613
|
+
}
|
|
614
|
+
throw new Error(`Domain ${domainName} does not support magnitude comparison`);
|
|
615
|
+
};
|
|
616
|
+
const renderComparable = (value) => {
|
|
617
|
+
if (value instanceof Date) {
|
|
618
|
+
return value.toISOString();
|
|
619
|
+
}
|
|
620
|
+
return JSON.stringify(value);
|
|
621
|
+
};
|
|
622
|
+
// ======== Helpers ========
|
|
623
|
+
// Resolves undefined value to literal if it looks like a string
|
|
624
|
+
function resolveValueOrLiteral(valMap, value) {
|
|
625
|
+
if (valMap && valMap.origin === Origin.defined && value === undefined) {
|
|
626
|
+
return isLiteralValue(valMap.term)
|
|
627
|
+
? { value: String(valMap.term) }
|
|
628
|
+
: { value: undefined, error: `Variable ${valMap.term} is not defined` };
|
|
629
|
+
}
|
|
630
|
+
return { value };
|
|
631
|
+
}
|
|
632
|
+
// Returns OK if "set empty" and variable exists
|
|
633
|
+
function shouldSkipEmpty(featureStep, term, shared) {
|
|
634
|
+
return (featureStep.in.includes('set empty ') && shared.get(term) !== undefined) ? OK : undefined;
|
|
635
|
+
}
|
|
636
|
+
// Wraps shared.set in try/catch
|
|
637
|
+
function trySetVariable(shared, opts, provenance) {
|
|
638
|
+
try {
|
|
639
|
+
shared.set({ term: String(opts.term), value: opts.value, domain: opts.domain, origin: opts.origin, readonly: opts.readonly }, provenance);
|
|
640
|
+
return OK;
|
|
641
|
+
}
|
|
642
|
+
catch (e) {
|
|
643
|
+
return actionNotOK(e.message);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
196
646
|
//# sourceMappingURL=variables-stepper.js.map
|