@lifi/composer-sdk 0.4.0 → 0.5.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/README.md +98 -5
- package/dist/authoring/FlowBuilderCore.cjs +1 -5
- package/dist/authoring/FlowBuilderCore.cjs.map +1 -1
- package/dist/authoring/FlowBuilderCore.d.cts +9 -7
- package/dist/authoring/FlowBuilderCore.d.ts +9 -7
- package/dist/authoring/FlowBuilderCore.js +2 -6
- package/dist/authoring/FlowBuilderCore.js.map +1 -1
- package/dist/authoring/handles.cjs +5 -2
- package/dist/authoring/handles.cjs.map +1 -1
- package/dist/authoring/handles.d.cts +25 -6
- package/dist/authoring/handles.d.ts +25 -6
- package/dist/authoring/handles.js +3 -1
- package/dist/authoring/handles.js.map +1 -1
- package/dist/authoring/raw.cjs +6 -1
- package/dist/authoring/raw.cjs.map +1 -1
- package/dist/authoring/raw.d.cts +33 -3
- package/dist/authoring/raw.d.ts +33 -3
- package/dist/authoring/raw.js +5 -1
- package/dist/authoring/raw.js.map +1 -1
- package/dist/authoring/signatureArgs.cjs.map +1 -1
- package/dist/authoring/signatureArgs.d.cts +16 -12
- package/dist/authoring/signatureArgs.d.ts +16 -12
- package/dist/authoring/signatureArgs.js.map +1 -1
- package/dist/client.cjs +94 -58
- package/dist/client.cjs.map +1 -1
- package/dist/client.d.cts +61 -2
- package/dist/client.d.ts +61 -2
- package/dist/client.js +94 -58
- package/dist/client.js.map +1 -1
- package/dist/discovery.cjs.map +1 -1
- package/dist/discovery.d.cts +31 -2
- package/dist/discovery.d.ts +31 -2
- package/dist/generated/config.generated.cjs.map +1 -1
- package/dist/generated/config.generated.d.cts +35 -3
- package/dist/generated/config.generated.d.ts +35 -3
- package/dist/generated/operations.generated.cjs +9 -0
- package/dist/generated/operations.generated.cjs.map +1 -1
- package/dist/generated/operations.generated.d.cts +33 -2
- package/dist/generated/operations.generated.d.ts +33 -2
- package/dist/generated/operations.generated.js +9 -0
- package/dist/generated/operations.generated.js.map +1 -1
- package/dist/index.cjs +5 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -7
- package/dist/index.d.ts +26 -7
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/{raw-IxCF_bSS.d.ts → raw-BvRghN3r.d.ts} +3 -2
- package/dist/{raw-Dj_2rBSQ.d.cts → raw-s1rer8X3.d.cts} +3 -2
- package/dist/raw.cjs +2 -0
- package/dist/raw.cjs.map +1 -1
- package/dist/raw.d.cts +1 -1
- package/dist/raw.d.ts +1 -1
- package/dist/raw.js +2 -1
- package/dist/raw.js.map +1 -1
- package/dist/routeAmount-DdbJYJyM.d.cts +55 -0
- package/dist/routeAmount-DdbJYJyM.d.ts +55 -0
- package/dist/routeAmount.cjs +38 -0
- package/dist/routeAmount.cjs.map +1 -0
- package/dist/routeAmount.d.cts +2 -0
- package/dist/routeAmount.d.ts +2 -0
- package/dist/routeAmount.js +13 -0
- package/dist/routeAmount.js.map +1 -0
- package/dist/run/inputs.cjs +14 -15
- package/dist/run/inputs.cjs.map +1 -1
- package/dist/run/inputs.d.cts +8 -0
- package/dist/run/inputs.d.ts +8 -0
- package/dist/run/inputs.js +14 -15
- package/dist/run/inputs.js.map +1 -1
- package/dist/run/simulate.cjs +5 -0
- package/dist/run/simulate.cjs.map +1 -1
- package/dist/run/simulate.d.cts +5 -2
- package/dist/run/simulate.d.ts +5 -2
- package/dist/run/simulate.js +5 -0
- package/dist/run/simulate.js.map +1 -1
- package/dist/sdk.cjs +3 -0
- package/dist/sdk.cjs.map +1 -1
- package/dist/sdk.d.cts +42 -2
- package/dist/sdk.d.ts +42 -2
- package/dist/sdk.js +3 -0
- package/dist/sdk.js.map +1 -1
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +18 -2
- package/dist/types.d.ts +18 -2
- package/package.json +6 -13
- package/src/authoring/FlowBuilderCore.ts +13 -15
- package/src/authoring/handles.ts +41 -5
- package/src/authoring/raw.ts +39 -4
- package/src/authoring/signatureArgs.ts +17 -13
- package/src/client.ts +204 -58
- package/src/discovery.ts +31 -1
- package/src/examples/flySwap.ts +167 -0
- package/src/examples/lifiZap.ts +4 -0
- package/src/examples/lifiZapAsync.ts +88 -0
- package/src/examples/routeTokenPair.ts +58 -0
- package/src/examples/run.ts +56 -6
- package/src/examples/supportedChains.ts +122 -0
- package/src/generated/config.generated.ts +45 -3
- package/src/generated/operations.generated.ts +56 -8
- package/src/index.ts +33 -1
- package/src/raw.ts +1 -1
- package/src/routeAmount.ts +61 -0
- package/src/run/inputs.ts +30 -11
- package/src/run/simulate.ts +14 -2
- package/src/sdk.ts +47 -0
- package/src/types.ts +20 -1
package/README.md
CHANGED
|
@@ -122,9 +122,11 @@ if (result.status === 'success') {
|
|
|
122
122
|
## API surface
|
|
123
123
|
|
|
124
124
|
**SDK factory**
|
|
125
|
+
|
|
125
126
|
- `createComposeSdk({ baseUrl, apiKey, fetch? })` — creates the SDK instance
|
|
126
127
|
|
|
127
128
|
**Flow building**
|
|
129
|
+
|
|
128
130
|
- `sdk.flow(chainId, options)` — creates a `FlowBuilder`
|
|
129
131
|
- `builder.<namespace>.<operation>(id, { bind, config })` — adds an operation, returns typed `OutputHandle<T>` per port
|
|
130
132
|
- `builder.untypedOp(id, op, args)` — escape hatch for operations not in the manifest (returns `void`; use `raw.ref<T>()` to reference its outputs)
|
|
@@ -132,12 +134,16 @@ if (result.status === 'success') {
|
|
|
132
134
|
- `sdk.request(flow, { signer, inputs, preconditions, sweepTo, ... })` — builds a compile request
|
|
133
135
|
|
|
134
136
|
**HTTP client**
|
|
137
|
+
|
|
135
138
|
- `sdk.client.compile(request)` — sends the flow to the backend and returns a `ComposeCompileResult` (discriminated union: `status: 'success'` or `status: 'partial'`)
|
|
136
139
|
- `sdk.client.getManifest()` — fetches the operation manifest
|
|
137
|
-
- `sdk.client.
|
|
140
|
+
- `sdk.client.getChains()` / `sdk.getSupportedChains()` — fetches the chains this deployment supports as `readonly ChainSummary[]` (`{ chainId, name }`), ascending by `chainId`. A chain is listed once the VM contract is confirmed deployed on it, which is the same condition `compile`, `route` and `simulate` validate against — so a chain missing from this list is rejected by those methods. The converse does not hold: a listed chain can still have no routing edge for a given pair, reported by `route` as `no_route_error`. `name` is a display label maintained by the deployment, so render it but key on `chainId`; a chain with no configured label reports `Chain <id>`. The list tracks the backend's chain registry, which refreshes periodically, and is not cached by the SDK.
|
|
141
|
+
- `sdk.client.getZapPacks(options?)` — fetches the available routing edges grouped by protocol, returning `ZapPackOverview[]`. The catalog is dynamic and is not cached by the SDK. Filter through `GetZapPacksOptions`. An edge with `recipient: 'required'` requires the zap's `recipient` binding, delivers the output externally, and produces an output that cannot be chained or swept. An edge with `availability: 'future'` produces an output that settles only after the initiating transaction; `route` compiles it for you, and on the authoring path it needs `lifi.zapAsync` with a bound `recipient`. Availability governs custody only — a future output still reports `simulated.amountOut` (an in-transaction estimate; the delivered amount arrives after settlement), with `simulated.amountOutMin` present only when a minimum source applied. The catalog publishes no settlement duration: a deferred output's ETA rides on the compile result as `producedResources[*].etaSeconds` (expected seconds until the proceeds land, present only alongside `availability: 'future'` and only when the protocol publishes an estimate; absence means unknown, not zero).
|
|
138
142
|
- `sdk.client.simulate(request)` / `sdk.simulate(request)` — simulates a raw, pre-encoded transaction and returns a `SimulateResult` (discriminated union: `status: 'ok' | 'revert' | 'error'`). See [Simulating a raw transaction](#simulating-a-raw-transaction).
|
|
143
|
+
- `sdk.client.route(request)` / `sdk.route(request)` — compiles a single from/to token pair on one chain via `POST /compose/route`, returning the same `ComposeCompileResult` as `compile()`. See [Routing a token pair](#routing-a-token-pair).
|
|
139
144
|
|
|
140
145
|
**Helpers**
|
|
146
|
+
|
|
141
147
|
- `resources.erc20(address, chainId)` / `resources.native(chainId)` — resource constructors
|
|
142
148
|
- `guards.*` — guard factories (e.g., slippage)
|
|
143
149
|
- `materialisers.*` — materialiser factories (directDeposit, balanceOf, call)
|
|
@@ -145,6 +151,88 @@ if (result.status === 'success') {
|
|
|
145
151
|
- `raw.ref<T>(path)` — create a typed `$ref` pointer for use in bind slots (escape hatch for `untypedOp` outputs)
|
|
146
152
|
- `raw.guard(kind, config?)` / `raw.materialiser(kind, config?)` — low-level factories for guards and materialisers
|
|
147
153
|
- `buildSimulateRequest({ result, chainId, signer, trackedBalances, requirements?, block?, value? })` — assembles a `SimulateRequest` from a compile result (pure, no I/O)
|
|
154
|
+
- `routeAmount.exact(amount)` / `routeAmount.all(simAmount)` — amount constructors for `sdk.route(...)`
|
|
155
|
+
|
|
156
|
+
## Routing a token pair
|
|
157
|
+
|
|
158
|
+
Authoring a flow is the general path: you decide the operations and how they chain. `sdk.route(...)` is the shortcut for the case where the backend can decide for you — you hand it a from/to token pair, an amount, and a signer, and it returns the same submit-ready `ComposeCompileResult` that `compile()` returns.
|
|
159
|
+
|
|
160
|
+
As of today the backend authors a single zap step for that pair: one edge of its routing catalog — entering or exiting a protocol position, a wrap or unwrap, a mint or burn. It is not a swap aggregator, so a pair with no catalog edge (WETH to USDC, for instance) comes back as `no_route_error`. The endpoint is meant to author more complex flows in future releases, so read the single-step limit as where the server is today rather than as the boundary of the API.
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
import { createComposeSdk, routeAmount } from '@lifi/composer-sdk';
|
|
164
|
+
|
|
165
|
+
const sdk = createComposeSdk({
|
|
166
|
+
baseUrl: 'https://composer.li.quest',
|
|
167
|
+
apiKey: process.env.LIFI_API_KEY!,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const result = await sdk.route({
|
|
171
|
+
chainId: 1,
|
|
172
|
+
fromToken: '0x0000000000000000000000000000000000000000', // native ETH
|
|
173
|
+
toToken: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH — a `wrap` edge
|
|
174
|
+
amount: routeAmount.exact(100_000_000_000_000_000n), // 0.1 ETH
|
|
175
|
+
signer: '0xYourAddress...',
|
|
176
|
+
slippageBps: 100,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
if (result.status === 'success') {
|
|
180
|
+
console.log(result.transactionRequest); // to, data, value, gasLimit
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Discovering routable pairs
|
|
185
|
+
|
|
186
|
+
The catalog is dynamic — protocols, chains, and token blacklists move — so discover pairs instead of hardcoding them:
|
|
187
|
+
|
|
188
|
+
```ts
|
|
189
|
+
const packs = await sdk.client.getZapPacks({ protocols: 'aave' });
|
|
190
|
+
|
|
191
|
+
for (const pack of packs) {
|
|
192
|
+
for (const edge of pack.edges) {
|
|
193
|
+
console.log(pack.protocol, edge.type, edge.in.address, '->', edge.out.address);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Every edge is one routable pair: `edge.in` and `edge.out` are the `fromToken` and `toToken` of a `route` call on `edge.in.chainId`, and `inAmountMin` / `inAmountMax` bound the amount when the protocol constrains it. Two edge fields change how you call `route`:
|
|
199
|
+
|
|
200
|
+
- **`recipient: 'required'`** — the protocol mints to a named receiver, so set `sweepTo` when the output should not land with the signer.
|
|
201
|
+
- **`availability: 'future'`** — the output only exists once the transaction settles. `route` skips these unless you pass `allowAsyncRoutes: true`, so by default a pair reachable only this way is a `no_route_error` and every produced resource is `availability: 'now'`. Opt in and `sweepTo` names the future output's owner, no slippage floor is applied, and you must track settlement rather than treat the transaction as completing the job. A synchronous edge still wins whenever the pair has one, so read `producedResources[*].availability` on the result to know which representation you got.
|
|
202
|
+
|
|
203
|
+
### Amounts
|
|
204
|
+
|
|
205
|
+
The `amount` field is a discriminated union with two variants, built by `routeAmount`:
|
|
206
|
+
|
|
207
|
+
- **`routeAmount.exact(amount)`** — spend exactly `amount` of `fromToken`, in its smallest unit. Use this when you know the figure.
|
|
208
|
+
- **`routeAmount.all(simAmount)`** — spend the signer's whole `fromToken` balance, resolved on-chain at execution time. `simAmount` is only an estimate: the quote, the price impact, and the returned approvals are computed against it, but the amount actually moved is the real balance when the transaction lands. Pass your best estimate of the balance — a wildly wrong figure produces a poor quote. Not supported for a native `fromToken`: a gas coin arrives via `msg.value`, so there is no balance to sweep, and the server rejects the request.
|
|
209
|
+
|
|
210
|
+
Both accept a `bigint` or a decimal string. `bigint` values are serialised for you.
|
|
211
|
+
|
|
212
|
+
### Options
|
|
213
|
+
|
|
214
|
+
`sdk.route(...)` shares its optional fields with the authoring path, and the server applies the same defaults. Omit a field to take the default; the SDK sends only what you set.
|
|
215
|
+
|
|
216
|
+
- **`slippageBps`** — tolerance in basis points. Defaults to `100` (1%).
|
|
217
|
+
- **`maxPriceImpactBps`** — reject the route if the priced impact exceeds this. No cap by default.
|
|
218
|
+
- **`integratorFeeBps`** — your fee, in basis points, taken from the input amount before the route executes. Requires an integration-scoped `apiKey`; a non-zero value without one is rejected.
|
|
219
|
+
- **`referrer`** — attribution string passed to the routing provider.
|
|
220
|
+
- **`sweepTo`** — address that receives the output and any leftover dust. Accepts a literal address or `{ $ref: 'context.sender' }`. Defaults to the signer.
|
|
221
|
+
- **`checkOnChainAllowances`** — when `true`, the server reads current allowances and omits approvals that are already sufficient. Defaults to `false`.
|
|
222
|
+
- **`simulationPolicy`** — `'strict'` (default) or `'allow-revert'`, exactly as on the authoring path. See [Simulation policy and partial results](#simulation-policy-and-partial-results).
|
|
223
|
+
- **`simulateUserProgram`** — when `true`, additionally simulates the final user-facing transaction after the structured simulation succeeds. Defaults to `false`.
|
|
224
|
+
- **`allowAsyncRoutes`** — when `true`, accept a route whose output arrives only after the initiating transaction settles. Defaults to `false`, which leaves such a pair a `no_route_error` and guarantees every produced resource is `availability: 'now'`. Set it only if you track settlement and can live without a slippage floor.
|
|
225
|
+
|
|
226
|
+
### When to author a flow instead
|
|
227
|
+
|
|
228
|
+
`route` gives you one server-authored step. Reach for `sdk.flow(...)` when you need anything it does not express: several operations chained together, splitting one input across several destinations, guards on intermediate outputs, explicit preconditions, arithmetic, or funding from proxy-held balances. Swapping WETH to USDC and then depositing the USDC into Aave is a flow; depositing USDC straight into Aave is a route. An asynchronous position (`availability: 'future'`) as the destination is a route once you pass `allowAsyncRoutes: true` — the server picks `lifi.zapAsync` for you — but authoring the flow yourself is still the way to combine it with other steps.
|
|
229
|
+
|
|
230
|
+
### Errors
|
|
231
|
+
|
|
232
|
+
`route` maps failures the same way `compile` does — see [Error handling](#error-handling). Two are specific to this path:
|
|
233
|
+
|
|
234
|
+
- **`NOT_FOUND`** with `kind: 'no_route_error'` — the backend has no routing edge for the pair on that chain. `sdk.client.getZapPacks()` reports which edges exist.
|
|
235
|
+
- **`VALIDATION_ERROR`** — the pair is routable but the request is rejected, for example a price impact above `maxPriceImpactBps`, or a simulation revert under the default `'strict'` policy.
|
|
148
236
|
|
|
149
237
|
## Simulation policy and partial results
|
|
150
238
|
|
|
@@ -164,12 +252,12 @@ if (result.status === 'success') {
|
|
|
164
252
|
} else {
|
|
165
253
|
// result.status === 'partial'
|
|
166
254
|
// Simulation reverted, but a transaction is still available (without gasLimit).
|
|
167
|
-
console.log(result.error.kind);
|
|
255
|
+
console.log(result.error.kind); // 'simulation_revert'
|
|
168
256
|
console.log(result.error.message); // human-readable revert description
|
|
169
257
|
|
|
170
258
|
// Revert diagnostics
|
|
171
259
|
const revert = result.simulationRevert;
|
|
172
|
-
console.log(revert.code);
|
|
260
|
+
console.log(revert.code); // e.g. 3
|
|
173
261
|
console.log(revert.rawErrorBytes); // raw ABI-encoded error
|
|
174
262
|
|
|
175
263
|
// Decoded error candidates (when available)
|
|
@@ -186,6 +274,7 @@ if (result.status === 'success') {
|
|
|
186
274
|
```
|
|
187
275
|
|
|
188
276
|
The `simulationPolicy` field accepts two values:
|
|
277
|
+
|
|
189
278
|
- `'strict'` (default) — revert causes a thrown `ComposeError` with code `VALIDATION_ERROR`
|
|
190
279
|
- `'allow-revert'` — revert returns a partial result with `status: 'partial'`
|
|
191
280
|
|
|
@@ -201,7 +290,7 @@ const result = await builder.compile({
|
|
|
201
290
|
|
|
202
291
|
## Simulating a raw transaction
|
|
203
292
|
|
|
204
|
-
`sdk.client.simulate(...)` (and the `sdk.simulate(...)` pass-through) answer a question the compile pipeline does not:
|
|
293
|
+
`sdk.client.simulate(...)` (and the `sdk.simulate(...)` pass-through) answer a question the compile pipeline does not: _if I send this exact transaction, how do specific token balances change and how much gas does it burn?_ It takes a raw, pre-encoded transaction (a `to`, hex `data`, optional native `value`), funds a sender, runs it in one `eth_call`, and reports the watched balances before/after, their signed deltas, and the inner-call gas.
|
|
205
294
|
|
|
206
295
|
```ts
|
|
207
296
|
import { createComposeSdk } from '@lifi/composer-sdk';
|
|
@@ -255,7 +344,7 @@ switch (result.status) {
|
|
|
255
344
|
|
|
256
345
|
`requirements` is the funding-instruction union (`Erc20Balance`, `NativeBalance`, `Erc20Allowance`); use the zero address as a `trackedBalances` `token` to watch native balance. Amount fields accept `bigint` (serialised to decimal strings) or strings. The caps `SIMULATE_MAX_TRACKED_BALANCES` and `SIMULATE_MAX_REQUIREMENTS` (both `40`) are exported for reference.
|
|
257
346
|
|
|
258
|
-
Unlike `compile`, a `revert` is returned (not thrown): a revert is a
|
|
347
|
+
Unlike `compile`, a `revert` is returned (not thrown): a revert is a _successful simulation_ whose execution reverted. Only transport failures and HTTP 400/401/403/404/429/5xx throw `ComposeError`.
|
|
259
348
|
|
|
260
349
|
To simulate a transaction you just compiled, `buildSimulateRequest(...)` assembles the request without re-typing the transaction fields. `chainId` and `signer` are required (a compile result carries neither), and `trackedBalances`/`requirements` cannot be inferred from a flow:
|
|
261
350
|
|
|
@@ -325,6 +414,7 @@ The `src/examples/` directory contains complete working examples:
|
|
|
325
414
|
|
|
326
415
|
- **lifiSwap** — Single token swap (WETH to USDC)
|
|
327
416
|
- **lifiZap** — Swap into a DeFi position
|
|
417
|
+
- **lifiZapAsync** — Zap into a position whose output settles after the initiating transaction (future availability)
|
|
328
418
|
- **swapAndZap** — Multi-step: swap then deposit
|
|
329
419
|
- **splitAndZap** — Split a resource and zap each portion into a different vault
|
|
330
420
|
- **splitWithArithmetic** — Split then verify with add/subtract/assertEqual assertions
|
|
@@ -347,6 +437,8 @@ The `src/examples/` directory contains complete working examples:
|
|
|
347
437
|
- **aaveClaimRewards** — Claim accrued Aave rewards and forward the claimed amount to a recipient
|
|
348
438
|
- **aaveSetEMode** — Switch the proxy's Aave v3 eMode category
|
|
349
439
|
- **untypedOpWithTypedRef** — Insert an untyped operation node via `untypedOp`, then bridge its output into typed operations using `raw.ref<T>()`
|
|
440
|
+
- **routeTokenPair** — Compile a from/to token pair via `sdk.route(...)`, with both the `exact` and `all` amount variants
|
|
441
|
+
- **supportedChains** — Cache `sdk.getSupportedChains()`, then preflight a chain and label it from the response's `name`, so an unsupported one fails locally with a message naming the alternatives
|
|
350
442
|
|
|
351
443
|
## Staging channel
|
|
352
444
|
|
|
@@ -370,6 +462,7 @@ const sdk = createComposeSdk({
|
|
|
370
462
|
Calling a staged operation against a backend that does not have it enabled fails at runtime as a service-level error; the typed surface being present does not guarantee backend availability.
|
|
371
463
|
|
|
372
464
|
The channel and the backend URL are independent:
|
|
465
|
+
|
|
373
466
|
- The `staging` **channel** is durable — it is a permanent property of `main`, and which operations it carries rotates over time as ops graduate to `latest`.
|
|
374
467
|
- The `baseUrl` is **per-environment and disposable** — it is a runtime argument, not a build-time property, so the same staging build can target whichever backend currently has the ops enabled.
|
|
375
468
|
|
|
@@ -23,10 +23,6 @@ __export(FlowBuilderCore_exports, {
|
|
|
23
23
|
module.exports = __toCommonJS(FlowBuilderCore_exports);
|
|
24
24
|
var import_handles = require("./handles.js");
|
|
25
25
|
var import_raw = require("./raw.js");
|
|
26
|
-
const toRef = (bindable) => {
|
|
27
|
-
if ("_tag" in bindable) return (0, import_handles.handleToRef)(bindable);
|
|
28
|
-
return bindable;
|
|
29
|
-
};
|
|
30
26
|
const isResource = (decl) => typeof decl === "object" && decl !== null && "kind" in decl;
|
|
31
27
|
const createFlowBuilderCore = (chainId, options) => {
|
|
32
28
|
const id = options.name ?? crypto.randomUUID();
|
|
@@ -53,7 +49,7 @@ const createFlowBuilderCore = (chainId, options) => {
|
|
|
53
49
|
nodeIds.add(nodeId);
|
|
54
50
|
const bind = {};
|
|
55
51
|
for (const [key, val] of Object.entries(args.bind)) {
|
|
56
|
-
bind[key] =
|
|
52
|
+
bind[key] = (0, import_handles.toBindValue)(val);
|
|
57
53
|
}
|
|
58
54
|
const node = {
|
|
59
55
|
id: nodeId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/authoring/FlowBuilderCore.ts"],"sourcesContent":["import type {\n AppliedGuard,\n Call,\n Flow,\n FlowInput,\n Ref,\n Resource,\n SolType,\n} from '@lifi/compose-spec';\n\nimport type {\n InputDecl,\n InputSchema,\n OutputKind,\n TypedGuard,\n TypedRef,\n} from '../types.js';\n\nimport type {\n InputHandle,\n OutputHandle,\n ResourceInputHandle,\n} from './handles.js';\nimport { handleToRef } from './handles.js';\nimport { ref } from './raw.js';\n\n/**\n * Arguments for calling an operation node in a flow.\n */\nexport interface CallArgs {\n /** Maps parameter names to input/output handles or raw `$ref` pointers. */\n readonly bind: Record<string, AnyBindable>;\n /** Optional operation-specific configuration. */\n readonly config?: object;\n /** Optional guards (e.g. slippage checks) applied to this operation's outputs. */\n readonly guards?: readonly TypedGuard[];\n}\n\n/**\n * Any value that can be bound to an operation parameter: an input handle,\n * an output handle from a previous operation, or a typed `$ref` pointer.\n *\n * Use `raw.ref<T>()` to create a typed ref from a raw path string.\n */\nexport type AnyBindable = InputHandle | OutputHandle | TypedRef;\n\nconst toRef = (bindable: AnyBindable): Ref => {\n if ('_tag' in bindable) return handleToRef(bindable);\n return bindable;\n};\n\n/**\n * Provides references to runtime context values available inside a flow.\n * Access via `builder.context`.\n */\nexport interface ContextAccessor {\n /** A `$ref` pointing to the transaction sender (signer) address. */\n readonly sender: TypedRef<'address'>;\n /** A `$ref` pointing to the on-chain execution (proxy) address. */\n readonly executionAddress: TypedRef<'address'>;\n}\n\ntype InputHandleOf<D extends InputDecl> = D extends Resource\n ? ResourceInputHandle\n : D extends SolType\n ? InputHandle<D>\n : InputHandle;\n\n/**\n * A mapped type that converts an input schema into typed handles.\n * Resource inputs produce {@link ResourceInputHandle}; scalar inputs produce {@link InputHandle}.\n *\n * @typeParam T - The flow's input schema.\n */\nexport type InputHandles<T extends InputSchema> = {\n readonly [K in keyof T]: InputHandleOf<T[K]>;\n};\n\n/**\n * Options for creating a new flow builder.\n *\n * @typeParam T - The flow's input schema.\n */\nexport interface FlowOptions<T extends InputSchema> {\n /** Optional human-readable name for the flow. Defaults to a random UUID. */\n readonly name?: string;\n /** Input declarations mapping names to resource or scalar types. */\n readonly inputs: T;\n}\n\n/**\n * A Flow document branded with its input schema for type-safe request building.\n *\n * The `__inputs` field is a phantom type — it exists only at compile time for\n * TypeScript inference and is never present at runtime.\n */\nexport type TypedFlow<T extends InputSchema = InputSchema> = Flow & {\n readonly __inputs?: T;\n};\n\n/**\n * Core flow builder interface providing low-level access to flow construction.\n *\n * For most use cases, prefer the {@link FlowBuilder} type returned by `sdk.flow()`\n * which adds generated operation methods and a `compile` method.\n *\n * @typeParam T - The flow's input schema.\n */\nexport interface FlowBuilderCore<T extends InputSchema = InputSchema> {\n /** Runtime context references (sender address, execution address). */\n readonly context: ContextAccessor;\n /** Typed handles for each declared flow input, used to bind inputs to operations. */\n readonly inputs: InputHandles<T>;\n /**\n * Appends an untyped operation node to the flow. This is an escape hatch\n * for operations not yet covered by the generated methods.\n *\n * Use `raw.ref<T>()` to reference this node's outputs in subsequent typed\n * operations.\n *\n * @param id - Unique node identifier within the flow.\n * @param op - The operation name (e.g. `\"custom.vaultQuery\"`).\n * @param args - Bind map, config, and optional guards for the node.\n * @throws Error if a node with the same `id` already exists in the flow.\n */\n readonly untypedOp: (\n id: string,\n op: string,\n args: {\n readonly bind: Record<string, Ref>;\n readonly config: Record<string, unknown>;\n readonly guards?: readonly AppliedGuard[];\n },\n ) => void;\n /** Serialises the builder state into a {@link TypedFlow} document. */\n readonly build: () => TypedFlow<T>;\n}\n\n/** Maps output port names to their output kind for compile-time and runtime validation. */\nexport type OutputSpec = Record<string, OutputKind>;\n\n/** Internal interface exposed to bindGeneratedOps — not part of the public API. */\nexport interface FlowBuilderInternal<\n T extends InputSchema = InputSchema,\n> extends FlowBuilderCore<T> {\n readonly call: <O extends OutputSpec>(\n nodeId: string,\n op: string,\n args: CallArgs,\n outputs: O,\n ) => { readonly [K in keyof O & string]: OutputHandle<O[K]> };\n}\n\nconst isResource = (decl: InputDecl): decl is Resource =>\n typeof decl === 'object' && decl !== null && 'kind' in decl;\n\n/**\n * Creates a new flow builder targeting the given chain.\n *\n * @param chainId - The EVM chain ID (e.g. `1` for Ethereum mainnet).\n * @param options - Flow configuration including input declarations.\n * @returns A {@link FlowBuilderInternal} instance.\n * @internal\n */\nexport const createFlowBuilderCore = <T extends InputSchema>(\n chainId: number,\n options: FlowOptions<T>,\n): FlowBuilderInternal<T> => {\n const id = options.name ?? crypto.randomUUID();\n const flowInputs: FlowInput[] = [];\n const nodes: Call[] = [];\n const nodeIds = new Set<string>();\n\n const inputHandles = {} as Record<string, InputHandle | ResourceInputHandle>;\n for (const [name, decl] of Object.entries(options.inputs)) {\n if (isResource(decl)) {\n if (decl.chainId !== chainId) {\n throw new Error(\n `Input \"${name}\" has chainId ${decl.chainId} but flow targets chain ${chainId}`,\n );\n }\n flowInputs.push({ name, resource: decl });\n inputHandles[name] = { _tag: 'input', inputName: name, resource: decl };\n } else {\n flowInputs.push({ name, type: decl });\n inputHandles[name] = { _tag: 'input', inputName: name };\n }\n }\n\n const call = <O extends OutputSpec>(\n nodeId: string,\n op: string,\n args: CallArgs,\n outputs: O,\n ): { readonly [K in keyof O & string]: OutputHandle<O[K]> } => {\n if (nodeIds.has(nodeId)) throw new Error(`Duplicate node id: \"${nodeId}\"`);\n nodeIds.add(nodeId);\n\n const bind: Record<string, Ref> = {};\n for (const [key, val] of Object.entries(args.bind)) {\n bind[key] = toRef(val);\n }\n\n const node: Call = {\n id: nodeId,\n op,\n bind,\n config: (args.config ?? {}) as Record<string, unknown>,\n ...(args.guards && args.guards.length > 0 && { guards: args.guards }),\n };\n nodes.push(node);\n\n type Result = { readonly [K in keyof O & string]: OutputHandle<O[K]> };\n return new Proxy({} as Result, {\n get: (_target, prop): OutputHandle | undefined => {\n if (typeof prop !== 'string' || prop === 'then') return undefined;\n if (!(prop in outputs)) {\n throw new Error(\n `Op \"${op}\" has no output port \"${prop}\". Valid ports: ${Object.keys(\n outputs,\n ).join(', ')}`,\n );\n }\n return { _tag: 'output', nodeId, portName: prop };\n },\n });\n };\n\n const untypedOp = (\n nodeId: string,\n op: string,\n args: {\n readonly bind: Record<string, Ref>;\n readonly config: Record<string, unknown>;\n readonly guards?: readonly AppliedGuard[];\n },\n ): void => {\n if (nodeIds.has(nodeId)) throw new Error(`Duplicate node id: \"${nodeId}\"`);\n nodeIds.add(nodeId);\n const node: Call = {\n id: nodeId,\n op,\n bind: args.bind,\n config: args.config,\n ...(args.guards && args.guards.length > 0 && { guards: args.guards }),\n };\n nodes.push(node);\n };\n\n const build = (): TypedFlow<T> => ({\n version: 1,\n id,\n chainId,\n inputs: [...flowInputs],\n nodes: [...nodes],\n });\n\n const context: ContextAccessor = {\n sender: ref<'address'>('context.sender'),\n executionAddress: ref<'address'>('context.executionAddress'),\n };\n\n return {\n context,\n inputs: inputHandles as InputHandles<T>,\n call,\n untypedOp,\n build,\n };\n};\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/authoring/FlowBuilderCore.ts"],"sourcesContent":["import type {\n AppliedGuard,\n BindValue,\n Call,\n Flow,\n FlowInput,\n Resource,\n SolType,\n} from '@lifi/compose-spec';\n\nimport type {\n InputDecl,\n InputSchema,\n OutputKind,\n TypedGuard,\n TypedLiteral,\n TypedRef,\n} from '../types.js';\n\nimport type {\n InputHandle,\n OutputHandle,\n ResourceInputHandle,\n} from './handles.js';\nimport { toBindValue } from './handles.js';\nimport { ref } from './raw.js';\n\n/**\n * Arguments for calling an operation node in a flow.\n */\nexport interface CallArgs {\n /** Maps parameter names to input/output handles or raw `$ref` pointers. */\n readonly bind: Record<string, AnyBindable>;\n /** Optional operation-specific configuration. */\n readonly config?: object;\n /** Optional guards (e.g. slippage checks) applied to this operation's outputs. */\n readonly guards?: readonly TypedGuard[];\n}\n\n/**\n * Any value that can be bound to an operation parameter: an input handle, an\n * output handle from a previous operation, a typed `$ref` pointer, or a typed\n * literal.\n *\n * Use `raw.ref<T>()` to create a typed ref from a raw path string, and\n * `raw.literal<T>()` to bake in a constant.\n */\nexport type AnyBindable = InputHandle | OutputHandle | TypedRef | TypedLiteral;\n\n/**\n * Provides references to runtime context values available inside a flow.\n * Access via `builder.context`.\n */\nexport interface ContextAccessor {\n /** A `$ref` pointing to the transaction sender (signer) address. */\n readonly sender: TypedRef<'address'>;\n /** A `$ref` pointing to the on-chain execution (proxy) address. */\n readonly executionAddress: TypedRef<'address'>;\n}\n\ntype InputHandleOf<D extends InputDecl> = D extends Resource\n ? ResourceInputHandle\n : D extends SolType\n ? InputHandle<D>\n : InputHandle;\n\n/**\n * A mapped type that converts an input schema into typed handles.\n * Resource inputs produce {@link ResourceInputHandle}; scalar inputs produce {@link InputHandle}.\n *\n * @typeParam T - The flow's input schema.\n */\nexport type InputHandles<T extends InputSchema> = {\n readonly [K in keyof T]: InputHandleOf<T[K]>;\n};\n\n/**\n * Options for creating a new flow builder.\n *\n * @typeParam T - The flow's input schema.\n */\nexport interface FlowOptions<T extends InputSchema> {\n /** Optional human-readable name for the flow. Defaults to a random UUID. */\n readonly name?: string;\n /** Input declarations mapping names to resource or scalar types. */\n readonly inputs: T;\n}\n\n/**\n * A Flow document branded with its input schema for type-safe request building.\n *\n * The `__inputs` field is a phantom type — it exists only at compile time for\n * TypeScript inference and is never present at runtime.\n */\nexport type TypedFlow<T extends InputSchema = InputSchema> = Flow & {\n readonly __inputs?: T;\n};\n\n/**\n * Core flow builder interface providing low-level access to flow construction.\n *\n * For most use cases, prefer the {@link FlowBuilder} type returned by `sdk.flow()`\n * which adds generated operation methods and a `compile` method.\n *\n * @typeParam T - The flow's input schema.\n */\nexport interface FlowBuilderCore<T extends InputSchema = InputSchema> {\n /** Runtime context references (sender address, execution address). */\n readonly context: ContextAccessor;\n /** Typed handles for each declared flow input, used to bind inputs to operations. */\n readonly inputs: InputHandles<T>;\n /**\n * Appends an untyped operation node to the flow. This is an escape hatch\n * for operations not yet covered by the generated methods.\n *\n * Use `raw.ref<T>()` to reference this node's outputs in subsequent typed\n * operations.\n *\n * @param id - Unique node identifier within the flow.\n * @param op - The operation name (e.g. `\"custom.vaultQuery\"`).\n * @param args - Bind map, config, and optional guards for the node.\n * @throws Error if a node with the same `id` already exists in the flow.\n */\n readonly untypedOp: (\n id: string,\n op: string,\n args: {\n readonly bind: Record<string, BindValue>;\n readonly config: Record<string, unknown>;\n readonly guards?: readonly AppliedGuard[];\n },\n ) => void;\n /** Serialises the builder state into a {@link TypedFlow} document. */\n readonly build: () => TypedFlow<T>;\n}\n\n/** Maps output port names to their output kind for compile-time and runtime validation. */\nexport type OutputSpec = Record<string, OutputKind>;\n\n/** Internal interface exposed to bindGeneratedOps — not part of the public API. */\nexport interface FlowBuilderInternal<\n T extends InputSchema = InputSchema,\n> extends FlowBuilderCore<T> {\n readonly call: <O extends OutputSpec>(\n nodeId: string,\n op: string,\n args: CallArgs,\n outputs: O,\n ) => { readonly [K in keyof O & string]: OutputHandle<O[K]> };\n}\n\nconst isResource = (decl: InputDecl): decl is Resource =>\n typeof decl === 'object' && decl !== null && 'kind' in decl;\n\n/**\n * Creates a new flow builder targeting the given chain.\n *\n * @param chainId - The EVM chain ID (e.g. `1` for Ethereum mainnet).\n * @param options - Flow configuration including input declarations.\n * @returns A {@link FlowBuilderInternal} instance.\n * @internal\n */\nexport const createFlowBuilderCore = <T extends InputSchema>(\n chainId: number,\n options: FlowOptions<T>,\n): FlowBuilderInternal<T> => {\n const id = options.name ?? crypto.randomUUID();\n const flowInputs: FlowInput[] = [];\n const nodes: Call[] = [];\n const nodeIds = new Set<string>();\n\n const inputHandles = {} as Record<string, InputHandle | ResourceInputHandle>;\n for (const [name, decl] of Object.entries(options.inputs)) {\n if (isResource(decl)) {\n if (decl.chainId !== chainId) {\n throw new Error(\n `Input \"${name}\" has chainId ${decl.chainId} but flow targets chain ${chainId}`,\n );\n }\n flowInputs.push({ name, resource: decl });\n inputHandles[name] = { _tag: 'input', inputName: name, resource: decl };\n } else {\n flowInputs.push({ name, type: decl });\n inputHandles[name] = { _tag: 'input', inputName: name };\n }\n }\n\n const call = <O extends OutputSpec>(\n nodeId: string,\n op: string,\n args: CallArgs,\n outputs: O,\n ): { readonly [K in keyof O & string]: OutputHandle<O[K]> } => {\n if (nodeIds.has(nodeId)) throw new Error(`Duplicate node id: \"${nodeId}\"`);\n nodeIds.add(nodeId);\n\n const bind: Record<string, BindValue> = {};\n for (const [key, val] of Object.entries(args.bind)) {\n bind[key] = toBindValue(val);\n }\n\n const node: Call = {\n id: nodeId,\n op,\n bind,\n config: (args.config ?? {}) as Record<string, unknown>,\n ...(args.guards && args.guards.length > 0 && { guards: args.guards }),\n };\n nodes.push(node);\n\n type Result = { readonly [K in keyof O & string]: OutputHandle<O[K]> };\n return new Proxy({} as Result, {\n get: (_target, prop): OutputHandle | undefined => {\n if (typeof prop !== 'string' || prop === 'then') return undefined;\n if (!(prop in outputs)) {\n throw new Error(\n `Op \"${op}\" has no output port \"${prop}\". Valid ports: ${Object.keys(\n outputs,\n ).join(', ')}`,\n );\n }\n return { _tag: 'output', nodeId, portName: prop };\n },\n });\n };\n\n const untypedOp = (\n nodeId: string,\n op: string,\n args: {\n readonly bind: Record<string, BindValue>;\n readonly config: Record<string, unknown>;\n readonly guards?: readonly AppliedGuard[];\n },\n ): void => {\n if (nodeIds.has(nodeId)) throw new Error(`Duplicate node id: \"${nodeId}\"`);\n nodeIds.add(nodeId);\n const node: Call = {\n id: nodeId,\n op,\n bind: args.bind,\n config: args.config,\n ...(args.guards && args.guards.length > 0 && { guards: args.guards }),\n };\n nodes.push(node);\n };\n\n const build = (): TypedFlow<T> => ({\n version: 1,\n id,\n chainId,\n inputs: [...flowInputs],\n nodes: [...nodes],\n });\n\n const context: ContextAccessor = {\n sender: ref<'address'>('context.sender'),\n executionAddress: ref<'address'>('context.executionAddress'),\n };\n\n return {\n context,\n inputs: inputHandles as InputHandles<T>,\n call,\n untypedOp,\n build,\n };\n};\n"],"mappings":"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","names":[]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Resource, SolType,
|
|
2
|
-
import { TypedRef, InputSchema, InputDecl, TypedGuard, OutputKind } from '../types.cjs';
|
|
1
|
+
import { Resource, SolType, BindValue, AppliedGuard, Flow } from '@lifi/compose-spec';
|
|
2
|
+
import { TypedRef, TypedLiteral, InputSchema, InputDecl, TypedGuard, OutputKind } from '../types.cjs';
|
|
3
3
|
import { InputHandle, OutputHandle, ResourceInputHandle } from './handles.cjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -14,12 +14,14 @@ interface CallArgs {
|
|
|
14
14
|
readonly guards?: readonly TypedGuard[];
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
|
-
* Any value that can be bound to an operation parameter: an input handle,
|
|
18
|
-
*
|
|
17
|
+
* Any value that can be bound to an operation parameter: an input handle, an
|
|
18
|
+
* output handle from a previous operation, a typed `$ref` pointer, or a typed
|
|
19
|
+
* literal.
|
|
19
20
|
*
|
|
20
|
-
* Use `raw.ref<T>()` to create a typed ref from a raw path string
|
|
21
|
+
* Use `raw.ref<T>()` to create a typed ref from a raw path string, and
|
|
22
|
+
* `raw.literal<T>()` to bake in a constant.
|
|
21
23
|
*/
|
|
22
|
-
type AnyBindable = InputHandle | OutputHandle | TypedRef;
|
|
24
|
+
type AnyBindable = InputHandle | OutputHandle | TypedRef | TypedLiteral;
|
|
23
25
|
/**
|
|
24
26
|
* Provides references to runtime context values available inside a flow.
|
|
25
27
|
* Access via `builder.context`.
|
|
@@ -86,7 +88,7 @@ interface FlowBuilderCore<T extends InputSchema = InputSchema> {
|
|
|
86
88
|
* @throws Error if a node with the same `id` already exists in the flow.
|
|
87
89
|
*/
|
|
88
90
|
readonly untypedOp: (id: string, op: string, args: {
|
|
89
|
-
readonly bind: Record<string,
|
|
91
|
+
readonly bind: Record<string, BindValue>;
|
|
90
92
|
readonly config: Record<string, unknown>;
|
|
91
93
|
readonly guards?: readonly AppliedGuard[];
|
|
92
94
|
}) => void;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { Resource, SolType,
|
|
2
|
-
import { TypedRef, InputSchema, InputDecl, TypedGuard, OutputKind } from '../types.js';
|
|
1
|
+
import { Resource, SolType, BindValue, AppliedGuard, Flow } from '@lifi/compose-spec';
|
|
2
|
+
import { TypedRef, TypedLiteral, InputSchema, InputDecl, TypedGuard, OutputKind } from '../types.js';
|
|
3
3
|
import { InputHandle, OutputHandle, ResourceInputHandle } from './handles.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -14,12 +14,14 @@ interface CallArgs {
|
|
|
14
14
|
readonly guards?: readonly TypedGuard[];
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
|
-
* Any value that can be bound to an operation parameter: an input handle,
|
|
18
|
-
*
|
|
17
|
+
* Any value that can be bound to an operation parameter: an input handle, an
|
|
18
|
+
* output handle from a previous operation, a typed `$ref` pointer, or a typed
|
|
19
|
+
* literal.
|
|
19
20
|
*
|
|
20
|
-
* Use `raw.ref<T>()` to create a typed ref from a raw path string
|
|
21
|
+
* Use `raw.ref<T>()` to create a typed ref from a raw path string, and
|
|
22
|
+
* `raw.literal<T>()` to bake in a constant.
|
|
21
23
|
*/
|
|
22
|
-
type AnyBindable = InputHandle | OutputHandle | TypedRef;
|
|
24
|
+
type AnyBindable = InputHandle | OutputHandle | TypedRef | TypedLiteral;
|
|
23
25
|
/**
|
|
24
26
|
* Provides references to runtime context values available inside a flow.
|
|
25
27
|
* Access via `builder.context`.
|
|
@@ -86,7 +88,7 @@ interface FlowBuilderCore<T extends InputSchema = InputSchema> {
|
|
|
86
88
|
* @throws Error if a node with the same `id` already exists in the flow.
|
|
87
89
|
*/
|
|
88
90
|
readonly untypedOp: (id: string, op: string, args: {
|
|
89
|
-
readonly bind: Record<string,
|
|
91
|
+
readonly bind: Record<string, BindValue>;
|
|
90
92
|
readonly config: Record<string, unknown>;
|
|
91
93
|
readonly guards?: readonly AppliedGuard[];
|
|
92
94
|
}) => void;
|
|
@@ -1,9 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { toBindValue } from "./handles.js";
|
|
2
2
|
import { ref } from "./raw.js";
|
|
3
|
-
const toRef = (bindable) => {
|
|
4
|
-
if ("_tag" in bindable) return handleToRef(bindable);
|
|
5
|
-
return bindable;
|
|
6
|
-
};
|
|
7
3
|
const isResource = (decl) => typeof decl === "object" && decl !== null && "kind" in decl;
|
|
8
4
|
const createFlowBuilderCore = (chainId, options) => {
|
|
9
5
|
const id = options.name ?? crypto.randomUUID();
|
|
@@ -30,7 +26,7 @@ const createFlowBuilderCore = (chainId, options) => {
|
|
|
30
26
|
nodeIds.add(nodeId);
|
|
31
27
|
const bind = {};
|
|
32
28
|
for (const [key, val] of Object.entries(args.bind)) {
|
|
33
|
-
bind[key] =
|
|
29
|
+
bind[key] = toBindValue(val);
|
|
34
30
|
}
|
|
35
31
|
const node = {
|
|
36
32
|
id: nodeId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/authoring/FlowBuilderCore.ts"],"sourcesContent":["import type {\n AppliedGuard,\n Call,\n Flow,\n FlowInput,\n Ref,\n Resource,\n SolType,\n} from '@lifi/compose-spec';\n\nimport type {\n InputDecl,\n InputSchema,\n OutputKind,\n TypedGuard,\n TypedRef,\n} from '../types.js';\n\nimport type {\n InputHandle,\n OutputHandle,\n ResourceInputHandle,\n} from './handles.js';\nimport { handleToRef } from './handles.js';\nimport { ref } from './raw.js';\n\n/**\n * Arguments for calling an operation node in a flow.\n */\nexport interface CallArgs {\n /** Maps parameter names to input/output handles or raw `$ref` pointers. */\n readonly bind: Record<string, AnyBindable>;\n /** Optional operation-specific configuration. */\n readonly config?: object;\n /** Optional guards (e.g. slippage checks) applied to this operation's outputs. */\n readonly guards?: readonly TypedGuard[];\n}\n\n/**\n * Any value that can be bound to an operation parameter: an input handle,\n * an output handle from a previous operation, or a typed `$ref` pointer.\n *\n * Use `raw.ref<T>()` to create a typed ref from a raw path string.\n */\nexport type AnyBindable = InputHandle | OutputHandle | TypedRef;\n\nconst toRef = (bindable: AnyBindable): Ref => {\n if ('_tag' in bindable) return handleToRef(bindable);\n return bindable;\n};\n\n/**\n * Provides references to runtime context values available inside a flow.\n * Access via `builder.context`.\n */\nexport interface ContextAccessor {\n /** A `$ref` pointing to the transaction sender (signer) address. */\n readonly sender: TypedRef<'address'>;\n /** A `$ref` pointing to the on-chain execution (proxy) address. */\n readonly executionAddress: TypedRef<'address'>;\n}\n\ntype InputHandleOf<D extends InputDecl> = D extends Resource\n ? ResourceInputHandle\n : D extends SolType\n ? InputHandle<D>\n : InputHandle;\n\n/**\n * A mapped type that converts an input schema into typed handles.\n * Resource inputs produce {@link ResourceInputHandle}; scalar inputs produce {@link InputHandle}.\n *\n * @typeParam T - The flow's input schema.\n */\nexport type InputHandles<T extends InputSchema> = {\n readonly [K in keyof T]: InputHandleOf<T[K]>;\n};\n\n/**\n * Options for creating a new flow builder.\n *\n * @typeParam T - The flow's input schema.\n */\nexport interface FlowOptions<T extends InputSchema> {\n /** Optional human-readable name for the flow. Defaults to a random UUID. */\n readonly name?: string;\n /** Input declarations mapping names to resource or scalar types. */\n readonly inputs: T;\n}\n\n/**\n * A Flow document branded with its input schema for type-safe request building.\n *\n * The `__inputs` field is a phantom type — it exists only at compile time for\n * TypeScript inference and is never present at runtime.\n */\nexport type TypedFlow<T extends InputSchema = InputSchema> = Flow & {\n readonly __inputs?: T;\n};\n\n/**\n * Core flow builder interface providing low-level access to flow construction.\n *\n * For most use cases, prefer the {@link FlowBuilder} type returned by `sdk.flow()`\n * which adds generated operation methods and a `compile` method.\n *\n * @typeParam T - The flow's input schema.\n */\nexport interface FlowBuilderCore<T extends InputSchema = InputSchema> {\n /** Runtime context references (sender address, execution address). */\n readonly context: ContextAccessor;\n /** Typed handles for each declared flow input, used to bind inputs to operations. */\n readonly inputs: InputHandles<T>;\n /**\n * Appends an untyped operation node to the flow. This is an escape hatch\n * for operations not yet covered by the generated methods.\n *\n * Use `raw.ref<T>()` to reference this node's outputs in subsequent typed\n * operations.\n *\n * @param id - Unique node identifier within the flow.\n * @param op - The operation name (e.g. `\"custom.vaultQuery\"`).\n * @param args - Bind map, config, and optional guards for the node.\n * @throws Error if a node with the same `id` already exists in the flow.\n */\n readonly untypedOp: (\n id: string,\n op: string,\n args: {\n readonly bind: Record<string, Ref>;\n readonly config: Record<string, unknown>;\n readonly guards?: readonly AppliedGuard[];\n },\n ) => void;\n /** Serialises the builder state into a {@link TypedFlow} document. */\n readonly build: () => TypedFlow<T>;\n}\n\n/** Maps output port names to their output kind for compile-time and runtime validation. */\nexport type OutputSpec = Record<string, OutputKind>;\n\n/** Internal interface exposed to bindGeneratedOps — not part of the public API. */\nexport interface FlowBuilderInternal<\n T extends InputSchema = InputSchema,\n> extends FlowBuilderCore<T> {\n readonly call: <O extends OutputSpec>(\n nodeId: string,\n op: string,\n args: CallArgs,\n outputs: O,\n ) => { readonly [K in keyof O & string]: OutputHandle<O[K]> };\n}\n\nconst isResource = (decl: InputDecl): decl is Resource =>\n typeof decl === 'object' && decl !== null && 'kind' in decl;\n\n/**\n * Creates a new flow builder targeting the given chain.\n *\n * @param chainId - The EVM chain ID (e.g. `1` for Ethereum mainnet).\n * @param options - Flow configuration including input declarations.\n * @returns A {@link FlowBuilderInternal} instance.\n * @internal\n */\nexport const createFlowBuilderCore = <T extends InputSchema>(\n chainId: number,\n options: FlowOptions<T>,\n): FlowBuilderInternal<T> => {\n const id = options.name ?? crypto.randomUUID();\n const flowInputs: FlowInput[] = [];\n const nodes: Call[] = [];\n const nodeIds = new Set<string>();\n\n const inputHandles = {} as Record<string, InputHandle | ResourceInputHandle>;\n for (const [name, decl] of Object.entries(options.inputs)) {\n if (isResource(decl)) {\n if (decl.chainId !== chainId) {\n throw new Error(\n `Input \"${name}\" has chainId ${decl.chainId} but flow targets chain ${chainId}`,\n );\n }\n flowInputs.push({ name, resource: decl });\n inputHandles[name] = { _tag: 'input', inputName: name, resource: decl };\n } else {\n flowInputs.push({ name, type: decl });\n inputHandles[name] = { _tag: 'input', inputName: name };\n }\n }\n\n const call = <O extends OutputSpec>(\n nodeId: string,\n op: string,\n args: CallArgs,\n outputs: O,\n ): { readonly [K in keyof O & string]: OutputHandle<O[K]> } => {\n if (nodeIds.has(nodeId)) throw new Error(`Duplicate node id: \"${nodeId}\"`);\n nodeIds.add(nodeId);\n\n const bind: Record<string, Ref> = {};\n for (const [key, val] of Object.entries(args.bind)) {\n bind[key] = toRef(val);\n }\n\n const node: Call = {\n id: nodeId,\n op,\n bind,\n config: (args.config ?? {}) as Record<string, unknown>,\n ...(args.guards && args.guards.length > 0 && { guards: args.guards }),\n };\n nodes.push(node);\n\n type Result = { readonly [K in keyof O & string]: OutputHandle<O[K]> };\n return new Proxy({} as Result, {\n get: (_target, prop): OutputHandle | undefined => {\n if (typeof prop !== 'string' || prop === 'then') return undefined;\n if (!(prop in outputs)) {\n throw new Error(\n `Op \"${op}\" has no output port \"${prop}\". Valid ports: ${Object.keys(\n outputs,\n ).join(', ')}`,\n );\n }\n return { _tag: 'output', nodeId, portName: prop };\n },\n });\n };\n\n const untypedOp = (\n nodeId: string,\n op: string,\n args: {\n readonly bind: Record<string, Ref>;\n readonly config: Record<string, unknown>;\n readonly guards?: readonly AppliedGuard[];\n },\n ): void => {\n if (nodeIds.has(nodeId)) throw new Error(`Duplicate node id: \"${nodeId}\"`);\n nodeIds.add(nodeId);\n const node: Call = {\n id: nodeId,\n op,\n bind: args.bind,\n config: args.config,\n ...(args.guards && args.guards.length > 0 && { guards: args.guards }),\n };\n nodes.push(node);\n };\n\n const build = (): TypedFlow<T> => ({\n version: 1,\n id,\n chainId,\n inputs: [...flowInputs],\n nodes: [...nodes],\n });\n\n const context: ContextAccessor = {\n sender: ref<'address'>('context.sender'),\n executionAddress: ref<'address'>('context.executionAddress'),\n };\n\n return {\n context,\n inputs: inputHandles as InputHandles<T>,\n call,\n untypedOp,\n build,\n };\n};\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/authoring/FlowBuilderCore.ts"],"sourcesContent":["import type {\n AppliedGuard,\n BindValue,\n Call,\n Flow,\n FlowInput,\n Resource,\n SolType,\n} from '@lifi/compose-spec';\n\nimport type {\n InputDecl,\n InputSchema,\n OutputKind,\n TypedGuard,\n TypedLiteral,\n TypedRef,\n} from '../types.js';\n\nimport type {\n InputHandle,\n OutputHandle,\n ResourceInputHandle,\n} from './handles.js';\nimport { toBindValue } from './handles.js';\nimport { ref } from './raw.js';\n\n/**\n * Arguments for calling an operation node in a flow.\n */\nexport interface CallArgs {\n /** Maps parameter names to input/output handles or raw `$ref` pointers. */\n readonly bind: Record<string, AnyBindable>;\n /** Optional operation-specific configuration. */\n readonly config?: object;\n /** Optional guards (e.g. slippage checks) applied to this operation's outputs. */\n readonly guards?: readonly TypedGuard[];\n}\n\n/**\n * Any value that can be bound to an operation parameter: an input handle, an\n * output handle from a previous operation, a typed `$ref` pointer, or a typed\n * literal.\n *\n * Use `raw.ref<T>()` to create a typed ref from a raw path string, and\n * `raw.literal<T>()` to bake in a constant.\n */\nexport type AnyBindable = InputHandle | OutputHandle | TypedRef | TypedLiteral;\n\n/**\n * Provides references to runtime context values available inside a flow.\n * Access via `builder.context`.\n */\nexport interface ContextAccessor {\n /** A `$ref` pointing to the transaction sender (signer) address. */\n readonly sender: TypedRef<'address'>;\n /** A `$ref` pointing to the on-chain execution (proxy) address. */\n readonly executionAddress: TypedRef<'address'>;\n}\n\ntype InputHandleOf<D extends InputDecl> = D extends Resource\n ? ResourceInputHandle\n : D extends SolType\n ? InputHandle<D>\n : InputHandle;\n\n/**\n * A mapped type that converts an input schema into typed handles.\n * Resource inputs produce {@link ResourceInputHandle}; scalar inputs produce {@link InputHandle}.\n *\n * @typeParam T - The flow's input schema.\n */\nexport type InputHandles<T extends InputSchema> = {\n readonly [K in keyof T]: InputHandleOf<T[K]>;\n};\n\n/**\n * Options for creating a new flow builder.\n *\n * @typeParam T - The flow's input schema.\n */\nexport interface FlowOptions<T extends InputSchema> {\n /** Optional human-readable name for the flow. Defaults to a random UUID. */\n readonly name?: string;\n /** Input declarations mapping names to resource or scalar types. */\n readonly inputs: T;\n}\n\n/**\n * A Flow document branded with its input schema for type-safe request building.\n *\n * The `__inputs` field is a phantom type — it exists only at compile time for\n * TypeScript inference and is never present at runtime.\n */\nexport type TypedFlow<T extends InputSchema = InputSchema> = Flow & {\n readonly __inputs?: T;\n};\n\n/**\n * Core flow builder interface providing low-level access to flow construction.\n *\n * For most use cases, prefer the {@link FlowBuilder} type returned by `sdk.flow()`\n * which adds generated operation methods and a `compile` method.\n *\n * @typeParam T - The flow's input schema.\n */\nexport interface FlowBuilderCore<T extends InputSchema = InputSchema> {\n /** Runtime context references (sender address, execution address). */\n readonly context: ContextAccessor;\n /** Typed handles for each declared flow input, used to bind inputs to operations. */\n readonly inputs: InputHandles<T>;\n /**\n * Appends an untyped operation node to the flow. This is an escape hatch\n * for operations not yet covered by the generated methods.\n *\n * Use `raw.ref<T>()` to reference this node's outputs in subsequent typed\n * operations.\n *\n * @param id - Unique node identifier within the flow.\n * @param op - The operation name (e.g. `\"custom.vaultQuery\"`).\n * @param args - Bind map, config, and optional guards for the node.\n * @throws Error if a node with the same `id` already exists in the flow.\n */\n readonly untypedOp: (\n id: string,\n op: string,\n args: {\n readonly bind: Record<string, BindValue>;\n readonly config: Record<string, unknown>;\n readonly guards?: readonly AppliedGuard[];\n },\n ) => void;\n /** Serialises the builder state into a {@link TypedFlow} document. */\n readonly build: () => TypedFlow<T>;\n}\n\n/** Maps output port names to their output kind for compile-time and runtime validation. */\nexport type OutputSpec = Record<string, OutputKind>;\n\n/** Internal interface exposed to bindGeneratedOps — not part of the public API. */\nexport interface FlowBuilderInternal<\n T extends InputSchema = InputSchema,\n> extends FlowBuilderCore<T> {\n readonly call: <O extends OutputSpec>(\n nodeId: string,\n op: string,\n args: CallArgs,\n outputs: O,\n ) => { readonly [K in keyof O & string]: OutputHandle<O[K]> };\n}\n\nconst isResource = (decl: InputDecl): decl is Resource =>\n typeof decl === 'object' && decl !== null && 'kind' in decl;\n\n/**\n * Creates a new flow builder targeting the given chain.\n *\n * @param chainId - The EVM chain ID (e.g. `1` for Ethereum mainnet).\n * @param options - Flow configuration including input declarations.\n * @returns A {@link FlowBuilderInternal} instance.\n * @internal\n */\nexport const createFlowBuilderCore = <T extends InputSchema>(\n chainId: number,\n options: FlowOptions<T>,\n): FlowBuilderInternal<T> => {\n const id = options.name ?? crypto.randomUUID();\n const flowInputs: FlowInput[] = [];\n const nodes: Call[] = [];\n const nodeIds = new Set<string>();\n\n const inputHandles = {} as Record<string, InputHandle | ResourceInputHandle>;\n for (const [name, decl] of Object.entries(options.inputs)) {\n if (isResource(decl)) {\n if (decl.chainId !== chainId) {\n throw new Error(\n `Input \"${name}\" has chainId ${decl.chainId} but flow targets chain ${chainId}`,\n );\n }\n flowInputs.push({ name, resource: decl });\n inputHandles[name] = { _tag: 'input', inputName: name, resource: decl };\n } else {\n flowInputs.push({ name, type: decl });\n inputHandles[name] = { _tag: 'input', inputName: name };\n }\n }\n\n const call = <O extends OutputSpec>(\n nodeId: string,\n op: string,\n args: CallArgs,\n outputs: O,\n ): { readonly [K in keyof O & string]: OutputHandle<O[K]> } => {\n if (nodeIds.has(nodeId)) throw new Error(`Duplicate node id: \"${nodeId}\"`);\n nodeIds.add(nodeId);\n\n const bind: Record<string, BindValue> = {};\n for (const [key, val] of Object.entries(args.bind)) {\n bind[key] = toBindValue(val);\n }\n\n const node: Call = {\n id: nodeId,\n op,\n bind,\n config: (args.config ?? {}) as Record<string, unknown>,\n ...(args.guards && args.guards.length > 0 && { guards: args.guards }),\n };\n nodes.push(node);\n\n type Result = { readonly [K in keyof O & string]: OutputHandle<O[K]> };\n return new Proxy({} as Result, {\n get: (_target, prop): OutputHandle | undefined => {\n if (typeof prop !== 'string' || prop === 'then') return undefined;\n if (!(prop in outputs)) {\n throw new Error(\n `Op \"${op}\" has no output port \"${prop}\". Valid ports: ${Object.keys(\n outputs,\n ).join(', ')}`,\n );\n }\n return { _tag: 'output', nodeId, portName: prop };\n },\n });\n };\n\n const untypedOp = (\n nodeId: string,\n op: string,\n args: {\n readonly bind: Record<string, BindValue>;\n readonly config: Record<string, unknown>;\n readonly guards?: readonly AppliedGuard[];\n },\n ): void => {\n if (nodeIds.has(nodeId)) throw new Error(`Duplicate node id: \"${nodeId}\"`);\n nodeIds.add(nodeId);\n const node: Call = {\n id: nodeId,\n op,\n bind: args.bind,\n config: args.config,\n ...(args.guards && args.guards.length > 0 && { guards: args.guards }),\n };\n nodes.push(node);\n };\n\n const build = (): TypedFlow<T> => ({\n version: 1,\n id,\n chainId,\n inputs: [...flowInputs],\n nodes: [...nodes],\n });\n\n const context: ContextAccessor = {\n sender: ref<'address'>('context.sender'),\n executionAddress: ref<'address'>('context.executionAddress'),\n };\n\n return {\n context,\n inputs: inputHandles as InputHandles<T>,\n call,\n untypedOp,\n build,\n };\n};\n"],"mappings":"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","names":[]}
|
|
@@ -20,7 +20,8 @@ var handles_exports = {};
|
|
|
20
20
|
__export(handles_exports, {
|
|
21
21
|
handleToRef: () => handleToRef,
|
|
22
22
|
isInputHandle: () => isInputHandle,
|
|
23
|
-
isOutputHandle: () => isOutputHandle
|
|
23
|
+
isOutputHandle: () => isOutputHandle,
|
|
24
|
+
toBindValue: () => toBindValue
|
|
24
25
|
});
|
|
25
26
|
module.exports = __toCommonJS(handles_exports);
|
|
26
27
|
const isInputHandle = (h) => h._tag === "input";
|
|
@@ -34,10 +35,12 @@ const handleToRef = (h) => {
|
|
|
34
35
|
if (!h.portName) throw new Error("OutputHandle has empty portName");
|
|
35
36
|
return { $ref: `${h.nodeId}.${h.portName}` };
|
|
36
37
|
};
|
|
38
|
+
const toBindValue = (bindable) => "_tag" in bindable ? handleToRef(bindable) : bindable;
|
|
37
39
|
// Annotate the CommonJS export names for ESM import in node:
|
|
38
40
|
0 && (module.exports = {
|
|
39
41
|
handleToRef,
|
|
40
42
|
isInputHandle,
|
|
41
|
-
isOutputHandle
|
|
43
|
+
isOutputHandle,
|
|
44
|
+
toBindValue
|
|
42
45
|
});
|
|
43
46
|
//# sourceMappingURL=handles.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/authoring/handles.ts"],"sourcesContent":["import type {
|
|
1
|
+
{"version":3,"sources":["../../src/authoring/handles.ts"],"sourcesContent":["import type {\n BindValue,\n Resource,\n SolType,\n StaticSolType,\n} from '@lifi/compose-spec';\n\nimport type { OutputKind, TypedLiteral, TypedRef } from '../types.js';\n\n/**\n * A handle referencing a flow input declared in the flow's input schema.\n * The phantom `__outputKind` field carries the type parameter through\n * TypeScript's structural type system without affecting runtime shape.\n */\nexport interface InputHandle<T extends OutputKind = OutputKind> {\n readonly _tag: 'input';\n readonly inputName: string;\n readonly __outputKind?: T;\n}\n\n/**\n * An input handle for a resource (token) input, carrying the resource\n * declaration alongside the reference.\n */\nexport interface ResourceInputHandle extends InputHandle<'resource'> {\n readonly resource: Resource;\n}\n\n/**\n * A handle referencing an output port of a previously declared operation node.\n * The phantom `__outputKind` field carries the type parameter through\n * TypeScript's structural type system without affecting runtime shape.\n */\nexport interface OutputHandle<T extends OutputKind = OutputKind> {\n readonly _tag: 'output';\n readonly nodeId: string;\n readonly portName: string;\n readonly __outputKind?: T;\n}\n\n/** Discriminated union of all handle types that can be bound to operation arguments. */\nexport type AnyHandle = InputHandle | OutputHandle;\n\n/** Type guard that narrows an {@link AnyHandle} to an {@link InputHandle}. */\nexport const isInputHandle = (h: AnyHandle): h is InputHandle =>\n h._tag === 'input';\n\n/** Type guard that narrows an {@link AnyHandle} to an {@link OutputHandle}. */\nexport const isOutputHandle = (h: AnyHandle): h is OutputHandle =>\n h._tag === 'output';\n\n/**\n * Converts a handle to a JSON `$ref` pointer used in the flow document.\n * Input handles become `\"input.<name>\"`; output handles become `\"<nodeId>.<port>\"`.\n */\nexport const handleToRef = (h: AnyHandle): { $ref: string } => {\n if (h._tag === 'input') {\n if (!h.inputName) throw new Error('InputHandle has empty inputName');\n return { $ref: `input.${h.inputName}` };\n }\n if (!h.nodeId) throw new Error('OutputHandle has empty nodeId');\n if (!h.portName) throw new Error('OutputHandle has empty portName');\n return { $ref: `${h.nodeId}.${h.portName}` };\n};\n\n/**\n * For a bind slot expecting type T, widen the set of accepted handle types:\n * - `'bytes32'` is the raw 32-byte word type: accept any static-32-byte handle\n * (`uint256`/`uintN`/`address`/`bool`/`bytes32`) plus `'resource'` (a uint256\n * amount). At the IR1 boundary every value is a type-erased word, so any\n * static-32-byte handle is a sound bind for a `bytes32` payload port.\n * - `'uint256'` keeps its existing widening to also accept `'resource'`-tagged\n * handles (resources are uint256 amounts).\n * - every other type keeps exact-SolType matching.\n */\ntype SlotAssignable<T extends OutputKind> = T extends 'bytes32'\n ? StaticSolType | 'resource'\n : T extends 'uint256'\n ? T | 'resource'\n : T;\n\n/**\n * The values accepted by a typed slot that must resolve to a `$ref`:\n * - `OutputHandle<T>` / `InputHandle<T>` (same type)\n * - For `'uint256'` slots, also `'resource'`-tagged handles\n * - For `'bytes32'` slots, any static-32-byte handle (and `'resource'`)\n * - `TypedRef<T>` (typed context refs, subject to `SlotAssignable`)\n *\n * Plain `{ $ref: '...' }` objects are **not** accepted - use `raw.ref<T>()`\n * to create a typed ref when you need to reference a raw path.\n */\nexport type RefBindable<T extends OutputKind> =\n | OutputHandle<SlotAssignable<T>>\n | InputHandle<SlotAssignable<T>>\n | TypedRef<SlotAssignable<T>>;\n\n/**\n * Any ref-resolving bindable, with no per-slot type constraint.\n */\nexport type AnyRefBindable = InputHandle | OutputHandle | TypedRef;\n\n/**\n * The set of values accepted by a typed operation bind slot: everything\n * {@link RefBindable} accepts, plus a {@link TypedLiteral} constant baked\n * into the flow document. A `'resource'` slot rejects a literal: a token\n * resource is not a scalar constant.\n *\n * Use `raw.literal<T>()` to create one.\n */\nexport type Bindable<T extends OutputKind> =\n RefBindable<T> | LiteralBindable<SlotAssignable<T>>;\n\n// Distributes over the slot's assignable kinds and keeps the scalar ones: a\n// `'resource'` slot has no scalar arm, so its literal arm collapses to\n// `never` and a literal is rejected outright.\ntype LiteralBindable<K extends OutputKind> = K extends SolType\n ? TypedLiteral<K>\n : never;\n\n/**\n * Converts a bindable to the wire `bind` value it denotes: handles become\n * `$ref` pointers, refs and literals pass through as-is.\n */\nexport const toBindValue = (\n bindable: InputHandle | OutputHandle | TypedRef | TypedLiteral,\n): BindValue => ('_tag' in bindable ? handleToRef(bindable) : bindable);\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA4CO,MAAM,gBAAgB,CAAC,MAC5B,EAAE,SAAS;AAGN,MAAM,iBAAiB,CAAC,MAC7B,EAAE,SAAS;AAMN,MAAM,cAAc,CAAC,MAAmC;AAC7D,MAAI,EAAE,SAAS,SAAS;AACtB,QAAI,CAAC,EAAE,UAAW,OAAM,IAAI,MAAM,iCAAiC;AACnE,WAAO,EAAE,MAAM,SAAS,EAAE,SAAS,GAAG;AAAA,EACxC;AACA,MAAI,CAAC,EAAE,OAAQ,OAAM,IAAI,MAAM,+BAA+B;AAC9D,MAAI,CAAC,EAAE,SAAU,OAAM,IAAI,MAAM,iCAAiC;AAClE,SAAO,EAAE,MAAM,GAAG,EAAE,MAAM,IAAI,EAAE,QAAQ,GAAG;AAC7C;AA4DO,MAAM,cAAc,CACzB,aACe,UAAU,WAAW,YAAY,QAAQ,IAAI;","names":[]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { StaticSolType, Resource } from '@lifi/compose-spec';
|
|
2
|
-
import { OutputKind, TypedRef } from '../types.cjs';
|
|
1
|
+
import { StaticSolType, SolType, Resource, BindValue } from '@lifi/compose-spec';
|
|
2
|
+
import { OutputKind, TypedRef, TypedLiteral } from '../types.cjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* A handle referencing a flow input declared in the flow's input schema.
|
|
@@ -54,15 +54,34 @@ declare const handleToRef: (h: AnyHandle) => {
|
|
|
54
54
|
*/
|
|
55
55
|
type SlotAssignable<T extends OutputKind> = T extends 'bytes32' ? StaticSolType | 'resource' : T extends 'uint256' ? T | 'resource' : T;
|
|
56
56
|
/**
|
|
57
|
-
* The
|
|
57
|
+
* The values accepted by a typed slot that must resolve to a `$ref`:
|
|
58
58
|
* - `OutputHandle<T>` / `InputHandle<T>` (same type)
|
|
59
59
|
* - For `'uint256'` slots, also `'resource'`-tagged handles
|
|
60
60
|
* - For `'bytes32'` slots, any static-32-byte handle (and `'resource'`)
|
|
61
61
|
* - `TypedRef<T>` (typed context refs, subject to `SlotAssignable`)
|
|
62
62
|
*
|
|
63
|
-
* Plain `{ $ref: '...' }` objects are **not** accepted
|
|
63
|
+
* Plain `{ $ref: '...' }` objects are **not** accepted - use `raw.ref<T>()`
|
|
64
64
|
* to create a typed ref when you need to reference a raw path.
|
|
65
65
|
*/
|
|
66
|
-
type
|
|
66
|
+
type RefBindable<T extends OutputKind> = OutputHandle<SlotAssignable<T>> | InputHandle<SlotAssignable<T>> | TypedRef<SlotAssignable<T>>;
|
|
67
|
+
/**
|
|
68
|
+
* Any ref-resolving bindable, with no per-slot type constraint.
|
|
69
|
+
*/
|
|
70
|
+
type AnyRefBindable = InputHandle | OutputHandle | TypedRef;
|
|
71
|
+
/**
|
|
72
|
+
* The set of values accepted by a typed operation bind slot: everything
|
|
73
|
+
* {@link RefBindable} accepts, plus a {@link TypedLiteral} constant baked
|
|
74
|
+
* into the flow document. A `'resource'` slot rejects a literal: a token
|
|
75
|
+
* resource is not a scalar constant.
|
|
76
|
+
*
|
|
77
|
+
* Use `raw.literal<T>()` to create one.
|
|
78
|
+
*/
|
|
79
|
+
type Bindable<T extends OutputKind> = RefBindable<T> | LiteralBindable<SlotAssignable<T>>;
|
|
80
|
+
type LiteralBindable<K extends OutputKind> = K extends SolType ? TypedLiteral<K> : never;
|
|
81
|
+
/**
|
|
82
|
+
* Converts a bindable to the wire `bind` value it denotes: handles become
|
|
83
|
+
* `$ref` pointers, refs and literals pass through as-is.
|
|
84
|
+
*/
|
|
85
|
+
declare const toBindValue: (bindable: InputHandle | OutputHandle | TypedRef | TypedLiteral) => BindValue;
|
|
67
86
|
|
|
68
|
-
export { type AnyHandle, type Bindable, type InputHandle, type OutputHandle, type ResourceInputHandle, handleToRef, isInputHandle, isOutputHandle };
|
|
87
|
+
export { type AnyHandle, type AnyRefBindable, type Bindable, type InputHandle, type OutputHandle, type RefBindable, type ResourceInputHandle, handleToRef, isInputHandle, isOutputHandle, toBindValue };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { StaticSolType, Resource } from '@lifi/compose-spec';
|
|
2
|
-
import { OutputKind, TypedRef } from '../types.js';
|
|
1
|
+
import { StaticSolType, SolType, Resource, BindValue } from '@lifi/compose-spec';
|
|
2
|
+
import { OutputKind, TypedRef, TypedLiteral } from '../types.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* A handle referencing a flow input declared in the flow's input schema.
|
|
@@ -54,15 +54,34 @@ declare const handleToRef: (h: AnyHandle) => {
|
|
|
54
54
|
*/
|
|
55
55
|
type SlotAssignable<T extends OutputKind> = T extends 'bytes32' ? StaticSolType | 'resource' : T extends 'uint256' ? T | 'resource' : T;
|
|
56
56
|
/**
|
|
57
|
-
* The
|
|
57
|
+
* The values accepted by a typed slot that must resolve to a `$ref`:
|
|
58
58
|
* - `OutputHandle<T>` / `InputHandle<T>` (same type)
|
|
59
59
|
* - For `'uint256'` slots, also `'resource'`-tagged handles
|
|
60
60
|
* - For `'bytes32'` slots, any static-32-byte handle (and `'resource'`)
|
|
61
61
|
* - `TypedRef<T>` (typed context refs, subject to `SlotAssignable`)
|
|
62
62
|
*
|
|
63
|
-
* Plain `{ $ref: '...' }` objects are **not** accepted
|
|
63
|
+
* Plain `{ $ref: '...' }` objects are **not** accepted - use `raw.ref<T>()`
|
|
64
64
|
* to create a typed ref when you need to reference a raw path.
|
|
65
65
|
*/
|
|
66
|
-
type
|
|
66
|
+
type RefBindable<T extends OutputKind> = OutputHandle<SlotAssignable<T>> | InputHandle<SlotAssignable<T>> | TypedRef<SlotAssignable<T>>;
|
|
67
|
+
/**
|
|
68
|
+
* Any ref-resolving bindable, with no per-slot type constraint.
|
|
69
|
+
*/
|
|
70
|
+
type AnyRefBindable = InputHandle | OutputHandle | TypedRef;
|
|
71
|
+
/**
|
|
72
|
+
* The set of values accepted by a typed operation bind slot: everything
|
|
73
|
+
* {@link RefBindable} accepts, plus a {@link TypedLiteral} constant baked
|
|
74
|
+
* into the flow document. A `'resource'` slot rejects a literal: a token
|
|
75
|
+
* resource is not a scalar constant.
|
|
76
|
+
*
|
|
77
|
+
* Use `raw.literal<T>()` to create one.
|
|
78
|
+
*/
|
|
79
|
+
type Bindable<T extends OutputKind> = RefBindable<T> | LiteralBindable<SlotAssignable<T>>;
|
|
80
|
+
type LiteralBindable<K extends OutputKind> = K extends SolType ? TypedLiteral<K> : never;
|
|
81
|
+
/**
|
|
82
|
+
* Converts a bindable to the wire `bind` value it denotes: handles become
|
|
83
|
+
* `$ref` pointers, refs and literals pass through as-is.
|
|
84
|
+
*/
|
|
85
|
+
declare const toBindValue: (bindable: InputHandle | OutputHandle | TypedRef | TypedLiteral) => BindValue;
|
|
67
86
|
|
|
68
|
-
export { type AnyHandle, type Bindable, type InputHandle, type OutputHandle, type ResourceInputHandle, handleToRef, isInputHandle, isOutputHandle };
|
|
87
|
+
export { type AnyHandle, type AnyRefBindable, type Bindable, type InputHandle, type OutputHandle, type RefBindable, type ResourceInputHandle, handleToRef, isInputHandle, isOutputHandle, toBindValue };
|
|
@@ -9,9 +9,11 @@ const handleToRef = (h) => {
|
|
|
9
9
|
if (!h.portName) throw new Error("OutputHandle has empty portName");
|
|
10
10
|
return { $ref: `${h.nodeId}.${h.portName}` };
|
|
11
11
|
};
|
|
12
|
+
const toBindValue = (bindable) => "_tag" in bindable ? handleToRef(bindable) : bindable;
|
|
12
13
|
export {
|
|
13
14
|
handleToRef,
|
|
14
15
|
isInputHandle,
|
|
15
|
-
isOutputHandle
|
|
16
|
+
isOutputHandle,
|
|
17
|
+
toBindValue
|
|
16
18
|
};
|
|
17
19
|
//# sourceMappingURL=handles.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/authoring/handles.ts"],"sourcesContent":["import type {
|
|
1
|
+
{"version":3,"sources":["../../src/authoring/handles.ts"],"sourcesContent":["import type {\n BindValue,\n Resource,\n SolType,\n StaticSolType,\n} from '@lifi/compose-spec';\n\nimport type { OutputKind, TypedLiteral, TypedRef } from '../types.js';\n\n/**\n * A handle referencing a flow input declared in the flow's input schema.\n * The phantom `__outputKind` field carries the type parameter through\n * TypeScript's structural type system without affecting runtime shape.\n */\nexport interface InputHandle<T extends OutputKind = OutputKind> {\n readonly _tag: 'input';\n readonly inputName: string;\n readonly __outputKind?: T;\n}\n\n/**\n * An input handle for a resource (token) input, carrying the resource\n * declaration alongside the reference.\n */\nexport interface ResourceInputHandle extends InputHandle<'resource'> {\n readonly resource: Resource;\n}\n\n/**\n * A handle referencing an output port of a previously declared operation node.\n * The phantom `__outputKind` field carries the type parameter through\n * TypeScript's structural type system without affecting runtime shape.\n */\nexport interface OutputHandle<T extends OutputKind = OutputKind> {\n readonly _tag: 'output';\n readonly nodeId: string;\n readonly portName: string;\n readonly __outputKind?: T;\n}\n\n/** Discriminated union of all handle types that can be bound to operation arguments. */\nexport type AnyHandle = InputHandle | OutputHandle;\n\n/** Type guard that narrows an {@link AnyHandle} to an {@link InputHandle}. */\nexport const isInputHandle = (h: AnyHandle): h is InputHandle =>\n h._tag === 'input';\n\n/** Type guard that narrows an {@link AnyHandle} to an {@link OutputHandle}. */\nexport const isOutputHandle = (h: AnyHandle): h is OutputHandle =>\n h._tag === 'output';\n\n/**\n * Converts a handle to a JSON `$ref` pointer used in the flow document.\n * Input handles become `\"input.<name>\"`; output handles become `\"<nodeId>.<port>\"`.\n */\nexport const handleToRef = (h: AnyHandle): { $ref: string } => {\n if (h._tag === 'input') {\n if (!h.inputName) throw new Error('InputHandle has empty inputName');\n return { $ref: `input.${h.inputName}` };\n }\n if (!h.nodeId) throw new Error('OutputHandle has empty nodeId');\n if (!h.portName) throw new Error('OutputHandle has empty portName');\n return { $ref: `${h.nodeId}.${h.portName}` };\n};\n\n/**\n * For a bind slot expecting type T, widen the set of accepted handle types:\n * - `'bytes32'` is the raw 32-byte word type: accept any static-32-byte handle\n * (`uint256`/`uintN`/`address`/`bool`/`bytes32`) plus `'resource'` (a uint256\n * amount). At the IR1 boundary every value is a type-erased word, so any\n * static-32-byte handle is a sound bind for a `bytes32` payload port.\n * - `'uint256'` keeps its existing widening to also accept `'resource'`-tagged\n * handles (resources are uint256 amounts).\n * - every other type keeps exact-SolType matching.\n */\ntype SlotAssignable<T extends OutputKind> = T extends 'bytes32'\n ? StaticSolType | 'resource'\n : T extends 'uint256'\n ? T | 'resource'\n : T;\n\n/**\n * The values accepted by a typed slot that must resolve to a `$ref`:\n * - `OutputHandle<T>` / `InputHandle<T>` (same type)\n * - For `'uint256'` slots, also `'resource'`-tagged handles\n * - For `'bytes32'` slots, any static-32-byte handle (and `'resource'`)\n * - `TypedRef<T>` (typed context refs, subject to `SlotAssignable`)\n *\n * Plain `{ $ref: '...' }` objects are **not** accepted - use `raw.ref<T>()`\n * to create a typed ref when you need to reference a raw path.\n */\nexport type RefBindable<T extends OutputKind> =\n | OutputHandle<SlotAssignable<T>>\n | InputHandle<SlotAssignable<T>>\n | TypedRef<SlotAssignable<T>>;\n\n/**\n * Any ref-resolving bindable, with no per-slot type constraint.\n */\nexport type AnyRefBindable = InputHandle | OutputHandle | TypedRef;\n\n/**\n * The set of values accepted by a typed operation bind slot: everything\n * {@link RefBindable} accepts, plus a {@link TypedLiteral} constant baked\n * into the flow document. A `'resource'` slot rejects a literal: a token\n * resource is not a scalar constant.\n *\n * Use `raw.literal<T>()` to create one.\n */\nexport type Bindable<T extends OutputKind> =\n RefBindable<T> | LiteralBindable<SlotAssignable<T>>;\n\n// Distributes over the slot's assignable kinds and keeps the scalar ones: a\n// `'resource'` slot has no scalar arm, so its literal arm collapses to\n// `never` and a literal is rejected outright.\ntype LiteralBindable<K extends OutputKind> = K extends SolType\n ? TypedLiteral<K>\n : never;\n\n/**\n * Converts a bindable to the wire `bind` value it denotes: handles become\n * `$ref` pointers, refs and literals pass through as-is.\n */\nexport const toBindValue = (\n bindable: InputHandle | OutputHandle | TypedRef | TypedLiteral,\n): BindValue => ('_tag' in bindable ? handleToRef(bindable) : bindable);\n"],"mappings":"AA4CO,MAAM,gBAAgB,CAAC,MAC5B,EAAE,SAAS;AAGN,MAAM,iBAAiB,CAAC,MAC7B,EAAE,SAAS;AAMN,MAAM,cAAc,CAAC,MAAmC;AAC7D,MAAI,EAAE,SAAS,SAAS;AACtB,QAAI,CAAC,EAAE,UAAW,OAAM,IAAI,MAAM,iCAAiC;AACnE,WAAO,EAAE,MAAM,SAAS,EAAE,SAAS,GAAG;AAAA,EACxC;AACA,MAAI,CAAC,EAAE,OAAQ,OAAM,IAAI,MAAM,+BAA+B;AAC9D,MAAI,CAAC,EAAE,SAAU,OAAM,IAAI,MAAM,iCAAiC;AAClE,SAAO,EAAE,MAAM,GAAG,EAAE,MAAM,IAAI,EAAE,QAAQ,GAAG;AAC7C;AA4DO,MAAM,cAAc,CACzB,aACe,UAAU,WAAW,YAAY,QAAQ,IAAI;","names":[]}
|