Skip to content

Instantly share code, notes, and snippets.

@SuperOleg39
Created January 28, 2024 12:15
Show Gist options
  • Select an option

  • Save SuperOleg39/aea9c5c3a2b0711ac65388bf47567b09 to your computer and use it in GitHub Desktop.

Select an option

Save SuperOleg39/aea9c5c3a2b0711ac65388bf47567b09 to your computer and use it in GitHub Desktop.

Миграции

v3.x.x

[13.11.2023] - Удалена логика beforeReporters, к RemoteReporter теперь применяются filters и extensions

Раньше метод logger.addBeforeReporter использовался для добавления клиентского RemoteReporter, для отправки логов в сервис loggly без базовой фильтрации по уровню (которая настраивается через методы logger.setLevel и logger.enable).

Теперь обработка RemoteReporter встроена в @tinkoff/logger и он работает как обычный reporter, но без базовой фильтрации по уровню логов (но в RemoteReporter всегда была своя фильтрация).

Метод logger.addBeforeReporter теперь под капотом вызывает logger.addReporter.

Поведение, когда и какой лог отправляется в loggly, должно остаться без изменений.

Ломающее изменение, теперь к RemoteReporter применяются фильтры и расширения, это может повлиять на отправку и структуру логов если у вас есть кастомные filters и extensions.

[13.11.2023] - Добавлен модуль AsyncLocalStorageModule

ASYNC_LOCAL_STORAGE_TOKEN интегрирован с LoggerModule и дает удобную возможность обогатить серверные логи юзер-специфичной информацией

Удалена поддержка опции alias в tramvai.json

@tramvai/cli теперь поддерживает baseUrl и paths из tsconfig.json, убедитесь что ваш tsconfig содержит актуальную конфигурацию алиасов и удалите опцию из tramvai.json

Обновлена библиотека svgo

Если вы использовали опцию svgo.plugins в tramvai.json, необходимо сменить формат конфигурации, например если было:

{
  "svgo": {
    "plugins": [
      {
        "collapseGroups": false
      }
    ]
  }
}

Надо заменить на:

{
  "svgo": {
    "plugins": [
      {
        "name": "collapseGroups",
        "active": false
      }
    ]
  }
}

Требования по раннеру для @tramvai/test-unit

Начиная с 2-ой версии, из-за использования fastify, нам необходима поддержка префикса node: при импорте модулей в случае использования библиотеки @tramvai/test-unit. Поддержка префикса есть в jest, но начинается только с версии >=27.1.1, поэтому в случае ошибок при миграции на более свежую версию, может потребоваться обновить и jest в зависимости от вашей текущей версии.

Если вы используете другой тест-раннер, то убедитесь что он поддерживает импорты с перфиксом node: для стандартных модулей Node.js.

[25.05.2023] v2.107.0 - Минимальная поддерживаемая версия nodejs >= 16

Теперь tramvai поддерживает работоспособность для nodejs минимум 16 версии. С указанной версии это делается официально, но скорее всего что-то не будет работать уже в предыдущих релизах на более старых версиях nodejs.

Для обновления:

  • заменить образы в ci с nodejs на версию >=16 при использовании tramvai
  • при прогоне тестов playwright версия образа playwright должна быть больше 1.17.0

[14.03.2023] v2.79.7 - Изменена логика загрузки переменных из env.development.js и env.js при использовании tramvai static

Команда tramvai static теперь не загружает env.development.js и env.js файлы по-умолчанию. Это сделано потому, что сборка приложения для tramvai static – это продакшн сборка, и если загружать для нее указанные файлы это будет неявным образом влиять на работу самой команды. Если все еще нужно использовать эти файлы при выполнении команды, используйте переменную DANGEROUS_UNSAFE_ENV_FILES=true.

[01.02.2023] v2.65.0 - Рефакторинг конфига tramvai.json

Конфиг полностью переработан, чтобы упростить работу с ним как для пользователей, так и для разработчиков самого tramvai.

