@lifi/compose-spec
Version:
Public wire-format types and schemas for Compose flows
67 lines (57 loc) • 2.17 kB
text/typescript
import type { Ref } from "./flowSchema.js";
export type ParsedRef =
| { readonly scope: "input"; readonly port: string }
| { readonly scope: "output"; readonly node: string; readonly port: string }
| { readonly scope: "context"; readonly key: ContextKey };
export type RefScope = ParsedRef["scope"];
export type ContextKey = "sender" | "executionAddress";
export const isContextKey = (key: string): key is ContextKey =>
key === "sender" || key === "executionAddress";
/**
* Ref prefixes that parseRef intercepts before the fallback `output` scope.
* A node whose id matches one of these would produce ambiguous refs
* (e.g. `context.sender` could be a context ref or node-output ref).
* Validation must reject these as node ids.
*/
export const RESERVED_REF_SCOPES: ReadonlySet<string> = new Set([
"input",
"context",
"literal",
]);
export const isRef = (v: unknown): v is Ref =>
typeof v === "object" &&
v !== null &&
"$ref" in v &&
typeof (v as { $ref: unknown }).$ref === "string";
export const parseRef = (ref: Ref): ParsedRef => {
const dot = ref.$ref.indexOf(".");
if (dot === -1)
throw new Error(`Invalid ref: "${ref.$ref}" (must contain a dot)`);
const prefix = ref.$ref.slice(0, dot);
const suffix = ref.$ref.slice(dot + 1);
if (prefix === "input") return { scope: "input", port: suffix };
if (prefix === "context") {
if (!isContextKey(suffix))
throw new Error(`Unknown context key: "${suffix}"`);
return { scope: "context", key: suffix };
}
return { scope: "output", node: prefix, port: suffix };
};
export const foldRef = <R>(
ref: ParsedRef,
cases: {
readonly input: (port: string) => R;
readonly context: (key: ContextKey) => R;
readonly output: (node: string, port: string) => R;
}
): R => {
if (ref.scope === "input") return cases.input(ref.port);
if (ref.scope === "context") return cases.context(ref.key);
return cases.output(ref.node, ref.port);
};
export const refKey = (ref: Ref): string =>
foldRef(parseRef(ref), {
input: (port) => `input:${port}`,
context: (key) => `context:${key}`,
output: (node, port) => `output:${node}:${port}`,
});