|
26 | 26 | npm install --save-dev patch-package |
27 | 27 | ``` |
28 | 28 |
|
29 | | -## Sheaf Module |
30 | | - |
31 | | -The sheaf module provides a dispatch abstraction for routing method calls across multiple capability objects (_sections_) that each cover a region of a shared interface. |
32 | | - |
33 | | -### Overview |
34 | | - |
35 | | -``` |
36 | | -sheafify({ name, sections, compartment? }) → Sheaf |
37 | | -sheaf.getGlobalSection({ lift }) → section proxy |
38 | | -sheaf.getSection({ guard, lift }) → section proxy |
39 | | -``` |
40 | | - |
41 | | -Each call on the proxy is dispatched to whichever section covers that method. When multiple sections are eligible, a **lift** selects among them. A lift is an `AsyncGenerator` coroutine that yields candidates one at a time and receives the accumulated error history on each resume — enabling retry, fallback, and cost-aware routing without callers needing to know the selection strategy. |
42 | | - |
43 | | -### Defining sections |
44 | | - |
45 | | -```ts |
46 | | -import { sheafify, constant, callable } from '@metamask/kernel-utils'; |
47 | | - |
48 | | -const sheaf = sheafify({ |
49 | | - name: 'Wallet', |
50 | | - sections: [ |
51 | | - { |
52 | | - exo: walletA, |
53 | | - metadata: constant({ cost: 10, push: false }), |
54 | | - }, |
55 | | - { |
56 | | - exo: walletB, |
57 | | - // callable metadata is evaluated per-call with the actual arguments |
58 | | - metadata: callable((args) => ({ cost: 1 + 0.1 * (args[0] as number) })), |
59 | | - }, |
60 | | - { |
61 | | - exo: walletC, |
62 | | - // source metadata is compiled once at sheafify time via the compartment |
63 | | - metadata: source(`(args) => ({ cost: 5 + 0.01 * args[0] })`), |
64 | | - }, |
65 | | - ], |
66 | | - compartment, // required only when using source-kind metadata |
67 | | -}); |
68 | | -``` |
69 | | - |
70 | | -**Metadata kinds:** |
71 | | -| Kind | When evaluated | Use case | |
72 | | -|------|---------------|----------| |
73 | | -| `constant(v)` | Never (static) | Fixed priority or capability flags | |
74 | | -| `callable(fn)` | Each call | Arg-dependent cost, remaining spend | |
75 | | -| `source(str)` | Each call (compiled at construction) | Sandboxed cost functions | |
76 | | - |
77 | | -### Writing a lift |
78 | | - |
79 | | -A lift receives `EvaluatedSection<Partial<M>>[]` (germs) and a context, and yields candidates in preference order. It receives a snapshot of all accumulated errors on each `gen.next(errors)` call. |
80 | | - |
81 | | -```ts |
82 | | -import type { Lift } from '@metamask/kernel-utils'; |
83 | | - |
84 | | -// Yield cheapest section first; fall back in cost order on failure |
85 | | -const cheapest: Lift<{ cost: number }> = async function* (germs) { |
86 | | - yield* [...germs].sort( |
87 | | - (a, b) => (a.metadata?.cost ?? Infinity) - (b.metadata?.cost ?? Infinity), |
88 | | - ); |
89 | | -}; |
90 | | - |
91 | | -const section = sheaf.getGlobalSection({ lift: cheapest }); |
92 | | -``` |
93 | | - |
94 | | -### Composing lifts |
95 | | - |
96 | | -```ts |
97 | | -import { |
98 | | - withFilter, |
99 | | - withRanking, |
100 | | - fallthrough, |
101 | | - proxyLift, |
102 | | -} from '@metamask/kernel-utils'; |
103 | | - |
104 | | -// Filter out sections with insufficient remaining spend |
105 | | -const spendable = withFilter<Cost>( |
106 | | - (germ, { args }) => |
107 | | - (germ.metadata?.remainingSpend ?? Infinity) >= (args[0] as number), |
108 | | -); |
109 | | - |
110 | | -// Sort by cost before passing to the inner lift |
111 | | -const byCost = withRanking<Cost>( |
112 | | - (a, b) => (a.metadata?.cost ?? Infinity) - (b.metadata?.cost ?? Infinity), |
113 | | -); |
114 | | - |
115 | | -// Try local sections first, fall through to remote on exhaustion |
116 | | -const withFallback = fallthrough(localLift, remoteLift); |
117 | | - |
118 | | -// Compose: filter → rank → select |
119 | | -const lift = spendable(byCost(cheapest)); |
120 | | -``` |
121 | | - |
122 | | -`withFilter` and `withRanking` are pure input transforms that return the inner lift's generator directly. `fallthrough` sequences two lifts via `yield*`, which forwards the error array to each inner lift. `proxyLift` is the primitive for adding logic (logging, circuit-breaking) between yields. |
123 | | - |
124 | | -### Error handling |
125 | | - |
126 | | -When all candidates are exhausted, `driveLift` throws: |
127 | | - |
128 | | -``` |
129 | | -Error: No viable section for <method> |
130 | | - cause: [Error: ..., Error: ..., ...] // all accumulated attempt errors |
131 | | -``` |
132 | | - |
133 | 29 | ## Contributing |
134 | 30 |
|
135 | 31 | This package is part of a monorepo. Instructions for contributing can be found in the [monorepo README](https://github.com/MetaMask/ocap-kernel#readme). |
0 commit comments