1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# 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)