projects/mimic/README.md
01Project · 2021
02

Mimic

03
TypeScript interfaces, brought to life as data
05

Mimic turns a TypeScript interface into an endless supply of realistic data. Write the shape you want and get a generator back. Call it once for a row, call it ten thousand times for a table, and every result is different.

06

The trick is that the schema is just TypeScript. The generators are themselves types, glued in front of your code, so the editor autocompletes them and the compiler checks them. Mimic runs that compiler in your browser, lowers your types into a small portable tree, and turns the tree into functions. It can even guess what a field should hold from its name alone.

07

I built it in 2021 to feed a streaming-data feature I was working on, then published it to npm as ts-mimic with a playground you can open right now. It is the same idea this whole site keeps circling: you describe what you mean, and the machine produces the thing.

08
TypeScriptCompiler APIfakerstring-similarityAngularnpm · ts-mimic
archived · 2021github.com/chcaru/mimic
notein-context.md

I made the type the schema

In 2021 I was building a streaming-data feature and needed mountains of realistic test data, shaped exactly like the real thing. Hand-writing it was tedious, and every tool I tried wanted me to describe my data in its own language: a JSON schema, a config file, a chain of builder calls. I already had a perfect description of the shape sitting in my code. It was the TypeScript type.

So I made the type the schema. TypeScript's type system is the most expressive shape language most of us touch every day, and it comes with autocomplete and checking for free. Mimic ships a prelude of generator types, asFirstName, asEmail, BoundArray, Sometimes, and a hundred and seventy more, and prepends it to your code. Your interfaces typecheck against real types, your editor suggests generators as you write, and none of it feels like a foreign DSL.

Underneath, it leans on the genuine TypeScript compiler instead of a parser of my own. Mimic feeds your code to the compiler through an in-memory file, asks for the syntax tree, and walks it. That hands it every corner of the type system for free: unions, tuples, template literals, optional fields, references between types, all of it. The whole thing runs client side, so your shapes never leave the page.

A type is an intention. asNumberRange<20, 40> says I want a number in here without saying which one. Mimic translates that intention into concrete reality, over and over, each result a little different. Today you would ask a model for the same favor in plain English. In 2021 you wrote the type, and the compiler handed you the shape.

  • PipelineTS → AST → type tree → generators
  • Describe withinterfaces · unions · tuples · templates
  • Generators~180 faker-backed primitives
  • Inferenceguess from a property name
  • Runsentirely in your browser
  • Shippednpm · ts-mimic · 2021
architecturemimic.pipeline
prependblank fieldprimitivesschema.tsinterfaces & type aliasesTypeScript Compilerin-browser · CompilerHostSyntax treethe parsed programLower the typeswalk · parseTypeMimic type treeportable · serializableCompile to closurescreateTypeGeneratorGenerators() => data · composablemock dataMimic preludeas* types · prependedGuess from a namenearest match · fuzzyfaker~180 primitives
schemaphasetype treegeneratorslibrarydata

The schema is just TypeScript. Mimic prepends a prelude of generator types, runs the real compiler in your browser, and lowers the result into a small portable tree. Every node of that tree becomes a function; the leaves call faker. Call the top generator and a fresh, fully-shaped object falls out, every single time.

walkthroughhow-it-makes-data
  1. 01
    prelude

    Hand it the vocabulary

    A library of generator types, asFirstName, asEmail, BoundArray, Sometimes, around a hundred and eighty in all, is prepended to your code. Your interfaces typecheck against them and your editor autocompletes them. The generators are just types.

  2. 02
    parse

    Run the real compiler

    Mimic spins up the actual TypeScript compiler against an in-memory file and asks for the syntax tree. No regex, no hand-rolled parser. The genuine article, running in the browser.

  3. 03
    lower

    Walk the types

    It walks the tree and lowers every interface, union, array, tuple, and template literal into a compact description of its own: a small tree that knows the shape but nothing about TypeScript.

  4. 04
    infer

    Fill in the blanks

    A field with no type is matched, by name, against every known generator using string similarity. city finds asCity, phoneNumber finds asPhoneNumber. The schema can be almost empty and still produce something believable.

  5. 05
    compile

    Build the generators

    Each node of that description becomes a small function. They nest and reference each other, so a Person generator calls a Name generator calls asFirstName. Call the top one and a whole object falls out, different every time.

notabledesign-choices

The decisions worth keeping

None of the pieces are exotic. faker makes the values, the TypeScript compiler does the parsing, a string-similarity library does the guessing. What I like is the seam where they meet: the decision to let the type system itself be the schema.

  1. 01

    The type system is the schema

    You describe data the way you already describe everything else in TypeScript: an interface, a union, an optional field. The generators are real types in a prelude, so the whole schema typechecks and autocompletes. No JSON schema, no decorators, no config format to learn.

    in contextMost mock-data tools invent a description language and ask you to learn it. Mimic noticed that the best one was already open in your editor. The shape of your data and the recipe for generating it become the same artifact, with full tooling, and nobody has to remember a second syntax.

  2. 02

    A compiler in the browser

    Rather than parse TypeScript myself, Mimic runs the genuine compiler against an in-memory source file through a custom CompilerHost, asks for the syntax tree, and walks it. Every feature of the type system comes along for free.

    in contextIt would have been faster, at first, to scan for the word interface and split on commas. It would also have been a dead end the moment someone wrote a union or a template literal. Borrowing the real compiler meant the hard parts were already correct, and it meant the whole tool could run client side with nothing sent anywhere.

  3. 03

    Guess from a name

    A property with no type is matched against every generator name by similarity. address becomes a street address, phoneNumber becomes a phone number, avatar becomes an image URL. You can sketch a whole object out of bare field names and get something plausible back.

    in contextThis is the feature that makes the first five minutes feel like magic. You paste an interface you already had, annotate nothing, and data appears. A small, fuzzy heuristic doing a lot of friendly work.

  4. 04

    Two stages, one tiny runtime

    Parsing and generating are kept apart. The compiler and the walk produce a portable type tree; a separate step turns that tree into closures. The tree knows nothing about TypeScript.

    in contextThe split means the part that actually makes data is small and self-contained. The heavy compiler does its work once, up front, and what is left is a tree you could serialize, ship, or run somewhere with no compiler at all.

resultsschemas-it-runs

Three small schemas, each leaning on a different part of the type system. The output is the kind of thing Mimic hands back. Run any of them again and every value changes.

contact.tstypescript
// no types, only field names
interface Contact {
    firstName;
    email;
    phoneNumber;
    city;
}
{
  "firstName": "Maybell",
  "email": "Era.Hahn@gmail.com",
  "phoneNumber": "(458) 213-9981",
  "city": "Port Cassie"
}
Not one type annotation. Each field is matched to a generator by its name alone.
roster.tstypescript
// references, unions, chance, sized arrays
interface Card {
    type: 'visa' | 'mastercard';
    number: asCreditCardNumber;
}

interface Player {
    name: asFirstName;
    card: Card;
    avatar: Sometimes<.6, asAvatar>;
}

type Roster = { players: BoundArray<Player, 4, 2>; };
{
  "players": [
    {
      "name": "Cleve",
      "card": { "type": "mastercard", "number": "6011-7256-9183-3829" },
      "avatar": "https://s3.amazonaws.com/uifaces/.../128.jpg"
    },
    {
      "name": "Rosario",
      "card": { "type": "visa", "number": "4532-8821-0049-1175" }
    }
  ]
}
Types reference types. The union picks at random, Sometimes flips a weighted coin, BoundArray sizes itself between two and four. The second player's avatar simply did not come up.
server.tstypescript
// template literals stitch generated pieces
interface Server {
    host: `${asDomainWord}.prod.internal`;
    ip: asIP;
    region: 'us-east' | 'eu-west' | 'ap-south';
    uptime: asNumberRange<90, 100>;
}
{
  "host": "tommie.prod.internal",
  "ip": "153.152.29.108",
  "region": "eu-west",
  "uptime": 97
}
A template-literal type drops a generated word into a fixed pattern, exactly as it would type-check. Mimic honors the same composition the compiler does.
try itrun-it-yourself.ts

The real library, ported into this page. Describe a shape or edit one of the examples, then press Generate. The TypeScript compiler parses your types and faker fills them in, all in your browser.

person.tstypescript
generated data
Press Generate. Mimic loads on demand, then turns these types into data, right here in your browser.
The TypeScript compiler and faker load only when you press Generate. Nothing leaves this page.
Prefer the full thing?Live demonpm: ts-mimic