Миграция на новый конфиг должна произойти автоматически при обновлении на последние версии tramvai. В случае каких-то проблем или несостыковок в конфиге после миграции используйте возможности IDE по валидации JSON схемы чтобы определить неправильные настройки и вообще доступные настройки и их опции

[05.12.2022] v2.41.0 - Миграция на swc (опционально)

  1. Поменяйте настройку commands.serve.configurations.experiments.transpilation на значение swc
  2. Запустите сборку и проверьте вывод cli на наличие ворнингов:
    • неподдерживаемое свойство alias - cli теперь умеет работать с tsconfig-paths, поэтому вы теперь можете просто убрать конфиг alias из tramvai.json и задать конфиг в вашем tsconfig.json (скорее всего у вас уже даже задан этот конфиг если вы использовали алиасы)
    • другие неподдерживаемые свойства - их аналога нет, т.к. это legacy настройки, которые уже не рекомендуются к использованию и желательно от них отказаться

[07.11.2022] v2.38.0 - переход на новый форк mountebank - @sotqa/mountebank-fork

Необходимо заменить в tramvai.json и Dockerfile вашего приложения все названия старого форка mountebank-fork на новый @sotqa/mountebank-fork

[19.10.2022] v2.36.0 - granular chunking включен по умолчанию

Granular chunking - режим разделения кода, когда создается множество мелких shared чанков для минимального итогового размера кода на отдельных страницах.

Подробнее про конфигурацию режима в гайде Bundle Optimization

[30.09.2022] v2.31.0 - Обновление на react-query@4

Раньше под капотом в @tramvai/react-query использовался react-query@3, теперь же используемой версией становится @tanstack/react-query@4.

Миграция

Со стороны tramvai мы постарались сохранить все те интерфейсы, которые использовались для задания query. Однако некоторое поведение по умолчанию, возвращаемые результаты и кеширование изменилось в соответствии с изменениями в самой либе.

Для полного понимания, что изменилось при обновлении советую обратиться к документации также учитывайте:

  • т.к. поменялось название библиотеки, то все внутренние импорты в tramvai поменялись. Проблемы могут быть только если вы каким-то образом использовали оригинальную библиотеку. В таком случае желательно перейти полностью на аналогичные импорты из @tramvai/react-query
  • для формирования query/mutation key всё ещё можно использовать простые строковые значения - tramvai автоматически преобразует их в массив
  • обратите внимание что пропали некоторые статусы запроса
  • обратите внимание что теперь нельзя использовать undefined в качестве результата запроса
  • изменилась логика работы при отсутствии сети. Это может влиять на поведение запросов
  • проверьте также другие изменения в changelog если вы используете некоторые редкие фичи react-query

[27.09.2022] v2.32.0 - Удалена обратная совместимость с express

Библиотека express больше не используется в @tramvai/module-server (под капотом используется fastify) и также больше не используется слой обратной совместимости, который позволяет использовать старые токены, завязанные на express.

Подробнее про изменения:

  • убраны токены REQUEST, RESPONSE которые предоставляли сущности req, res из express. Вместо них предполагается использовать абстракции REQUEST_MANAGER_TOKEN и RESPONSE_MANAGER_TOKEN
  • больше недоступны токены вида WEB_APP_*, которые предоставляли доступ к инстансу express и позволяли подписаться на разные этапы инициализации сервера, чтобы добавить свою логику к express. Полноценной замены нет, в целом не предполагается использование таких сущностей и большая необходимость в них (тем не менее, в tramvai, на самом деле, теперь используются аналогичные токены для fastify, но мы предпочитаем считать их внутренними деталями реализации, которые недоступны пользователям для использования)
  • papi также ушло от поддержки express - рефакторинг papi

Миграция

Для большинства приложений не ожидается каких-либо ломающих изменений, кроме случаев:

  • активного использования papi - в этом случае смотри миграцию для papi
  • использование токенов, упомянутых выше, которые больше недоступны - при возможности перейти на доступные абстракции

[12.09.2022] v2.25.1 - Повышена минимальная версия Node и React

