<!-- Generated by npm run gen:skill from tools/skill/extend/operation.md - do not edit. -->

# A custom operation

Declared per process (in each process that uses it), href relative to the process's file:

```xml
<process name="p1">
  <operations>
    <operation name="slugify" href="ops/text.js"/>
  </operations>
  <step id="go">
    <operation name="slugify" value="{#v1#title}" alias="slug"/>
  </step>
</process>
```

```js
// ops/text.js: an export named as the operation, or the default export
export async function slugify(process, context, op, evt) {
  const name = await context.expr.evaluateText(op.getAttribute("alias"), evt);
  if( !name ) throw new Error("slugify: no alias attribute");  // the error step's description
  const out = new EVT_EventObject();
  out.value = String(evt.value || "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
  context.setAlias(name, out);
}
```

- `op`: a copy of the `<operation>` element. Its children's `{...}` are filled already, its own attributes are not: fill each with `await context.expr.evaluateText(op.getAttribute("x"), evt)`. `fn.noParse = 1` hands over the children unfilled.
- `evt`: the `value=` reference, resolved: `{...}` text gives `evt.value`; data (`#doc#/r/@t`) gives `evt.dataDoc` and `evt.xpath`, read with `new SYS_URL(evt.dataDoc).selectSingleText(evt.xpath)`.
- Write a node: `new SYS_URL(evt.dataDoc).replaceText(text, xpath, null, context)`.
- `context`: the application instance: `getAlias(name)`, `setAlias(name, evt)`, `getView(name)`, `expr`. `process.error(msg)` logs.
- Return a Promise (an `async` function does); the step waits for it. To fail, throw an Error as above: the step's `error=` step runs with its message (`!#/error/description`), else it is logged and the step goes on.

## Test

As a function, with stubs: `slugify(process, context, el, evt)` with `el.getAttribute`, `context.expr.evaluateText` and `context.setAlias` stubbed; assert the alias, and that it rejects. In a running app: a trigger, then read `$slug`.

## In xios-core

`operations/PO_<name>.js` with `export default async function PO_<name>(process, context, op, evt)`; an `<operation group name title><template>` entry in `<process>` of `config/system.xml`; the name in `t_operationNameBuiltIn` of `config/schemas/process.xsd`, plus an `op_<name>` type and `xs:alternative` for its children; a suite under `tests/operations/` (as `js_operations.xml`), then `npm run scan-tests`.
