One value at a time
Sometimes you don't want a dataset. You want a surname, here, on this line of a test — the job a faker library does. TDC answers that from the same data packs its configs draw on, so the name in your unit test and the name in your million-row fixture come from one list.
All five implementations have it, and the same seed gives the same value in each:
- TypeScript
- Python
- Java
- C#
- Rust
import { tdc } from 'tdcv2';
tdc.person.lastName(); // Jones
from tdcv2 import tdc
tdc.person.lastName() # Jones
import io.github.nickliapin.tdc.quick.Quick;
Quick tdc = Quick.tdc();
tdc.get("person.lastName"); // Jones
using Tdcv2.Quick;
dynamic tdc = Quick.Tdc;
tdc.person.lastName(); // Jones
use tdcv2::quick::Quick;
let mut tdc = Quick::new();
tdc.get("person.lastName")?; // Jones
That is the whole API. Everything below is that one call with something in front of it.
Every value shown on this page was drawn under the seed demo, so you can
reproduce it. Without a seed each call is fresh; Making it
repeat is where the seed goes.
Every call is independent. Nothing here ties one value to another — no parent=,
no <switch> on a drawn column, no uniq, no <compute>. A coherent record
is a config; see Your first dataset. Use
this when the values genuinely don't need to agree with each other.
One rule: a dot is a dot
person.male.firstName in your code is person.male.firstName in a config and in
the reference. There is no second vocabulary to learn.
- TypeScript
- Python
- Java
- C#
- Rust
tdc.person.lastName(); // Jones
tdc.person.male.firstName(); // Robert
tdc.person.female.firstName(); // Linda
tdc.company.industry(); // Pharmaceuticals
tdc.color.name(); // Emerald
tdc.food.dish(); // Chicken Tikka Masala
tdc.person.lastName() # Jones
tdc.person.male.firstName() # Robert
tdc.person.female.firstName() # Linda
tdc.company.industry() # Pharmaceuticals
tdc.color.name() # Emerald
tdc.food.dish() # Chicken Tikka Masala
tdc.get("person.lastName"); // Jones
tdc.get("person.male.firstName"); // Robert
tdc.get("person.female.firstName"); // Linda
tdc.get("company.industry"); // Pharmaceuticals
tdc.get("color.name"); // Emerald
tdc.get("food.dish"); // Chicken Tikka Masala
tdc.person.lastName(); // Jones
tdc.person.male.firstName(); // Robert
tdc.person.female.firstName(); // Linda
tdc.company.industry(); // Pharmaceuticals
tdc.color.name(); // Emerald
tdc.food.dish(); // Chicken Tikka Masala
tdc.get("person.lastName")?; // Jones
tdc.get("person.male.firstName")?; // Robert
tdc.get("person.female.firstName")?; // Linda
tdc.get("company.industry")?; // Pharmaceuticals
tdc.get("color.name")?; // Emerald
tdc.get("food.dish")?; // Chicken Tikka Masala
The segments are spelled the way the packs spell them, camelCase and all, in Python and C# as much as in TypeScript. They are not names the library chose; renaming them per language would be a second vocabulary to keep in step with the reference, with a config, and with four other implementations.
A bare address is read against the active locale, exactly as in a config. In
en you get Jones; switch the locale and the same line gives you a Russian
surname.
TypeScript, Python and C# walk the address as members — tdc.person.lastName() —
because each of those languages can answer for a member that does not exist until
it is asked for. Java and Rust take the address as a string instead.
That is a decision, not a gap. The member shape needs one generated method per
address, and a generated surface can only cover the packs inside the artifact.
Most packs are downloaded at run time, so a generated tdc.lang().ru() would not
exist for a pack that was installed a minute ago, while get("ru.person.lastName")
works the moment the download finishes.
Naming a pack outright
An address can reach past the active locale and name a pack. Java and Rust write
that address as it is. TypeScript, Python and C# put common, country or lang
in front of it — those three words carry no meaning inside an address, and they
are there so the completion list at tdc. stays a list of categories rather than
a wall of 122 pack codes.
| Reaches | TypeScript, Python, C# | Java, Rust |
|---|---|---|
| the active locale | tdc.person.lastName() | "person.lastName" |
| the shared pack — the same in every language | tdc.common.id.uuid() | "common.id.uuid" |
| one country's pack | tdc.country.usa.docs.ssn() | "usa.docs.ssn" |
| one language's pack | tdc.lang.ru.person.lastName() | "ru.person.lastName" |
- TypeScript
- Python
- Java
- C#
- Rust
tdc.common.id.uuid(); // 3ff6ff76-6ea7-4fad-8b99-3075a14cc7e9
tdc.common.internet.email(); // u99o89qpeo@test-qu8y3h.invalid
tdc.common.finance.iban(); // DE62299399441396459682
tdc.common.finance.currency(); // Swedish Krona
tdc.country.usa.docs.ssn(); // 699209702
tdc.country.usa.finance.aba_routing(); // 659939946
tdc.common.id.uuid() # 3ff6ff76-6ea7-4fad-8b99-3075a14cc7e9
tdc.common.internet.email() # u99o89qpeo@test-qu8y3h.invalid
tdc.common.finance.iban() # DE62299399441396459682
tdc.common.finance.currency() # Swedish Krona
tdc.country.usa.docs.ssn() # 699209702
tdc.country.usa.finance.aba_routing() # 659939946
tdc.get("common.id.uuid"); // 3ff6ff76-6ea7-4fad-8b99-3075a14cc7e9
tdc.get("common.internet.email"); // u99o89qpeo@test-qu8y3h.invalid
tdc.get("common.finance.iban"); // DE62299399441396459682
tdc.get("common.finance.currency"); // Swedish Krona
tdc.get("usa.docs.ssn"); // 699209702
tdc.get("usa.finance.aba_routing"); // 659939946
tdc.common.id.uuid(); // 3ff6ff76-6ea7-4fad-8b99-3075a14cc7e9
tdc.common.internet.email(); // u99o89qpeo@test-qu8y3h.invalid
tdc.common.finance.iban(); // DE62299399441396459682
tdc.common.finance.currency(); // Swedish Krona
tdc.country.usa.docs.ssn(); // 699209702
tdc.country.usa.finance.aba_routing(); // 659939946
tdc.get("common.id.uuid")?; // 3ff6ff76-6ea7-4fad-8b99-3075a14cc7e9
tdc.get("common.internet.email")?; // u99o89qpeo@test-qu8y3h.invalid
tdc.get("common.finance.iban")?; // DE62299399441396459682
tdc.get("common.finance.currency")?; // Swedish Krona
tdc.get("usa.docs.ssn")?; // 699209702
tdc.get("usa.finance.aba_routing")?; // 659939946
Those two identifiers are not shaped like one — they carry real check digits, the same ones a config would produce.
An address that isn't installed says so
common, en and the USA pack ship inside all five artifacts. Anything else is a
download away, and asking for it before it is there gets you a named failure
rather than a blank:
- TypeScript
- Python
- Java
- C#
- Rust
tdc.lang.ru.person.lastName();
// TdcQuickError: the "ru" pack is not installed, so "ru.person.lastName" cannot be
// drawn. Install it with `tdcv2 pack add ru` (run `tdcv2 init` once first, to say
// where packs go).
tdc.lang.ru.person.lastName()
# TdcQuickError: the "ru" pack is not installed, so "ru.person.lastName" cannot be
# drawn. Install it with `tdcv2 pack add ru` (run `tdcv2 init` once first, to say
# where packs go).
tdc.get("ru.person.lastName");
// TdcQuickException: the "ru" pack is not installed, so "ru.person.lastName" cannot
// be drawn. Install it with `java -jar tdcv2-cli.jar pack add ru` — or `tdcv2 pack
// add ru` if you have aliased the CLI jar — after `java -jar tdcv2-cli.jar init`
// once, to say where packs go.
tdc.lang.ru.person.lastName();
// TdcQuickException: the "ru" pack is not installed, so "ru.person.lastName" cannot
// be drawn. Install it with `tdcv2 pack add ru` (run `tdcv2 init` once first, to say
// where packs go).
tdc.get("ru.person.lastName");
// Err(QuickError): the "ru" pack is not installed, so "ru.person.lastName" cannot be
// drawn. Install it with `tdcv2 pack add ru` (run `tdcv2 init` once first, to say
// where packs go).
Only Java's wording differs, and only because Maven puts nothing on the PATH:
advice to run tdcv2 would be advice a Java reader cannot type. The command line
itself is the same in all five. See Installing
packs.
A misspelled segment is a different failure, and says so: person.lastNam comes
back as unknown address "person.lastNam" (locale "en"). Did you mean "en.person.lastName"?
Many at once
Ask for n values in one call instead of calling in a loop — it is one draw of
n values, not n draws of one.
- TypeScript
- Python
- Java
- C#
- Rust
tdc.person.lastName.many(5);
// [ 'Jones', 'Bush', 'Armstrong', 'Andrews', 'Jimenez' ]
tdc.person.lastName.many(5)
# ['Jones', 'Bush', 'Armstrong', 'Andrews', 'Jimenez']
List<String> names = tdc.many("person.lastName", 5);
// [Jones, Bush, Armstrong, Andrews, Jimenez]
IReadOnlyList<string> names = tdc.person.lastName.many(5);
// Jones, Bush, Armstrong, Andrews, Jimenez
let names = tdc.many("person.lastName", 5)?;
// ["Jones", "Bush", "Armstrong", "Andrews", "Jimenez"]
Making it repeat
By default every call is fresh — that is what you want in a scratch script. Pin a seed and the values become part of the test rather than a variable in it. Pinning a seed also returns a new object rather than changing the one you called it on, so two tests can hold different seeds at the same time.
- TypeScript
- Python
- Java
- C#
- Rust
const t = tdc.seed('demo');
t.person.lastName(); // Jones, today and next year
const ru = tdc.seed('fixtures').locale('ru');
const en = tdc.seed('fixtures').locale('en');
ru.person.lastName(); // Ткаченко
en.person.lastName(); // Pearson
t = tdc.seed("demo")
t.person.lastName() # Jones, today and next year
ru = tdc.seed("fixtures").locale("ru")
en = tdc.seed("fixtures").locale("en")
ru.person.lastName() # Ткаченко
en.person.lastName() # Pearson
Quick t = Quick.seeded("demo");
t.get("person.lastName"); // Jones, today and next year
Quick ru = Quick.seeded("fixtures").locale("ru");
Quick en = Quick.seeded("fixtures").locale("en");
ru.get("person.lastName"); // Ткаченко
en.get("person.lastName"); // Pearson
dynamic t = Quick.Seed("demo");
t.person.lastName(); // Jones, today and next year
dynamic ru = Quick.Seed("fixtures").locale("ru");
dynamic en = Quick.Seed("fixtures").locale("en");
ru.person.lastName(); // Ткаченко
en.person.lastName(); // Pearson
let mut t = Quick::seeded("demo");
t.get("person.lastName")?; // Jones, today and next year
let mut ru = Quick::seeded("fixtures").locale("ru");
let mut en = Quick::seeded("fixtures").locale("en");
ru.get("person.lastName")?; // Ткаченко
en.get("person.lastName")?; // Pearson
Generators without a pack
The engine's own generators are reachable too, for the values that come from a
rule rather than from a list. They take attributes instead of an address, so they
live under one name of their own — pack categories are already called date,
text and word, which leaves the top level occupied.
- TypeScript
- Python
- Java
- C#
- Rust
tdc.gen.number('18..80'); // 66
tdc.gen.regex('[A-Z]{2}-[0-9]{4}'); // FZ-3994
tdc.gen.number("18..80") # 66
tdc.gen.regex("[A-Z]{2}-[0-9]{4}") # FZ-3994
tdc.gen("number", "18..80"); // 66
tdc.gen("regex", "[A-Z]{2}-[0-9]{4}"); // FZ-3994
tdc.gen.number("18..80"); // 66
tdc.gen.regex("[A-Z]{2}-[0-9]{4}"); // FZ-3994
tdc.gen("number", &[("value", "18..80")])?; // 66
tdc.gen("regex", &[("value", "[A-Z]{2}-[0-9]{4}")])?; // FZ-3994
Every generator and its attributes are in the generators reference.
Values are always strings
Including numbers and dates. The engine's world is text — that is what lets one config produce CSV, SQL and JSON without changing — and a return type that varied with the address would be a different contract in each of the five. Convert at the call site when you need a number:
- TypeScript
- Python
- Java
- C#
- Rust
const age = Number(tdc.gen.number('18..80'));
age = int(tdc.gen.number("18..80"))
int age = Integer.parseInt(tdc.gen("number", "18..80"));
int age = int.Parse(tdc.gen.number("18..80"));
let age: u32 = tdc.gen("number", &[("value", "18..80")])?.parse()?;
When to use a config instead
Reach for a config the moment two values have to agree: a city that belongs to its country, an order total that matches its lines, a 30% share that has to be exactly 30%. That is what the rest of this documentation is about, and it starts at Your first dataset.
See also
- TypeScript, Python, Java, C#, Rust — the same five packages, for whole datasets.
- Data packs — what a pack is and how addresses are organized.
- Installing packs — adding the other 120.