Теперь Tramvai заточен под Node 14+ и React 18+. И гарантируется работа именно с этими версиями. Пожалуйста обновите версии у себя в репозитории

[12.09.2022] v2.24.0 - Переход с глобальной переменной initalState на json

Смигрировал с initialState который мы закидывали как строку, на прокидывания JSON, для того, чтобы уменьшить количества обходов initialState и пересоздавания строки. Если вы напрямую брали строку window.initialState, то теперь этой переменной не будет и нужно доставать данные из токена INITIAL_APP_STATE_TOKEN

[07.09.2022] v2.24.0 - Рефакторинг работы с PAPI

  • изменён интерфейс createPapiMethod:
    • улучшена типизация
    • под капотом используется fastify
    • нельзя получить доступ к req, res запроса и нужно использовать абстракции из tramvai: REQUEST_MANAGER_TOKEN, RESPONSE_MANAGER_TOKEN (они поставляются автоматически первым аргументом при вызове)
    • deps теперь можно получить через this.deps в хендлере
    • специфичный логер доступен для хендлера через this.log
    • удалены многие опции, пока оставлена только опция timeout
  • файловые апи (которые создаются как отдельные файлы в специальной папке которая указывается через tramvai.json в application.commands.build.options.serverApiDir) теперь тоже должны использовать createPapiMethod для создания papi (только для них не нужно указывать path параметр т.к. он выводится автоматически)
  • в backward-compatibility модулях больше не инициализируются старые legacy papi

Миграция

Новый интерфейс

  • Вместо того, чтобы получать параметры запроса из req используйте параметры, которые передаются в качестве первого аргумента handler. Прокидываются общие параметры, которые могут понадобится и тайпинги подскажут что именно доступно. Если что всегда можно использовать requestManager поле, чтобы получить доступ к абстракции REQUEST_MANAGER_TOKEN
  • для формирования ответа вместо использования res возвращайте ответ явно через return в функции handler. Чтобы проставить дополнительные параметры запроса можно использовать responseManager из параметров вызова, которые представляет собой RESPONSE_MANAGER_TOKEN
  • чтобы получить доступ к deps используйте this.deps внутри handler
  • для логгирования вместо самостоятельно использования LOGGER_TOKEN используйте this.log внутри handler

Как было:

const provider = {
  provide: SERVER_MODULE_PAPI_PRIVATE_ROUTE,
  multi: true,
  useFactory: () => {
    return createPapiMethod({
      method: 'post',
      path: '/debug-http-request',
      async handler({ interceptor, req }) {
        const { delay = 10000 } = req.body;

        if (delay) {
          interceptor.setDelay(delay);
        } else {
          interceptor.setIntercept(true);
        }

        return interceptor.getStatus();
      },
      deps: {
        interceptor: INTERCEPTOR_TOKEN,
      },
    });
  },
};

Как стало:

const provider = {
  provide: SERVER_MODULE_PAPI_PRIVATE_ROUTE,
  multi: true,
  useFactory: () => {
    return createPapiMethod({
      method: 'post',
      path: '/debug-http-request',
      async handler({ body }) {
        const { delay = 10000 } = body;
        const { interceptor } = this.deps;

        this.log.info({
          event: 'delay',
          delay,
        });

        if (delay) {
          interceptor.setDelay(delay);
        } else {
          interceptor.setIntercept(true);
        }

        return interceptor.getStatus();
      },
      deps: {
        interceptor: INTERCEPTOR_TOKEN,
      },
    });
  },
};

Файловые апи

  • вместо экспорта функции делайте export default createPapiMethod(...)
  • определение интерфейса аналогично комментариям про интерфейс выше. Единственное отличие - не надо задавать path опцию papi т.к. она будет выведена автоматически по пути к файлу
  • если раньше вы использовали rootDeps и mapRootDeps то теперь можно использовать только deps и для того, чтобы задать зависимости со скоупом SINGLETON нужно их инициализировать на уровне самого приложения.

Как было:

import { Request, Response } from '@tramvai/papi';
import { CREATE_CACHE_TOKEN } from '@tramvai/module-common';

export const rootDeps = {
  createCache: CREATE_CACHE_TOKEN,
};

export const mapRootDeps = ({ createCache }: typeof rootDeps) => {
  return {
    cache: createCache('memory'),
  };
};

export default (req: Request, res: Response, { cache }: ReturnType<typeof mapRootDeps>) => {
  const {
    body: { a, b },
    method,
  } = req;

  if (method !== 'POST') {
    throw new Error('only post methods');
  }

  if (!a || !b) {
    return {
      error: true,
      message: 'body parameters a and b should be set',
    };
  }

  const key = `${a},${b}`;

  if (cache.has(key)) {
    return { error: false, fromCache: true, result: cache.get(key) };
  }

  const result = +a + +b;

  cache.set(key, result);

  return { error: false, fromCache: false, result };
};

Как стало:

import { createPapiMethod } from '@tramvai/papi';
// создаём токен отдельно и провайдим его на уровне приложения
import { PAPI_CACHE_TOKEN } from '../tokens';

export default createPapiMethod({
  async handler({ body, requestManager }) {
    const { cache } = this.deps;
    const method = requestManager.getMethod();
    const { a, b } = body;

    if (method !== 'POST') {
      throw new Error('only post methods');
    }

    if (!a || !b) {
      return {
        error: true,
        message: 'body parameters a and b should be set',
      };
    }

    const key = `${a},${b}`;

    if (cache.has(key)) {
      return { error: false, fromCache: true, result: cache.get(key) };
    }

    const result = +a + +b;

    cache.set(key, result);

    return { error: false, fromCache: false, result };
  },
  deps: {
    cache: PAPI_CACHE_TOKEN,
  },
});

Удаление deprecated роутинга

Во 2.16.0 версии трамвая удалены следующие библиотеки, связанные с legacy роутингом:

  • @tramvai/module-route
  • @tramvai/tokens-route

Перед переходом на 2.x.x мажорку, рекомендуем сначала провести миграцию на новые модули роутинга:

[17.08.2022] v2.9.0 - важные изменения в работе с экшенами

Общий гайд по типизации в tramvai приложениях

Новый хелпер declareAction

Новый хелпер declareAction приходит на смену старому createAction. Старый хелпер помечен как deprecated и не рекомендуется к использованию.

Преимущества нового хелпера:

  • улучшения типизации - теперь можно лучше контролировать список аргументов, которые получает экшен: можно указывать несколько аргументов, если аргументов нет, то это теперь нормально обрабатывается при вызове, опциональные аргументы теперь работают правильно при вызове
  • функция для логики экшена теперь требует указания только аргументов, которые принимает функции. Нет необходимости указывать аргументы, которые раньше проставлялись при вызове (context, deps) или игнорировать их
  • все дополнительные возможности для экшенов теперь передаются через this (deps, dispatch, getState и т.п.), поэтому для их использования потребуется указывать полноценную function, а не стрелочную функцию
  • добавился новый параметр conditionsFailResult который позволяет определить как должен вести себя экшен при вызове, если conditions для него не выполняются. По умолчанию, эта настройка ведёт себя также как и раньше - экшен просто резолвится с undefined без фактического выполнения. Теперь также можно указать, что должна быть сгенерирована соответствующая ошибка.

Миграция

Важно declareAction не совместим со старым ConsumerContext, что может создавать проблемы при расхождении версий tramvai - особенно это актуально для child-app. В этом случае миграция на новую версию tramvai должна быть выполнена сначала в рутовых приложениях и уже когда все перейдут на tramvai@2, child-app смогут использовать declareAction в своём коде

Миграция с createAction должна быть довольно простой, нужно только поменять способ обращения к deps и context, а вместо стрелочной использовать обычную функцию

Было:

export const fetchPokemonAction = createAction({
  name: 'fetchPokemon',
  fn: async (context, payload: string, deps) => {
    const { name } = deps.pageService.getCurrentRoute().params;

    const pokemonResponse = await deps.pokeapiHttpClient.get<Pokemon>(
      `/pokemon/${payload || name}`
    );

    context.dispatch(pokemonLoadedEvent(pokemonResponse.payload));
  },
  deps: {
    pokeapiHttpClient: POKEAPI_HTTP_CLIENT,
    pageService: PAGE_SERVICE_TOKEN,
  },
  conditions: {
    always: true,
  },
});

Стало:

export const fetchPokemonAction = declareAction({
  name: 'fetchPokemon',
  async fn(payload: string) {
    // заменили на обычную функцию и оставили только параметры самого экшена
    // заменили deps на this.deps
    const { name } = this.deps.pageService.getCurrentRoute().params;

    const pokemonResponse = await this.deps.pokeapiHttpClient.get<Pokemon>(
      `/pokemon/${paylod || name}`
    );

    // заменили context.dispatch на this.dispatch
    this.dispatch(pokemonLoadedEvent(pokemonResponse.payload));
  },
  deps: {
    pokeapiHttpClient: POKEAPI_HTTP_CLIENT,
    pageService: PAGE_SERVICE_TOKEN,
  },
  conditions: {
    always: true,
  },
});

Примерный вариант рецепта миграции (для jscodeshift)

Пример команды для запуска:

# Из требований: иметь установленный jscodeshift/typescript в проекте

# transformer.ts - файл с кодом трансформера (код ниже)
# ./src/actions/*.ts - какие файлы заденет миграция

# Подробнее ознакомиться можно в документации jscodeshift

