From f1ecdabd3cd463b013a8f1a8c72aea5c80e23bf9 Mon Sep 17 00:00:00 2001 From: Convly Date: Thu, 13 Oct 2022 11:01:35 +0200 Subject: [PATCH] Add basis for strapi/data-transfer core package + transfer engine --- packages/core/data-transfer/LICENSE | 22 ++ .../core/data-transfer/lib/engine/index.ts | 212 +++++++++++++++++ packages/core/data-transfer/lib/index.ts | 0 packages/core/data-transfer/package.json | 53 +++++ packages/core/data-transfer/tsconfig.json | 11 + .../data-transfer/types/common-entities.d.ts | 215 ++++++++++++++++++ packages/core/data-transfer/types/index.d.ts | 4 + .../core/data-transfer/types/providers.d.ts | 34 +++ .../data-transfer/types/transfer-engine.d.ts | 135 +++++++++++ packages/core/data-transfer/types/utils.d.ts | 27 +++ yarn.lock | 29 +++ 11 files changed, 742 insertions(+) create mode 100644 packages/core/data-transfer/LICENSE create mode 100644 packages/core/data-transfer/lib/engine/index.ts create mode 100644 packages/core/data-transfer/lib/index.ts create mode 100644 packages/core/data-transfer/package.json create mode 100644 packages/core/data-transfer/tsconfig.json create mode 100644 packages/core/data-transfer/types/common-entities.d.ts create mode 100644 packages/core/data-transfer/types/index.d.ts create mode 100644 packages/core/data-transfer/types/providers.d.ts create mode 100644 packages/core/data-transfer/types/transfer-engine.d.ts create mode 100644 packages/core/data-transfer/types/utils.d.ts diff --git a/packages/core/data-transfer/LICENSE b/packages/core/data-transfer/LICENSE new file mode 100644 index 0000000000..cbf83deca9 --- /dev/null +++ b/packages/core/data-transfer/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2015-present Strapi Solutions SAS + +Portions of the Strapi software are licensed as follows: + +- All software that resides under an "ee/" directory (the “EE Software”), if that directory exists, is licensed under the license defined in "ee/LICENSE". + +- All software outside of the above-mentioned directories or restrictions above is available under the "MIT Expat" license as set forth below. + +MIT Expat License + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/core/data-transfer/lib/engine/index.ts b/packages/core/data-transfer/lib/engine/index.ts new file mode 100644 index 0000000000..6f349ebc87 --- /dev/null +++ b/packages/core/data-transfer/lib/engine/index.ts @@ -0,0 +1,212 @@ +import { pipeline } from 'stream'; +import { chain } from 'stream-chain'; +import { + IDestinationProvider, + ISourceProvider, + ITransferEngine, + ITransferEngineOptions, +} from '../../types'; + +export class TransferEngine implements ITransferEngine { + sourceProvider: ISourceProvider; + destinationProvider: IDestinationProvider; + options: ITransferEngineOptions; + + constructor( + sourceProvider: ISourceProvider, + destinationProvider: IDestinationProvider, + options: ITransferEngineOptions + ) { + this.sourceProvider = sourceProvider; + this.destinationProvider = destinationProvider; + this.options = options; + } + + private assertStrapiVersionIntegrity(sourceVersion?: string, destinationVersion?: string) { + const strategy = this.options.versionMatching; + + if (!sourceVersion || !destinationVersion) { + return; + } + + if (strategy === 'ignore') { + return; + } + + if (strategy === 'exact' && sourceVersion === destinationVersion) { + return; + } + + const sourceTokens = sourceVersion.split('.'); + const destinationTokens = destinationVersion.split('.'); + + const [major, minor, patch] = sourceTokens.map( + (value, index) => value === destinationTokens[index] + ); + + if ( + (strategy === 'major' && major) || + (strategy === 'minor' && major && minor) || + (strategy === 'patch' && major && minor && patch) + ) { + return; + } + + throw new Error( + `Strapi versions doesn't match (${strategy} check): ${sourceVersion} does not match with ${destinationVersion} ` + ); + } + + async boostrap(): Promise { + await Promise.all([ + // bootstrap source provider + this.sourceProvider.bootstrap?.(), + // bootstrap destination provider + this.destinationProvider.bootstrap?.(), + ]); + } + + async close(): Promise { + await Promise.all([ + // close source provider + this.sourceProvider.close?.(), + // close destination provider + this.destinationProvider.close?.(), + ]); + } + + async integrityCheck(): Promise { + const sourceMetadata = await this.sourceProvider.getMetadata(); + const destinationMetadata = await this.destinationProvider.getMetadata(); + + if (!sourceMetadata || !destinationMetadata) { + return true; + } + + try { + // Version check + this.assertStrapiVersionIntegrity( + sourceMetadata?.strapi?.version, + destinationMetadata?.strapi?.version + ); + + return true; + } catch (error) { + if (error instanceof Error) { + console.error('Integrity checks failed:', error.message); + } + + return false; + } + } + + async transfer(): Promise { + try { + await this.boostrap(); + + const isValidTransfer = await this.integrityCheck(); + + if (!isValidTransfer) { + throw new Error( + `Unable to transfer the data between ${this.sourceProvider.name} and ${this.destinationProvider.name}.\nPlease refer to the log above for more information.` + ); + } + + await this.transferEntities(); + + // NOTE: to split into multiple steps + // entities <> links <> files + // do we need to ignore files from transferEntities & transferLinks? + await this.transferMedia(); + + await this.transferLinks(); + + await this.transferConfiguration(); + + await this.close(); + } catch (e) { + console.log('error', e); + // Rollback the destination provider if an exception is thrown during the transfer + // Note: This will be configurable in the future + // await this.destinationProvider?.rollback(e); + } + } + + async transferEntities(): Promise { + // const inStream = await this.sourceProvider.streamEntities?.(); + // const outStream = await this.destinationProvider.getEntitiesStream?.(); + // if (!inStream || !outStream) { + // console.log('Unable to transfer entities, one of the stream is missing'); + // return; + // } + // return new Promise((resolve, reject) => { + // pipeline( + // // We might want to use a json-chain's Chain here since they allow transforms + // // streams as regular functions (that allows object as parameter & return type) + // inStream, + // // chain([ + // // (data) => { + // // console.log('hello', data); + // // return data; + // // }, + // // ]), + // outStream, + // (e: NodeJS.ErrnoException | null, value: unknown) => { + // if (e) { + // console.log('Something wrong happened', e); + // reject(e); + // return; + // } + // console.log('value', value); + // console.log('All the entities have been transferred'); + // resolve(); + // } + // ); + // }); + } + + async transferLinks(): Promise { + // const inStream = await this.sourceProvider.streamLinks?.(); + // const outStream = await this.destinationProvider.getLinksStream?.(); + + // if (!inStream || !outStream) { + // console.log('Unable to transfer links, one of the stream is missing'); + // return; + // } + + // return new Promise((resolve, reject) => { + // pipeline( + // // We might want to use a json-chain's Chain here since they allow transforms + // // streams as regular functions (that allows object as parameter & return type) + // inStream as any, + // // chain([ + // // (data) => { + // // console.log('hello', data); + // // return data; + // // }, + // // ]), + // outStream as any, + // (e: Error) => { + // if (e) { + // console.log('Something wrong happened', e); + // reject(e); + // return; + // } + // console.log('All the links have been transferred'); + // resolve(); + // } + // ); + // }); + return new Promise((resolve) => resolve()); + } + + async transferMedia(): Promise { + console.log('transferMedia not yet implemented'); + return new Promise((resolve) => resolve()); + } + + async transferConfiguration(): Promise { + console.log('transferConfiguration not yet implemented'); + return new Promise((resolve) => resolve()); + } +} diff --git a/packages/core/data-transfer/lib/index.ts b/packages/core/data-transfer/lib/index.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/core/data-transfer/package.json b/packages/core/data-transfer/package.json new file mode 100644 index 0000000000..42a63c6a2a --- /dev/null +++ b/packages/core/data-transfer/package.json @@ -0,0 +1,53 @@ +{ + "name": "@strapi/data-transfer", + "version": "4.4.3", + "description": "Data transfer capabilities for Strapi", + "keywords": [ + "strapi", + "data", + "transfer", + "import", + "export", + "backup", + "restore" + ], + "license": "SEE LICENSE IN LICENSE", + "author": { + "name": "Strapi Solutions SAS", + "email": "hi@strapi.io", + "url": "https://strapi.io" + }, + "maintainers": [ + { + "name": "Strapi Solutions SAS", + "email": "hi@strapi.io", + "url": "https://strapi.io" + } + ], + "main": "./dist/index.js", + "types": "./src/index.ts", + "scripts": { + "build": "tsc -p tsconfig.json", + "prepublish": "tsc -p tsconfig.json" + }, + "directories": { + "lib": "./dist" + }, + "dependencies": { + "@strapi/logger": "4.4.3", + "chalk": "4.1.2", + "prettier": "2.7.1", + "stream-chain": "2.2.5", + "stream-json": "1.7.4", + "tar": "6.1.11" + }, + "devDependencies": { + "@tsconfig/node16": "1.0.3", + "@types/stream-chain": "2.0.1", + "typescript": "4.8.4" + }, + "engines": { + "node": ">=14.19.1 <=18.x.x", + "npm": ">=6.0.0" + } +} diff --git a/packages/core/data-transfer/tsconfig.json b/packages/core/data-transfer/tsconfig.json new file mode 100644 index 0000000000..39d7857be7 --- /dev/null +++ b/packages/core/data-transfer/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "@tsconfig/node16/tsconfig.json", + "compilerOptions": { + "strict": true, + "lib": ["ESNEXT"], + "skipLibCheck": true, + "outDir": "dist" + }, + "include": ["types", "lib/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/packages/core/data-transfer/types/common-entities.d.ts b/packages/core/data-transfer/types/common-entities.d.ts new file mode 100644 index 0000000000..65ae7e2a2d --- /dev/null +++ b/packages/core/data-transfer/types/common-entities.d.ts @@ -0,0 +1,215 @@ +import { GetAttributesValues } from '@strapi/strapi'; +import { SchemaUID } from '@strapi/strapi/lib/types/utils'; + +export interface IMetadata { + strapi?: { + version?: string; + + plugins?: { + name: string; + version: string; + }[]; + }; + + createdAt?: string; +} + +/** + * Common TransferEngine format to represent a Strapi entity + * @template T The schema UID this entity represents + */ +export interface IEntity { + /** + * UID of the parent type (content-type, component, etc...) + */ + type: T; + + /** + * Reference of the entity. + * Might be deprecated and replaced by a "ref" or "reference" property in the future + */ + id: number | string; + + /** + * The entity data (attributes value) + */ + data: GetAttributesValues; +} + +/** + * Union type that regroups all the different kinds of link + */ +export type ILink = IBasicLink | IMorphLink | ICircularLink | IComponentLink | IDynamicZoneLink; + +/** + * Default generic link structure + */ +interface IDefaultLink { + /** + * The link type + * (useful for providers (destination) to adapt the logic following what kind of link is processed) + */ + kind: string; + + /** + * Left side of the link + * It should hold information about the entity that owns the dominant side of the link + */ + left: { + /** + * Entity UID + * (restricted to content type) + */ + type: Strapi.ContentTypeUIDs; + /** + * Reference ID of the entity + */ + ref: number | string; + }; + + /** + * Right side of the link + * It should hold information about the entity attached to the left side of the link + */ + right: { + /** + * Entity UID + * (can be a content type or a component) + */ + type: SchemaUID; + /** + * Reference ID of the entity + */ + ref: number | string; + }; +} + +/** + * Basic link between two content type entities + */ +interface IBasicLink extends IDefaultLink { + kind: 'relation.basic'; + + right: { + /** + * The right side of a relation.basic link must be a content type + */ + type: Strapi.ContentTypeUIDs; + /** + * Reference ID of the entity + */ + ref: number | string; + }; +} + +/** + * Polymorphic link (one source content type to multiple different content types) + */ +interface IMorphLink extends IDefaultLink { + kind: 'relation.morph'; + + right: { + /** + * The right side of a relation.morph link must be a content type + */ + type: Strapi.ContentTypeUIDs; + /** + * Reference ID of the target entity + */ + ref: number | string; + /** + * The target attribute used to hold the value + */ + attribute: string; + /** + * Can contain the link's position (relative to other similar links) + */ + order: number; + }; +} + +/** + * Regular link with the left and right sides having the save content-type + */ +interface ICircularLink extends IDefaultLink { + kind: 'relation.circular'; +} + +/** + * Link from a content type to a component + */ +interface IComponentLink extends IDefaultLink { + kind: 'component.basic'; + + right: { + /** + * The right side of the link must be a component + */ + type: Strapi.ComponentUIDs; + /** + * Reference ID of the component + */ + ref: number | string; + /** + * The attribute used to hold the link value in the component + */ + attribute: string; + /** + * Can contain the link's position (relative to other similar links) + */ + order: number; + }; +} + +/** + * Link from a content type to a dynamic zone + * Very similar to the component link but with a different name + */ +interface IDynamicZoneLink extends IDefaultLink { + kind: 'dynamiczone.basic'; + + right: { + /** + * The right side of the link must be a component + */ + type: Strapi.ComponentUIDs; + /** + * Reference ID of the component + */ + ref: number | string; + /** + * The attribute used to hold the link value in the component + */ + attribute: string; + /** + * MUST contain the link's position relative to other links + * bound to the same dynamic zone (aka. left side of the link) + */ + order: number; + }; +} + +/** + * Represent a piece of a media file + * + * /!\ Draft Version /!\ + * + * Note: even individual media will probably get streamed chunk by chunk, + * we need a way to identify to which entity they're related to. + * + * Also, it might get tricky to apply specific transformations to media as a whole + */ +export interface IMedia { + /** + * The media mime type + */ + type: 'png' | 'pdf'; // | ... | ... + /** + * Reference ID for the media + */ + ref: number | string; + /** + * Data chunk (as a buffer) that contains a part of the file + */ + chunk: Buffer | Buffer[]; +} diff --git a/packages/core/data-transfer/types/index.d.ts b/packages/core/data-transfer/types/index.d.ts new file mode 100644 index 0000000000..7dfc6eabad --- /dev/null +++ b/packages/core/data-transfer/types/index.d.ts @@ -0,0 +1,4 @@ +export * from './common-entities'; +export * from './providers'; +export * from './transfer-engine'; +export * from './utils'; diff --git a/packages/core/data-transfer/types/providers.d.ts b/packages/core/data-transfer/types/providers.d.ts new file mode 100644 index 0000000000..f1fe44d384 --- /dev/null +++ b/packages/core/data-transfer/types/providers.d.ts @@ -0,0 +1,34 @@ +import { Stream } from './utils'; +import { IMetadata } from './common-entities'; + +type ProviderType = 'source' | 'destination'; + +interface IProvider { + type: ProviderType; + name: string; + + bootstrap?(): Promise | void; + close?(): Promise | void; + getMetadata(): IMetadata | Promise; +} + +export interface ISourceProvider extends IProvider { + // Getters for the source's transfer streams + streamEntities?(): Stream | Promise; + streamLinks?(): Stream | Promise; + streamMedia?(): Stream | Promise; + streamConfiguration?(): Stream | Promise; +} + +export interface IDestinationProvider extends IProvider { + /** + * Optional rollback implementation + */ + rollback?(e: T): void | Promise; + + // Getters for the destination's transfer streams + getEntitiesStream?(): Stream | Promise; + getLinksStream?(): Stream | Promise; + getMediaStream?(): Stream | Promise; + getConfigurationStream?(): Stream | Promise; +} diff --git a/packages/core/data-transfer/types/transfer-engine.d.ts b/packages/core/data-transfer/types/transfer-engine.d.ts new file mode 100644 index 0000000000..c96c789b1d --- /dev/null +++ b/packages/core/data-transfer/types/transfer-engine.d.ts @@ -0,0 +1,135 @@ +import { SchemaUID } from '@strapi/strapi/lib/types/utils'; +import { IEntity, ILink, IMedia } from './common-entities'; +import { ITransferRule } from './utils'; +import { ISourceProvider, IDestinationProvider } from './provider'; + +/** + * Defines the capabilities and properties of the transfer engine + */ +export interface ITransferEngine { + /** + * Provider used as a source which that will stream its data to the transfer engine + */ + sourceProvider: ISourceProvider; + /** + * Provider used as a destination that will receive its data from the transfer engine + */ + destinationProvider: IDestinationProvider; + /** + * The options used to customize the behavio of the transfer engine + */ + options: ITransferEngineOptions; + + /** + * Runs the integrity check which will make sure it's possible + * to transfer data from the source to the provider. + * + * Note: It requires to read the content of the source & destination metadata files + */ + integrityCheck(): Promise; + + /** + * Start streaming selected data from the source to the destination + */ + transfer(): Promise; + + /** + * Run the bootstrap lifecycle method of each provider + * + * Note: The bootstrap method can be used to initialize database + * connections, open files, etc... + */ + boostrap(): Promise; + + /** + * Run the close lifecycle method of each provider + * + * Note: The close method can be used to gracefully close connections, cleanup the filesystem, etc.. + */ + close(): Promise; + + /** + * Start the entities transfer by connecting the + * related source and destination providers streams + */ + transferEntities(): Promise; + + /** + * Start the links transfer by connecting the + * related source and destination providers streams + */ + transferLinks(): Promise; + + /** + * Start the media transfer by connecting the + * related source and destination providers streams + */ + transferMedia(): Promise; + + /** + * Start the configuration transfer by connecting the + * related source and destination providers streams + */ + transferConfiguration(): Promise; +} + +/** + * Options used to customize the TransferEngine behavior + * + * Note: Please add your suggestions. Also, we'll need to consider matching what is + * written for the CLI with those options at one point + * + * Note: here, we're listing the TransferEngine options, not the individual providers' options + */ +export interface ITransferEngineOptions { + /** + * The strategy to use when importing the data from the source to the destination + * Note: Should we keep this here or fully delegate the strategies logic to the destination? + */ + strategy: 'restore' | 'merge'; + /** + * What kind of version matching should be done between the source and the destination metadata? + * @example + * "exact" // must be a strict equality, whatever the format used + * "ignore" // do not check if versions match + * "major" // only the major version should match. 4.3.9 and 4.4.1 will work, while 3.3.2 and 4.3.2 won't + * "minor" // both the major and minor version should match. 4.3.9 and 4.3.11 will work, while 4.3.9 and 4.4.1 won't + * "patch" // every part of the version should match. Similar to "exact" but only work on semver. + */ + versionMatching: 'exact' | 'ignore' | 'major' | 'minor' | 'patch'; + + // List of global transform streams to integrate into the final pipelines + common?: { + rules?: ITransferRule[]; + }; + + /** + * Options related to the transfer of the entities + */ + entities?: { + /** + * Transformation rules for entities + */ + rules?: ITransferRule<(entity: IEntity) => boolean>[]; + }; + + /** + * Options related to the transfer of the links + */ + links?: { + /** + * Transformation rules for links + */ + rules?: ITransferRule<(link: T) => boolean>[]; + }; + + /** + * Options related to the transfer of the links + */ + media?: { + /** + * Transformation rules for media chunks + */ + rules?: ITransferRule<(media: T) => boolean>[]; + }; +} diff --git a/packages/core/data-transfer/types/utils.d.ts b/packages/core/data-transfer/types/utils.d.ts new file mode 100644 index 0000000000..11739709e9 --- /dev/null +++ b/packages/core/data-transfer/types/utils.d.ts @@ -0,0 +1,27 @@ +import { Readable, Writable, Duplex, Transform } from 'stream'; + +/** + * Default signature for transfer rules' filter methods + */ +type TransferRuleFilterSignature = (...params: unknown[]) => boolean; + +/** + * Define a transfer rule which will be used to intercept + * and potentially modify the transferred data + */ +export interface ITransferRule< + T extends TransferRuleFilterSignature = TransferRuleFilterSignature +> { + /** + * Filter method used to select which data should be transformed + */ + filter?: T; + /** + * Transform middlewares which will be applied to the filtered data + */ + transforms: StreamItem[]; +} + +export type TransformFunction = (chunk: any, encoding?: string) => any; +export type StreamItem = Stream | TransformFunction; +type Stream = Readable | Writable | Duplex | Transform; diff --git a/yarn.lock b/yarn.lock index 5bf9098e87..e24fbaea14 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5693,6 +5693,11 @@ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A== +"@tsconfig/node16@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" + integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== + "@types/accepts@*", "@types/accepts@^1.3.5": version "1.3.5" resolved "https://registry.yarnpkg.com/@types/accepts/-/accepts-1.3.5.tgz#c34bec115cfc746e04fe5a059df4ce7e7b391575" @@ -6272,6 +6277,13 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c" integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw== +"@types/stream-chain@2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@types/stream-chain/-/stream-chain-2.0.1.tgz#4d3cc47a32609878bc188de0bae420bcfd3bf1f5" + integrity sha512-D+Id9XpcBpampptkegH7WMsEk6fUdf9LlCIX7UhLydILsqDin4L0QT7ryJR0oycwC7OqohIzdfcMHVZ34ezNGg== + dependencies: + "@types/node" "*" + "@types/tapable@^1", "@types/tapable@^1.0.5": version "1.0.8" resolved "https://registry.yarnpkg.com/@types/tapable/-/tapable-1.0.8.tgz#b94a4391c85666c7b73299fd3ad79d4faa435310" @@ -20776,6 +20788,11 @@ stream-browserify@^3.0.0: inherits "~2.0.4" readable-stream "^3.5.0" +stream-chain@2.2.5, stream-chain@^2.2.5: + version "2.2.5" + resolved "https://registry.yarnpkg.com/stream-chain/-/stream-chain-2.2.5.tgz#b30967e8f14ee033c5b9a19bbe8a2cba90ba0d09" + integrity sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA== + stream-each@^1.1.0: version "1.2.3" resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae" @@ -20805,6 +20822,13 @@ stream-http@^3.2.0: readable-stream "^3.6.0" xtend "^4.0.2" +stream-json@1.7.4: + version "1.7.4" + resolved "https://registry.yarnpkg.com/stream-json/-/stream-json-1.7.4.tgz#e41637f93c5aca7267009ca8a3f6751e62331e69" + integrity sha512-ja2dde1v7dOlx5/vmavn8kLrxvNfs7r2oNc5DYmNJzayDDdudyCSuTB1gFjH4XBVTIwxiMxL4i059HX+ZiouXg== + dependencies: + stream-chain "^2.2.5" + stream-shift@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" @@ -21860,6 +21884,11 @@ typescript@4.6.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.2.tgz#fe12d2727b708f4eef40f51598b3398baa9611d4" integrity sha512-HM/hFigTBHZhLXshn9sN37H085+hQGeJHJ/X7LpBWLID/fbc2acUMfU+lGD98X81sKP+pFa9f0DZmCwB9GnbAg== +typescript@4.8.4: + version "4.8.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6" + integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ== + typescript@^4.6.2: version "4.7.4" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235"