<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Posts on redgeoff</title><link>https://redgeoff.com/posts/</link><description>Recent content in Posts on redgeoff</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Mon, 05 Oct 2026 15:38:00 -0700</lastBuildDate><atom:link href="https://redgeoff.com/posts/index.xml" rel="self" type="application/rss+xml"/><item><title>My first trading models knew which companies would survive</title><link>https://redgeoff.com/posts/trading-model-leakage/</link><pubDate>Mon, 05 Oct 2026 15:38:00 -0700</pubDate><guid>https://redgeoff.com/posts/trading-model-leakage/</guid><description>&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/trading-model-leakage/survivors-hero.jpg"&#10;&#9;&#9;&#9;alt="Rows of grey gravestones receding into fog under a dusk sky, with a single bright red line chart climbing through them from lower left to upper right"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Image credit: author, drawn with code&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;My first live trading bot started trading real money on December 2, 2024. The models&#10;behind it had been trained on years of stock-market history, and every company in&#10;that history had something in common: it was in the S&amp;amp;P 500 on the day I trained the&#10;model.&lt;/p&gt;&#10;&lt;p&gt;That sounds harmless until you think about what it leaves out. A company that was in&#10;the index in 2016 and went bankrupt in 2019 wasn&amp;rsquo;t in the training data at all, and a&#10;company that joined the index in 2025 was in it all the way back. In other words, the&#10;models learned from a version of the market where nothing ever failed and every stock&#10;was already a future winner.&lt;/p&gt;&#10;&lt;p&gt;That was a leak, and it went live. It wasn&amp;rsquo;t the only one either.&lt;/p&gt;&#10;&lt;p&gt;If you haven&amp;rsquo;t built one of these systems, here&amp;rsquo;s what I mean by a leak. You can&amp;rsquo;t&#10;test a trading model by waiting ten years to see how it does, so instead you backtest&#10;it: replay history, let the model pick stocks as if it were living through each date,&#10;and score those picks against what actually happened next. A leak is any information&#10;that sneaks into that replay which the model couldn&amp;rsquo;t have had on the date in&#10;question. It doesn&amp;rsquo;t have to be anything as obvious as tomorrow&amp;rsquo;s price. Knowing which&#10;companies will still be around in five years is enough, and the result is a backtest&#10;that looks better than anything you could have done for real.&lt;/p&gt;&#10;&lt;p&gt;The standard defence is to train walk-forward. For each rebalancing date (the days the&#10;portfolio is reshuffled, roughly monthly for me), the model trains only on data from&#10;before that date, picks its stocks, holds them until the next rebalancing date, and&#10;then the whole thing steps forward and repeats. That way, no single model is ever&#10;scored on data it was trained on. Walk-forward is necessary, but as you&amp;rsquo;ll see, every&#10;leak in this post got through it, because each one smuggled the future in somewhere&#10;other than the training dates: the list of stocks, the shape of the data, or the data&#10;itself, through prices adjusted the wrong way or figures dated before anyone could have&#10;known them.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/trading-model-leakage/walk-forward-leaks.png"&#10;&#9;&#9;&#9;alt="Diagram of walk-forward training: four steps, each training on the past, picking on the rebalancing date and holding until the next one, moving forward in time. Below it, the three ways the leaks in this post got past walk-forward: the list of stocks (survivorship), the shape of the data (columns and rows decided by the whole run), and the data itself (prices adjusted the wrong way, or figures dated before anyone could know them). At the bottom, the check that catches the second: training step by step must give bit-for-bit the same picks as training all at once."&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;So, this post is about the leaks I shipped, the ones I caught in research before they&#10;shipped, and the times an AI was sure it had found a leak and was wrong. Every one of&#10;them is a reason that backtests are hard to trust, including your own. Along the way&#10;I&amp;rsquo;ve ended up with one rule for all three: you can&amp;rsquo;t reason your way to the answer. You&#10;need a test that makes a leak show up as a number.&lt;/p&gt;&#10;&lt;h2 id="survivors"&gt;Survivors&lt;/h2&gt;&#10;&lt;p&gt;What gave it away was a backtest that was too good. I tune the model by running&#10;hyperparameter searches, where I backtest hundreds of versions of the model with&#10;different settings and compare them. In August 2025, one of those runs came back with a&#10;2020 that was simply impossible, and most of that year&amp;rsquo;s profit came from a single&#10;holding. When I went to look it up, neither Yahoo Finance nor&#10;Google Finance had any prices for its ticker before 2021, and my note from the&#10;investigation reads: &amp;ldquo;Perhaps the symbol was renamed?&amp;rdquo;&lt;/p&gt;&#10;&lt;p&gt;It had been. It was Chesapeake Energy, which had been dropped from the S&amp;amp;P 500 in&#10;2018, did a 1-for-200 reverse split in April 2020, went bankrupt later that year, and&#10;eventually merged, took on a new name and rejoined the index in 2025. My backtest was&#10;holding it in 2020, two years after it had left the index, because the training code&#10;only knew about the index as it stood today. (Keep that reverse split in&#10;mind, as it shows up again later in this post.)&lt;/p&gt;&#10;&lt;p&gt;Fixing survivorship bias turned out to be mostly a data problem. First, I needed the&#10;S&amp;amp;P 500&amp;rsquo;s membership for every day of the training period, not just the current list.&#10;I built this from the public record of index changes and had a couple of AI models&#10;double-check the list. Then I needed price history for the companies that had left the&#10;index, many of which no longer trade. Finally, the training loop had to resolve the&#10;set of stocks it was allowed to pick from (its universe) date by date, so that a model&#10;trained on 2016 data only ever saw companies that were actually in the index in 2016.&lt;/p&gt;&#10;&lt;p&gt;When the fix first ran, the backtest fell apart. The note I left myself in the pull&#10;request was, in all caps, &amp;ldquo;THE ANNUALIZED RETURN IS NEAR 0!!!&amp;rdquo; That&amp;rsquo;s what removing a&#10;leak is supposed to do, and it&amp;rsquo;s a very unpleasant thing to watch.&lt;/p&gt;&#10;&lt;p&gt;The fix reached the production training path in late September 2025, about ten months&#10;after the first live trade.&lt;/p&gt;&#10;&lt;p&gt;There was one experiment from that period that I&amp;rsquo;m still glad I ran. I wondered whether&#10;a small, controlled leak might be acceptable: train the first stretch of the model on a&#10;later snapshot of the index, so that it learned from today&amp;rsquo;s large companies, and then&#10;walk forward point-in-time from there. It gave no reliable improvement. My conclusion&#10;at the time was that a model trained on a fixed list of companies never sees a stock&#10;enter or leave the index, which leaves it less prepared for the real index, where that&#10;happens all the time.&lt;/p&gt;&#10;&lt;p&gt;Unfortunately, that wasn&amp;rsquo;t the end of it. A year later, in October 2026, a copy of the&#10;equal-weight S&amp;amp;P 500 that an agent and I had rebuilt from my research data beat the&#10;real equal-weight index fund by a margin that a copy has no business beating it by.&#10;The two moved almost in lockstep, so the copy was tracking the right thing, but it was&#10;consistently doing better. That&amp;rsquo;s the same &amp;ldquo;too good&amp;rdquo; signature, and it turned out to&#10;be survivorship again, coming from two places.&lt;/p&gt;&#10;&lt;p&gt;The first was the membership list itself. The public record of index changes I&amp;rsquo;d built&#10;it from is sparse before about 2007, so the early lists were short and slightly wrong.&#10;To check, we rebuilt membership from the holdings of an S&amp;amp;P 500 index fund, which has&#10;to file its complete list of holdings with the SEC. Against those filings, my list was&#10;74 companies short in 2001 and still 5 short in 2009, and it even contained a few companies&#10;that hadn&amp;rsquo;t gone public yet. The AI models I&amp;rsquo;d asked to double-check the list in 2025&#10;hadn&amp;rsquo;t caught any of this.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/trading-model-leakage/sp500-membership-vs-spy.png"&#10;&#9;&#9;&#9;alt="Line chart from 1995 to 2026 of how many S&amp;amp;P 500 companies my list held on each date SPY filed its holdings with the SEC. SPY&amp;#39;s count sits near 500 throughout. My list starts at 379 in 1995, 121 short, is 74 short in 2001 and 5 short in 2009, and matches from 2010."&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;How many S&amp;amp;P 500 members my list knew about, against the holdings SPY filed with the SEC. The gap is the least my 2025 fix still left out, since the list also held a few companies that hadn&amp;rsquo;t gone public yet.&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;The second was prices. Many of the companies that failed&#10;outright, like Lehman Brothers, Bear Stearns, Enron and WorldCom, have no price data in&#10;either of my data sources, so even with a correct list they can never be picked or&#10;held in a backtest.&lt;/p&gt;&#10;&lt;p&gt;This one mostly affects my long-history research backtests rather than the live&#10;system, since the data the live configuration was chosen from covers 99.6% of index&#10;members. But it means that for my backtests before about 2016, the honest reading is&#10;&amp;ldquo;probably an upper bound&amp;rdquo;, and fixing it properly needs a data source that still sells&#10;prices for companies that no longer exist.&lt;/p&gt;&#10;&lt;h2 id="so-what-did-it-cost"&gt;So, What Did It Cost?&lt;/h2&gt;&#10;&lt;p&gt;I don&amp;rsquo;t know, and I think the reason is worth explaining.&lt;/p&gt;&#10;&lt;p&gt;First, a correction to the intuition. A live model can&amp;rsquo;t see the future, because on the&#10;day it makes a prediction there&amp;rsquo;s no future data to see. Today&amp;rsquo;s S&amp;amp;P 500 list is the&#10;correct list for today. So, the live models were never peeking at tomorrow&amp;rsquo;s prices.&#10;The damage was in two other places:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;The backtests I used to choose the model and its settings were inflated by the leak,&#10;so the configuration that went live was selected on a flattering number.&lt;/li&gt;&#10;&lt;li&gt;The live model had been trained on a history with no failures in it, so whatever it&#10;had learned, it had learned from survivors.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Putting a price on that requires comparing what the model expected with what the&#10;account actually did, period by period, and in 2025 I had no mechanism for tracking&#10;that gap. Moreover, the model worked from end-of-day closing prices while the orders&#10;filled the next morning, so any gap I computed mixed model error with hours of ordinary&#10;market movement.&lt;/p&gt;&#10;&lt;p&gt;That was a consequence of how the early system worked. It ran at a fairly leisurely&#10;pace after the market closed: download the day&amp;rsquo;s prices, retrain the model, decide&#10;what to buy and sell, and queue the orders for the next morning. Since then, a lot of&#10;optimization and engineering has gone into doing all of that during the trading day.&#10;The system pulls a price snapshot at a fixed time in the morning, trains the model on&#10;it and starts trading, with a median of about 12 minutes from the snapshot to the first&#10;fill. For bots that run this way, the model&amp;rsquo;s prices and the fill prices are now minutes&#10;apart instead of a night apart.&lt;/p&gt;&#10;&lt;p&gt;There are also now reports that split a bot&amp;rsquo;s live result into what the model&#10;predicted, what the portfolio&amp;rsquo;s composition delivered and what execution cost. When I finally ran those reports across the live history in September&#10;2026, every period I analyzed had run without a fixed price time, so the execution&#10;numbers came with a caveat that they couldn&amp;rsquo;t really be read as execution at all. The&#10;measurement that would have priced the leak simply didn&amp;rsquo;t exist yet.&lt;/p&gt;&#10;&lt;h2 id="the-leak-the-llms-couldnt-find"&gt;The Leak the LLMs Couldn&amp;rsquo;t Find&lt;/h2&gt;&#10;&lt;p&gt;Survivorship was actually the second leak to reach production. The first was subtler,&#10;and it&amp;rsquo;s where most of the testing in the rest of this post came from.&lt;/p&gt;&#10;&lt;p&gt;In March 2025, while analyzing the returns of a newer model, I started to worry that&#10;there was leakage in the older one. So, I compared two ways of training the same model.&#10;One trained incrementally, one rebalancing period at a time, exactly as it would run&#10;live and exactly as walk-forward requires. The other trained over the whole period at once. Given the same data and the&#10;same settings, they should have produced the same results, but the annualized returns&#10;were different.&lt;/p&gt;&#10;&lt;p&gt;I asked the LLMs I was working with to explain the difference and they couldn&amp;rsquo;t. The&#10;pull request I opened to debug it says so directly: &amp;ldquo;The LLMs appear to be unable to&#10;determine why the annualized returns for leakage_check.py and train_and_predict.py&#10;differ. Let&amp;rsquo;s roll up our sleeves and figure this out.&amp;rdquo;&lt;/p&gt;&#10;&lt;p&gt;Rolling up my sleeves meant writing a logger. I logged every call to &lt;code&gt;fit()&lt;/code&gt; and&#10;&lt;code&gt;predict()&lt;/code&gt;, along with the shape and columns of every input, to a JSON file without&#10;timestamps, ran both paths and diffed the files. Then I shortened the run until I had&#10;the smallest period that still showed a difference and stepped through it in the&#10;debugger.&lt;/p&gt;&#10;&lt;p&gt;The difference was in the shape of the training matrix. Which columns it had, and which&#10;rows survived a filter for missing values, depended on the data loaded for the whole&#10;run rather than just the training window. When training all at once, the model for an&#10;early period was shaped by companies whose data only began years later. When training&#10;incrementally, it didn&amp;rsquo;t know those companies existed yet. Neither path was reading a&#10;future price directly, but the future was deciding what the training data looked like,&#10;and that turned out to be enough to change the results.&lt;/p&gt;&#10;&lt;p&gt;The comparison that found the leak became a test: train incrementally on part of the&#10;data, continue on the rest, and the picks must be bit-for-bit identical to training on&#10;all of it from scratch. It started as a script that I ran by hand, then became the code&#10;path that production uses to train, and it now runs every week in the automated test&#10;suite. If&#10;I could only keep one test from this whole project, it would be this one.&lt;/p&gt;&#10;&lt;h2 id="the-fix-that-switched-off-most-of-the-model"&gt;The Fix That Switched Off Most of the Model&lt;/h2&gt;&#10;&lt;p&gt;Unfortunately, the March 2025 refactor fixed the leak and broke something else, and&#10;nothing noticed for thirteen months.&lt;/p&gt;&#10;&lt;p&gt;Near the end of the data-preparation step, the refactored code selected the training&#10;columns by matching column names against a list of ticker symbols. The raw return&#10;column for each stock is named after its ticker, so it matched. However, the engineered&#10;features (moving averages and volatility measures built from those returns) have the&#10;ticker as a prefix rather than as the whole name, so they didn&amp;rsquo;t match and were&#10;dropped. A later step, which lines up every iteration&amp;rsquo;s columns with the model&amp;rsquo;s&#10;expected list of features, then quietly put them back, filled with zeros.&lt;/p&gt;&#10;&lt;p&gt;The model ran, the tests passed and the features were all present in the matrix. Every&#10;one of them was zero, and that version went into production along with the leak fix.&lt;/p&gt;&#10;&lt;p&gt;It finally came out in April 2026, while an LLM and I were wiring a new family of&#10;features into the training pipeline. Earlier that same day we had found a different&#10;silent drop, where three new settings were decoded from the model&amp;rsquo;s configuration and&#10;then never passed to the model. When we looked at how much the trained models relied on each&#10;input (their feature importance), every engineered feature had an importance of&#10;exactly zero.&lt;/p&gt;&#10;&lt;p&gt;The fix went in behind a flag so that it could be compared with the old behaviour, and&#10;a week later it became the default. If you read my last post, you may recall a change&#10;that did nothing, where two runs came back bit-for-bit identical. This is the same&#10;smell from a different angle, and I now treat an exact zero with the same suspicion as&#10;an exact match.&lt;/p&gt;&#10;&lt;h2 id="corporate-actions-keep-coming-back"&gt;Corporate Actions Keep Coming Back&lt;/h2&gt;&#10;&lt;p&gt;Most of the leaks I found in 2026 came from the same place: splits, reverse splits and&#10;dividends, which are known as corporate actions.&lt;/p&gt;&#10;&lt;p&gt;A quick primer, since everything below depends on it. A raw price is what a stock&#10;actually traded at on a given day. When a company does a 20-for-1 split, every share&#10;becomes 20 shares worth a twentieth as much, so the raw price drops 95% overnight even&#10;though no one lost a cent. A reverse split is the opposite: 200 shares become one, and&#10;the raw price jumps 200-fold. An adjusted price rewrites the history before each event&#10;so that the series is continuous, e.g. by dividing every price before a 20-for-1 split&#10;by 20, and it does something similar for dividends. Neither series is wrong. They&#10;answer different questions, and the bugs below all came from asking one series a&#10;question meant for the other.&lt;/p&gt;&#10;&lt;p&gt;I told the stock-dividend story in&#10;&lt;a href="https://redgeoff.com/posts/ai-agents-test-gate/"&gt;my last post&lt;/a&gt;, where a test asserted&#10;a data convention that never occurs in practice. Here are three more.&lt;/p&gt;&#10;&lt;h3 id="the-model-learned-to-short-stock-splits"&gt;The Model Learned to Short Stock Splits&lt;/h3&gt;&#10;&lt;p&gt;In a long/short research line, which buys some stocks and bets against others by&#10;shorting them, so that it profits when they fall, one data source&amp;rsquo;s features were&#10;computed from raw prices rather than adjusted ones. So, when Amazon split 20:1 in&#10;June 2022, the raw series showed a one-day move of about −95%. Alphabet&amp;rsquo;s 20:1 split about&#10;six weeks later looked the same, and Tesla&amp;rsquo;s 3:1 split read as −67%.&lt;/p&gt;&#10;&lt;p&gt;A −95% day dominates any ranking of stocks against each other, and the model&amp;rsquo;s&#10;momentum-style features, which look at how much a stock has risen or fallen recently,&#10;carried it for one to three months afterwards. The model learned that stocks with that&#10;shape made good shorts, but they weren&amp;rsquo;t failing companies. They had just split.&lt;/p&gt;&#10;&lt;p&gt;A Copilot agent found it while comparing the same strategy across two data vendors. One&#10;vendor&amp;rsquo;s prices came pre-adjusted, the other&amp;rsquo;s feature panel had been built from raw&#10;prices, and the two short sides disagreed by far more than data noise should explain.&#10;A simple count shows the problem: in a 2019 sample, 9.7% of the symbols in the raw panel&#10;had a single-day move bigger than 50%, against 6.1% in the other vendor&amp;rsquo;s adjusted data.&lt;/p&gt;&#10;&lt;p&gt;What I find interesting is why raw prices had been chosen in the first place. Adjusted&#10;prices rewrite the past, e.g. a 2022 split changes the adjusted price for 2018. That&#10;looks exactly like lookahead, so raw prices seemed like the safe choice. However, these&#10;features were ratios: a price today divided by a price from some days ago. For any&#10;window that doesn&amp;rsquo;t straddle a split, the split factor cancels out and raw and adjusted&#10;prices give identical values. For a window that does straddle a split, the adjusted&#10;ratio is the true economic return and the raw one is an artifact. In other words, the&#10;thing that looked like lookahead wasn&amp;rsquo;t, and the thing chosen to avoid it was the bug.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/trading-model-leakage/amzn-split-raw-vs-adjusted.png"&#10;&#9;&#9;&#9;alt="Two-panel chart of Amazon from March to September 2022. Top: the raw price falls from about $2,450 to about $125 on the June 6 split, while the adjusted price runs straight through. Bottom: the one-month return computed from raw prices drops to about minus 95 percent for 21 trading days after the split; computed from adjusted prices it stays within its normal range, and the two lines are identical outside those 21 days."&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Amazon&amp;rsquo;s 2022 split as raw and adjusted prices. The raw one-month return reads as a 95% crash for a month: the shape the model learned to treat as a good short.&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;h3 id="ratios-want-adjusted-levels-want-raw"&gt;Ratios Want Adjusted, Levels Want Raw&lt;/h3&gt;&#10;&lt;p&gt;The mirror image turned up about a month later. The same research line had a&#10;minimum-price filter so that it wouldn&amp;rsquo;t trade anything under $10, and it was being&#10;applied to adjusted prices.&lt;/p&gt;&#10;&lt;p&gt;After a large reverse split, adjustment inflates a stock&amp;rsquo;s earlier history. A shell&#10;company that really traded at a fraction of a cent, and then did a reverse split of a&#10;few hundred to one, ends up with an adjusted history of tens to thousands of dollars.&#10;So, those names passed the $10 floor, were trained on and were shorted, and the backtest&#10;booked wins on positions that nobody could have actually held. Claude found this one&#10;while investigating short-side results that still looked wrong after the earlier fixes.&#10;Gating on the raw price instead took the number of candidate instances priced under&#10;$10 from 11,194 to zero.&lt;/p&gt;&#10;&lt;p&gt;The rule I took from the pair is that a ratio wants adjusted prices, because it&amp;rsquo;s asking&#10;how much a holding changed, while a price level wants raw prices, because it&amp;rsquo;s asking&#10;what the stock actually traded at. Neither series is a safe default.&lt;/p&gt;&#10;&lt;p&gt;After four layers of price corruption had been removed from that research line, its log&#10;summed it up plainly: the earlier positive results had been &amp;ldquo;largely the corruption we&#10;removed.&amp;rdquo;&lt;/p&gt;&#10;&lt;h3 id="the-screen-in-front-of-everything-was-leaking"&gt;The Screen in Front of Everything Was Leaking&lt;/h3&gt;&#10;&lt;p&gt;This last one is the one I find most instructive, because it was in the tool I use to&#10;decide what is worth testing in the first place.&lt;/p&gt;&#10;&lt;p&gt;I screen new signal ideas (candidate inputs for the model) with a quick check that&#10;runs in seconds, before committing to a hyperparameter search that runs for days. It&#10;asks a simple question: on each past date, did ranking stocks by this signal put them&#10;in roughly the right order of what they did next? In September 2026, an agent&#10;using that screen to check a textbook momentum signal for another project found that&#10;the screen itself had two problems.&lt;/p&gt;&#10;&lt;p&gt;The first was that it loaded prices through a function whose &lt;code&gt;adjusted&lt;/code&gt; flag defaults to&#10;&lt;code&gt;False&lt;/code&gt;, and which returns its price column under the key &lt;code&gt;&amp;quot;Adj Close&amp;quot;&lt;/code&gt; either way. The&#10;variable was named for adjusted prices, the column was named for adjusted prices and&#10;every statistic downstream said adjusted, but the data was raw.&lt;/p&gt;&#10;&lt;p&gt;The second was that it built its universe once, for the whole run, from every company&#10;that had ever been in the index. On the dates checked, 42 to 47% of the names it was&#10;ranking on a given date weren&amp;rsquo;t index members on that date. Some joined later, which is&#10;lookahead, since companies are added to the index because they grew. Others had left&#10;years before.&lt;/p&gt;&#10;&lt;p&gt;The tell had been sitting in the documentation for months, as a statistic with a note&#10;beside it saying &amp;ldquo;investigate&amp;rdquo;. It traced back to Chesapeake&amp;rsquo;s 1-for-200 reverse split&#10;in April 2020, the same reverse split from the survivorship investigation a year&#10;earlier. Here, raw prices turned it into a gain of more than 10,000%, and it was counted&#10;twice because the universe carried both the old and the new ticker. The problem also&#10;generalizes. Reverse splits tend to happen to companies in trouble, and companies in&#10;trouble are exactly what sits at the bottom of a ranking by recent performance, so any&#10;signal that sorts on losers is contaminated in the same direction.&lt;/p&gt;&#10;&lt;p&gt;Fortunately, the decisions I had made with that screen were based on rank correlation,&#10;which only asks whether the stocks came out in the right order, not by how much, so a&#10;few enormous outliers barely move it. When the agent recomputed a signal on&#10;corrected data, its rank correlation hardly changed, so those decisions probably still&#10;stand. Two other columns in the screen&amp;rsquo;s output didn&amp;rsquo;t survive. A leak doesn&amp;rsquo;t always&#10;invalidate everything it touches, and most of the work is finding out which numbers it&#10;actually reached.&lt;/p&gt;&#10;&lt;h2 id="time-has-two-dates"&gt;Time Has Two Dates&lt;/h2&gt;&#10;&lt;p&gt;Point-in-time doesn&amp;rsquo;t just mean the date on the row.&lt;/p&gt;&#10;&lt;p&gt;Some data describes a period but only becomes public later. One data vendor only keys&#10;its quarterly company financials by the end of the quarter they describe. A&#10;company&amp;rsquo;s December quarter isn&amp;rsquo;t public until its annual report is filed, around the&#10;start of March for the largest companies, and the vendor then needs time to pick it up.&#10;A reporting lag of 45 trading days after the period end made those numbers &amp;ldquo;known&amp;rdquo; a&#10;week or two before the vendor would actually have had them. That setting had already stood&#10;out in my hyperparameter search results, and the vendor&amp;rsquo;s own documentation settled it, as the&#10;period end is the only date it provides. So, I removed the option.&lt;/p&gt;&#10;&lt;p&gt;EDGAR, the SEC&amp;rsquo;s filing database, does record the filing date, so when I later needed historical share&#10;counts, I keyed them on the date they were filed, typically about five weeks after the&#10;quarter they describe. Keying on the period end would have used numbers that nobody&#10;could have known yet.&lt;/p&gt;&#10;&lt;p&gt;One vendor field had no code bug at all. An earnings-surprise figure (how far a company&amp;rsquo;s reported&#10;earnings beat or missed what analysts expected) was being computed against the&#10;analysts&amp;rsquo; average forecast as it stood after later revisions, rather than as it&#10;stood on the day the earnings came out. The fix was to rebuild it from point-in-time&#10;estimate snapshots, which meant dropping every year before the snapshots begin and&#10;about a fifth of the symbols, which had no snapshots at all. Sometimes an honest fix&#10;makes your dataset smaller.&lt;/p&gt;&#10;&lt;h2 id="the-false-alarms"&gt;The False Alarms&lt;/h2&gt;&#10;&lt;p&gt;Leakage can also go wrong in the other direction.&lt;/p&gt;&#10;&lt;p&gt;In August 2025, in the middle of the survivorship work, two issues drafted with an AI&amp;rsquo;s&#10;help claimed there was lookahead in my volume filter and in how the code decided which&#10;symbols were available. Both read convincingly. When I traced the dates, though, the&#10;rebalancing decision happens at the end of the window the code was reading, so all of&#10;that data was already known. My reply on the first one starts, &amp;ldquo;Actually, I believe we&#10;DO NOT have a lookahead bias.&amp;rdquo;&lt;/p&gt;&#10;&lt;p&gt;A year later, an agent flagged code that was, in its words, &amp;ldquo;target-leakage-shaped&amp;rdquo;:&#10;the training target also appears among the input features. It does, and at a glance it&#10;looks alarming. However, tracing every date boundary showed no lookahead, as the&#10;training window ends where the test window begins and returns are measured forward from&#10;the pick date. The agent&amp;rsquo;s follow-up claim, that the model must therefore be a simple&#10;momentum ranker, didn&amp;rsquo;t survive a measurement either. That&amp;rsquo;s two confident answers,&#10;each corrected by measuring rather than arguing.&lt;/p&gt;&#10;&lt;p&gt;The repo&amp;rsquo;s instruction file for agents now says that any leakage claim must name the&#10;specific date boundary that it violates and cite the code path, and those boundaries&#10;are pinned by tests named &lt;code&gt;test_no_lookahead_*&lt;/code&gt;.&lt;/p&gt;&#10;&lt;h2 id="who-found-what"&gt;Who Found What?&lt;/h2&gt;&#10;&lt;p&gt;When I went back through the issues and pull requests to write this post, the pattern&#10;was clearer than I&amp;rsquo;d remembered. I found the early leaks, the ones that reached&#10;production, from numbers that were too good or didn&amp;rsquo;t match: an impossible year and two&#10;training paths that disagreed. The 2026 leaks were mostly found by agents, and almost&#10;never while they were looking for leaks. They turned up while we were wiring new&#10;features, building a new test harness, comparing two data vendors or checking a rebuilt&#10;index against the real one, with me reviewing what came back. The agents also raised most of the false alarms.&lt;/p&gt;&#10;&lt;p&gt;The tests mostly arrived after the leak that justified them. I don&amp;rsquo;t think that&amp;rsquo;s a&#10;failure. A test written before you&amp;rsquo;ve been bitten is a guess about where the bugs are,&#10;whereas a test written afterwards is a record of where they were.&lt;/p&gt;&#10;&lt;h2 id="making-leaks-show-up-as-numbers"&gt;Making Leaks Show Up as Numbers&lt;/h2&gt;&#10;&lt;p&gt;Here&amp;rsquo;s what actually caught things, roughly in the order that I&amp;rsquo;d add them to a new&#10;project:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Invariance.&lt;/strong&gt; A result for a past window must not depend on the future. Training&#10;incrementally must match training all at once, and moving a run&amp;rsquo;s end date must not&#10;change an earlier window. The March 2025 leak broke exactly this, and so did a&#10;similar one I found later in a different research line, where extending a run&amp;rsquo;s end&#10;date changed results for years that were already over.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Counts of the impossible.&lt;/strong&gt; Single-day moves beyond 50%, prices under a cent passing&#10;a $10 filter, feature importance of exactly zero, or a year of returns that no&#10;strategy has ever produced. Several of the leaks in this post would have shown up in a&#10;count like that well before they showed up in a result.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Point-in-time by construction.&lt;/strong&gt; Resolve index membership per date, from the best&#10;source you can find, and key vendor fields on when they became knowable, not on the&#10;period they describe.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Compare against something real.&lt;/strong&gt; A copy of an index fund that beats the fund, or&#10;two vendors that disagree, is a leak or a data bug until proven otherwise.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;A pure-noise null.&lt;/strong&gt; Run the whole pipeline on random-walk prices, where there&amp;rsquo;s no&#10;signal by construction. If it finds one, it leaks. In one research line, after four&#10;leak fixes, the null finally sat on zero, and that was the result I trusted most.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Replication on independent data.&lt;/strong&gt; When an agent hands you an encouraging&#10;statistic, rerun it on data it hasn&amp;rsquo;t seen before you believe it.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;hr&gt;&#10;&lt;p&gt;&lt;em&gt;This describes software I run on my own accounts. It is not investment advice and I&#10;do not offer investment services.&lt;/em&gt;&lt;/p&gt;&#10;</description></item><item><title>I let AI agents write most of a live-trading codebase. Here's the gate that made it safe.</title><link>https://redgeoff.com/posts/ai-agents-test-gate/</link><pubDate>Mon, 28 Sep 2026 16:50:00 -0700</pubDate><guid>https://redgeoff.com/posts/ai-agents-test-gate/</guid><description>&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/ai-agents-test-gate/railroad-crossing.jpg"&#10;&#9;&#9;&#9;alt="A lowered railroad crossing barrier with glowing red lights across a wet road at dusk, a train blurring past behind it"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Image credit: AI-generated&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;For the last 21 months I have been the only engineer on TopSet, a system that trades&#10;real money. It picks a portfolio with machine-learning models, rebalances on a&#10;schedule, and executes through live broker APIs on infrastructure I own end to end.&#10;It is my own capital, not anyone else&amp;rsquo;s.&lt;/p&gt;&#10;&lt;p&gt;Agents wrote most of the code. Claude Code, Cursor, Copilot. About 1,600 pull&#10;requests in those 21 months, which is more than I could have typed in three times&#10;the span.&lt;/p&gt;&#10;&lt;p&gt;That leaves you with a choice everyone adopting these tools runs into. You can read&#10;every line the agent produces, in which case you have bought yourself a slower&#10;version of writing it yourself. Or you can skim, merge, and move fast, in which case&#10;you accumulate code that looks right and isn&amp;rsquo;t. On a system that places orders with&#10;real money, the second option ends with trades you didn&amp;rsquo;t intend.&lt;/p&gt;&#10;&lt;p&gt;I took a third option: make something other than my attention the thing that decides&#10;what ships. Tests became the gate. Nothing merges or deploys without a green build,&#10;and my own reading moved up a level, to design, to test coverage, and to the handful&#10;of results I refuse to let change quietly.&lt;/p&gt;&#10;&lt;p&gt;That worked. It also failed in ways I did not expect, and the failures are the&#10;interesting part, so I&amp;rsquo;ll spend most of this post there.&lt;/p&gt;&#10;&lt;h2 id="what-i-actually-read"&gt;What I actually read&lt;/h2&gt;&#10;&lt;p&gt;&amp;ldquo;Tests are the gate&amp;rdquo; is not the same as &amp;ldquo;I stopped looking.&amp;rdquo; What changed is where I&#10;spend attention.&lt;/p&gt;&#10;&lt;p&gt;Most pull requests get a light review. Research code I skim. Changes to the parts&#10;that decide what to buy or how to trade it, the model training and the execution&#10;paths, I read properly, looking for whether the approach is reasonable rather than&#10;whether every line is correct. The tests cover the lines. I cover the idea.&lt;/p&gt;&#10;&lt;p&gt;Then there is one rule I do not bend. If a change moves a snapshot, or shifts the&#10;result of an end-to-end test, that is a stop. Snapshots and integration results are&#10;supposed to be stable, so a diff in one of them means either the change did something&#10;I didn&amp;rsquo;t intend, or it did something I did intend and I now owe an explanation. An&#10;agent will happily regenerate a snapshot to make a build go green. That is the single&#10;most dangerous thing these tools do, and it is why the snapshot files live in the&#10;repo where a diff is visible in review.&lt;/p&gt;&#10;&lt;p&gt;For anything involving a calculation I have not verified before, the tests are not&#10;enough either. I have the agent write a report script, and there are now 161 of them,&#10;that dumps the pipeline to CSV. I open the CSV in a spreadsheet and check the&#10;arithmetic by hand: return per trade, slippage, annualized return. Then I spot-check&#10;inputs, picking a few prices and going back to the raw data directory to confirm&#10;those were the prices the system actually used.&lt;/p&gt;&#10;&lt;p&gt;It is slow and it is not clever, and it has caught things nothing else would. A test&#10;asserts that a function returns what the author believed it should return. A&#10;spreadsheet asks whether the number is true.&lt;/p&gt;&#10;&lt;h2 id="what-the-gate-is"&gt;What the gate is&lt;/h2&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/ai-agents-test-gate/test-gate.png"&#10;&#9;&#9;&#9;alt="Diagram of the test gate: every agent pull request runs lint, types, about 7,500 tests, an empty-database check, a migration round trip and terraform validate before it can merge and deploy. A weekly suite runs end-to-end rebalancings, a 100-run flake hunt, training snapshots and leakage checks against main. Production errors page me on Discord, and every fix comes back through the same gate. Underneath: what I still check myself."&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Every pull request runs:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;lint (ruff) and type checking (mypy)&lt;/li&gt;&#10;&lt;li&gt;about 7,500 tests across 331 test modules&lt;/li&gt;&#10;&lt;li&gt;an assertion that the test database is empty afterward&lt;/li&gt;&#10;&lt;li&gt;a migration rollback-and-reapply, all the way to base and back&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;terraform validate&lt;/code&gt; against both AWS accounts&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;If any of that fails, nothing merges. The build is the merge gate, and it is not&#10;advisory.&lt;/p&gt;&#10;&lt;p&gt;Once a week, on a schedule, a heavier set runs:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;two end-to-end rebalancing runs against a mock broker&lt;/li&gt;&#10;&lt;li&gt;the same integration test 100 times in a row, looking for flakes&lt;/li&gt;&#10;&lt;li&gt;nine deterministic model-training snapshot runs, pinned inside Docker&lt;/li&gt;&#10;&lt;li&gt;two leakage checks on the training pipeline&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Running the same integration test 100 times only makes sense because the test is not&#10;the same each time. The mock broker fills orders probabilistically: a 20% chance per&#10;10-second cycle, 70% of those complete and the rest partial at a random size, at&#10;random prices. Every iteration produces a different interleaving of fills, partial&#10;fills and timing. A hundred runs is a search for race conditions, not a hundred&#10;repeats of one path.&lt;/p&gt;&#10;&lt;p&gt;What it finds are real concurrency bugs wearing flaky-test costumes. Worker threads&#10;leaking between tests. A queued task waking up after teardown had already deleted the&#10;row it wanted. A data loader that wasn&amp;rsquo;t thread-safe and corrupted a shared index&#10;under load. Each one showed up first as &amp;ldquo;that test fails sometimes.&amp;rdquo;&lt;/p&gt;&#10;&lt;p&gt;The weekly set exists because a flaky gate is worse than no gate. If a test fails once&#10;every thirty runs, and twenty agent PRs land in a week, that one test goes red most&#10;weeks. Have a few like it and something red is always on screen, and you start&#10;ignoring red. Hunting flakes is not hygiene here. It is what&#10;keeps the gate meaningful.&lt;/p&gt;&#10;&lt;h2 id="the-tests-that-matter-are-about-money-not-functions"&gt;The tests that matter are about money, not functions&lt;/h2&gt;&#10;&lt;p&gt;Unit tests are cheap and agents write them happily. They are not what I trust.&lt;/p&gt;&#10;&lt;p&gt;What I trust are end-to-end tests that drive a full rebalancing against a mock&#10;broker, with the awkward parts turned on. They&amp;rsquo;re named after the ways money goes&#10;wrong:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;orders that have to be resubmitted&lt;/li&gt;&#10;&lt;li&gt;a rebalancing that resumes from the middle, with partial fills already on the books&lt;/li&gt;&#10;&lt;li&gt;a resume that has both buys and sells outstanding&lt;/li&gt;&#10;&lt;li&gt;smart orders cancelled mid-flight, on the sell side and then the buy side&lt;/li&gt;&#10;&lt;li&gt;deposits and repeating withdrawals arriving while a rebalancing is in progress&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;These are exactly the control flows a language model gets plausibly wrong.&#10;Interrupted work, partial state, money arriving in the middle of an operation: the&#10;code for each of these reads fine in isolation. It&amp;rsquo;s the interaction that bites.&lt;/p&gt;&#10;&lt;p&gt;Here is one that made it past review and got caught by the constraint at the bottom&#10;of the stack. When the last pending buy of a rebalancing had its amount adjusted for&#10;pending withdrawals, the adjusted value could land on exactly zero. The existing&#10;guard raised an error on a negative amount, but zero isn&amp;rsquo;t negative. The&#10;affordability check, &lt;code&gt;available_cash &amp;gt;= amount&lt;/code&gt;, was &lt;code&gt;0 &amp;gt;= 0&lt;/code&gt;, so it passed. The&#10;order was promoted, and the database rejected it: &lt;code&gt;amount &amp;gt; 0&lt;/code&gt; is a check constraint&#10;on that table.&lt;/p&gt;&#10;&lt;p&gt;The interesting part is what that did to the retry. The integrity error fired inside&#10;the same transaction that carried the completing order&amp;rsquo;s status update, so the&#10;rollback discarded that too. The scheduler came back around, found identical inputs,&#10;and did it again. Not a self-healing retry: a stuck rebalancing.&lt;/p&gt;&#10;&lt;p&gt;The fix took three attempts, and the second one is the useful story. Gating the&#10;promotion on &lt;code&gt;amount &amp;gt; 0&lt;/code&gt; looks correct and does nothing, because the code had&#10;already assigned the adjusted value to a live ORM object. That marked it dirty, and&#10;the next autoflush in the same transaction persisted the zero regardless of whether&#10;the order was ever promoted. The condition guarded the decision, not the mutation.&lt;/p&gt;&#10;&lt;p&gt;I only know that because the failure was reproduced live against &lt;code&gt;main&lt;/code&gt; before any&#10;fix was written, and the first fix was re-run against that same repro rather than&#10;checked by reading the code. When an agent is writing the patch, &amp;ldquo;I read it and it&#10;looks right&amp;rdquo; is the single least reliable signal available to you.&lt;/p&gt;&#10;&lt;h2 id="three-ways-a-green-build-lied-to-me"&gt;Three ways a green build lied to me&lt;/h2&gt;&#10;&lt;p&gt;This is the part I&amp;rsquo;d want to read, so here are the three worst cases, all real, all&#10;from this codebase.&lt;/p&gt;&#10;&lt;h3 id="1-the-test-asserted-a-convention-that-does-not-exist"&gt;1. The test asserted a convention that does not exist&lt;/h3&gt;&#10;&lt;p&gt;Corporate actions have to be applied to historical prices, and stock dividends are&#10;the fiddly ones. My broker&amp;rsquo;s API returns a &lt;code&gt;rate&lt;/code&gt; for each. The code treated that&#10;rate as a bare fraction: a 5% stock dividend arrives as &lt;code&gt;0.05&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;It isn&amp;rsquo;t. It&amp;rsquo;s the ratio of new shares to old. Every one of the 101 stock-dividend&#10;records across 43 symbols in my data has a rate of at least 1.0016. Not one is a&#10;bare fraction. The convention the code assumed does not occur in the data at all.&lt;/p&gt;&#10;&lt;p&gt;The unit test passed, because the test asserted the same wrong convention with a&#10;fixture value of &lt;code&gt;rate: 0.05&lt;/code&gt;, a number that cannot appear in production. The test&#10;and the code were wrong in exactly the same way, which is what you get when both are&#10;written in the same sitting from the same misunderstanding.&lt;/p&gt;&#10;&lt;p&gt;The effect was that each stock dividend roughly halved the adjusted price, and it&#10;compounded across events. One symbol with nine of them came out deflated by about&#10;490×, turning a real price history into a fake ramp of more than a thousand-fold&#10;over the period. And because every price stayed between a few cents and a few&#10;hundred dollars, a check for implausible price levels would not have flagged it&#10;either.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/ai-agents-test-gate/scco-stock-dividends.png"&#10;&#9;&#9;&#9;alt="Log-scale chart of Southern Copper (SCCO) adjusted close from 2016 to 2026. The fixed series runs from about $15 to $200. The buggy series starts about 490 times lower, near 3 cents, and steps up at each of nine stock-dividend ex-dates in 2024 to 2026 until it meets the fixed series."&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Southern Copper&amp;rsquo;s real daily prices, adjusted twice by the same code. The only difference is whether the stock-dividend factor is 1 / rate or 1 / (1 + rate).&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;The fix was a corrected fixture, plus a regression test that runs a nine-event&#10;sequence and asserts the cumulative adjustment factor stays sane. The lesson I took:&#10;a fixture is a claim about the world, and nobody reviews fixtures.&lt;/p&gt;&#10;&lt;h3 id="2-the-test-passed-on-nan"&gt;2. The test passed on NaN&lt;/h3&gt;&#10;&lt;p&gt;In a research tool, an agent added a second way to select a model configuration and&#10;reported the result in the PR: 100% agreement with the existing selector, at every&#10;sample size tried.&lt;/p&gt;&#10;&lt;p&gt;That number was not real. The tool built its pool from a source that was missing the&#10;columns the default scoring version needs, so every score came out NaN. With all&#10;scores NaN, the selector fell through to a fixed tie-break order, which of course&#10;agrees with itself 100% of the time at every sample size.&lt;/p&gt;&#10;&lt;p&gt;The test that was supposed to cover this had been passing on those same NaN scores&#10;for as long as it had existed. It never checked anything. Green, meaningless, and&#10;quoted in a PR description as a finding.&lt;/p&gt;&#10;&lt;p&gt;The follow-up PR made the condition a hard error rather than a silent NaN, pinned&#10;the test to a scoring version where the columns exist, and added guards. It also&#10;flagged that an older result produced the same way might carry the same artifact.&#10;That is the right instinct: when you find a lie, go and check what else it told you.&lt;/p&gt;&#10;&lt;h3 id="3-the-change-did-nothing-at-all"&gt;3. The change did nothing at all&lt;/h3&gt;&#10;&lt;p&gt;An agent wired a new training target through what looked like the right code path.&#10;The first screen ran clean and produced numbers bit-for-bit identical to the&#10;baseline. Every metric, to four decimal places.&lt;/p&gt;&#10;&lt;p&gt;Identical results are not a pass. They&amp;rsquo;re a smell. Two different configurations&#10;producing byte-equal output usually means one of them didn&amp;rsquo;t happen.&lt;/p&gt;&#10;&lt;p&gt;It hadn&amp;rsquo;t. The runner always sets a parameter that routes execution to a completely&#10;separate function, which builds its own training target and never looked at the new&#10;field. Unpickling both runs and diffing the actual selections showed 238 of 238&#10;identical picks for the test year. After the real fix, 0 of 238 were identical, and&#10;a regression test now asserts the two modes produce different output.&lt;/p&gt;&#10;&lt;p&gt;Nothing failed. The compute was spent. The result would have gone into the research&#10;log as &amp;ldquo;no effect,&amp;rdquo; which is a conclusion, and it would have been wrong.&lt;/p&gt;&#10;&lt;h3 id="the-shape-of-all-three"&gt;The shape of all three&lt;/h3&gt;&#10;&lt;p&gt;In every case the build was green, and the number was wrong. Tests prove that the&#10;code does what the tests say. They say nothing about whether what the tests say is&#10;true, whether the code under test ran at all, or whether the data going in is what&#10;you think.&lt;/p&gt;&#10;&lt;p&gt;That is the agent-era failure mode, and it is worse with agents than without them,&#10;for a simple reason: an agent writes the code and the test together, from one&#10;reading of the problem. If the reading is wrong, both artifacts are wrong, and they&#10;agree. A human pair would at least have had two readings.&lt;/p&gt;&#10;&lt;p&gt;What I do about it now, concretely:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Distrust results that are identical, unchanged, or perfect. Check the output, not&#10;the exit code.&lt;/li&gt;&#10;&lt;li&gt;Treat fixtures as claims. If a fixture value can&amp;rsquo;t occur in production, that&amp;rsquo;s a&#10;bug in the test.&lt;/li&gt;&#10;&lt;li&gt;Make impossible states loud. NaN, missing data, and empty inputs should raise, not&#10;flow through and produce a number.&lt;/li&gt;&#10;&lt;li&gt;When something is found to be wrong, go back and check what else that thing told&#10;you.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="the-reviewer-that-isnt-a-test"&gt;The reviewer that isn&amp;rsquo;t a test&lt;/h2&gt;&#10;&lt;p&gt;Some mistakes are not code-shaped and no test will ever catch them.&lt;/p&gt;&#10;&lt;p&gt;So I added a second kind of review: a deliberately skeptical reviewer persona, run&#10;against a finished result rather than a diff, with instructions to argue that the&#10;result is not real. I iterated on the persona with Claude until it stopped being&#10;agreeable.&lt;/p&gt;&#10;&lt;p&gt;It found things I am glad it found. That a configuration had been tuned by&#10;repeatedly checking results on data I had promised myself I would only look at once,&#10;so my holdout wasn&amp;rsquo;t a holdout any more. That a cost model was running with zero&#10;financing cost while the strategy it priced used nearly four times leverage.&lt;/p&gt;&#10;&lt;p&gt;Neither is a bug. Both are the kind of error that invalidates a conclusion, and no&#10;test suite in the world was going to tell me.&lt;/p&gt;&#10;&lt;h2 id="the-gate-after-the-gate"&gt;The gate after the gate&lt;/h2&gt;&#10;&lt;p&gt;CI stops caring the moment something merges. After that the system runs unattended,&#10;on a schedule, against a live broker, and the question changes from &amp;ldquo;is this code&#10;correct&amp;rdquo; to &amp;ldquo;is this thing behaving right now.&amp;rdquo;&lt;/p&gt;&#10;&lt;p&gt;Every ERROR the system logs in production becomes a CloudWatch alarm that posts to a&#10;Discord channel on my phone. That is the whole alerting stack, and for a one-person&#10;operation it is enough: I am not running a rotation, I just need to know.&lt;/p&gt;&#10;&lt;p&gt;The loop from there is the same one I use for everything else. Point an agent at the&#10;production logs, hand it a local copy of the database from around the failure, ask&#10;for a diagnosis and a fix with a test. The logs and the data are usually enough for&#10;it to find the problem, and the test it writes is the thing that stops the problem&#10;coming back. Several of the bugs in this post arrived that way rather than through&#10;CI.&lt;/p&gt;&#10;&lt;p&gt;Worth saying plainly: the tests did not catch those. The tests caught the next&#10;occurrence.&lt;/p&gt;&#10;&lt;h2 id="what-stays-human"&gt;What stays human&lt;/h2&gt;&#10;&lt;p&gt;Architecture. Test design. What &amp;ldquo;done&amp;rdquo; means. Merge judgment when the tests pass and&#10;something still feels off.&lt;/p&gt;&#10;&lt;p&gt;And one more thing that turned out to matter: the repo&amp;rsquo;s own instruction file. Every&#10;time an agent reached a wrong conclusion in a way another agent would repeat, I&#10;wrote the correction down there. One rule says that any claim of data leakage must&#10;name the specific date boundary it violates, because two different agents had&#10;asserted leakage from reasoning alone, and both were wrong. That file is now a list&#10;of the mistakes this codebase invites. It&amp;rsquo;s the closest thing I have to&#10;institutional memory for a team of one.&lt;/p&gt;&#10;&lt;h2 id="what-it-cost"&gt;What it cost&lt;/h2&gt;&#10;&lt;p&gt;The per-PR suite takes about ten minutes. That is the tax on every change, and it is&#10;the number I would defend hardest: ten minutes is short enough that I never route&#10;around it, and long enough to run tests that touch a database and a fake broker.&lt;/p&gt;&#10;&lt;p&gt;The machine bill is every one of the 3,000 GitHub Actions minutes my plan includes,&#10;plus roughly $20 a month for extra minutes on top. The agents themselves are Claude&#10;Pro and Cursor Pro, $20 each. I also paid $40 a month for Copilot until GitHub moved&#10;it to per-token billing, which would have raised my bill more than tenfold, so that&#10;work moved to Claude Code and Cursor. So the tools that wrote most of this codebase&#10;cost tens of dollars a month, like the compute spent checking them, and neither is&#10;what the project actually cost.&lt;/p&gt;&#10;&lt;p&gt;What it actually cost is fixtures and flakes. Writing a mock broker that fails in&#10;realistic ways, maintaining a self-contained dataset so model training can be tested&#10;at all, and then the long tail of chasing tests that fail once in thirty runs. None&#10;of that is glamorous and all of it is load-bearing. Every hour I skipped there came&#10;back later as an hour of not being able to trust a result.&lt;/p&gt;&#10;&lt;p&gt;And sometimes the gate itself is the thing that&amp;rsquo;s broken. The 100-run soak lives in a&#10;shell script rather than inline in CI, because the inline version ran under a POSIX&#10;shell where &lt;code&gt;{1..100}&lt;/code&gt; isn&amp;rsquo;t brace expansion. It was a literal string. The loop ran&#10;exactly once, and reported success, for longer than I&amp;rsquo;d like to admit.&lt;/p&gt;&#10;&lt;p&gt;The tests are not where I over-built. Every gate in this post exists because&#10;something broke, or nearly did, and the existing checks let it through. I would add&#10;all of them again.&lt;/p&gt;&#10;&lt;p&gt;Where I over-built is everything the tests had to cover.&lt;/p&gt;&#10;&lt;p&gt;I started TopSet expecting to turn it into a product, so I built it for customers who&#10;never existed. A bot lifecycle with eleven kinds of change request, each one a queued,&#10;locked, asynchronous workflow object, so that deposits and withdrawals and stops could&#10;be requested safely by someone who wasn&amp;rsquo;t me. Two broker integrations instead of one.&#10;A staging environment that mirrors production, behind a VPN. Cross-account backups&#10;with compliance-grade retention locks on a database that holds my own trades.&lt;/p&gt;&#10;&lt;p&gt;For one person running their own accounts, several of those could have been a script I&#10;ran by hand while watching it.&lt;/p&gt;&#10;&lt;p&gt;That is the part that compounds, and it is why I am putting it in a post about&#10;testing. Every one of those surfaces is something the gate has to cover. The change&#10;requests need end-to-end tests for money arriving mid-rebalance. Two brokers means two&#10;of everything, including the mocks. The staging environment is a second place for&#10;migrations to fail. I did not pay for the product shape once, in the building. I have&#10;paid for it every week since, in the size of the harness that has to keep it honest.&lt;/p&gt;&#10;&lt;p&gt;If I were starting again for a book of one, I would build the trading and the research&#10;properly and leave the product machinery until somebody asked for it.&lt;/p&gt;&#10;&lt;h2 id="if-youre-rolling-this-out-to-a-team"&gt;If you&amp;rsquo;re rolling this out to a team&lt;/h2&gt;&#10;&lt;p&gt;I&amp;rsquo;ve done this alone, with tests I wrote and one person&amp;rsquo;s judgment. That is not the&#10;same problem as a team of fifteen, and I&amp;rsquo;d be careful about anyone who tells you&#10;otherwise. But some of it transfers, and the ordering I&amp;rsquo;d insist on comes from a&#10;team I did run: at a healthcare company, requiring automated tests on every PR is&#10;what took active medium and high bugs down 72% and weekly hotfixes from 7 to 1.5.&#10;That was before agents. It&amp;rsquo;s the same first move.&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;strong&gt;Fix the gate before you raise throughput.&lt;/strong&gt; Coverage on the paths that lose&#10;money or data, and no tolerated flakes. Agents multiply whatever your CI already&#10;is.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;The author owns the change, whoever typed it.&lt;/strong&gt; &amp;ldquo;The agent wrote it&amp;rdquo; is not a&#10;review comment, and it is not a defense in a postmortem.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Review the result, not the diff.&lt;/strong&gt; For anything that produces a number, ask&#10;what would look different if the change had done nothing.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Measure escaped defects and hotfix rate.&lt;/strong&gt; Not lines, not PR counts. Agents&#10;make those metrics meaningless overnight.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;The part I would not change: the gate is what let one person move at this speed on a&#10;system where mistakes cost real money. The part I underestimated: a gate is a claim&#10;about correctness, and claims need checking too.&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;p&gt;&lt;em&gt;This describes software I run on my own accounts. It is not investment advice and I&#10;do not offer investment services.&lt;/em&gt;&lt;/p&gt;&#10;</description></item><item><title>Running MosaicML's MPT-7B, a ChatGPT Competitor, on AWS SageMaker</title><link>https://redgeoff.com/posts/mpt-7b-on-aws-sagemaker/</link><pubDate>Tue, 18 Jul 2023 08:48:01 -0700</pubDate><guid>https://redgeoff.com/posts/mpt-7b-on-aws-sagemaker/</guid><description>&lt;p&gt;In this blog post, I’m going to take you step-by-step through the process of running MosaicML’s ChatGPT competitor, MPT-7B, on your own AWS SageMaker instance.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/mpt-7b-on-aws-sagemaker/mpt-7b-sagemaker.png"&#10;&#9;&#9;&#9;alt="MPT-7B and AWS SageMaker"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;MPT-7B and AWS SageMaker. Image credit: &lt;a href="https://www.mosaicml.com/blog/mpt-30b"&gt;MosaicML&lt;/a&gt; and Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Are you excited about the capabilities of ChatGPT, but have concerns about exposing your sensitive data to OpenAI? Luckily, there are alternatives that you can run on your own infrastructure. One such alternative is MosaicML&amp;rsquo;s MPT-7b, a competitor to ChatGPT, which we&amp;rsquo;ll explore in this blog post.&lt;/p&gt;&#10;&lt;h3 id="introduction-to-mosaicml-and-mpt-7b"&gt;Introduction to MosaicML and MPT-7B&lt;/h3&gt;&#10;&lt;p&gt;MosaicML, recently &lt;a href="https://www.reuters.com/markets/deals/databricks-strikes-13-bln-deal-generative-ai-startup-mosaicml-2023-06-26"&gt;acquired by Databricks for $1.3 billion&lt;/a&gt;, has been making waves in the ML community with their MPT-7B model, a supposed competitor to ChatGPT. Despite its promise, running this model can be daunting due to sparse documentation and its heavy resource requirements. However, one can run MPT-7B on AWS SageMaker in a Jupyter notebook, an environment that is beginner-friendly and highly flexible for rapid iteration. This setup allows you to test the model’s feasibility and hardware requirements before deciding to move into production.&lt;/p&gt;&#10;&lt;h3 id="running-mpt-7b-on-aws-sagemaker"&gt;Running MPT-7B on AWS SageMaker&lt;/h3&gt;&#10;&lt;p&gt;Running MPT-7B in a Jupyter notebook on AWS SageMaker provides several benefits. Not only can you pay just for what you use and turn it off when you&amp;rsquo;re done, but the ability to easily rerun portions of your code without having to reload the model saves time during iterative development. But do beware! If you forget to stop your notebook instances, the charges can quickly add up.&lt;/p&gt;&#10;&lt;p&gt;While this method is relatively convenient, there are some considerations you must take into account. Firstly, loading the model can take up to 20 minutes even on a high-performance GPU, making this process somewhat time-consuming. Also, the cost is a factor to consider, as the running cost is at least $4 per hour. You&amp;rsquo;ll need to run MPT-7B on at least a &lt;em&gt;p3.2xlarge&lt;/em&gt; instance; anything smaller does not seem feasible. If you opt for EC2 instead of SageMaker, you&amp;rsquo;ll have to ask AWS for permission to use a &lt;em&gt;p3.2xlarge&lt;/em&gt; instance.&lt;/p&gt;&#10;&lt;p&gt;In the next sections I’ll take you step-by-step through how to run the MPT-7B model in your very own SageMaker jupyter notebook:&lt;/p&gt;&#10;&lt;h4 id="step-1---open-the-sagemaker-console"&gt;Step 1 - Open the SageMaker Console&lt;/h4&gt;&#10;&lt;p&gt;Fire up the AWS Console and search for SageMaker:&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/mpt-7b-on-aws-sagemaker/sage-maker.png"&#10;&#9;&#9;&#9;alt="Search for SageMaker"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Search for SageMaker. Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;h4 id="step-2---create-a-notebook-instance"&gt;Step 2 - Create a notebook instance&lt;/h4&gt;&#10;&lt;p&gt;From the left-side menu, select &lt;em&gt;Notebook-&amp;gt;Notebook instances&lt;/em&gt;:&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/mpt-7b-on-aws-sagemaker/notebook-instances.png"&#10;&#9;&#9;&#9;alt="Notebook instances"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Notebook instances. Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Click the &lt;em&gt;Create notebook instance&lt;/em&gt; button:&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/mpt-7b-on-aws-sagemaker/create-notebook-instance.png"&#10;&#9;&#9;&#9;alt="Create notebook instance"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Create notebook instance. Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Specify an instance name. Choose the instance type &lt;em&gt;m1.p3.2xlarge&lt;/em&gt;. Unfortunately, it seems that an instance as powerful as &lt;em&gt;m1.p3.2xlarge&lt;/em&gt; is required, or else your instance may run out of memory or take an excessive amount of time to respond to even the simplest questions. However, please note that this instance will cost approximately $4/hr, so it&amp;rsquo;s important to monitor your usage carefully.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/mpt-7b-on-aws-sagemaker/create-instance-2.png"&#10;&#9;&#9;&#9;alt="Specify notebook instance details"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Specify notebook instance details. Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Create a new IAM role:&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/mpt-7b-on-aws-sagemaker/create-role.png"&#10;&#9;&#9;&#9;alt="Create a new role"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Create a new role. Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;If your test environment doesn’t have any particularly sensitive data in it then you can grant access to &lt;em&gt;Any S3 bucket&lt;/em&gt;. Otherwise, you’ll need to be more explicit.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/mpt-7b-on-aws-sagemaker/create-role-s3.png"&#10;&#9;&#9;&#9;alt="Specify S3 access"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Specify S3 access. Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Click the &lt;em&gt;Create notebook instance&lt;/em&gt; button:&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/mpt-7b-on-aws-sagemaker/create-instance-3.png"&#10;&#9;&#9;&#9;alt="Create notebook instance"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Create notebook instance. Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;The notebook will then be in a &lt;em&gt;Pending&lt;/em&gt; status. This will likely last for about 10 minutes:&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/mpt-7b-on-aws-sagemaker/notebook-pending.png"&#10;&#9;&#9;&#9;alt="Pending notebook instance"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Pending notebook instance. Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;In the meantime, we’ll download a notebook so that we can upload it after the AWS SageMaker instance has finished provisioning.&lt;/p&gt;&#10;&lt;h4 id="step-3---download-the-notebook"&gt;Step 3 - Download the notebook&lt;/h4&gt;&#10;&lt;p&gt;Head on over to the notebook at &lt;a href="https://colab.research.google.com/drive/1kJr2LHHLKYkbnNutVYEkt2vrYsbO38aw"&gt;MPT-7B on AWS SageMaker.ipynb&lt;/a&gt; and download it:&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/mpt-7b-on-aws-sagemaker/colab-notebook.png"&#10;&#9;&#9;&#9;alt="The notebook on Google Colab"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;The notebook on Google Colab. Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/mpt-7b-on-aws-sagemaker/download-colab-notebook.png"&#10;&#9;&#9;&#9;alt="Download the notebook"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Download the notebook. Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;In this notebook you’ll notice two main code blocks. The first block loads the MPT-7B tokenizer and model:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; torch &lt;span style="color:#f92672"&gt;import&lt;/span&gt; cuda, bfloat16&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; transformers &lt;span style="color:#f92672"&gt;import&lt;/span&gt; AutoTokenizer, AutoModelForCausalLM, AutoConfig&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;device &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;cuda:&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;cuda&lt;span style="color:#f92672"&gt;.&lt;/span&gt;current_device()&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; cuda&lt;span style="color:#f92672"&gt;.&lt;/span&gt;is_available() &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;cpu&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;tokenizer &lt;span style="color:#f92672"&gt;=&lt;/span&gt; AutoTokenizer&lt;span style="color:#f92672"&gt;.&lt;/span&gt;from_pretrained(&lt;span style="color:#e6db74"&gt;&amp;#34;mosaicml/mpt-7b-chat&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; trust_remote_code&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;config&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{&lt;span style="color:#e6db74"&gt;&amp;#34;init_device&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;meta&amp;#34;&lt;/span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;model &lt;span style="color:#f92672"&gt;=&lt;/span&gt; AutoModelForCausalLM&lt;span style="color:#f92672"&gt;.&lt;/span&gt;from_pretrained(&lt;span style="color:#e6db74"&gt;&amp;#34;mosaicml/mpt-7b-chat&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; trust_remote_code&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; config&lt;span style="color:#f92672"&gt;=&lt;/span&gt;config,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; torch_dtype&lt;span style="color:#f92672"&gt;=&lt;/span&gt;bfloat16)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;print(&lt;span style="color:#e6db74"&gt;f&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;device=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;{&lt;/span&gt;device&lt;span style="color:#e6db74"&gt;}&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;print(&lt;span style="color:#e6db74"&gt;&amp;#39;model loaded&amp;#39;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The tokenizer is used to encode the question sent to the model and decode the response from the model. Additionally, we obtain the device specification for our GPU so that we can configure the model to utilize it later:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; time&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; IPython.display &lt;span style="color:#f92672"&gt;import&lt;/span&gt; Markdown&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; torch&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;from&lt;/span&gt; transformers &lt;span style="color:#f92672"&gt;import&lt;/span&gt; StoppingCriteria, StoppingCriteriaList&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# mtp-7b is trained to add &amp;#34;&amp;lt;|endoftext|&amp;gt;&amp;#34; at the end of generations&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;stop_token_ids &lt;span style="color:#f92672"&gt;=&lt;/span&gt; [tokenizer&lt;span style="color:#f92672"&gt;.&lt;/span&gt;eos_token_id]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Define custom stopping criteria object.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Source: https://github.com/pinecone-io/examples/blob/master/generation/llm-field-guide/mpt-7b/mpt-7b-huggingface-langchain.ipynb&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;StopOnTokens&lt;/span&gt;(StoppingCriteria):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;__call__&lt;/span&gt;(self, input_ids: torch&lt;span style="color:#f92672"&gt;.&lt;/span&gt;LongTensor,scores: torch&lt;span style="color:#f92672"&gt;.&lt;/span&gt;FloatTensor,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;**&lt;/span&gt;kwargs) &lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt; bool:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; stop_id &lt;span style="color:#f92672"&gt;in&lt;/span&gt; stop_token_ids:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; input_ids[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;][&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;] &lt;span style="color:#f92672"&gt;==&lt;/span&gt; stop_id:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;False&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;stopping_criteria &lt;span style="color:#f92672"&gt;=&lt;/span&gt; StoppingCriteriaList([StopOnTokens()])&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ask_question&lt;/span&gt;(question, max_length&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;100&lt;/span&gt;):&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; start_time &lt;span style="color:#f92672"&gt;=&lt;/span&gt; time&lt;span style="color:#f92672"&gt;.&lt;/span&gt;time()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Encode the question&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; input_ids &lt;span style="color:#f92672"&gt;=&lt;/span&gt; tokenizer&lt;span style="color:#f92672"&gt;.&lt;/span&gt;encode(question, return_tensors&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;pt&amp;#39;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Use the GPU&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; input_ids &lt;span style="color:#f92672"&gt;=&lt;/span&gt; input_ids&lt;span style="color:#f92672"&gt;.&lt;/span&gt;to(device)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Generate a response&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; output &lt;span style="color:#f92672"&gt;=&lt;/span&gt; model&lt;span style="color:#f92672"&gt;.&lt;/span&gt;generate(&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; input_ids,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; max_new_tokens&lt;span style="color:#f92672"&gt;=&lt;/span&gt;max_length,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; temperature&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;0.9&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; stopping_criteria&lt;span style="color:#f92672"&gt;=&lt;/span&gt;stopping_criteria&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;# Decode the response&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; response &lt;span style="color:#f92672"&gt;=&lt;/span&gt; tokenizer&lt;span style="color:#f92672"&gt;.&lt;/span&gt;decode(output[:, input_ids&lt;span style="color:#f92672"&gt;.&lt;/span&gt;shape[&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;]:][&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;],&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; skip_special_tokens&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;True&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; end_time &lt;span style="color:#f92672"&gt;=&lt;/span&gt; time&lt;span style="color:#f92672"&gt;.&lt;/span&gt;time()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; duration &lt;span style="color:#f92672"&gt;=&lt;/span&gt; end_time &lt;span style="color:#f92672"&gt;-&lt;/span&gt; start_time&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; display(Markdown(response))&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; print(&lt;span style="color:#e6db74"&gt;&amp;#34;Function duration:&amp;#34;&lt;/span&gt;, duration, &lt;span style="color:#e6db74"&gt;&amp;#34;seconds&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note the use of &lt;code&gt;stopping_critera&lt;/code&gt;, which is needed or else the model will just start babbling on, even after it has answered our question.&lt;/p&gt;&#10;&lt;p&gt;See &lt;a href="https://huggingface.co/transformers/v4.12.5/main_classes/model.html#transformers.generation_utils.GenerationMixin.generate"&gt;model generate parameters&lt;/a&gt; if you want to explore the different options.&lt;/p&gt;&#10;&lt;p&gt;Now, let’s upload this notebook to SageMaker.&lt;/p&gt;&#10;&lt;h4 id="step-4---upload-the-notebook"&gt;Step 4 - Upload the notebook&lt;/h4&gt;&#10;&lt;p&gt;Hopefully by this time your SageMaker notebook instance has finished being provisioned. When it has, click the &lt;em&gt;Open Jupyter&lt;/em&gt; link:&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/mpt-7b-on-aws-sagemaker/created-notebook-instances.png"&#10;&#9;&#9;&#9;alt="Open Jupyter"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Open Jupyter. Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Then, click the &lt;em&gt;Upload&lt;/em&gt; button in the top-right corner of your screen and select the notebook that you just downloaded:&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/mpt-7b-on-aws-sagemaker/upload-notebook.png"&#10;&#9;&#9;&#9;alt="Upload the notebook"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Upload the notebook. Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Set the kernel to &lt;em&gt;conda_python3&lt;/em&gt;:&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/mpt-7b-on-aws-sagemaker/set-kernel.png"&#10;&#9;&#9;&#9;alt="Set the kernel"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Set the kernel. Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;h4 id="step-5---run-the-notebook"&gt;Step 5 - Run the notebook&lt;/h4&gt;&#10;&lt;p&gt;Select &lt;em&gt;Cell -&amp;gt; Run All&lt;/em&gt;:&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/mpt-7b-on-aws-sagemaker/run-all.png"&#10;&#9;&#9;&#9;alt="Run all cells"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Run all cells. Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;An hourglass logo will then appear in the browser tab:&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/mpt-7b-on-aws-sagemaker/wait-for-notebook.png"&#10;&#9;&#9;&#9;alt="Wait for notebook"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Wait for notebook. Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;You’ll then need to wait about 10 minutes for the model to be downloaded:&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/mpt-7b-on-aws-sagemaker/notebook-downloads-model.png"&#10;&#9;&#9;&#9;alt="Download model"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Download model. Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;After it runs, you’ll see the answer to the question &lt;em&gt;Explain to me the difference between nuclear fission and fusion&lt;/em&gt;:&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/mpt-7b-on-aws-sagemaker/fission-vs-fusion.png"&#10;&#9;&#9;&#9;alt="Explain to me the difference between nuclear fission and fusion"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Explain to me the difference between nuclear fission and fusion. Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Since the model and tokenizer have already been loaded above, you can simply modify the &lt;em&gt;ask_question&lt;/em&gt; code block and click the &lt;em&gt;Run&lt;/em&gt; button to ask any other questions. This will save you from spending 10 minutes each time you want to test a new question.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/mpt-7b-on-aws-sagemaker/capital-of-france.png"&#10;&#9;&#9;&#9;alt="What is the capital of France?"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;What is the capital of France?. Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;h4 id="step-6---stop-the-notebook"&gt;Step 6 - Stop the notebook&lt;/h4&gt;&#10;&lt;p&gt;As soon as you have finished testing the model, you’ll want to head back to your list of notebook instances and stop it. If you don’t, $4/hr will add up very quickly 💸&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/mpt-7b-on-aws-sagemaker/stop-notebook.png"&#10;&#9;&#9;&#9;alt="Stop the notebook"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Stop the notebook. Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;h3 id="performance-comparison"&gt;Performance Comparison&lt;/h3&gt;&#10;&lt;p&gt;In terms of performance, my preliminary tests suggest that MPT-7B&amp;rsquo;s results might not be as good as ChatGPT’s. It does a decent job answering questions like &lt;em&gt;What is the capital of France?&lt;/em&gt;, &lt;em&gt;Explain to me the difference between nuclear fission and fusion&lt;/em&gt;, and &lt;em&gt;Write python code that converts a csv into pdf&lt;/em&gt;. But for questions like_What is the capital of Belize?_ it fails pretty horribly:&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/mpt-7b-on-aws-sagemaker/capital-of-belize.png"&#10;&#9;&#9;&#9;alt="What is the capital of Belize?"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;What is the capital of Belize? Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;I am currently collecting more data and will conduct a comprehensive comparative analysis in a follow-up blog post. In that post, I will compare the question and answer performance of MPT-7B, MPT-30B, Falcon-40b, and ChatGPT using actual conversation history.&lt;/p&gt;&#10;&lt;h3 id="from-testing-to-production"&gt;From Testing to Production&lt;/h3&gt;&#10;&lt;p&gt;Once you&amp;rsquo;re ready to transition from testing to production, SageMaker offers an additional benefit - the ability to create an endpoint for your model. With SageMaker, you can auto-scale based on demand to the endpoint, optimizing your resources.&lt;/p&gt;&#10;&lt;h3 id="additional-tips"&gt;Additional Tips&lt;/h3&gt;&#10;&lt;p&gt;Be mindful that it&amp;rsquo;s easy for your process to get forked while running in a Jupyter notebook and run out of memory. If this happens, simply shut down the kernel and run all the commands again.&lt;/p&gt;&#10;&lt;p&gt;If you&amp;rsquo;re curious about running this model on a platform other than AWS, Google Colab Pro is another viable option at $9/month. However, based on our testing, we found that we exhausted the provided credits within just a few hours. 😳&lt;/p&gt;&#10;&lt;p&gt;Another challenge you may face is the inability to utilize the &lt;a href="https://huggingface.co/mosaicml/mpt-7b#how-to-use"&gt;Triton optimization&lt;/a&gt; on SageMaker due to a CUDA version incompatibility. Unfortunately, AWS&amp;rsquo;s current P3 instances do not include a recent CUDA version. Therefore, if you wish to utilize the Triton optimization, you will need to create an EC2 container with command line access. However, it&amp;rsquo;s important to note that you will also require special permission from AWS Support to run an instance with 8 VCPUs. In a future post, I will provide a detailed guide on how to integrate Triton and utilize a more cost-effective GPU cloud provider, such as &lt;a href="lambdalabs.com"&gt;Lambda Labs&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h3 id="final-thoughts"&gt;Final Thoughts&lt;/h3&gt;&#10;&lt;p&gt;While MosaicML&amp;rsquo;s MPT-7B offers a viable alternative to OpenAI&amp;rsquo;s ChatGPT, it presents its own set of challenges. Running the model can be time-consuming, expensive, and the available documentation is lacking. However, the ability to keep the model in-house and protect your data from being exposed to OpenAI can be compelling for certain use cases.&lt;/p&gt;&#10;&lt;p&gt;SageMaker offers great convenience for quickly testing the model and provides the flexibility to transition to production when you&amp;rsquo;re ready. Whether you&amp;rsquo;re just starting with MPT-7B or have been using it for a while, we hope this guide has provided valuable insights.&lt;/p&gt;&#10;&lt;p&gt;Stay tuned for our next blog post, where we&amp;rsquo;ll delve deeper into the performance comparisons between MPT-7B, MPT-30B, Falcon-40b, and ChatGPT.&lt;/p&gt;&#10;&lt;p&gt;See the following links if you are interested in learning more about &lt;a href="https://www.mosaicml.com/blog/mpt-7b"&gt;MPT-7B&lt;/a&gt; or its larger variant, &lt;a href="https://www.mosaicml.com/blog/mpt-30b"&gt;MPT-30B&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;And remember, whether you&amp;rsquo;re working with ChatGPT or MPT-7B, the key is to ensure your use case is served without compromising data privacy and cost-effectiveness. Happy tinkering!&lt;/p&gt;&#10;&lt;h3 id="want-a-turn-key-solution-for-using-mpt-or-chatgpt-with-your-data"&gt;Want a Turn-Key Solution for Using MPT or ChatGPT with Your Data?&lt;/h3&gt;&#10;&lt;p&gt;At &lt;a href="https://mindfuldataai.com?utm_source=redgeoff_com&amp;amp;utm_campaign=mpt-7b"&gt;MindfulDataAI.com&lt;/a&gt;, we offer ChatGPT for businesses. If you&amp;rsquo;re interested in leveraging ChatGPT, MPT, or other models with your company&amp;rsquo;s data, please get in touch with us.&lt;/p&gt;&#10;</description></item><item><title>My Journey with ChatGPT for Business: Unleashing the Power of AI for Software Engineers</title><link>https://redgeoff.com/posts/chatgpt-for-business/</link><pubDate>Sat, 08 Jul 2023 07:16:09 -0700</pubDate><guid>https://redgeoff.com/posts/chatgpt-for-business/</guid><description>&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/chatgpt-for-business/gpt4.jpg"&#10;&#9;&#9;&#9;alt="ChatGPT for business"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Image credit: &lt;a href="https://unsplash.com/@dkoi"&gt;D koi&lt;/a&gt; on &lt;a href="https://unsplash.com"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;h3 id="introduction"&gt;Introduction&lt;/h3&gt;&#10;&lt;p&gt;As a software engineer, I&amp;rsquo;m constantly searching for tools and technologies that can revolutionize the way I work. Today, I want to share a prototype app: ChatGPT for Business. I believe if we follow through on building this, it has the potential to transform our industry. Join me as I take you through my experiences, challenges, and the immense possibilities that lie ahead.&lt;/p&gt;&#10;&lt;h3 id="from-crypto-trading-to-ai-assisted-productivity"&gt;From Crypto Trading to AI-Assisted Productivity&lt;/h3&gt;&#10;&lt;p&gt;The story begins with me leaving my Director of Engineering job at a fast-growing startup that was about to be acquired. I stumbled into the world of crypto trading after attending a web3 event in early 2022, where I heard about arbitrage opportunities. Eager to explore further, I began experimenting with free bots at Pionex and discovered the passive income potential of trading bots. That&amp;rsquo;s when everything changed.&lt;/p&gt;&#10;&lt;p&gt;Driven by curiosity, I reverse-engineered a promising bot, conducted extensive backtesting using historical price data from Binance, and optimized its parameters using genetic mutation techniques. Deploying it on AWS, I witnessed consistent daily returns, achieving a remarkable 18% annual return on a sizable investment. But as the market landscape shifted, I realized that luck played a significant role in my success, and a long-term buy-and-hold strategy would yield better results. When the SEC&amp;rsquo;s lawsuit against Binance and Coinbase loomed, I secured my crypto in wallets and closed my trading operations.&lt;/p&gt;&#10;&lt;h3 id="discovering-the-power-of-chatgpt"&gt;Discovering the Power of ChatGPT&lt;/h3&gt;&#10;&lt;p&gt;During this transformative period, I found myself increasingly relying on ChatGPT, a powerful generative AI model. What began as a tool for trivial questions quickly evolved into something much more. ChatGPT gradually replaced around 80% of my Google searches, becoming indispensable for coding technical analysis algorithms, troubleshooting Docker configurations, proofreading emails, and even generating pitch deck outlines. I realized that ChatGPT had become my go-to resource, surpassing Google searches in many aspects. However, it was missing the capability to search my private data and access any information released after 2021.&lt;/p&gt;&#10;&lt;h3 id="the-spark-of-innovation"&gt;The Spark of Innovation&lt;/h3&gt;&#10;&lt;p&gt;One day, over coffee, a friend shared an interesting challenge: scraping information from multiple mom-and-pop websites to create a comprehensive activities-for-kids marketplace. It was at that moment that I had a revelation—what if ChatGPT could help? Instead of writing custom web scrapers for each site, could ChatGPT extract the desired information? Intrigued, I stumbled upon an informative post titled &lt;a href="https://beebom.com/how-train-ai-chatbot-custom-knowledge-base-chatgpt-api"&gt;&amp;ldquo;How to Train an AI Chatbot With Custom Knowledge Base Using ChatGPT API&amp;rdquo;&lt;/a&gt;. Inspired by its possibilities, I pointed ChatGPT to a Boys and Girls Club website, asking questions about activities in Seattle, their schedules, and costs. To my delight, the results were remarkably accurate, and the cost of using OpenAI’s API was surprisingly affordable.&lt;/p&gt;&#10;&lt;h3 id="unlocking-the-power-of-chatgpt-for-business"&gt;Unlocking the Power of ChatGPT for Business&lt;/h3&gt;&#10;&lt;p&gt;Fueled by this success, I couldn&amp;rsquo;t help but wonder how I could further enhance my productivity using ChatGPT. To my surprise, I discovered that very few business products allowed users to leverage ChatGPT with their own data. Existing solutions fell short in terms of integrations, customization, and scalability. &lt;a href="https://myaskai.com"&gt;MyAskAI&lt;/a&gt; catered to individuals with one-off files and only allowed two questions per message thread. &lt;a href="https://aisera.com"&gt;AiSera&lt;/a&gt; seemed more focused on building chatbots with custom data. &lt;a href="https://customgpt.ai"&gt;CustomGPT&lt;/a&gt; lacked the necessary integrations with data sources like Google Drive, Salesforce, and Confluence. &lt;a href="https://gerev.ai"&gt;Gerev.ai&lt;/a&gt;, although open-source and appealing, failed to index a 5-page PDF on a brand-new MacBook Pro. &lt;a href="https://klu.so"&gt;Klu&lt;/a&gt; had an impressive turnkey integration with sources like Google Drive, but its &amp;ldquo;Beta AI&amp;rdquo; spun endlessly when asked a basic question about a single 5-page PDF file.&lt;/p&gt;&#10;&lt;p&gt;This realization sparked a vision in me—I wanted to build a product specifically designed to empower all businesses, not just the ones with the funds to train new AI models, with ChatGPT&amp;rsquo;s capabilities. Excited by the possibilities, I reached out to my friend Arty, a talented engineer, and we began brainstorming ideas together.&lt;/p&gt;&#10;&lt;h3 id="seeking-validation-and-refining-the-vision"&gt;Seeking Validation and Refining the Vision&lt;/h3&gt;&#10;&lt;p&gt;To refine our concept and gain support, we sought guidance from a local venture capitalist (VC). We were told that our idea might be too broad and that we should instead focus on a specific industry. However, we remained unconvinced that there isn&amp;rsquo;t a place for a general-purpose ChatGPT tool for businesses. Therefore, we decided to rely on data to guide us in the right direction. Undeterred, we developed a playbook, crafted surveys, optimized keywords for SEO and advertising, and created compelling website mock-ups. But we knew that true validation would come from our fellow software engineers—the very people who could benefit from ChatGPT for Business—&lt;strong&gt;YOU!&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;Imagine, if you will…&lt;/p&gt;&#10;&lt;p&gt;A ChatGPT-powered teammate, seamlessly integrated with your data sources and the web, providing real-time insights, securely linked to source documents, and offering turnkey integrations with your company&amp;rsquo;s tools and systems. We envision automated document synchronization, real-time web searches, single sign-on (SSO) integration, access controls, conversation sharing, onboarding and training, and robust security and compliance measures.&lt;/p&gt;&#10;&lt;p&gt;Imagine being able get the status of a Jira ticket in real-time:&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/chatgpt-for-business/mindful-screen-jira.png"&#10;&#9;&#9;&#9;alt="Jira status"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Or, what about keeping tabs on the competition?&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/chatgpt-for-business/mindful-screen-competitor.png"&#10;&#9;&#9;&#9;alt="Competitor research"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Or, even synthesizing reports from raw data. Sharing and explaining insights (like a goalie, answering questions before you need to!). The more documents you share with the agent, the more time it will save for you as you could even let this AI agent handle basic questions on your behalf!&lt;/p&gt;&#10;&lt;div style="width:100%;height:0;padding-bottom:54%;position:relative;"&gt;&#10; &lt;iframe src="https://giphy.com/embed/KXJeRE9I6MvZ2ZPwY8" width="100%" height="100%" style="position:absolute" frameBorder="0" class="giphy-embed" allowFullScreen&gt;&#10; &lt;/iframe&gt;&#10;&lt;/div&gt;&#10;&lt;figcaption&gt;&lt;small&gt;&lt;i&gt;Image credit: &lt;a href="https://giphy.com/gifs/nhl-hockey-ice-17-18-season-KXJeRE9I6MvZ2ZPwY8"&gt;Ice Hockey Dancing&lt;/a&gt; by &lt;a href="https://giphy.com/channel/nhl"&gt;NHL&lt;/a&gt;&lt;/i&gt;&lt;/small&gt;&lt;/figcaption&gt;&#10;&#10;&lt;p&gt;The possibilities are endless.&lt;/p&gt;&#10;&lt;h3 id="join-the-journey"&gt;Join the Journey&lt;/h3&gt;&#10;&lt;p&gt;And now, dear reader, we invite you to embark on this incredible journey with us. We think we’re on to something, but we want your thoughts! Your insights and feedback are essential as we shape the future of ChatGPT for Business. Together, we can unleash the potential of ChatGPT, transforming the way we work, and empowering ourselves and our teams with a tool that will revolutionize communication. Please take a moment to share your thoughts by participating in our quick &lt;a href="https://mindfuldataai.com/?utm_campaign=2023_blog_origin_story&amp;amp;utm_source=redgeoff_com"&gt;survey&lt;/a&gt;. Additionally, you can also &lt;a href="https://mindfuldataai.com/survey.html?utm_campaign=2023_blog_origin_story&amp;amp;utm_source=redgeoff_com"&gt;register for the beta release&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt;&#10;&lt;p&gt;Our journey with ChatGPT for Business has been one of passion, innovation, and relentless pursuit. We have witnessed firsthand the immense potential of AI in our field, and we’re determined to harness that power to create a tool that will revolutionize software engineering. Thank you for reading, and we eagerly await your input as we shape the future of ChatGPT for Business together.&lt;/p&gt;&#10;</description></item><item><title>Run a Free VPN Server on AWS (2022 Edition)</title><link>https://redgeoff.com/posts/free-vpn-aws-2022/</link><pubDate>Tue, 15 Nov 2022 10:48:50 -0800</pubDate><guid>https://redgeoff.com/posts/free-vpn-aws-2022/</guid><description>&lt;p&gt;This tutorial will take you through the steps of setting up an EC2 instance running OpenVPN server. It will then cover how to grant and revoke access to your VPN server.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/vpn-2022/vpn-splash.jpg"&#10;&#9;&#9;&#9;alt="Free VPN on AWS"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Image credit: &lt;a href="https://unsplash.com/@privecstasy"&gt;Privecstasy&lt;/a&gt; on &lt;a href="https://unsplash.com"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;AWS has an awesome firewall built into its core services, which can easily be used to make sure that only certain ports are open to the outside world. One extra step that we can take is to run a VPN server that serves as the gateway to our protected AWS resource, e.g. EC2, RDS, etc&amp;hellip; We can then shutdown direct access to our AWS resources just by revoking access via our VPN server. This is very useful if you need to revoke access for a former employee.&lt;/p&gt;&#10;&lt;p&gt;AWS now offers a &lt;a href="https://aws.amazon.com/vpn/"&gt;managed VPN Service&lt;/a&gt;, but this service costs at least $72 a month and is even more expensive when your VPN serves a lot of traffic. A lot of smaller organizations don&amp;rsquo;t need all the features of the managed service and instead can run their own VPN server for just the cost of an EC2 instance. You can even use the free tier so that you don&amp;rsquo;t have to pay anything for the EC2 instance for the 1st year.&lt;/p&gt;&#10;&lt;h3 id="step-1--create-the-vpn-security-group"&gt;Step 1 — Create the VPN Security Group&lt;/h3&gt;&#10;&lt;p&gt;Overview: security groups allow your servers to communicate with each other in a private cloud while exposing specific ports to the world. We are going to create a security group to allow VPN access to our VPN server. We will assume that all your other AWS resources are members of the default security group and that the default security group does not allow access from the outside world.&lt;/p&gt;&#10;&lt;p&gt;Log in at &lt;a href="https://aws.amazon.com"&gt;https://aws.amazon.com&lt;/a&gt;, type &lt;em&gt;EC2&lt;/em&gt; in the search box, and click on the target to go to the EC2 Dashboard.&lt;/p&gt;&#10;&lt;p&gt;From the EC2 dashboard, click &lt;em&gt;Security Groups&lt;/em&gt;:&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/vpn-2022/security-groups.png"&#10;&#9;&#9;&#9;alt="Security Groups"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Click &lt;em&gt;Create security group&lt;/em&gt;:&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/vpn-2022/create-security-group.png"&#10;&#9;&#9;&#9;alt="Create Security Group"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Enter a name and description of &lt;em&gt;vpn&lt;/em&gt; and specify the following inbound rules on ports &lt;em&gt;22&lt;/em&gt;, &lt;em&gt;443&lt;/em&gt;, &lt;em&gt;943&lt;/em&gt;, and &lt;em&gt;1194&lt;/em&gt;. Note: the protocol for port &lt;em&gt;1194&lt;/em&gt; is &lt;strong&gt;UDP&lt;/strong&gt;.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/vpn-2022/inbound-rules.png"&#10;&#9;&#9;&#9;alt="Ports"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Note: if the IP addresses that your team uses are static then you can add yet another layer of security by specifying an IP address range in the &lt;em&gt;Source&lt;/em&gt; of your rules. However, you’ll want to leave the &lt;em&gt;Source&lt;/em&gt; open to anywhere if you want your team to be able to connect from any IP as they may be working from a hotel, home, cafe, etc…&lt;/p&gt;&#10;&lt;h3 id="step-2--create-the-ec2-instance"&gt;Step 2 — Create the EC2 Instance&lt;/h3&gt;&#10;&lt;p&gt;Return to the EC2 Dashboard and then click &lt;em&gt;Launch instances&lt;/em&gt;:&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/vpn-2022/launch-instances.png"&#10;&#9;&#9;&#9;alt="Launch instances"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Select Ubuntu (you can of course select almost any other OS that runs OpenVPN, but this tutorial is tailored to Ubuntu)&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/vpn-2022/ubuntu-22.png"&#10;&#9;&#9;&#9;alt="Ubuntu 22"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Select &lt;em&gt;t2.micro&lt;/em&gt;:&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/vpn-2022/t2-nano.png"&#10;&#9;&#9;&#9;alt="t2 nano"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Note: you can use a nano instance instead of a micro instance, but nano instances are not eligible for the free tier.&lt;/p&gt;&#10;&lt;p&gt;In the &lt;em&gt;Network settings&lt;/em&gt; section, select your default VPC and disable the auto-assign public IP option. Then, select both your default security group and the security group that you created above for the VPN.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/vpn-2022/network-settings.png"&#10;&#9;&#9;&#9;alt="Edit security groups"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Click &lt;em&gt;Launch instance&lt;/em&gt;&lt;/p&gt;&#10;&lt;h3 id="step-3--disable-sourcedestination-check"&gt;Step 3 — Disable Source/Destination Check&lt;/h3&gt;&#10;&lt;p&gt;From the list of instances, select the VPN instance and then &lt;em&gt;Actions -&amp;gt; Networking -&amp;gt; Change source/destination check&lt;/em&gt; from the drop down menu.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/vpn-2022/instance-source-destination.png"&#10;&#9;&#9;&#9;alt="Instance source destination check"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Select &lt;em&gt;Stop&lt;/em&gt; and click &lt;em&gt;Save&lt;/em&gt;. This is needed as otherwise, your VPN server will not be able to connect to your other AWS resources.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/vpn-2022/edit-source-destination.png"&#10;&#9;&#9;&#9;alt="Edit source destination check"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;h3 id="step-4--create-an-elastic-ip-address"&gt;Step 4 — Create an Elastic IP Address&lt;/h3&gt;&#10;&lt;p&gt;Overview: when an EC2 instance is stopped and restarted, the Public IP address changes. We want the IP address of our VPN server to remain static so we’ll use an Elastic IP Address.&lt;/p&gt;&#10;&lt;p&gt;From the E2C Dashboard, select &lt;em&gt;Elastic IPs&lt;/em&gt;:&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/vpn-2022/elastic-ips-menu.png"&#10;&#9;&#9;&#9;alt="Elastic IPs"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Click &lt;em&gt;Allocate Elastic IP address&lt;/em&gt;:&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/vpn-2022/allocate-elastic-ip.png"&#10;&#9;&#9;&#9;alt="Allocate Elastic IP address"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Make a note of your new Elastic IP address as this will be the Public IP Address of your VPN server. We will refer to this address later as &lt;em&gt;PUBLIC-IP-OF-VPN-SERVER&lt;/em&gt;.&lt;/p&gt;&#10;&lt;p&gt;Select the IP address you just created and click &lt;em&gt;Associate Elastic IP address&lt;/em&gt;:&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/vpn-2022/associate-elastic-ip.png"&#10;&#9;&#9;&#9;alt="Associate Elastic IP address"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Then, select the Elastic IP and click &lt;em&gt;Associate address&lt;/em&gt; from the drop down menu.&lt;/p&gt;&#10;&lt;p&gt;Select the EC2 instance you created above and click &lt;em&gt;Associate&lt;/em&gt;:&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/vpn-2022/ec2-elastic-ip.png"&#10;&#9;&#9;&#9;alt="EC2 Elastic IP"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;h3 id="step-5-install-and-configure-the-openvpn-server"&gt;Step 5— Install and Configure the OpenVPN Server&lt;/h3&gt;&#10;&lt;p&gt;SSH into your VPN server:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;$ ssh ubuntu@PUBLIC-IP-OF-VPN-SERVER&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Download our helper scripts and set up a default config:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;$ git clone https://github.com/redgeoff/openvpn-server-vagrant&#10;$ cd openvpn-server-vagrant&#10;$ cp config-default.sh config.sh&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Edit config.sh and enter in your configuration. Note: &lt;em&gt;PUBLIC_IP&lt;/em&gt; should be equal to the Elastic IP Address that you created above.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;$ nano config.sh&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Switch to root:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;$ sudo su -&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You&amp;rsquo;ll now update Ubuntu. Note: you will be prompted several times and when you do, just press the &lt;em&gt;Enter&lt;/em&gt; key.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;$ /home/ubuntu/openvpn-server-vagrant/ubuntu.sh&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Now, install OpenVPN. Note: you will be prompted several times and when you do, just press the &lt;em&gt;Enter&lt;/em&gt; key.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;$ /home/ubuntu/openvpn-server-vagrant/openvpn.sh&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;At this point, the OpenVPN server is running.&lt;/p&gt;&#10;&lt;h3 id="step-6--add-the-route"&gt;Step 6 — Add the Route&lt;/h3&gt;&#10;&lt;p&gt;Routes must be added to the server so that your team’s clients know which traffic to route to the VPN server.&lt;/p&gt;&#10;&lt;p&gt;You can determine the proper subnet by returning to your list of EC2 instances, clicking on a target instance and identifying the Private IP.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/vpn-2022/identify-private-ip.png"&#10;&#9;&#9;&#9;alt="Identify Private IP"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;Your network will be the first 2 parts of the Private IP appended with zeros, e.g. &lt;em&gt;172.31.0.0&lt;/em&gt;&lt;/p&gt;&#10;&lt;p&gt;On the VPN server edit &lt;em&gt;/etc/openvpn/server/server.conf&lt;/em&gt; and add something like the following:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;push &amp;#34;route 172.31.0.0 255.255.0.0&amp;#34;&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then, restart the VPN server with:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;$ systemctl restart openvpn-server@server.service&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="step-7--grant-access-to-your-vpn"&gt;Step 7 — Grant Access to Your VPN&lt;/h3&gt;&#10;&lt;p&gt;Note: We assume that you are still SSH’d into the VPN and logged in as root.&lt;/p&gt;&#10;&lt;p&gt;Run the following command and be sure to replace &lt;em&gt;client&lt;/em&gt; below with a unique name for your user/client.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;$ /home/ubuntu/openvpn-server-vagrant/add-client.sh client&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You’ll then find a configuration file at&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;~/client-configs/files/client-name.ovpn&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You will want to provide this file to the individual on your team who will be connecting to your VPN. SCP is handy for downloading this .ovpn file from your VPN server.&lt;/p&gt;&#10;&lt;p&gt;Your team can use one of various VPN clients such as &lt;a href="https://tunnelblick.net/"&gt;Tunnelblick&lt;/a&gt; (OS X) and &lt;a href="https://openvpn.net/community-downloads/"&gt;OpenVPN&lt;/a&gt; (Linux, iOS, Android and Windows). After installing one of these clients, they should be able to set up the VPN config just by double clicking on the .ovpn file.&lt;/p&gt;&#10;&lt;p&gt;Note: once connected to the VPN, your users will want to use the Private IPs of your AWS resources. You may want to use &lt;em&gt;nslookup&lt;/em&gt; to lookup the IP address from the AWS custom domain names provided by AWS. You’ll probably want to use Route 53 to create subdomain records that route to the Private IPs.&lt;/p&gt;&#10;&lt;h3 id="step-8--revoke-access-to-your-vpn"&gt;Step 8 — Revoke Access to Your VPN&lt;/h3&gt;&#10;&lt;p&gt;Note: We assume that you are still SSH’d into the VPN and logged in as root.&lt;/p&gt;&#10;&lt;p&gt;Run the following command and be sure to replace &lt;em&gt;client&lt;/em&gt; below with a unique name for your user/client.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;$ /home/ubuntu/openvpn-server-vagrant/revoke-full.sh client&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="troubleshooting"&gt;Troubleshooting&lt;/h3&gt;&#10;&lt;p&gt;If your VPN client reports a &lt;em&gt;TLS handshake failed&lt;/em&gt; error then this is most likely because your VPN security group (Step 1) is incorrect. Make sure that you have the correct ports and protocols specified — a common problem is not specifying UDP for port 1194.&lt;/p&gt;&#10;</description></item><item><title>We Migrated From AWS Lambda to ECS, but Hope to Eventually Migrate Back</title><link>https://redgeoff.com/posts/lambda-to-ecs/</link><pubDate>Tue, 17 Aug 2021 07:16:09 -0700</pubDate><guid>https://redgeoff.com/posts/lambda-to-ecs/</guid><description>&lt;p&gt;The pitfalls of Lambda that made us switch away&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/lambda-to-ecs/lambda-vs-ecs.png"&#10;&#9;&#9;&#9;alt="Lambda vs ECS"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Image credit: &lt;a href="https://unsplash.com/@attentieattentie"&gt;Attentie Attentie&lt;/a&gt; on &lt;a href="https://unsplash.com"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;&lt;em&gt;Author’s note: The views expressed in this post are my own and not necessarily those of my employer.&lt;/em&gt;&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;p&gt;This is a story about how we migrated from Lambda to ECS because of a 2% error rate and limited concurrency controls. And, about how AWS has more recently fixed their error-rate issues and how enhancements to concurrency controls could win us back to Lambda.&#10;When I first started at &lt;a href="https://knockcrm.com"&gt;Knock&lt;/a&gt; in 2018, we were running our entire stack on AWS EC2 instances that we were manually managing.&lt;/p&gt;&#10;&lt;p&gt;As a result, it was painful to scale and maintain our infrastructure, and we were wasting money as we were unable to distribute our resource consumption. We were considering a managed container service like AWS Elastic Container Service (ECS) and AWS’s most recent serverless option, Lambda.&lt;/p&gt;&#10;&lt;p&gt;AWS claimed that Lambda was a reliable, feature-rich environment that abstracts away any notion of having to manage a server, and we were excited about the prospect of just being able to focus on the code.&lt;/p&gt;&#10;&lt;p&gt;Unfortunately, after a year of using Lambda in production, we observed Lambda was a relatively unreliable technology and when it was used to deliver customer-facing features, a 2% error rate in the AWS layer resulted in a very poor user experience. So, we decided to migrate some of our customer-facing Lambdas to ECS.&lt;/p&gt;&#10;&lt;p&gt;Before we dive into the details, I’d like to clarify that this post is not a hate letter to AWS about Lambda.&lt;/p&gt;&#10;&lt;p&gt;As you will read later, in 2020, AWS made improvements to Lambda networking, which has almost completely eroded the error rates we were seeing.&lt;/p&gt;&#10;&lt;p&gt;This leaves just the concern of concurrency, which is something that can be mitigated in certain use cases, but not all.&lt;/p&gt;&#10;&lt;p&gt;Because there is one shared concurrency pool for all your Lambdas, a burst of activity in any of your Lambdas can easily lead to significant throttling of mission-critical functionality, which results in an unacceptable user experience.&lt;/p&gt;&#10;&lt;p&gt;Overall, I’m hopeful that AWS will eventually enhance the concurrency model for Lambda and remain committed to the promise of a world where engineers spend less time managing servers.&lt;/p&gt;&#10;&lt;h3 id="what-were-we-running-on-lambda"&gt;What Were We Running On Lambda?&lt;/h3&gt;&#10;&lt;p&gt;Let’s start by describing the types of services we had running on Lambda, as after all, we can sometimes tolerate latency and failures in certain use cases.&lt;/p&gt;&#10;&lt;p&gt;The first class of services comprises a backend of RESTful APIs fronted by API Gateway and backed by a Lambda function for each route.&lt;/p&gt;&#10;&lt;p&gt;We had both Node.js (Typescript) and Python code running in this layer. Although the programming language choices here have little impact on what we discuss in this post, they do impact cost and overall Lambda concurrency and latency.&lt;/p&gt;&#10;&lt;p&gt;The main callout here is that most of these APIs were being called by frontends synchronously and thus, significant latency and failure is unacceptable as it manifests as downtime to the user.&lt;/p&gt;&#10;&lt;p&gt;The second class of services mostly consists of background tasks that read messages from SQS queues and then perform IO operations on RDS instances and S3 buckets.&lt;/p&gt;&#10;&lt;p&gt;In our case, nothing in this pipeline is too time-sensitive, so if these background tasks fail occasionally, a retry mechanism is enough to keep our system healthy and everyone happy.&lt;/p&gt;&#10;&lt;h3 id="up-to-2-error-rates-in-lambda-from-20192020"&gt;Up to 2% Error Rates in Lambda From 2019–2020&lt;/h3&gt;&#10;&lt;p&gt;It wasn’t until we launched RESTful APIs in production that we began to notice that transient errors in the AWS layer were common for AWS Lambda. In particular, we started noticing errors in CloudWatch where a Lambda would timeout before it even ran any of our application-layer code. We knew the application code was not running as the very first thing our application code does is log the incoming request and these log statements from our application layer were missing. In other words, there was nothing that we could tune to prevent this from happening as the issue was occurring in the AWS layers. For example:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;START RequestId: 56a12bf8–43eb-4b6c-8a20-c73fa2152c6b Version: $LATEST&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;[Missing entries from application logs]&lt;/strong&gt;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;END RequestId: 56a12bf8–43eb-4b6c-8a20-c73fa2152c6b&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;REPORT RequestId: 56a12bf8–43eb-4b6c-8a20-c73fa2152c6b Duration: 30019.42 ms Billed Duration: 30000 ms Memory Size: 1024 MB Max Memory Used: 77 MB&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;2019–08–22T20:10:31.270Z 56a12bf8–43eb-4b6c-8a20-c73fa2152c6b &lt;strong&gt;Task timed out after 30.02 seconds&lt;/strong&gt;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;On August 28th, 2019, We reached out to AWS via a support ticket and they confirmed that a 1–2% failure rate was somewhat normal:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Just to summarize the conversation, we talked about a 1%-2% failure rate not being outside of the realm of possibility in spikes, and the recommended implementation is backoff and retry on the client side.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;AWS recommended a retry mechanism, but in practice, the only way to detect these problems was by setting a more aggressive timeout and then retrying.&lt;/p&gt;&#10;&lt;p&gt;Some of our legacy code in the critical code path can take several seconds to run during heavy times of load and Lambda cold starts used to be fairly significant.&lt;/p&gt;&#10;&lt;p&gt;So, let’s say that we implemented a five-second timeout and then retried at this point. This would result in the user having to wait at least five seconds, which is unacceptable when it happens 2% of the time!&lt;/p&gt;&#10;&lt;p&gt;Moreover, our backend is distributed, and therefore operations are not always atomic, meaning that there would be an increased possibility of duplicate requests when aborting the Lambda invocation and retrying the request.&lt;/p&gt;&#10;&lt;p&gt;This was a major deal-breaker for us, so we decided to start migrating away from Lambda at the end of 2019.&lt;/p&gt;&#10;&lt;h3 id="shared-concurrency-leads-to-throttled-execution-and-frustrated-users"&gt;Shared Concurrency Leads To Throttled Execution and Frustrated Users&lt;/h3&gt;&#10;&lt;p&gt;One could argue that the concurrency control capabilities of Lambda are severely lacking as there is a single per-region cap on Lambda currency per AWS account.&lt;/p&gt;&#10;&lt;p&gt;This means that any bursting Lambda activity can cause customer-facing Lambdas to be throttled. Here, I’m defining customer-facing Lambdas as those fielding synchronous requests from our users via a UI.&lt;/p&gt;&#10;&lt;p&gt;In other words, Lambda-powered background tasks can starve these customer-facing Lambdas and can essentially result in your app becoming unresponsive. This manifests as an outage to your users and is completely unacceptable when it occurs frequently.&lt;/p&gt;&#10;&lt;p&gt;As illustrated in the next set of graphs, you can see that spikes in customer-facing errors roughly correlate to the spikes in our account’s Lambda concurrency:&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/lambda-to-ecs/concurrent-executions.png"&#10;&#9;&#9;&#9;alt="Errors from hitting max concurrency"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;Errors from hitting max concurrency. Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;This cannot be easily prevented as there is a limit of 1,000 concurrent Lambdas on our account, and the only way to bound Lambda executions is by reserving part of this pool of 1,000. In other words, each time you reserve concurrency for a Lambda, you forever take away potential concurrency for your other Lambdas, resulting in an awkward juggling act where you have some Lambdas that are statically bounded and others that are not.&lt;/p&gt;&#10;&lt;p&gt;If you are familiar with AWS Elastic Container Service (ECS) or Elastic Kubernetes Service (EKS), you’ll know that it is fairly trivial to control the number of containers (worker pool) by scaling up or down the number of tasks running for a service. In other words, you have full control over how you allocate your resources and aren’t locked into a primitive per-function concurrency model — like you are with Lambda. In a later section in this post, we’ll investigate how I think AWS could improve its concurrency controls to facilitate better resource sharing.&lt;/p&gt;&#10;&lt;p&gt;Given Lambda’s basic concurrency controls, is there anything we can do to prevent throttling our user-facing Lambdas? Yes, you can:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;temporarily constrain the concurrency of more Lambdas with reserved concurrency&lt;/li&gt;&#10;&lt;li&gt;file a request with AWS support to bump up your concurrency limit from 1,000&lt;/li&gt;&#10;&lt;li&gt;increase the batch size on your SQS reads to a maximum of 10. This means that it takes fewer Lambdas to service the same SQS traffic. Let’s just hope you structured your code to work this way before your Lambdas were throttled&lt;/li&gt;&#10;&lt;li&gt;set the reserved or provisioned concurrency for user-facing Lambdas. This is fine if you only have a few Lambdas, but if you have one for each RESTful API route, you could have hundreds of Lambdas and you’ll quickly get into a situation where you actually lock up all of your concurrency. For example, imagine you have just 10 Lambdas/routes and set the reserved concurrency at 20 for each of these Lambdas, resulting in a total reserved concurrency of 20 x 10 = 200 (recall you have a max of 1,000 for your entire AWS account). That number can quickly add up and what’s more is that each of these Lambdas is now bound to a max concurrency of 20, meaning that your user-facing Lambdas aren’t going to scale as your traffic increases, which is a big reason why you even use Lambda in the first place.&lt;/li&gt;&#10;&lt;li&gt;stand up a SQS queue in front of API endpoints for things that are resource-intensive and/or can be processed asynchronously, e.g., email sending and report generation. This allows the customer-facing Lambdas to complete their execution faster as the resource-intensive work is deferred. Although this is typically a good pattern as it reduces latency to your user, it’s not always enough to keep your Lambda concurrency low enough to prevent throttling and it may just increase the concurrency among your background tasks if you are using Lambda to run your background tasks.&lt;/li&gt;&#10;&lt;li&gt;Move your user-facing Lambdas to a different region so that you have access to a different concurrency pool of 1,000. This can greatly increase the complexity of your deployments and it may even mean that other services like Cognito, DynamoDB, SQS, etc., would need to be moved to another region.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;A lot of these mitigations are good things to consider when using Lambda, but they don’t solve the root problem of very limited concurrency controls leading to throttling. If you use these mitigations, you’re basically just incurring tech debt that you’ll be on the hook for later.&lt;/p&gt;&#10;&lt;p&gt;Poor error rates and unavoidable throttling? These were both deal-breakers, so we had no choice but to migrate away from Lambda.&lt;/p&gt;&#10;&lt;h3 id="refactoring-our-code-so-that-it-can-run-on-both-lambda-and-ecs"&gt;Refactoring Our Code So That It Can Run On Both Lambda And ECS&lt;/h3&gt;&#10;&lt;p&gt;Now that we had decided to migrate away from Lambda, we wondered if there was a way to make a minimal set of changes to our code so that it could also run in a Docker environment.&lt;/p&gt;&#10;&lt;p&gt;We chose AWS’s Elastic Container Service (ECS) to run our containers as it is relatively easy to manage and something that our DevOps team had the bandwidth to support. Fortunately, we noticed that the Lambda handlers we already implemented utilize a nice abstraction, which could be used to translate from a Docker-supported framework to a Lambda one and vice-versa. All we needed to do was create a wrapper that would:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;translate the ExpressJS request payload to the event payload needed by the Lambda handler&lt;/li&gt;&#10;&lt;li&gt;translate the Lambda response to an ExpressJS response&lt;/li&gt;&#10;&lt;li&gt;use a wrapper that would allow us to reuse all our existing Lambda handler code and run it on top of the ExpressJS framework! We chose to go with ExpressJS to build the RESTful API as we were using Node.js and ExpressJS is a battle-tested framework. The following implementation is in JS but the pattern will work with any web framework and any programming language. (Our actual implementation is in TypeScript.)&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Let’s take a look at some code to see how we did this. Consider a simplified version of a Lambda handler that implements a &lt;code&gt;POST /lead/:leadId&lt;/code&gt; route, which accepts a JSON body of &lt;code&gt;{ name, email }&lt;/code&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;leadService&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;./lead-service&amp;#39;&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Define a handler for `PUT /lead/:leadId` that accepts the request body&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// payload `{ name, email }`&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;handler&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;) =&amp;gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Parse the request parameters&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;email&lt;/span&gt; } &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;JSON&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;parse&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;body&lt;/span&gt;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;leadId&lt;/span&gt; } &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;pathParameters&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Assume this is the service-layer code that actually performs the&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// updating of the lead&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;leadService&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;update&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;leadId&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;email&lt;/span&gt;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Return a 200 success code and the full lead&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;statusCode&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;200&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;body&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;JSON&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;stringify&lt;/span&gt;({&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;id&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;leadId&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;email&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;export&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;handler&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We created a &lt;code&gt;lambdaToExpress&lt;/code&gt; function that translates from ExpressJS to Lambda and back again, using the following code:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Convert from a Lambda to an ExpressJS handler&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;lambdaToExpress&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;handler&lt;/span&gt;) =&amp;gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;) =&amp;gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Repackage the express parameters as an event object for the Lambda&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;body&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;JSON&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;stringify&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;body&lt;/span&gt;),&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;headers&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;headers&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;queryStringParameters&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;query&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;pathParameters&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;req&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;params&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;handlerRes&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;await&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;handler&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Instruct express to use the response from the Lambda &#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;res&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;status&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;handlerRes&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;statusCode&lt;/span&gt;).&lt;span style="color:#a6e22e"&gt;json&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;JSON&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;parse&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;handlerRes&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;body&lt;/span&gt;))&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; };&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;export&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;lambdaToExpress&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, we registered our ExpressJS routes using our new wrapper. Here is its code:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;express&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;express&amp;#39;&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;cors&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;cors&amp;#39;&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;handler&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;./lambda&amp;#39;&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;lambdaToExpress&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;./lambda-to-express&amp;#39;&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;PORT&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3000&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;app&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;express&lt;/span&gt;();&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;use&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;cors&lt;/span&gt;());&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;use&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;express&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;json&lt;/span&gt;()); &lt;span style="color:#75715e"&gt;// Parse JSON bodies&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;put&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;/lead/:leadId&amp;#39;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;lambdaToExpress&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;handler&lt;/span&gt;));&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;listen&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;PORT&lt;/span&gt;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With just a few lines of code, we enhanced our single codebase to support both Lambda and ExpressJS. One thing to note is that both the &lt;code&gt;lambdaToExpress&lt;/code&gt; and &lt;code&gt;handler&lt;/code&gt; functions are performing a &lt;code&gt;JSON.stringify()&lt;/code&gt; and &lt;code&gt;JSON.parse()&lt;/code&gt;, which adds a tiny bit of extra overhead. That being said, these operations are very quick and unnoticeable in the grand scheme of things.&lt;/p&gt;&#10;&lt;p&gt;The code was all set, but what about anything provided by AWS’s API Gateway?&lt;/p&gt;&#10;&lt;p&gt;As part of our API is external-facing, we needed to support rate-limiting. To achieve this, we incorporated &lt;a href="https://github.com/animir/node-rate-limiter-flexible/wiki/Overall-example#authorized-and-not-authorized-users"&gt;node-rate-limiter-flexible&lt;/a&gt; (backed by ElasticCache) to implement rate-limiting. And, we simply added this rate-limiting check to our &lt;code&gt;lambdaToExpress&lt;/code&gt; wrapper to implement this in a single place.&lt;/p&gt;&#10;&lt;p&gt;We were using the &lt;a href="https://www.serverless.com/"&gt;Serverless Framework&lt;/a&gt; to deploy our Lambdas, and you could probably use this framework to deploy code to ECS, but we find &lt;a href="https://www.terraform.io/"&gt;Terraform&lt;/a&gt; to be a slightly more versatile tool for configuring non-serverless infrastructure and were already using Terraform to deploy other microservices.&lt;/p&gt;&#10;&lt;p&gt;So, we created &lt;a href="https://www.terraform.io/docs/language/modules/develop/index.html"&gt;Terraform Modules&lt;/a&gt; alongside our &lt;code&gt;serverless.yml&lt;/code&gt; configuration and deployed them with &lt;a href="https://www.jenkins.io/doc/book/pipeline/jenkinsfile/"&gt;Jenkinsfiles&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;And now, we were running on ECS!&lt;/p&gt;&#10;&lt;p&gt;We have been happily running a portion of our RESTful API backend on AWS Lambda for over a year and have found that we have reduced our compute costs by 90%! In a future post, I’ll dive into a cost comparison between Lambda and ECS.&lt;/p&gt;&#10;&lt;h3 id="lambda-reduced-its-error-rate-from-2-to-00000625"&gt;Lambda Reduced Its Error Rate From 2% To 0.0000625%&lt;/h3&gt;&#10;&lt;p&gt;If you recall from earlier, in August 2019, we found that AWS Lambda could suffer from an error rate of up to 2%!&lt;/p&gt;&#10;&lt;p&gt;In September 2019, AWS released a blob post touting &lt;a href="https://aws.amazon.com/blogs/compute/announcing-improved-vpc-networking-for-aws-lambda-functions/"&gt;improvements to networking for Lambda&lt;/a&gt; that would reduce latency incurred with cold starts. We were very excited about this news as it meant that AWS had acknowledged the problem and was actively working on a fix.&lt;/p&gt;&#10;&lt;p&gt;We had migrated some of our backends from Lambda to ECS but had a lot of remaining functionality on Lambda.&lt;/p&gt;&#10;&lt;p&gt;In 2021, we performed another analysis and found that the latest version of Lambda was resulting in an error rate of only 0.0000625%. This is a significant improvement from 2%!&lt;/p&gt;&#10;&lt;p&gt;Here is the analysis we did for our auth-service, a RESTful API responsible for authentication and authorization functionality in our system. In a seven-day period, the auth-service had about 1.6 million invocations and only one resulted in a timeout.&lt;/p&gt;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/lambda-to-ecs/auth-service-invocations.png"&#10;&#9;&#9;&#9;alt="auth-service invocations"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;auth-service invocations. Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;figure&gt;&lt;img src="https://redgeoff.com/posts/lambda-to-ecs/auth-service-errors.png"&#10;&#9;&#9;&#9;alt="auth-service errors"&gt;&lt;figcaption&gt;&#10;&#9;&#9;&#9;&lt;p&gt;auth-service invocations. Image credit: Author&lt;/p&gt;&#10;&#9;&#9;&lt;/figcaption&gt;&#10;&lt;/figure&gt;&#10;&#10;&lt;p&gt;With error rates as low as this, our error rate concern with user-facing Lambdas has been eliminated! But what about the lingering concern with concurrency controls?&lt;/p&gt;&#10;&lt;h3 id="how-could-aws-improve-its-concurrency-controls"&gt;How Could AWS Improve Its Concurrency Controls?&lt;/h3&gt;&#10;&lt;p&gt;If you recall, we ended up performing many of the concurrency mitigations mentioned above and moved a number of our Lambdas to ECS.&lt;/p&gt;&#10;&lt;p&gt;Afterward, we were left with a relatively comfortable set of about 200 Lambdas, and as a result, bursting concurrency is now less of a concern.&lt;/p&gt;&#10;&lt;p&gt;That being said, we now avoid using Lambda for any new user-facing services. And, we generally avoid using it for substantial background jobs as we are worried that excessive use will result in throttling our few remaining user-facing Lambdas.&lt;/p&gt;&#10;&lt;p&gt;Despite this current strategy, I’m hoping the day will come when AWS implements a more feature-rich implementation of Lambda concurrency controls that better mimics that of an ECS environment. Here are some enhancements that would make a huge difference:&lt;/p&gt;&#10;&lt;h4 id="max-concurrency"&gt;Max Concurrency&lt;/h4&gt;&#10;&lt;p&gt;Let’s define the Max Concurrency of a Lambda as a setting for the max concurrent invocations of a specific Lambda. This value is not the same as Reserved Concurrency in that the value of Max Concurrency wouldn’t subtract from the overall concurrency available for the AWS account.&lt;/p&gt;&#10;&lt;p&gt;Here’s an example: assume Lambda A has a Max Concurrency of 100 and your account has a global max concurrency of 1,000 (the AWS default).&lt;/p&gt;&#10;&lt;p&gt;Lambda A would not be throttled until there are over 100 instances of Lambda A running concurrently. Other Lambdas, e.g., Lambda B/C, can still consume up to 1,000 instances, if Lambda A has 0 running instances.&lt;/p&gt;&#10;&lt;p&gt;This in essence means that no matter what, Lambda A cannot consume more than 100 instances of the total 1,000, but doesn’t require us to set aside 100 instances from our total 1,000 for Lambda A.&lt;/p&gt;&#10;&lt;p&gt;This setting is somewhat akin to having an ECS service that can burst to an upper bound of max tasks but doesn’t require us to reserve a portion of our cluster, even if that service is not running all the instances.&lt;/p&gt;&#10;&lt;h4 id="group-max-concurrency"&gt;Group Max Concurrency&lt;/h4&gt;&#10;&lt;p&gt;We consider a service to be a group of related Lambdas. For example, consider 10 Lambdas that comprise 10 RESTful endpoints of a service. Or, 10 Lambdas that define the workers of an ETL pipeline. Often, we’d like to define an upper bound on concurrency for a group of Lambdas, e.g., we don’t want our ETL pipeline to consume more than 100 out of our total of 1,000 current Lambda invocations.&lt;/p&gt;&#10;&lt;p&gt;A Group Max Concurrency could effectively be accomplished by first allowing you to tag Lambdas to be part of a group. Second, you could then set the Group Max Concurrency for that group. Or AWS could even reuse the concept of a “stack” and have a Stack Max Concurrency.&lt;/p&gt;&#10;&lt;p&gt;This Group Max Concurrency is similar to how you may have an ECS service where each container is performing multiple (a group of) operations, e.g., a web server fielding requests for multiple RESTful API endpoints.&lt;/p&gt;&#10;&lt;h4 id="ability-to-increase-account-wide-max-concurrency-without-contacting-aws-support"&gt;Ability to increase account-wide Max Concurrency without contacting AWS support&lt;/h4&gt;&#10;&lt;p&gt;Some will point out that you can always increase your total Lambda concurrency just by contacting AWS support.&lt;/p&gt;&#10;&lt;p&gt;In fact, we actually did this to bump our Full account concurrency from 1,000 to 2,000. AWS support was pretty amenable to this, but it took a couple of days for them to make the change and we needed to provide justification.&lt;/p&gt;&#10;&lt;p&gt;All the while, our user-facing Lambdas were throttled, and our users were upset. I’ve even heard of companies being able to get AWS to increase their max concurrency to hundreds of thousands of Lambdas.&lt;/p&gt;&#10;&lt;p&gt;That’s great, but why is special approval even needed? Isn’t the fact that a company is willing to pay for the increased usage enough?&lt;/p&gt;&#10;&lt;p&gt;To compare with ECS, this max-concurrency-per-account setting is similar to how you can easily customize the number of nodes in an ECS cluster, which effectively defines a max number of ECS tasks that can be run concurrently.&lt;/p&gt;&#10;&lt;p&gt;And of course, ECS has autoscaling capabilities that allow you to scale your cluster based on your own needs. None of this scaling requires any special approval from AWS.&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;p&gt;If AWS were able to implement these controls, it would bring AWS Lambda’s concurrency controls to parity with those of ECS and EKS, and I’d once again advocate for using Lambda in many more areas of our system.&lt;/p&gt;&#10;&lt;h3 id="when-does-lambda-shine"&gt;When Does Lambda Shine?&lt;/h3&gt;&#10;&lt;p&gt;Even with its limited concurrency controls, Lambda shines in the following areas:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;For background tasks, e.g., ones that read off SQS. Sure, you can stand up something like &lt;a href="https://docs.celeryproject.org/en/stable/"&gt;celery&lt;/a&gt; or &lt;a href="https://github.com/OptimalBits/bull"&gt;bull&lt;/a&gt; to get something similar, but wiring up a Lambda to an SQS queue or one of the many other AWS triggers, is such a beautifully simple thing. Note: Lambda has a max timeout of 15 minutes, so it is only suitable for running relatively quick tasks.&lt;/li&gt;&#10;&lt;li&gt;When you have other Lambdas that don’t burst to consume all of the available concurrency, such as when you have a relatively small amount of Lambda activity.&lt;/li&gt;&#10;&lt;li&gt;When you wish to optimize autoscaling so that you only pay for what you use. Lambda automatically scales up and down at the single invocation layer so that you can save a ton of money during low traffic periods&lt;/li&gt;&#10;&lt;li&gt;As a place to run your code without the need to manage infrastructure&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt;&#10;&lt;p&gt;The promise of serverless — where developers don’t need to manage infrastructure — is very compelling, and I don’t want to let this dream die.&lt;/p&gt;&#10;&lt;p&gt;AWS has fixed its error-rate problem with Lambdas, but Lambda is still lacking better concurrency controls.&lt;/p&gt;&#10;&lt;p&gt;Until these concurrency controls are enhanced, you’ll probably want to use ECS or EKS for your user-facing services, if your Lambda usage is significant and will lead to throttling.&lt;/p&gt;&#10;&lt;p&gt;I suspect that AWS will continue to evolve the Lambda technology to the point where it becomes a strong alternative to ECS and when it does, I’ll once again tout the virtues of using Lambda everywhere.&lt;/p&gt;&#10;&lt;p&gt;Thanks for reading.&lt;/p&gt;&#10;</description></item><item><title>What are MSON Template Queries?</title><link>https://redgeoff.com/posts/template-queries/</link><pubDate>Mon, 08 Feb 2021 00:00:00 +0000</pubDate><guid>https://redgeoff.com/posts/template-queries/</guid><description>&#10; &lt;figure class="image"&gt;&#10; &#10; &lt;img src="https://redgeoff.com/posts/template-queries/template-queries.png" alt="Template Queries" /&gt;&#10; &#10; &#10; &lt;/figure&gt;&#10;&#10;&lt;p&gt;Template Queries are dynamic templates constructed with MongoDB-style operators. They allow you to customize MSON components with less code and are very extensible.&lt;/p&gt;&#10;&lt;h3 id="ok-but-what-the-heck-is-mson"&gt;OK, but what the heck is MSON?&lt;/h3&gt;&#10;&lt;p&gt;&lt;a href="https://github.com/redgeoff/mson"&gt;MSON&lt;/a&gt; is a low-code way to create apps from JSON. The ultimate goal of &lt;a href="https://github.com/redgeoff/mson"&gt;MSON&lt;/a&gt; is to allow anyone to develop software visually. You can also use pieces of &lt;a href="https://github.com/redgeoff/mson"&gt;MSON&lt;/a&gt; to turbo charge your existing apps.&lt;/p&gt;&#10;&lt;p&gt;A simple form component used to collect a name and email address could look like:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;MyForm&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Form&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fields&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; { &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;name&amp;#39;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;TextField&amp;#39;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;label&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Name&amp;#39;&lt;/span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; { &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;email&amp;#39;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;EmailField&amp;#39;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;label&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Email&amp;#39;&lt;/span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can read more about MSON&amp;rsquo;s language principles &lt;a href="https://redgeoff.com/posts/everyone-can-make-software"&gt;here&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h3 id="what-are-mongodb-operators"&gt;What are MongoDB operators?&lt;/h3&gt;&#10;&lt;p&gt;MongoDB uses an extensive query language that is written in JSON. Because this language is so powerful, a number of projects, that work independently from MongoDB, have adopted support for the query language:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;In-memory libraries for JS, like &lt;a href="https://github.com/kofrasa/mingo"&gt;mingo&lt;/a&gt; and &lt;a href="https://github.com/crcn/sift.js/"&gt;sift&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Operators in &lt;a href="https://sequelize.org/master/manual/model-querying-basics.html"&gt;sequelize ORM&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Other databases, like &lt;a href="https://stackoverflow.com/questions/45976416/couchdb-mango-queries-couchdb-2-0-1"&gt;CouchDB&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;This query language also contains a construct for &lt;a href="https://docs.mongodb.com/manual/reference/operator/aggregation/"&gt;pipeline aggregations&lt;/a&gt;, which can be used to execute operations like &lt;code&gt;$add&lt;/code&gt;, &lt;code&gt;$multiply&lt;/code&gt;, &lt;code&gt;$filter&lt;/code&gt;, &lt;code&gt;$map&lt;/code&gt;, etc&amp;hellip; For example:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;{&#10; $add: [ 1, 2 ]&#10;}&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="a-basic-mson-template-query"&gt;A basic MSON Template Query&lt;/h3&gt;&#10;&lt;p&gt;Now that you have that background, let&amp;rsquo;s take a look at an example where we extract the year from a user-provided date. Upon selecting a date with the date picker, you&amp;rsquo;ll see that the &lt;code&gt;Year&lt;/code&gt; field is populated:&lt;/p&gt;&#10;&#10;&lt;iframe src="https://codesandbox.io/embed/mson-template-query-6zb3b?fontsize=14&amp;hidenavigation=1&amp;theme=dark"&#10; style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"&#10; &#10; allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"&#10; sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"&#10; &gt;&lt;/iframe&gt;&#10;&lt;p&gt;Let&amp;rsquo;s step through this example. First, we construct a form with &lt;code&gt;date&lt;/code&gt; and &lt;code&gt;year&lt;/code&gt; fields:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Form&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fields&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;date&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;DateField&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;label&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Date&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;year&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;IntegerField&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;label&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Year&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Second, we listen for changes to the &lt;code&gt;date&lt;/code&gt; value, extract the year and then set the value of the &lt;code&gt;year&lt;/code&gt; field:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Form&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fields&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; ...,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;listeners&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;fields.date.value&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;actions&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Set&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;fields.year.value&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;$year&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;$toDate&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;{{fields.date.value}}&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;code&gt;$toDate&lt;/code&gt; operator is used to convert the date value, which is in milliseconds, to a date object. The &lt;code&gt;$year&lt;/code&gt; operator is used to extract the year portion from the date. The &lt;code&gt;Set&lt;/code&gt; action is native to MSON and is used to set the value of the &lt;code&gt;year&lt;/code&gt; field.&lt;/p&gt;&#10;&lt;p&gt;Pretty cool? Let&amp;rsquo;s go a bit deeper&amp;hellip;&lt;/p&gt;&#10;&lt;h3 id="how-can-we-make-this-reusable"&gt;How can we make this reusable?&lt;/h3&gt;&#10;&lt;p&gt;You can create custom actions, which can then be reused. Let&amp;rsquo;s assume that we want to create an &lt;code&gt;app.GetYear&lt;/code&gt; action that retrieves the year from a DateField value:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;compiler&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;registerComponent&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;app.GetYear&amp;#34;&lt;/span&gt;, {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Extend Set, a core MSON component, that sets a&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// property on another component&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Set&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Define the inputs to the custom action&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;schema&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Form&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fields&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;date&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;DateField&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;required&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Omitting the &amp;#34;name&amp;#34; here allows the value&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// to be passed to the next action via&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// &amp;#34;{{arguments}}&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;//&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// name: &amp;#34;&amp;#34;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Use the template query to extract the year&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// from the date&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;$year&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;$toDate&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;{{date}}&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can then use this custom action in a chain of actions:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;form&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;compiler&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;newComponent&lt;/span&gt;({&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Form&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fields&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; ...,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;listeners&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;fields.date.value&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;actions&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Extract the year and pass it to the next action&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;app.GetYear&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;date&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;{{fields.date.value}}&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Use the extracted year to set the year field&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Set&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;fields.year.value&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// {{arguments}} is the output of app.GetYear&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;{{arguments}}&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here is the completed example:&lt;/p&gt;&#10;&#10;&lt;iframe src="https://codesandbox.io/embed/mson-custom-action-cziwj?fontsize=14&amp;hidenavigation=1&amp;theme=dark"&#10; style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"&#10; &#10; allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"&#10; sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"&#10; &gt;&lt;/iframe&gt;&#10;&lt;h3 id="another-example-a-conditional-survey-question"&gt;Another example: a conditional survey question&lt;/h3&gt;&#10;&lt;p&gt;Let&amp;rsquo;s assume that we have a survey question and we want the user to be able to enter a free response when they select &lt;code&gt;Other&lt;/code&gt;:&lt;/p&gt;&#10;&#10;&lt;iframe src="https://codesandbox.io/embed/mson-dynamic-survey-question-713s8?fontsize=14&amp;hidenavigation=1&amp;theme=dark"&#10; style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"&#10; &#10; allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"&#10; sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"&#10; &gt;&lt;/iframe&gt;&#10;&lt;p&gt;We define the form and hide the &lt;code&gt;covidOther&lt;/code&gt; text field by default:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Form&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fields&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;covid&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;SelectField&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;label&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;My government&amp;#39;s response to COVID-19 was...&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fullWidth&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;options&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; { &lt;span style="color:#a6e22e"&gt;label&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Good&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;good&amp;#34;&lt;/span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; { &lt;span style="color:#a6e22e"&gt;label&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Bad&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;bad&amp;#34;&lt;/span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; { &lt;span style="color:#a6e22e"&gt;label&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Other&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;other&amp;#34;&lt;/span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;covidOther&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;TextField&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;label&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Please explain&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;multiline&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;hidden&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If the user selects &lt;code&gt;Other&lt;/code&gt;, we toggle the &lt;code&gt;hidden&lt;/code&gt; boolean property of the &lt;code&gt;covidOther&lt;/code&gt; field. The &lt;code&gt;$ne&lt;/code&gt; operator is shorthand for &lt;em&gt;not equal&lt;/em&gt;.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;listeners&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;fields.covid.value&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;actions&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Set&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;fields.covidOther.hidden&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;$ne&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;{{fields.covid.value}}&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;other&amp;#34;&lt;/span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="why-doesnt-mson-support-custom-js-in-templates"&gt;Why doesn&amp;rsquo;t MSON support custom JS in templates?&lt;/h3&gt;&#10;&lt;p&gt;By now, you can probably see the power of Template Queries, but you may be wondering why MSON doesn&amp;rsquo;t support custom JavaScript. To support custom JS in template parameters, it would require breaking two of MSON&amp;rsquo;s core design principles:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;Compilation by instantiation&lt;/strong&gt; - Components are &lt;em&gt;compiled&lt;/em&gt; into JS objects by simply instantiating a JS object and setting the props dynamically. This method of &lt;em&gt;compilation&lt;/em&gt; allows us to avoid a transpilation step and makes it much easier to dynamically modify components.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;strong&gt;Serialization &amp;amp; deserialization without &lt;code&gt;eval()&lt;/code&gt;&lt;/strong&gt; - Components can be serialized using &lt;code&gt;JSON.stringfy()&lt;/code&gt; and stored practically anywhere. Moreover, components can be deserialized by dynamically compiling the result of &lt;code&gt;JSON.parse()&lt;/code&gt;. As a result, raw JS (in a string), including JS template literals, are not supported as deserializing such JS would require the use of &lt;code&gt;eval()&lt;/code&gt; or &lt;code&gt;new Function()&lt;/code&gt;, which would expose a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval#never_use_eval!"&gt;XSS vulnerability&lt;/a&gt; and add significant performance issues.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;You can read more about this reasoning &lt;a href="https://github.com/redgeoff/mson/blob/master/DESIGN.md#why-doesnt-mson-support-custom-js-in-template-parameters"&gt;here&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h3 id="wrap-it-up"&gt;Wrap It Up!&lt;/h3&gt;&#10;&lt;p&gt;Template Queries add a ton of capability to &lt;a href="https://github.com/redgeoff/mson"&gt;MSON&lt;/a&gt; by providing another powerful way of customizing your &lt;a href="https://github.com/redgeoff/mson"&gt;MSON&lt;/a&gt; components. And, they can be particularly useful in minimizing the code it takes to conditionally chain a series of actions.&lt;/p&gt;&#10;&lt;p&gt;If you liked this post, please give it a like or two. Happy building!&lt;/p&gt;&#10;</description></item><item><title>How to autogenerate forms in React and Material-UI with MSON</title><link>https://redgeoff.com/posts/mson-react-material-ui-form/</link><pubDate>Mon, 22 Oct 2018 00:00:00 +0000</pubDate><guid>https://redgeoff.com/posts/mson-react-material-ui-form/</guid><description>&#10; &lt;figure class="image"&gt;&#10; &lt;a href="https://redgeoff.github.io/mson-react"&gt;&#10; &lt;img src="https://redgeoff.com/posts/mson-react-material-ui-form/demo.png" alt="MSON Demo" /&gt;&#10; &lt;/a&gt;&#10; &#10; &lt;/figure&gt;&#10;&#10;&lt;p&gt;Implementing great forms can be a real time-waster. With just a few lines of JSON, you can use &lt;a href="https://github.com/redgeoff/mson"&gt;MSON&lt;/a&gt; to generate forms that perform real-time validation and have a consistent layout. And, &lt;a href="https://github.com/redgeoff/mson"&gt;MSON&lt;/a&gt; comes with a bunch of cool stuff like date pickers, masked fields and field collections.&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt;: this post is geared towards those wishing to use Material-UI with React. Future versions of MSON will support other rendering layers.&lt;/p&gt;&#10;&lt;h3 id="what-the-heck-is-mson"&gt;What the heck is MSON?&lt;/h3&gt;&#10;&lt;p&gt;&lt;a href="https://github.com/redgeoff/mson"&gt;MSON&lt;/a&gt; is a declarative language that has the capabilities of an object-oriented language. The MSON compiler allows you to generate apps from JSON. The ultimate goal of MSON is to allow anyone to develop software visually. You can also use pieces of MSON to turbo charge your form development.&lt;/p&gt;&#10;&lt;h3 id="a-basic-form-generated-with-mson"&gt;A Basic Form Generated with MSON&lt;/h3&gt;&#10;&lt;p&gt;Simply declare your form in JSON. Then let the MSON compiler and UI-rendering layer autogenerate your UI:&lt;/p&gt;&#10;&#10;&lt;iframe src="https://codesandbox.io/embed/00ql22mx8w?fontsize=14&amp;hidenavigation=1&amp;theme=dark"&#10; style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"&#10; &#10; allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"&#10; sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"&#10; &gt;&lt;/iframe&gt;&#10;&lt;p&gt;Did you try submitting the form without filling in any values? Did you notice how real-time validation is automatically baked in?&lt;/p&gt;&#10;&lt;p&gt;Now, let’s take a closer look at what is happening. The first block of code contains a JSON definition that describes the fields in the form:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;definition&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Form&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fields&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;heading&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Text&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;text&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;# Form using [MSON](https://github.com/redgeoff/mson)&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;fullName&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;PersonFullNameField&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;required&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;birthday&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;DateField&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;label&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Birthday&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;required&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;phone&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;PhoneField&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;label&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Phone&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;submit&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;ButtonField&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;label&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Submit&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;type&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;submit&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;icon&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Send&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This code adds the following fields to your form:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;The &lt;em&gt;Text&lt;/em&gt; component displays some &lt;a href="https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet"&gt;markdown&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;The &lt;em&gt;PersonNameField&lt;/em&gt; is used to capture a person’s first and last names&lt;/li&gt;&#10;&lt;li&gt;The &lt;em&gt;DateField&lt;/em&gt; allows a user to choose a date using a slick date picker&lt;/li&gt;&#10;&lt;li&gt;The &lt;em&gt;PhoneField&lt;/em&gt; uses an input mask and country codes to guide the user when entering a phone number&lt;/li&gt;&#10;&lt;li&gt;The &lt;em&gt;SubmitField&lt;/em&gt; contains a &lt;em&gt;Send&lt;/em&gt; icon and allows the user to submit the form via a click or by pressing enter&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;Now, let’s take a look at the code that renders the component and handles the submit event:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;ReactDOM&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;render&lt;/span&gt;(&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;Component&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;definition&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{&lt;span style="color:#a6e22e"&gt;definition&lt;/span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;onSubmit&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{({ &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt; }) =&amp;gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;alert&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;JSON&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;stringify&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;getValues&lt;/span&gt;()));&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;/&amp;gt;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; document.&lt;span style="color:#a6e22e"&gt;getElementById&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;root&amp;#34;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That’s it!? Yup! The &lt;a href="https://github.com/redgeoff/mson-react"&gt;mson-react&lt;/a&gt; layer &lt;strong&gt;automatically&lt;/strong&gt; knows how to render the form component. It uses &lt;a href="https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern"&gt;pub/sub&lt;/a&gt; and &lt;a href="https://reactjs.org/docs/react-api.html#reactpurecomponent"&gt;Pure Components&lt;/a&gt; to keep the rendering up-to-date.&lt;/p&gt;&#10;&lt;p&gt;When there is no validation error and the user clicks the submit button, an event with the name equal to the button’s name is emitted. In our case, this event is called &lt;em&gt;submit&lt;/em&gt;. Therefore, we can define a handler using the &lt;em&gt;onSubmit&lt;/em&gt; property. To keep things simple, we just alert the user of the entered values. Typically you want to do something like contact an API, redirect, etc…&lt;/p&gt;&#10;&lt;h3 id="basic-form-2"&gt;Basic Form 2&lt;/h3&gt;&#10;&lt;p&gt;Now, let’s go a bit deeper into the CRUD with a different example:&lt;/p&gt;&#10;&#10;&lt;iframe src="https://codesandbox.io/embed/n10q2y7ojj?fontsize=14&amp;hidenavigation=1&amp;theme=dark"&#10; style="width:100%; height:500px; border:0; border-radius: 4px; overflow:hidden;"&#10; &#10; allow="accelerometer; ambient-light-sensor; camera; encrypted-media; geolocation; gyroscope; hid; microphone; midi; payment; usb; vr; xr-spatial-tracking"&#10; sandbox="allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts"&#10; &gt;&lt;/iframe&gt;&#10;&lt;p&gt;The first thing you may notice are the &lt;a href="https://github.com/redgeoff/mson#validators"&gt;validators&lt;/a&gt; in the definition:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;validators&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;where&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;fields.email.value&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;nope@example.com&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;field&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;email&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;must not be {{fields.email.value}}&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Each field has a default set of &lt;a href="https://github.com/redgeoff/mson#validators"&gt;validators&lt;/a&gt;, e.g. the &lt;em&gt;EmailField&lt;/em&gt; ensures that email addresses are in a valid format. You can also extend these validators for a particular field or even for an entire form. For example, you can prevent the user from entering &lt;em&gt;nope@example.com&lt;/em&gt;.&lt;/p&gt;&#10;&lt;p&gt;Next, let’s take a look at the code that loads the initial values when the component is mounted:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;onMount&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{({ &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt; }) =&amp;gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Load any initial data, e.g. from an API&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;setValues&lt;/span&gt;({&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;id&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;abc123&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;firstName&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Bob&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;lastName&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Marley&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;email&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;bob@example.com&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This code could just as easily be replaced by code that retrieves the values from some API asynchronously.&lt;/p&gt;&#10;&lt;p&gt;Finally, we use a more sophisticated event listener to handle the submit action. In a real-world app, you’d probably want to send a request to an API to save the data. You would receive a response from this API. If you receive an error, e.g. the email address is already in use, you can present this error to the user:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;onSubmit&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;{({ &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt; }) =&amp;gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// TODO: Contact some API with the data&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;submitting&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;getValues&lt;/span&gt;());&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Simulate response from API saying that email address is already in use and report this&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// error to the user&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;get&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;fields.email.value&amp;#34;&lt;/span&gt;) &lt;span style="color:#f92672"&gt;===&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;taken@example.com&amp;#34;&lt;/span&gt;) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;set&lt;/span&gt;({ &lt;span style="color:#e6db74"&gt;&amp;#34;fields.email.err&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;already in use&amp;#34;&lt;/span&gt; });&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; } &lt;span style="color:#66d9ef"&gt;else&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Everything was successful so redirect, show confirmation, etc...&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="live-demo"&gt;&lt;a href="https://redgeoff.com/mson-react/"&gt;Live Demo&lt;/a&gt;&lt;/h3&gt;&#10;&lt;p&gt;This post only touches on a small piece of what you can do using &lt;a href="https://github.com/redgeoff/mson"&gt;MSON&lt;/a&gt;. Because &lt;a href="https://github.com/redgeoff/mson"&gt;MSON&lt;/a&gt; is a full-featured language, you can declare all types of cool components. If you are interested in seeing more live examples, check out the &lt;a href="https://redgeoff.com/mson-react/"&gt;live demo&lt;/a&gt;.&lt;/p&gt;&#10;&#10; &lt;figure class="image"&gt;&#10; &lt;a href="https://redgeoff.github.io/mson-react"&gt;&#10; &lt;img src="https://redgeoff.com/posts/mson-react-material-ui-form/demo.png" alt="MSON Demo" /&gt;&#10; &lt;/a&gt;&#10; &#10; &lt;/figure&gt;&#10;&#10;&lt;h3 id="wrap-it-up"&gt;Wrap It Up!&lt;/h3&gt;&#10;&lt;p&gt;If you are using React and Material-UI, you can speed up your app development by autogenerating your forms from JSON. This can be particularly useful if you need to bootstrap an app quickly and don’t want to have to worry about building a UI from scratch.&#10;If you liked this post, please give it a clap or two. Happy &lt;a href="https://github.com/redgeoff/mson"&gt;autogenerating!&lt;/a&gt;&lt;/p&gt;&#10;</description></item><item><title>Creating a New Programming Language That Will Allow Anyone to Make Software</title><link>https://redgeoff.com/posts/everyone-can-make-software/</link><pubDate>Tue, 31 Jul 2018 00:00:00 +0000</pubDate><guid>https://redgeoff.com/posts/everyone-can-make-software/</guid><description>&lt;p&gt;I’ve spent the last 7 months working on a new programming language called MSON. Here is a nerdy post about why I did it, how it works and where I want to take it.&lt;/p&gt;&#10;&lt;div style="text-align: center;"&gt;&#10; &lt;figure class="image"&gt;&#10; &#10; &lt;img src="https://redgeoff.com/posts/everyone-can-make-software/mson-logo.png" alt="Everyone Can Make Software" /&gt;&#10; &#10; &#10; &lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="there-are-already-a-bazillion-programming-languages-why-do-we-need-another"&gt;There are already a bazillion programming languages, why do we need another?&lt;/h3&gt;&#10;&lt;p&gt;Software continues to eat the world and software development tools are evolving at lightning speeds, but it stills takes an in-depth knowledge of a programming language to develop software. Sure, there are millions of open-source projects that can be mashed together and robust cloud platforms that make the process easier, but it takes considerable skill to get all of these technologies working together. When I envision the future, I imagine the average person being able to make software, just like how the average person can create a spreadsheet today.&lt;/p&gt;&#10;&lt;p&gt;Creating a spreadsheet has become commonplace and this is largely a result of the intuitive user interface (UI) in MS Excel. Developing software; however, still remains something that is only done by a relative few and using complex programming languages. One could argue that it is somewhat trivial to create a UI that generates code, but it is almost infeasible to reverse the process and digest coded logic, as most programming languages have an almost infinite number of ways of representing logic. In other words, we don’t usually know how a piece of software will execute until we actually execute it. Creating a truly intuitive way of developing software requires a new paradigm, a new language that is easily read, modified and written by a UI.&lt;/p&gt;&#10;&lt;p&gt;It was back in 2003, when PHP was &lt;em&gt;the hotness&lt;/em&gt;, that I started constructing a framework called FORMfields. At the time, FORMfields was pretty cool as you could create forms with PHP objects and then FORMfields would automatically generate the corresponding HTML, CSS and JS. Soon after creating FORMfields, I co-founded a company called GoExpo that produces event management software that is powered by FORMfields. GoExpo grew to process millions of dollars in booth sales annually for some of the largest trade shows in the world and this was a testament to the effectiveness of FORMfields.&lt;/p&gt;&#10;&lt;p&gt;Over the years, FORMfields continued to be essential in allowing our team to create a consistent UI that could be modified quickly (remember, these were the days before Twitter Bootstrap and more modern UI frameworks). However, as browsers were becoming more capable, more logic was moving into the front end; and FORMfields, which is written in PHP, only runs in the back end.&lt;/p&gt;&#10;&lt;p&gt;Fast forward to 2018, where JavaScript is the workhorse of the web and the difference between the front end and back end can feel like night and day. GoExpo has been acquired and I now have time to revisit the notion of FORMfields with a more modern stack. I want the next generation of FORMfields to run in both the browser and the back end, be open source and become the foundation of something that can be used by the masses.&lt;/p&gt;&#10;&lt;p&gt;This line of thinking has given birth to &lt;a href="https://github.com/redgeoff/mson"&gt;MSON&lt;/a&gt;, an extensible declarative language that can be easily manipulated by a UI. MSON is comprised of just a few building blocks, yet it is as powerful as its non-declarative counterparts.&lt;/p&gt;&#10;&lt;p&gt;The purpose of this initial post is to introduce the core principles of the &lt;a href="https://github.com/redgeoff/mson"&gt;MSON&lt;/a&gt; programming language and elaborate on its unique design. I plan on writing a follow-up post later that will explain MSON from a layman’s perspective. This post; however, is rather technical as it is about the developer-layer stuff in MSON and not about the designed-for-all UI layer I’ll be writing on top. I will be elaborating on the following topics:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;Declarative Syntax&lt;/li&gt;&#10;&lt;li&gt;Components, Validators, Events &amp;amp; Listeners&lt;/li&gt;&#10;&lt;li&gt;Access Control&lt;/li&gt;&#10;&lt;li&gt;Inheritance, Template Parameters &amp;amp; Composition&lt;/li&gt;&#10;&lt;li&gt;Aggregate Components&lt;/li&gt;&#10;&lt;li&gt;Schemas and Self Documentation&lt;/li&gt;&#10;&lt;li&gt;User-Defined JavaScript Components&lt;/li&gt;&#10;&lt;li&gt;Using MSON in Any JavaScript Code&lt;/li&gt;&#10;&lt;li&gt;Reusing MSON Code Throughout the Full Stack&lt;/li&gt;&#10;&lt;li&gt;In/Out Properties&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;I will then finish with a statement about how I plan to monetize MSON and make it a self-sustaining project. I’d love to hear what you think.&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h3 id="language-principles"&gt;Language Principles&lt;/h3&gt;&#10;&lt;h4 id="declarative-syntax"&gt;Declarative Syntax&lt;/h4&gt;&#10;&lt;p&gt;MSON is short for Model Script Object Notation, which is intentionally similar to JSON (JavaScript Object Notation). In fact, MSON is a subset of JSON, so if you know JSON then you know the syntax of MSON!&lt;/p&gt;&#10;&lt;p&gt;Declarative languages are much easier for software to read and write as they define what the software must do without stating exactly how to do it. And JSON is a great foundation on which to build. It contains just a few main constructs, is ubiquitous and supported by a vast ecosystem.&lt;/p&gt;&#10;&lt;h4 id="components"&gt;Components&lt;/h4&gt;&#10;&lt;p&gt;The smallest building block in MSON is called a component. Components maintain state and can also control presentation and are very similar to the components now commonplace in most web frameworks. Components can inherit, contain or wrap other components. The rendering layer supports plugins for different environments and the default plugin supports React and Material-UI. Use of the rendering layer is optional, so components can be used on both the front end and back end.&lt;/p&gt;&#10;&lt;p&gt;A simple form component used to collect a name and email address could look like:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;MyForm&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Form&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fields&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;name&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;TextField&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;label&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Name&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;required&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;email&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;EmailField&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;label&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Email&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;submit&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;ButtonField&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;label&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Submit&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;icon&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;CheckCircle&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And when it renders it looks like:&lt;/p&gt;&#10;&lt;div style="text-align: center;"&gt;&#10; &lt;figure class="image"&gt;&#10; &#10; &lt;img src="https://redgeoff.com/posts/everyone-can-make-software/form.png" alt="Rendered Form" /&gt;&#10; &#10; &#10; &lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;And, real-time validation is automatically baked in:&lt;/p&gt;&#10;&#10; &lt;figure class="image"&gt;&#10; &#10; &lt;img src="https://redgeoff.com/posts/everyone-can-make-software/validation.png" alt="Real-time Form Validation" /&gt;&#10; &#10; &#10; &lt;/figure&gt;&#10;&#10;&lt;p&gt;The majority of the remaining examples in this post will focus on form components, as they are simple to visualize, but MSON can support any type of component, e.g. menus, snackbars, redirects, etc… In addition, you can use JavaScript to create user-defined components that can pretty much do anything else you can imagine.&lt;/p&gt;&#10;&lt;h3 id="validators"&gt;Validators&lt;/h3&gt;&#10;&lt;p&gt;Each field has a default set of validators, e.g. the EmailField ensures that email addresses are in a valid format. You can also extend these validators for a particular field or even for an entire form.&lt;/p&gt;&#10;&lt;p&gt;For example, you can prevent the user from entering &lt;em&gt;nope@example.com&lt;/em&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;MyForm&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Form&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fields&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; ...,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;validators&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;where&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;fields.email.value&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;nope@example.com&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;field&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;email&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;must not be {{fields.email.value}}&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Template parameters like &lt;code&gt;{{fields.email.value}}&lt;/code&gt; can be used to inject the values of fields. And, you can use any &lt;a href="https://docs.mongodb.com/manual/reference/operator/query/"&gt;MongoDB-style query&lt;/a&gt; in the &lt;code&gt;where&lt;/code&gt;. For example, if you had &lt;code&gt;password&lt;/code&gt; and &lt;code&gt;retypePassword&lt;/code&gt; fields, you could ensure that they are equivalent with:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;where&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;retypePassword.fields.value&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;$ne&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;{{fields.password.value}}&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;error&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; ...&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="events--listeners"&gt;Events &amp;amp; Listeners&lt;/h4&gt;&#10;&lt;p&gt;Changes to properties in a component generate events and you can create listeners that respond to these events with actions. There are basic actions that set, emit, email, contact APIs, etc… and custom actions can also be built using JavaScript.&lt;/p&gt;&#10;&lt;p&gt;The following example sets the value of the &lt;code&gt;email&lt;/code&gt; field based on the value supplied in the &lt;code&gt;name&lt;/code&gt; field when the user clicks the &lt;code&gt;submit&lt;/code&gt; button:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;MyForm&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Form&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fields&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; ...,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;validators&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; ...,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;listeners&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;submit&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;actions&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Set&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;fields.email.value&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;{{fields.name.value}}@example.com&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can also make this action conditional, e.g. only set the &lt;code&gt;email&lt;/code&gt; if it is blank:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;listeners&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;submit&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;actions&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Set&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;fields.email&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;$or&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;fields.email.value&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;{{fields.name.value}}@example.com&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And sometimes we want to nest actions so that a condition is met before all actions are executed:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;listeners&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;submit&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;actions&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Action&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;fields.email&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;$or&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;actions&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Set&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;fields.email.value&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;{{fields.name.value}}@example.com&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Set&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;fields.name.value&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;{{fields.name.value}} Builder&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="access-control"&gt;Access Control&lt;/h4&gt;&#10;&lt;p&gt;Unlike most programming languages, access control is a first-class citizen in MSON, so its easy to use without a lot of work. Access can be restricted at the form or field layers for the create, read, update and archive operations. (MSON is designed to encourage data archiving instead of deletion so that data can be restored when it is accidentally archived. You can, of course, permanently delete data when needed).&lt;/p&gt;&#10;&lt;p&gt;Each user can have any number of user-defined roles and access is then limited to users with specified roles. There is also a system role of &lt;code&gt;owner&lt;/code&gt; that is defined for the owner of the data. Field-layer access is checked first and if it is missing it will cascade to checking the form-layer access. When the access is undefined at the form layer (and not defined at the field-layer), all users have access.&lt;/p&gt;&#10;&lt;p&gt;Here is an example configuration:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;MyForm&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Form&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fields&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; ...,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;validators&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; ...,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;listeners&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; ...,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;access&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;form&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;create&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#39;admin&amp;#39;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;manager&amp;#39;&lt;/span&gt;],&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;read&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#39;admin&amp;#39;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;employee&amp;#39;&lt;/span&gt;],&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;update&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#39;admin&amp;#39;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;owner&amp;#39;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#39;manager&amp;#39;&lt;/span&gt;],&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;archive&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#39;admin&amp;#39;&lt;/span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fields&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;create&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#39;admin&amp;#39;&lt;/span&gt;],&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;update&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#39;owner&amp;#39;&lt;/span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Among other things, only users with the &lt;code&gt;admin&lt;/code&gt; or &lt;code&gt;manager&lt;/code&gt; roles can create records. In addition, only owners of a record can modify the &lt;code&gt;name&lt;/code&gt;.&lt;/p&gt;&#10;&lt;h4 id="inheritance"&gt;Inheritance&lt;/h4&gt;&#10;&lt;p&gt;Inheritance is used to add additional functionality to a component. For example, we can extend &lt;code&gt;MyForm&lt;/code&gt; and add a phone number:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;MyFormExtended&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;MyForm&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fields&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;phone&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;PhoneField&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;label&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Phone Number&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;before&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;submit&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can define new validators, listeners, access, etc… at this new layer. For example, we can pre-populate some data, lay out all fields on the same line and disable the email field by creating a listener for the &lt;code&gt;create&lt;/code&gt; event:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;MyFormExtended&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;MyForm&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fields&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; ...,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;listeners&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;create&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;actions&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Set&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;value&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Bob Builder&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;email&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;bob@example.com&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;phone&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;(206)-123-4567&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Set&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;fields.name.block&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Set&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;fields.email.block&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Set&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;fields.email.disabled&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;which would render as:&lt;/p&gt;&#10;&#10; &lt;figure class="image"&gt;&#10; &#10; &lt;img src="https://redgeoff.com/posts/everyone-can-make-software/bob-builder.png" alt="Bob Builder Form" /&gt;&#10; &#10; &#10; &lt;/figure&gt;&#10;&#10;&lt;h4 id="template-parameters"&gt;Template Parameters&lt;/h4&gt;&#10;&lt;p&gt;Template parameters are helpful when creating reusable components as they allow you to make pieces of your component dynamic. For example, let’s say that we want our first field and the label of our second field to be dynamic:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;MyTemplatedForm&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Form&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fields&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;{{firstField}}&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;secondField&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;label&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;{{secondFieldLabel}}&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;EmailField&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;we can then extend &lt;code&gt;MyTemplatedForm&lt;/code&gt; and fill in the pieces:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;MyFilledTemplatedForm&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;MyTemplatedForm&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;firstField&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;firstName&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;TextField&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;label&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;First Name&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;secondFieldLabel&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Email Address&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="composition"&gt;Composition&lt;/h4&gt;&#10;&lt;p&gt;The &lt;code&gt;componentToWrap&lt;/code&gt; property allows you to wrap components, enabling your reusable components to transform any component. For example, we can use composition to create a reusable component that adds a phone number:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;AddPhone&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Form&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;componentToWrap&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;{{baseForm}}&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fields&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;phone&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;PhoneField&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;label&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Phone Number&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;before&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;submit&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;and then pass in a component to be wrapped:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;MyFormWithPhone&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;AddPhone&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;baseForm&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;MyForm&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can even extend wrapped components, paving the way for a rich ecosystem of aggregate components comprised of other components.&lt;/p&gt;&#10;&lt;h4 id="aggregate-components"&gt;Aggregate Components&lt;/h4&gt;&#10;&lt;p&gt;MSON ships with a number of aggregate components such as the &lt;code&gt;RecordEditor&lt;/code&gt; and &lt;code&gt;RecordList&lt;/code&gt;, which make it easy to turn your form components into editable UIs with just a few lines of code.&lt;/p&gt;&#10;&lt;p&gt;Let’s define a user component:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;MyAccount&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Form&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fields&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;firstName&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;TextField&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;label&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;First Name&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }, &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;lastName&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;TextField&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;label&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Last Name&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;email&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;EmailField&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;label&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Email&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;we can then use a &lt;code&gt;RecordEditor&lt;/code&gt; to allow the user to edit her/his account:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;MyAccountEditor&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;RecordEditor&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;baseForm&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;MyAccount&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;label&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Account&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;rendering as:&lt;/p&gt;&#10;&#10; &lt;figure class="image"&gt;&#10; &#10; &lt;img src="https://redgeoff.com/posts/everyone-can-make-software/builder-display.png" alt="RecordEditor Detail" /&gt;&#10; &#10; &#10; &lt;/figure&gt;&#10;&#10;&lt;p&gt;and if you were to click the &lt;em&gt;Edit&lt;/em&gt; button you’d see:&lt;/p&gt;&#10;&#10; &lt;figure class="image"&gt;&#10; &#10; &lt;img src="https://redgeoff.com/posts/everyone-can-make-software/record-editor-edit.png" alt="RecordEditor Edit" /&gt;&#10; &#10; &#10; &lt;/figure&gt;&#10;&#10;&lt;p&gt;You can also use the &lt;code&gt;RecordList&lt;/code&gt; to display an editable list of these accounts:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;MyAccountsList&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;RecordList&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;label&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Accounts&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;baseFormFactory&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Factory&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;product&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; { &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;MyAccount&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;rendering as:&lt;/p&gt;&#10;&#10; &lt;figure class="image"&gt;&#10; &#10; &lt;img src="https://redgeoff.com/posts/everyone-can-make-software/record-list-edit.png" alt="RecordList Edit" /&gt;&#10; &#10; &#10; &lt;/figure&gt;&#10;&#10;&lt;h4 id="schemas-and-self-documentation"&gt;Schemas and Self Documentation&lt;/h4&gt;&#10;&lt;p&gt;Schemas must be defined for all components, which means that MSON is strongly typed. For example, a schema that defines boolean and date properties may look like:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;MyComponent&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Component&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;schema&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Form&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fields&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;hidden&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;BooleanField&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;help&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Whether or not the component is hidden&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;updatedAt&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;DateTimeField&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;required&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;, &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;help&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;When the component was updated&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Schemas can also contain documentation via &lt;code&gt;help&lt;/code&gt; properties, which means that components are self-documenting! In addition, schemas are inherited and can be overwritten to allow for more or even less constraints.&lt;/p&gt;&#10;&lt;h4 id="user-defined-javascript-components"&gt;User-Defined JavaScript Components&lt;/h4&gt;&#10;&lt;p&gt;The MSON compiler is written in JavaScript and can run in both the browser and in Node.js. As such, you can use any custom JS, including external JS libraries, to create your own components.&lt;/p&gt;&#10;&lt;p&gt;For example, here is a component that uses &lt;a href="https://momentjs.com/"&gt;Moment.js&lt;/a&gt; to set a &lt;code&gt;currentDay&lt;/code&gt; property to the current day:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;compiler&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;mson/lib/compiler&amp;#39;&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Component&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;mson/lib/component&amp;#39;&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Form&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;mson/lib/form&amp;#39;&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;TextField&lt;/span&gt; } &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;mson/lib/fields&amp;#39;&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;moment&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;moment&amp;#39;&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;MyComponent&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;extends&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Component&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// className is needed as JS minification strips the constructor name&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;className&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;MyComponent&amp;#39;&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;create&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;props&lt;/span&gt;) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;super&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;create&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;props&lt;/span&gt;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;set&lt;/span&gt;({&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Define a currentDay property&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;schema&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Form&lt;/span&gt;(&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fields&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;TextField&lt;/span&gt;({&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;currentDay&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ),&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Default currentDay&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;currentDay&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;moment&lt;/span&gt;().&lt;span style="color:#a6e22e"&gt;format&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;dddd&amp;#39;&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;compiler&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;registerComponent&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;MyComponent&amp;#39;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;MyComponent&lt;/span&gt;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And then &lt;code&gt;MyComponent&lt;/code&gt; can be used in any MSON code.&lt;/p&gt;&#10;&lt;p&gt;You can also do things like define custom asynchronous actions, e.g. one that POSTs form data:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;compiler&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;mson/lib/compiler&amp;#39;&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Action&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;mson/lib/actions/action&amp;#39;&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Form&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;mson/lib/form&amp;#39;&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;TextField&lt;/span&gt; } &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;mson/lib/fields&amp;#39;&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;MyAction&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;extends&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Action&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;className&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;MyAction&amp;#39;&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;create&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;props&lt;/span&gt;) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;super&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;create&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;props&lt;/span&gt;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;set&lt;/span&gt;({&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;schema&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Form&lt;/span&gt;(&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fields&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;TextField&lt;/span&gt;({&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;foo&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; )&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; });&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;async&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;act&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;props&lt;/span&gt;) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;form&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;FormData&lt;/span&gt;();&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;form&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;append&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;foo&amp;#39;&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;get&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;foo&amp;#39;&lt;/span&gt;));&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;account&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;props&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;form&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;append&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;firstName&amp;#39;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;account&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;get&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;firstName&amp;#39;&lt;/span&gt;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;form&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;append&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;lastName&amp;#39;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;account&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;get&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;lastName&amp;#39;&lt;/span&gt;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;form&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;append&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;email&amp;#39;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;account&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;get&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;email&amp;#39;&lt;/span&gt;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fetch&lt;/span&gt;({&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;https://api.example.com&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;method&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;POST&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;body&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;form&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;compiler&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;registerComponent&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#39;MyAction&amp;#39;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;MyAction&lt;/span&gt;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And then you can use this in your MSON code:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;MyAccountExtended&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;MyAccount&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;listeners&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;submit&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;actions&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;MyAction&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;foo&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;bar&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="using-mson-in-any-javascript-code"&gt;Using MSON in Any JavaScript Code&lt;/h4&gt;&#10;&lt;p&gt;There is always parity between compiled and uncompiled components so that the same feature set is supported by both compiled and uncompiled code. For example, our same &lt;code&gt;MyAccount&lt;/code&gt; component can also be defined as:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Form&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;mson/lib/form&amp;#39;&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;TextField&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;Email&lt;/span&gt; } &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;mson/lib/fields&amp;#39;&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;MyAccount&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;extends&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Form&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;className&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;MyAccount&amp;#39;&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;create&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;props&lt;/span&gt;) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;super&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;create&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;props&lt;/span&gt;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;set&lt;/span&gt;({&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fields&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;TextField&lt;/span&gt;({&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;firstName&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;label&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;First Name&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }),&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;TextField&lt;/span&gt;({&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;lastName&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;label&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Last Name&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }),&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;EmailField&lt;/span&gt;({&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;email&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;label&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Email&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In fact, converting MSON code to this type of code is basically what the compiler does. Although, the compiler doesn’t actually transpile MSON to JS, it merely instantiates JS code based on the MSON definitions.&lt;/p&gt;&#10;&lt;p&gt;Since all MSON code can be compiled to JS code, you can use MSON components in any JS code. For example, you can set some fields and validate the data:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;compiler&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;mson/lib/compiler&amp;#39;&lt;/span&gt;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Compile the MyAccount component&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;MyAccount&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;compiler&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;compile&lt;/span&gt;({&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;MyAccount&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Instantiate the JS class with a default value&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;const&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;myAccount&lt;/span&gt; &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;new&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;MyAccount&lt;/span&gt;({&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Default values&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;firstName&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Bob&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Set the remaining data&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;myAccount&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;set&lt;/span&gt;({&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;lastName&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Builder&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;email&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;invalid-email@&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;});&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// Make sure the values are valid&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;myAccount&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;validate&lt;/span&gt;();&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (&lt;span style="color:#a6e22e"&gt;myAccount&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;hasErr&lt;/span&gt;()) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;console&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;myAccount&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;getErrs&lt;/span&gt;());&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In other words, you can use MSON in your existing JS code to save time writing complex code. By declaring components in MSON, you’ll remove a lot of boilerplate code and reduce the possibility of bugs. You’ll also have code that has a standard structure and is framework agnostic. And this code doesn’t add any unneeded frameworks or back-end dependencies to your codebase.&lt;/p&gt;&#10;&lt;h4 id="reusing-mson-code-throughout-the-full-stack"&gt;Reusing MSON Code Throughout the Full Stack&lt;/h4&gt;&#10;&lt;p&gt;MSON components can be shared by both the front end and back end, allowing for key logic to be written once and then reused. For example, the same form validation rules can be enforced in the browser and by your back-end API.&lt;/p&gt;&#10;&lt;p&gt;Moreover, actions can be limited to the &lt;code&gt;backEnd&lt;/code&gt; or &lt;code&gt;frontEnd&lt;/code&gt;, so that the same component can adjust according to the host environment. For example, you may want a contact form to send an email to the user when it is used on the back end, but only display a &lt;a href="https://material-ui.com/components/snackbars/"&gt;snackbar&lt;/a&gt; on the front end:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Form&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fields&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;email&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;EmailField&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;label&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Email&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;message&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;TextField&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;label&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Message&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Submit&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;ButtonField&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;label&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Submit&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ],&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;listeners&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;submit&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;actions&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Send an email on the back end&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Email&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;layer&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;backEnd&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;{{fields.email.value}}&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;to&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;noreply@example.com&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;subject&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;My message&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;body&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;{{fields.message.value}}&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Detach so that user doesn&amp;#39;t have to wait for email&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// to send&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;detach&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Display a message to the user on the front end&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Snackbar&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;layer&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;frontEnd&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;message&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Thanks for the message&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 id="inout-properties"&gt;In/Out Properties&lt;/h4&gt;&#10;&lt;p&gt;Sometimes you want the presence of data, but don’t want it to be written or read from the back end. For example, your default user component may not allow for the password to be read or edited:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;MyUser&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Form&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;fields&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;name&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;TextField&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;label&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Name&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;email&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;EmailField&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;label&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Email&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;password&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;PasswordField&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;label&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Password&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;hidden&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;in&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;out&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;However, your &lt;code&gt;EditPasswordForm&lt;/code&gt; may need to allow such access:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:2;-o-tab-size:2;tab-size:2;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;EditPasswordForm&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;MyUser&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;listeners&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;create&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;actions&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Hide all fields&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Set&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;hidden&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Show password field&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Set&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;fields.password.hidden&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#75715e"&gt;// Allow user to write password to the back end&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;component&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;Set&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;fields.password.out&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;value&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;&#10;&lt;h3 id="next-steps"&gt;Next Steps&lt;/h3&gt;&#10;&lt;p&gt;Before publishing this post, I had showed MSON to only a handful of people. This may seem odd for an open-source project, but it was intentional, as I wanted to flesh out the language before doing a wider release. I didn’t want people to start using something where the building blocks would be changing drastically. Therefore, I started with a basic prototype of the compiler and then started building a sample app written in MSON. This allowed me to use an iterative approach to see what works and what doesn’t work. And after several rewrites of the compiler, thank goodness for unit tests and a patient wife, I’ve been able to implement a full-featured app. I’m sure the language will continue to evolve, but I now feel confident that MSON is a solid foundation on which to build.&lt;/p&gt;&#10;&lt;p&gt;The MSON compiler is completely written in JS and runs in both the browser and on Node.js. The repo is hosted on github at &lt;a href="https://github.com/redgeoff/mson"&gt;https://github.com/redgeoff/mson&lt;/a&gt; — if you like the idea please give it a star.&lt;/p&gt;&#10;&lt;p&gt;I also plan on monetizing this idea by developing out mson.co, which will host a UI that anyone can use to make software. The UI will read, modify and write MSON behind the scenes and a back end will automatically be created based on your component definitions. And for developers, you’ll have the option of using as much of the mson.co stack as you wish, e.g. you could bootstrap your app using the MSON UI Builder and then replace/customize the front end with your favorite technologies (the mson.co back end is exposed via a GraphQL API). Or, you can use MSON on just the front end and create your own custom back-end API.&lt;/p&gt;&#10;&lt;p&gt;I hope you enjoyed reading this post and I would love to hear your thoughts.&lt;/p&gt;&#10;</description></item><item><title>Running a Free VPN Server on AWS</title><link>https://redgeoff.com/posts/running-a-free-vpn-server-on-aws/</link><pubDate>Mon, 24 Jul 2017 07:39:02 -0800</pubDate><guid>https://redgeoff.com/posts/running-a-free-vpn-server-on-aws/</guid><description>&lt;div style="text-align: center;"&gt;&#10; &lt;figure class="image"&gt;&#10; &#10; &lt;img src="https://redgeoff.com/posts/vpn/network-with-vpn.png" alt="Network with VPN" /&gt;&#10; &#10; &#10; &lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;This tutorial will take you through the steps of setting up an EC2 instance that will run the OpenVPN Server. It will then cover how to grant and revoke access through the VPN Server.&lt;/p&gt;&#10;&lt;p&gt;AWS has an awesome firewall built into its core services which can easily be used to make sure that only certain ports are open to the outside world. One extra step that we can take is to run a VPN Server that serves as the gateway to our protected EC2 instances. We can then shutdown direct SSH access to our EC2 instances and also have the freedom to block access to our entire network just by revoking access via our VPN Server. The later is very useful if you need to revoke access for a former employee.&lt;/p&gt;&#10;&lt;h3 id="step-1-create-the-vpn-security-group"&gt;Step 1— Create the VPN Security Group&lt;/h3&gt;&#10;&lt;p&gt;Overview: security groups allow your servers to communicate with each other in a private cloud while exposing specific ports to the world. We are going to create a security group to allow VPN access to our VPN Server. We will assume that all your other EC2 instances are members of the default security group and that the default security group does not allow access from the outside world.&lt;/p&gt;&#10;&lt;p&gt;Log in at &lt;a href="https://aws.amazon.com"&gt;https://aws.amazon.com&lt;/a&gt;, type EC2 in the search box and click on the target to go to the EC2 Dashboard.&lt;/p&gt;&#10;&lt;p&gt;From the EC2 dashboard, click &lt;em&gt;Security Groups&lt;/em&gt;:&lt;/p&gt;&#10;&lt;div style="text-align: center;"&gt;&#10; &lt;figure class="image"&gt;&#10; &#10; &lt;img src="https://redgeoff.com/posts/vpn/security-group.png" alt="Security Groups" /&gt;&#10; &#10; &#10; &lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Click Create &lt;em&gt;Security Group&lt;/em&gt;:&lt;/p&gt;&#10;&lt;div style="text-align: center;"&gt;&#10; &lt;figure class="image"&gt;&#10; &#10; &lt;img src="https://redgeoff.com/posts/vpn/create-security-group.png" alt="Create Security Group" /&gt;&#10; &#10; &#10; &lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Enter a name and description of vpn and specify inbound rules on ports 22, 443, 943 and 1194. Note: the protocol for port 1194 is UDP.&lt;/p&gt;&#10;&lt;div style="text-align: center;"&gt;&#10; &lt;figure class="image"&gt;&#10; &#10; &lt;img src="https://redgeoff.com/posts/vpn/ports.png" alt="Ports" /&gt;&#10; &#10; &#10; &lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Note: if the IP addresses that your team uses are static then you can add yet another layer of security by specifying that IP address range in the Source of your rules. However, you’ll want to leave the Source blank if you want your team to be able to connect from different IPs as they may be working from a hotel, home, cafe, etc…&lt;/p&gt;&#10;&lt;h3 id="step-2--create-the-ec2-instance"&gt;Step 2 — Create the EC2 Instance&lt;/h3&gt;&#10;&lt;p&gt;Return to the EC2 Dashboard and then click &lt;em&gt;Launch Instance&lt;/em&gt;:&lt;/p&gt;&#10;&lt;div style="text-align: center;"&gt;&#10; &lt;figure class="image"&gt;&#10; &#10; &lt;img src="https://redgeoff.com/posts/vpn/create-instance.png" alt="Create EC2 Instance" /&gt;&#10; &#10; &#10; &lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Select Ubuntu (you can of course select almost any other OS that runs OpenVPN, but this tutorial is tailored for Ubuntu)&lt;/p&gt;&#10;&lt;div style="text-align: center;"&gt;&#10; &lt;figure class="image"&gt;&#10; &#10; &lt;img src="https://redgeoff.com/posts/vpn/ubuntu.png" alt="Ubuntu 16 AMI" /&gt;&#10; &#10; &#10; &lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Select t2.nano and click &lt;em&gt;Review and Launch&lt;/em&gt;&lt;/p&gt;&#10;&lt;div style="text-align: center;"&gt;&#10; &lt;figure class="image"&gt;&#10; &#10; &lt;img src="https://redgeoff.com/posts/vpn/t2-nano.png" alt="t2.nano" /&gt;&#10; &#10; &#10; &lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;On the next screen, click &lt;em&gt;Edit security groups&lt;/em&gt;&lt;/p&gt;&#10;&lt;div style="text-align: center;"&gt;&#10; &lt;figure class="image"&gt;&#10; &#10; &lt;img src="https://redgeoff.com/posts/vpn/security-groups.png" alt="Edit security groups" /&gt;&#10; &#10; &#10; &lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Select the &lt;em&gt;vpn&lt;/em&gt; and &lt;em&gt;default security groups&lt;/em&gt; and click &lt;em&gt;Review and Launch&lt;/em&gt;&lt;/p&gt;&#10;&lt;div style="text-align: center;"&gt;&#10; &lt;figure class="image"&gt;&#10; &#10; &lt;img src="https://redgeoff.com/posts/vpn/review-and-launch.png" alt="Review and Launch" /&gt;&#10; &#10; &#10; &lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Click &lt;em&gt;Launch&lt;/em&gt;, choose your key pair and then click &lt;em&gt;Launch Instances&lt;/em&gt;&lt;/p&gt;&#10;&lt;h3 id="step-3--disable-sourcedestination-check"&gt;Step 3 — Disable Source/Destination Check&lt;/h3&gt;&#10;&lt;p&gt;From the list of instances, select the VPN instance and then Networking-&amp;gt;Change Source/Dest. Check from the drop down menu. Then click Yes, Disable. This is needed as otherwise, your VPN server will not be able to connect to your other EC2 instances.&lt;/p&gt;&#10;&lt;div style="text-align: center;"&gt;&#10; &lt;figure class="image"&gt;&#10; &#10; &lt;img src="https://redgeoff.com/posts/vpn/disable-check.png" alt="Disable Source/Destination Check" /&gt;&#10; &#10; &#10; &lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="step-4-create-an-elastic-ip-address"&gt;Step 4— Create an Elastic IP Address&lt;/h3&gt;&#10;&lt;p&gt;Overview: when an EC2 instance is stopped and restarted, the Public IP address changes. We want the IP address of our VPN Server to remain static so we’ll use an Elastic IP Address.&lt;/p&gt;&#10;&lt;p&gt;From the E2c Dashboard, select &lt;em&gt;Elastic IPs&lt;/em&gt;&lt;/p&gt;&#10;&lt;div style="text-align: center;"&gt;&#10; &lt;figure class="image"&gt;&#10; &#10; &lt;img src="https://redgeoff.com/posts/vpn/elastic-ips.png" alt="Elastic IPs" /&gt;&#10; &#10; &#10; &lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Click &lt;em&gt;Allocate new address&lt;/em&gt;&lt;/p&gt;&#10;&lt;div style="text-align: center;"&gt;&#10; &lt;figure class="image"&gt;&#10; &#10; &lt;img src="https://redgeoff.com/posts/vpn/allocate-new-address.png" alt="Allocate New Address" /&gt;&#10; &#10; &#10; &lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Click &lt;em&gt;Allocate&lt;/em&gt; and then &lt;em&gt;Close&lt;/em&gt;.&lt;/p&gt;&#10;&lt;p&gt;Make a note of your Elastic IP address as this will be the Public IP Address of your VPN Server.&lt;/p&gt;&#10;&lt;p&gt;Then select the Elastic IP and click &lt;em&gt;Associate address&lt;/em&gt; from the drop down menu.&lt;/p&gt;&#10;&lt;div style="text-align: center;"&gt;&#10; &lt;figure class="image"&gt;&#10; &#10; &lt;img src="https://redgeoff.com/posts/vpn/associate-address.png" alt="Associate Address" /&gt;&#10; &#10; &#10; &lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Select the EC2 instance you just created and click &lt;em&gt;Associate&lt;/em&gt;.&lt;/p&gt;&#10;&lt;div style="text-align: center;"&gt;&#10; &lt;figure class="image"&gt;&#10; &#10; &lt;img src="https://redgeoff.com/posts/vpn/associate-private-ip.png" alt="Associate Private IP" /&gt;&#10; &#10; &#10; &lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 id="step-5-install-and-configure-the-openvpn-server"&gt;Step 5— Install and Configure the OpenVPN Server&lt;/h3&gt;&#10;&lt;p&gt;SSH into your VPN server:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;$ ssh ubuntu@PUBLIC-IP-OF-VPN-SERVER&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Download our helper scripts and set up a default config:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;$ git clone https://github.com/redgeoff/openvpn-server-vagrant&#10;$ cd openvpn-server-vagrant&#10;$ cp config-default.sh config.sh&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Edit config.sh and enter in your configuration. Note: PUBLIC_IP should be equal to the Elastic IP Address that you created above.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;$ nano config.sh&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Switch to root&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;$ sudo su -&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Update Ubuntu and install OpenVPN. Note: you will be prompted twice and when you do, select &lt;code&gt;Keep the local version currently installed&lt;/code&gt;&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;$ /home/ubuntu/openvpn-server-vagrant/ubuntu.sh&#10;$ /home/ubuntu/openvpn-server-vagrant/openvpn.sh&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;At this point, the OpenVPN Server is running.&lt;/p&gt;&#10;&lt;h3 id="step-6--add-the-route"&gt;Step 6 — Add the Route&lt;/h3&gt;&#10;&lt;p&gt;Routes must be added to the server so that your team’s clients know which traffic to route to the VPN Server.&lt;/p&gt;&#10;&lt;p&gt;You can determine the proper subnet by returning to your list of EC2 instances, clicking on a target instance and identifying the Private IP.&lt;/p&gt;&#10;&lt;div style="text-align: center;"&gt;&#10; &lt;figure class="image"&gt;&#10; &#10; &lt;img src="https://redgeoff.com/posts/vpn/identify-private-ip.png" alt="Identify Private IP" /&gt;&#10; &#10; &#10; &lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p&gt;Your network will be the first 2 parts of the Private IP appended with zeros, e.g. 172.31.0.0&lt;/p&gt;&#10;&lt;p&gt;On the VPN Server edit &lt;em&gt;/etc/openvpn/server.conf&lt;/em&gt; and add something like the following:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;push &amp;#34;route 172.31.0.0 255.255.0.0&amp;#34;&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then restart the VPN Server with:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;$ systemctl restart openvpn@server&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="step-7-grant-access-to-your-vpn"&gt;Step 7— Grant Access to Your VPN&lt;/h3&gt;&#10;&lt;p&gt;Note: We assume that you are still SSH’d into the VPN and logged in as root.&lt;/p&gt;&#10;&lt;p&gt;Run the following command and be sure to replace &lt;em&gt;client&lt;/em&gt; below with a unique name for your user/client.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;$ /home/ubuntu/openvpn-server-vagrant/add-client.sh client&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You’ll then find a configuration file at&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;~/client-configs/files/client-name.ovpn&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You will want to provide this file to the individual on your team who will be connecting to your VPN. SCP is handy for downloading this .ovpn file from your VPN Server.&lt;/p&gt;&#10;&lt;p&gt;Your team can use one of various VPN clients such as &lt;a href="https://tunnelblick.net/"&gt;Tunnelblick&lt;/a&gt; (OS X) and &lt;a href="https://openvpn.net/community-downloads/"&gt;OpenVPN&lt;/a&gt; (Linux, iOS, Android and Windows). After installing one of these clients they should be able to set up the VPN config just by double clicking on the .ovpn file.&lt;/p&gt;&#10;&lt;p&gt;Note: once connected to the VPN, your users will want to use the Private IPs of your EC2 instances. You’ll probably want to use Route 53 to create subdomain records that route to the Private IPs.&lt;/p&gt;&#10;&lt;h3 id="step-8-revoke-access-to-your-vpn"&gt;Step 8— Revoke Access to Your VPN&lt;/h3&gt;&#10;&lt;p&gt;Note: We assume that you are still SSH’d into the VPN and logged in as root.&lt;/p&gt;&#10;&lt;p&gt;Run the following command and be sure to replace &lt;em&gt;client&lt;/em&gt; below with a unique name for your user/client.&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;$ /home/ubuntu/openvpn-server-vagrant/revoke-full.sh client&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="troubleshooting"&gt;Troubleshooting&lt;/h3&gt;&#10;&lt;p&gt;If your VPN client reports a &lt;em&gt;TLS handshake failed&lt;/em&gt; error then this is most likely because your VPN security group (Step 1) is incorrect. Make sure that you have the correct ports and protocols specified — a common problem is not specifying UDP for port 1194.&lt;/p&gt;&#10;</description></item></channel></rss>