~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/atlibs

0
0
main
130 lines · 4.8 KB Raw History
# Compose modules

A composition runs modules as one program: instances of modules (yours, or anyone's by `<handle>/<name>`),
routes from one instance's hook to a module that implements it, policies that may veto a call, and
parameters. You write it in TypeScript; the `composition.json` it writes is what hosts load. Commit both.

In the module repo from [Write a module](write.md) (or a new one):

```bash run=network
atlibs new my-module > /dev/null && cd my-module && cargo wasm 2>&1 | tail -1
npm init -y > /dev/null && npm pkg set type=module && npm i @publicdomainrelay/atlibs@0.5.0 > /dev/null
atlibs add bobvmbuilder.bsky.social/compose 2>&1 | tail -1
```

```output
wrote ./atlibs.modules.js
```

`atlibs add` vendored `compose` and wrote `atlibs.modules.js`: `compose` (pinned) and `myModule`
(`modules/my-module`). Save as `compose.ts`:

```bash run=network
cat > compose.ts <<'EOF'
import { composition, policy } from "@publicdomainrelay/atlibs/compose/build.js";
import { compose, myModule } from "./atlibs.modules.js";

const c = composition({ name: "my-composition", description: "me describes an account; ui asks lookup to double n, if the even policy allows." });
const did = c.param("did", { label: "DID", values: { default: "did:plc:ocnuqjlzgohypj3zoznermms" } });
c.instance("me", myModule, { input: { did } });
const ui = c.instance("ui", compose);
const lookup = c.instance("lookup", compose);
const even = c.instance("even", compose);
c.route(ui, "com.example.compose.lookup", lookup, "ui needs n doubled", { policies: [policy(even, "even")] });
process.stdout.write(await c.json());
EOF
node compose.ts > my-composition.json && atlibs compose check my-composition.json | jq -c '[.instances[].name]'
```

```output
["me","ui","lookup","even"]
```

A route to a module that does not implement the hook, or an input outside the module's `config`, is a
type error in your editor, and `node compose.ts` names the line.

## Run it

`atlibs run <composition> <instance> <entry> [input]`:

```bash run=network
atlibs run my-composition.json me describe | jq -c .result
atlibs run my-composition.json me describe --params '{"inputs":{"did":"did:plc:z72i7hdynmk6r22z27h6tvur"}}' | jq -c .result
atlibs run my-composition.json ui caller '{"nsid":"com.example.compose.lookup","input":{"n":4}}' | jq -c .result.q.output.doubled
atlibs run my-composition.json ui caller '{"nsid":"com.example.compose.lookup","input":{"n":3}}' | jq -c .result.q
```

```output
{"collections":…,"handle":"bobvmbuilder.bsky.social"}
{"collections":…,"handle":"bsky.app"}
8
{"error":"policy even denied: n 3 is not even","ok":false}
```

## Publish, add, run

After [publishing my-module](publish.md) (the record names each module by at:// URI: a key becomes yours,
`<handle>/<name>[@pin]` its publisher's, with the full pin):

```bash run=account
git add compose.ts my-composition.json && git commit -m my-composition
atlibs publish composition my-composition.json --as you.bsky.social
cd "$(mktemp -d)"
atlibs add you.bsky.social/my-composition
atlibs run you.bsky.social/my-composition me describe
```

The rkey is the file name (`--rkey` to choose). Remove it: `atlibs publish delete com.fedfork.exec.composition/my-composition`.

## In a page

`atlibs stage --target spa` writes the composition and every module it names:

```bash run=network
atlibs stage --target spa --out site my-composition.json > /dev/null 2>&1
find site -type f | LC_ALL=C sort
```

```output
site/composition.json
site/modules/compose.json
site/modules/compose.wasm
site/modules/my-module.json
site/modules/my-module.wasm
site/stage.json
```

Save as `site/index.html`:

```html run=network browser site/index.html
<!DOCTYPE html>
<meta charset="utf-8">
<script type="importmap">
{ "imports": { "atlibs/": "https://cdn.jsdelivr.net/npm/@publicdomainrelay/atlibs@0.5.0/src/" } }
</script>
<output></output>
<script type="module">
import { bindParameters, loadComposition, runCompose, staged } from 'atlibs/compose/index.js'
import { createPluginContext } from 'atlibs/plugin-context/index.js'
import { hostCall } from 'atlibs/run-module/host-call.js'

const ctx = createPluginContext({ id: 'demo' })
const manifest = bindParameters(await (await fetch('composition.json')).json())
const composition = await loadComposition(ctx, manifest, { load: staged(location.href) })
const input = { nsid: 'com.example.compose.lookup', input: { n: 4 } }
const ran = await runCompose(ctx, composition, 'ui', 'caller', input, { callFor: () => hostCall({ ctx }) })
document.querySelector('output').textContent = JSON.stringify(ran.result.q.output.doubled)
</script>
```

```output
8
```

Serve `site/` (`python3 -m http.server --directory site`).

An instance whose input has a `$param`: `bindParameters(manifest, { inputs })` before `runCompose`.

More: handlers, callbacks, hooks, exports, bindings: [Compositions](../compositions.md).

Previous: [Publish](publish.md)