node node_modules/.bin/jscodeshift -t ./transformer.ts --extensions=ts --parser=ts ./src/actions/*.ts
import { API, FileInfo } from 'jscodeshift';

// Миграция createAction -> declareAction
// Текущие проблемы: добавляются пробелы перед и после метода fn, при замене поля объекта на метод объекта

function orderTypeParams(j, typeParams) {
  const anyTypeNode = j.tsAnyKeyword();

  if (!typeParams || typeParams.length < 1)
    return j.tsTypeParameterInstantiation([anyTypeNode, anyTypeNode, anyTypeNode]);

  if (typeParams.length === 1)
    return j.tsTypeParameterInstantiation([anyTypeNode, typeParams[0], anyTypeNode]);

  if (typeParams.length === 2)
    return j.tsTypeParameterInstantiation([
      j.tsTupleType([typeParams[1]]),
      typeParams[0],
      anyTypeNode,
    ]);

  return j.tsTypeParameterInstantiation([
    j.tsTupleType([typeParams[1]]),
    typeParams[0],
    typeParams[2],
  ]);
}

function normalizeFnBody({ j, pathToFn, contextName, contextNames, depsName, depsNames }) {
  const body = j.withParser('tsx')(pathToFn.body);
  body
    .find(j.Identifier, (node) => {
      if (contextName ? node.name === contextName : contextNames.indexOf(node.name) > -1)
        return true;
      if (depsName ? node.name === depsName : depsNames.indexOf(node.name) > -1) return true;

      return false;
    })
    .replaceWith((p) => {
      if (
        j.MemberExpression.check(p.parentPath.value) &&
        p.parentPath.value.object.name !== p.value.name
      )
        return p.value;

      if (p.value.name === contextName) return j.thisExpression();
      if (contextNames.indexOf(p.value.name) > -1)
        return j.memberExpression(j.thisExpression(), p.value);

      return j.memberExpression(
        j.memberExpression(j.thisExpression(), j.identifier('deps')),
        p.value
      );
    });

  // Здесь, так как прокидываем только одну node в парсер в начале функции,
  // то и вернется точно только одна node
  return body.nodes()[0];
}

function replaceFnMethod(j, p, isMethod = false) {
  const resultPath = isMethod ? p.value : p.value.value;
  const [ctx, payload, deps] = resultPath.params;

  const contextName = ctx && ctx.name;
  const contextNames: string[] = [];
  if (!contextName && ctx) {
    ctx.properties &&
      ctx.properties.forEach((el) =>
        typeof el.key.name === 'string' ? contextNames.push(el.key.name) : null
      );
  }

  const depsName = deps && deps.name;
  const depsNames: string[] = [];
  if (!depsName && deps) {
    deps.properties &&
      deps.properties.forEach((el) =>
        typeof el.key.name === 'string' ? depsNames.push(el.key.name) : null
      );
  }

  const body = normalizeFnBody({
    j,
    pathToFn: resultPath,
    contextNames,
    contextName,
    depsName,
    depsNames,
  });

  return j.objectMethod.from({
    kind: 'method',
    key: p.value.key,
    params: payload ? [payload] : [],
    body: j.BlockStatement.check(resultPath.body)
      ? body
      : j.blockStatement([j.returnStatement(body)]),
    computed: p.value.computed,
    async: resultPath.async,
  });
}

export default function transformer(file: FileInfo, api: API) {
  const j = api.jscodeshift;
  const root = j.withParser('tsx')(file.source);

  root
    .find(j.Identifier, (node) => node.name === 'createAction')
    .replaceWith((p) => {
      p.parentPath.value.typeParameters = orderTypeParams(
        j,
        p.parentPath.value.typeParameters && p.parentPath.value.typeParameters.params
      );

      return j.identifier('declareAction');
    });

  root
    .find(j.CallExpression, (p) => p.callee.name === 'declareAction')
    .find(j.ObjectMethod, (p) => p.key && p.key.name === 'fn')
    .replaceWith((p) => replaceFnMethod(j, p, true));

  root
    .find(j.CallExpression, (p) => p.callee.name === 'declareAction')
    .find(
      j.ObjectProperty,
      (p) => p.value.type === 'ArrowFunctionExpression' && p.key && p.key.name === 'fn'
    )
    .replaceWith((p) => replaceFnMethod(j, p));

  return root.toSource();
}

Execution Context Manager

Это в основном внутренняя сущность, которая позволяет теперь отслеживать контекст исполнения разной логики и использовать AbortController для отмены выполнения кода, который по какой-то причине стал неактуален. Раньше же такой код всегда выполнялся и тратил ресурсы.

Применение Execution Context Manager на данный момент:

  • при возникновении ошибки при выполнении линий CommandLineRunner вызывается соответствующий abortController.abort() что позволяет отменить выполнение других функций, которые работают в этих линиях (точнее говоря там, где правильно используется abortSignal из контекста выполнения - а это экшены, запросы и явное использование в других функциях)
  • в страничных экшенах на сервере - abort будет вызван при превышении лимита выполнения экшенов (хотя те экшены которые будут в процессе выполнения, всё равно продолжат исполняться, но они уже не смогут вызвать новые экшены или выполнять запросы после таймаута)
  • при выполнении запросов в рамках commandLine - если выполнение линий было аобртнуто, то все соответствующие запросы в рамках этой линий также будут аборнуты
  • Явно внутри экшенов для отмены выполнения текущего экшена - выполняется через вызов this.abortController.abort() внутри функции из declareAction

Миграция

Явная миграция не требуется.

Можно только использовать новые возможности в плане работы с abortSignal чтобы избегать выполнения лишних действий:

Изменения в логике работы react-query

@tramvai/react-query теперь использует внутри себя declareAction вместе с conditionsFailResult: 'reject', что означает, что результатом query которая не может быть выполнена из-за проверки conditions теперь будет ошибка, а не пустые данные как раньше.

Также при использовании авторизационных ролей в качестве conditions, если эти роли поменялись на клиенте, то все активные query, зависящие от ролей, будут перевызваны.

[15.07.2022] v2.2.2 - важные изменения

Общий гайд по типизации в tramvai приложениях

Улучшена типизация provide

Раньше, при использовании useFactory внутри provider, не проверялся тип токена и результата, возвращаемого фабрикой. Например, такая конструкция не выдавала ошибок:

const provider = provide({
  provide: NUMBER_TOKEN,
  useFactory: () => 'some string',
});

И приходилось вручную писать тип токена как результат вызова фабрики:

const provider = provide({
  provide: NUMBER_TOKEN,
  useFactory: (): typeof NUMBER_TOKEN => 1000,
});

Теперь происходит автоматическое сравнение типов результата вызова фабрики и токена. Это изменение может вызвать ошибки проверки типов в вашем приложении, для исправления ошибок, придерживайтесь нескольких правил:

  • Не указывайте тип результата вызова функции для useFactory
  • Не указывайте типа для аргумента deps у useFactory
  • Не указывайте массив типов для multi токенов

Улучшена типизация createToken

Раньше при создании токена с типом string, при получении этого токена из DI, тип полученного значения выводился как any. Примеры проблемных кейсов:

const provider = provide({
  provide: NUMBER_TOKEN,
  useFactory: ({ fakeNumber }) => {
    // тип fakeNumber выводится как `any`, нет ошибок компиляции
    return fakeNumber;
  },
  deps: {
    fakeNumber: STRING_TOKEN,
  },
});
const Component = () => {
  const fakeNumber = useDi(STRING_TOKEN);

  // тип fakeNumber выводится как `any`, нет ошибок компиляции
  return <>{fakeNumber.toFixed()}</>;
};

Теперь во всех кейсах корректно выводится тип токена и его значения. При появлении ошибок тайпчекинга в приложении, убедитесь в корректной работе с проблемными токенами.

Улучшена типизация multi токенов

Раньше приходилось указывать массив значения для multi токена:

const MULTI_TOKEN = createToken<number[]>('multi token', { multi: true });

При использовании этого токена для создания провайдера, приходилось вручную получать тип элемента массива:

const provider = provide({
  provide: MULTI_TOKEN,
  useFactory: (): typeof NUMBER_TOKEN[number] => 1000,
});

Сейчас корректные типы выводятся автоматически, и для создания значения, и для работы с зависимостями:

// создаем два мульти токена с одинаковыми типами
const MULTI_TOKEN = createToken<number>('multi token', { multi: true });
const ANOTHER_MULTI_TOKEN = createToken<number>('another multi token', { multi: true });

const provider = provide({
  provide: MULTI_TOKEN,
  // проверяется, что useFactory возвращает число
  // тип deps.anotherNumbers выводится как массив чисел
  useFactory: (deps) => {
    return deps.anotherNumbers[0] ?? 1000;
  },
  deps: {
    anotherNumbers: ANOTHER_MULTI_TOKEN,
  },
});

Для решения ошибок с тайпчекингом при использовании multi токенов, рекомендуется несколько шагов:

  • Отрефакторить токены, что бы в типах не было массивов, если только они там действительно не нужны
  • Убрать ручной вывод типов для провайдеров, такие конструкции как : typeof MULTI_TOKEN[number]

[27.06.2022] v2.0.0 - важные изменения

С этого дня 1.x.x мажорная версия tramvai переходит в состояние Supported, и будет получать только критичные патчи, до 27 декабря 2022г. При этом, в latest теге в npm остается именно 1.x.x версия, до того как 2.x.x мажорка стабилизируется.

2.x.x мажорная версия публикуется с тегом prerelease, и на данный момент не содержит breaking changes, но они планируются в ближайшем будущем.

[09.06.2022] v1.106.0 - важные изменения

В этой версии в зависимостях трамвай пакетов обновили все пакеты @tinkoff/request-* на мажорную версию. Что изменилось:

Перешли на lru-cache-nano, облегченный форк lru-cache и на сборку через @tramvai/build По итогу, это немного (2-3kb gzip) уменьшает размер клиентского бандла.

После обновления, стоит убедиться что у вас нет в клиентском коде более старых версий библиотек @tinkoff/request-* - дубликаты могут попасть из транзитивных зависимостей, либо могут быть явно указаны у вас в package.json.

[01.06.2022] v1.104.2 - важные изменения

С этого релиза минимально необходимая версии React - >=16.14.0

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment