Перейти к основному содержимому

TypeScript

Пакет для TypeScript — это эталонная реализация TDC. CLI хорош, когда нужен файл; библиотека нужна, чтобы получить данные прямо в коде — как строку или как живые JS-объекты — без запуска внешнего процесса и чтения файла.

import { TDC } from "tdcv2";

Создание TDC

Конструктор принимает либо путь к DSL-файлу (configFile), либо строку с DSL (configString). Runtime-параметры seed, count, locale и now можно переопределить из кода — они бьют значения из <env>.

const tdc = new TDC({
configString: `<tdc>
<env count="4" seed="demo" local="ru">
<sequence name="Gender"><gen type="text" value="Мужчина,Женщина"/></sequence>
<sequence name="MaleName" parent="Gender.Мужчина"><gen type="template" value="person.male.firstName"/></sequence>
<sequence name="FemaleName" parent="Gender.Женщина"><gen type="template" value="person.female.firstName"/></sequence>
<before><line><data>Пол,Имя</data></line></before>
</env>
<block><line><data>\${{Gender}},\${{MaleName}}\${{FemaleName}}</data></line></block>
</tdc>`,
});

console.log(tdc.toString());

Имя привязано к полу через parent: две последовательности, каждая под своей веткой, — иначе имя разыгрывалось бы независимо и мужчине досталось бы женское имя. На каждой строке заполнена ровно одна из них, поэтому в выводе они просто идут подряд.

node example.js
Пол,Имя
Женщина,Злата
Мужчина,Матвей
Мужчина,Павел
Женщина,Лариса

Переопределение из кода — эти значения бьют <env>:

const tdc = new TDC({
configFile: "./patients.tdc",
seed: "test-seed",
count: 100,
locale: "ru",
});

Для внешних файловых источников задайте папки данных (и базовую директорию для configString):

const tdc = new TDC({
configFile: "./configs/users.tdc",
dataPaths: ["./data", "./private-data"],
});

При configFile относительные пути src внутри .tdc считаются от папки этого файла; при configString базовую директорию baseDir задавайте вручную.

Остальные опции конструктора встречаются реже, но существуют не меньше:

ОпцияЧто делает
localeПереопределяет <env local=…> — конфиг проигрывает
defaultLocaleПодставляется, только если <env> вообще не объявил локаль. Ключ locale из tdcv2.config.json попадает именно сюда, а не в переопределение
mode"memory" или "disk" — тот же выбор, что и --mode
engine1, 2 или 3 — форсирует один движок и падает вместо отката
streamЛегаси-алиас для engine: 2

На разделение locale / defaultLocale стоит посмотреть внимательнее: "locale": "ru" в проектном конфиге не побеждает local="en" в файле конфига, и об этом ничего не сообщается. Переопределяют только locale самого конструктора и --locale в CLI.

У preflight(opts?) есть своя опция — output: либо "materialized" (по умолчанию — весь прогон держится целиком, как в toArray()), либо "streaming".

Терминальные методы

МетодЧто возвращаетДля чего
toString()весь вывод одной строкоймаленькие / средние результаты
writeFile(path)пишет вывод в файл (частями)файл любой величины
toIterator()генератор строк (по одной карточке)большой текст без общей строки
toStream()Node.js Readablepipe в файл / HTTP / архиватор
toColumns()колонки; числа как Float64Arrayчисловые конвейеры, много прогонов
toArray()массив объектов-строкмаленькие объектные фикстуры
iterate()генератор объектов-строкобъектный вывод без массива
getAt(index)одну объектную строку по индексуточечный доступ
preflight(opts?)диагностику по памяти или undefinedпроверка до большого запуска
seedInfo(){ seed, generated }узнать / залогировать сид
toStringAsync()весь вывод, промисомконфиг с type="http"
writeFileAsync(path)пишет вывод, промисомконфиг с type="http"
usesHttp()true, если конфиг ходит по сетивыбрать одну из двух пар

toString/writeFile/toIterator/toStream — текстовый вывод через диск, память O(числа полей). Замеры смотрите на странице Большие объёмы.

