npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@flow-as-code/hcl

v0.2.1

Published

HCL as a third view over FlowDoc: a lossless parser, a terraform fmt-exact formatter, and the flowascode resource companion.

Readme

flow-as-code

@flow-as-code/hcl

npm i @flow-as-code/hcl

Apache-2.0, Node 22.12 or newer. Browser-safe: no Node built-ins anywhere in its module graph, so the studio bundles it.

Site and docs: https://flow-as-code.dev/. This package on npm: https://www.npmjs.com/package/@flow-as-code/hcl.

HCL as a third view over FlowDoc (ADR-0007): a document can have a <name>.flow.tf companion, a flowascode_contact_flow resource the flow-as-code/flowascode Terraform provider applies, instead of a <name>.flow.ts. The mapping is the contract in conformance/hcl/README.md, which the provider implements too. This package is its TypeScript side.

Two layers ship. The syntax layer is a lossless lexer and parser for the HCL native syntax, a formatter that writes exactly what terraform fmt and tofu fmt write, literal evaluation, and HCL string quoting. The document layer on top of it writes a FlowDoc as its companion (fromFlowDoc), reads a companion back (toFlowDoc), regenerates a companion without losing what the user wrote beside the document, and emits a set of documents as one configuration (emitFlowascode).

Documents

import { fromFlowDoc, toFlowDoc } from "@flow-as-code/hcl";

const tf = fromFlowDoc(doc, {
  bindings: { "queue:appointments": "aws_connect_queue.appointments.arn" },
  fileName: "appointment-line.flowdoc.json",
});
const { doc: back, sidecar, warnings } = toFlowDoc(tf, { fileName: "appointment-line.flow.tf" });

fromFlowDoc writes the resource by the contract's rules 1 to 17: one action block per action, a typed sub-block wherever the catalog's shape accepts the action's Parameters and generic otherwise, reference fields as keys ("queue:appointments") bound once in refs, an unbound key as null under a # TODO comment, and position only where the canvas moved an action off the auto-layout. The output is a terraform fmt fixed point. Given previous, the companion on disk, it carries what rule 24 names from it rather than from the document: refs values by key, instance_id, tags, state, external_invocation_enabled, lint, provider, lifecycle, depends_on, and comment lines marked @keep directly above the resource or an action.

toFlowDoc reads a companion to its document in synth normal form and a sidecar of what the file holds beside it (refs bindings, lint, tags, the instance_id expression, kept comments, and every value it normalized). A reference field may hold a key, a JSONPath, the full token ("$${cdref:queue:appointments}"), or a resource address the contract's sugar table lists (aws_connect_queue.appointments.arn), which reads as the key plus a binding, so the next regeneration writes the key. Anything else is an HclError whose code is the contract's and whose path names the attribute (action[set-queue].update_contact_target_queue.queue_id); the message says which refs entry to write instead. A refs key no action uses is a warning, dropped on the next regeneration.

The view holds what a canvas or a .flow.ts holds and no more: content.Metadata, which an exported flow may carry, is not written, and a fractional console position is written rounded.

Emitting a set

import { emitFlowascode } from "@flow-as-code/hcl";

const { files } = emitFlowascode(docs, {
  addressMap: { "queue:appointments": "aws_connect_queue.appointments.arn" },
});
// flows.tf, variables.tf, versions.tf.example

emitFlowascode is flow-cli emit --target flowascode: one resource per document in flows.tf, written as fromFlowDoc writes a companion, with each refs map bound from the address map (keyed as @flow-as-code/tf's is) or to what the set emits itself, and a version and alias resource for every module a flow in the set invokes by alias. What nothing binds is null under a # TODO comment, which the provider refuses at plan time. It refuses a literal ARN in the address map, a name that is not a slug, and two documents with one address, listing every problem on EmitFlowascodeError.problems. It never writes a <name>.flow.tf.

Parse, print, format

import { format, parse, print } from "@flow-as-code/hcl";

const file = parse(text, "appointment-line.flow.tf");
print(file) === text; // every character is kept: whitespace, comments, escapes
format(text); // the canonical layout, as terraform fmt writes it

parse returns a syntax tree over the file's full token array: every node records its first and last token, so it can be printed as written (sourceOf), located in an error, or have the comments around it found. A syntax error is an HclError whose message starts with file:line:column.

format is a port of hclwrite's formatter, pass for pass: it indents each line by its bracket depth, sets the space between each pair of tokens by hclwrite's rules, and aligns the = of consecutive single-line attributes and their trailing comments. A line that opens a multi-line value is left out of the alignment run, as hclwrite leaves it. The package's conformance test holds format to the text of every Terraform file committed to this repository, and to tofu fmt itself when RUN_TOFU_VALIDATE=1. Terraform's fmt command also rewrites some legacy syntax (an interpolation-only "${var.x}" becomes var.x); format does not.

Literals and strings

import { evaluateLiteral, quote, unquoteLiteral } from "@flow-as-code/hcl";

evaluateLiteral(expr, file); // strings, numbers, booleans, null, tuples, objects
quote("Balance: ${amount}"); // "Balance: $${amount}"

evaluateLiteral refuses anything that needs an evaluation context (a variable, a call, an interpolation) with the contract's NON_LITERAL_VALUE code. quote writes a string by the contract's rule 16: \\, \", \n, \r and \t escaped, any other control character as \uXXXX, ${ and %{ doubled so they stay literal, everything else raw. A lone surrogate has no UTF-8 spelling and is refused either way with LONE_SURROGATE.