Про асинхронную пару есть одно правило: конфиг с <gen type="http"> обязан пользоваться ею. Сетевой вызов нельзя сделать из синхронной функции, поэтому toString() на таком конфиге бросает исключение, а не отдаёт половину датасета:

const tdc = new TDC({ configFile: "./enriched.tdc" });
const text = tdc.usesHttp() ? await tdc.toStringAsync() : tdc.toString();

usesHttp() отвечает на этот вопрос, ничего не запуская, — ровно так поступает и CLI. Во всём остальном пути ведут себя одинаково: для конфига без http-генератора toStringAsync() — это toString(), завёрнутый в промис. Меняется другое — воспроизводимость: прогон, который ходит в сервис, повторяем ровно настолько, насколько повторяем сам сервис.

Объектный вывод

В тестах часто удобнее работать с живыми объектами, чем парсить CSV/JSON — можно проверять row.Gender напрямую. Это дают toArray(), iterate() и getAt(index). Объектный вывод игнорирует <block> и текстовые обёртки — берёт только материализованные <sequence>:

  • простая sequence становится скалярным свойством;
  • составная sequence становится вложенным объектом;
  • sequence с фильтром по родителю даёт undefined в строках, где она не применима.

getAt(index) — это работа одной строки, и индекс не важен: на конфигурации в 200 000 строк getAt(0) и getAt(199999) возвращают примерно за 2 мс, тогда как toArray() на том же запуске — около 210 мс. iterate() в сумме стоит столько же, сколько toArray(), но массив не держит.

const tdc = new TDC({
configString: `<tdc>
<env count="4" seed="demo" local="ru">
<sequence name="Gender"><gen type="text" value="Мужчина,Женщина"/></sequence>
<sequence name="Person">
<gen name="Code" type="regex" value="[0-9]{4}"/>
</sequence>
<sequence name="MaleName" parent="Gender.Мужчина"><gen type="template" value="person.male.firstName"/></sequence>
<sequence name="FemaleName" parent="Gender.Женщина"><gen type="template" value="person.female.firstName"/></sequence>
</env>
<block><line><data>игнорируется</data></line></block>
</tdc>`,
});

console.log(tdc.getAt(0)); // женская строка
console.log(tdc.getAt(1)); // мужская строка
node objects.js
{
Gender: 'Женщина',
Person: { Code: '7541' },
MaleName: undefined,
FemaleName: 'Милана'
}
{
Gender: 'Мужчина',
Person: { Code: '1506' },
MaleName: 'Иван',
FemaleName: undefined
}

Person — вложенный объект. MaleName и FemaleName присутствуют обе, но на каждой строке заполнена ровно одна: вторая равна undefined, потому что её parent на этой строке не совпал. Так в объектном выводе и выглядит фильтр по родителю.

Те же значения, по одной строке

Объектные методы читают из того движка, в который конфиг направил роутер, — из того же, что и toString(). Значения совпадают, а getAt(index) стоит одной строки, а не всего прогона до неё: спросить девятимиллионную строку у конфига на десять миллионов — работа на одну строку.

Одно значение без конфига

Пакет экспортирует ещё и tdc — он вытягивает одно значение из тех же пакетов данных, которые читает конфиг: ни файла, ни <env>, один вызов.

import { tdc } from "tdcv2";

tdc.person.lastName(); // Jones
tdc.country.usa.docs.ssn(); // 699209702, с настоящими контрольными цифрами
tdc.person.lastName.many(5); // сразу пять
tdc.seed("demo").locale("ru").person.lastName(); // закреплено и по-русски

Каждый встроенный адрес — настоящее свойство типа, поэтому опечатка становится ошибкой компиляции, а автодополнение работает без всяких плагинов. Вся поверхность — на странице По одному значению.

Одни и те же имена во всех языках

Объект готового прогона отзывается на одни и те же имена во всех пяти пакетах, записанные по правилам каждого языка. Таблица здесь, и наборы тестов её проверяют, а не принимают на веру.

Смотрите также

  • CLI — тот же движок из командной строки.
  • Большие объёмы — потоковые методы и память.