diff --git a/package.json b/package.json index 9b9ac3a3580ea..6419a1218576d 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,6 @@ "es5-ext": "0.10.53", "lodash": "^4.17.0", "@types/node": "^20", - "@types/ramda": "0.27.40", "thrift": "0.20.0" }, "license": "MIT", diff --git a/packages/cubejs-query-orchestrator/src/orchestrator/PreAggregationLoader.ts b/packages/cubejs-query-orchestrator/src/orchestrator/PreAggregationLoader.ts index 16703906b76e1..30aeaf5c8671d 100644 --- a/packages/cubejs-query-orchestrator/src/orchestrator/PreAggregationLoader.ts +++ b/packages/cubejs-query-orchestrator/src/orchestrator/PreAggregationLoader.ts @@ -990,7 +990,7 @@ export class PreAggregationLoader { actualTables, ); const versionEntriesToSave = R.pipe< - VersionEntry[], + [VersionEntry[]], { [index: string]: VersionEntry[] }, Array<[string, VersionEntry[]]>, VersionEntry[] @@ -1000,7 +1000,7 @@ export class PreAggregationLoader { R.map(p => p[1][0]) )(versionEntries); const structureVersionsToSave = R.pipe< - VersionEntry[], + [VersionEntry[]], VersionEntry[], { [index: string]: VersionEntry[] }, Array<[string, VersionEntry[]]>, diff --git a/packages/cubejs-query-orchestrator/src/orchestrator/QueryOrchestrator.ts b/packages/cubejs-query-orchestrator/src/orchestrator/QueryOrchestrator.ts index 46e55da79519f..b3214ffdf1d96 100644 --- a/packages/cubejs-query-orchestrator/src/orchestrator/QueryOrchestrator.ts +++ b/packages/cubejs-query-orchestrator/src/orchestrator/QueryOrchestrator.ts @@ -3,7 +3,7 @@ import R from 'ramda'; import { getEnv } from '@cubejs-backend/shared'; import { CubeStoreDriver } from '@cubejs-backend/cubestore-driver'; -import { QueryCache, QueryBody, TempTable } from './QueryCache'; +import { QueryCache, QueryBody, TempTable, PreAggTableToTempTable } from './QueryCache'; import { PreAggregations, PreAggregationDescription, getLastUpdatedAtTimestamp } from './PreAggregations'; import { DriverFactory, DriverFactoryByDataSource } from './DriverFactory'; import { LocalQueueEventsBus } from './LocalQueueEventsBus'; @@ -224,28 +224,20 @@ export class QueryOrchestrator { }; } - const usedPreAggregations = R.pipe( + const usedPreAggregations = R.pipe< + [Array], + Record, + Record + >( R.fromPairs, + // This requires @types/ramda@0.29 or newer + // @ts-ignore R.map((pa: TempTable) => ({ targetTableName: pa.targetTableName, refreshKeyValues: pa.refreshKeyValues, lastUpdatedAt: pa.lastUpdatedAt, })), - )( - preAggregationsTablesToTempTables as unknown as [ - number, // TODO: we actually have a string here - { - buildRangeEnd: string, - lastUpdatedAt: number, - queryKey: unknown, - refreshKeyValues: [{ - 'refresh_key': string, - }][], - targetTableName: string, - type: string, - }, - ][] - ); + )(preAggregationsTablesToTempTables); if (this.rollupOnlyMode && Object.keys(usedPreAggregations).length === 0) { throw new Error( diff --git a/packages/cubejs-schema-compiler/package.json b/packages/cubejs-schema-compiler/package.json index e19ff22146c90..2a53ff41d6888 100644 --- a/packages/cubejs-schema-compiler/package.json +++ b/packages/cubejs-schema-compiler/package.json @@ -68,6 +68,7 @@ "@types/inflection": "^1.5.28", "@types/jest": "^29", "@types/node": "^20", + "@types/node-dijkstra": "^2.5.6", "@types/ramda": "^0.27.34", "@types/sqlstring": "^2.3.0", "@types/syntax-error": "^1.4.1", diff --git a/packages/cubejs-schema-compiler/src/adapter/BaseDimension.ts b/packages/cubejs-schema-compiler/src/adapter/BaseDimension.ts index 34e8fde294ed4..0009b07cbaade 100644 --- a/packages/cubejs-schema-compiler/src/adapter/BaseDimension.ts +++ b/packages/cubejs-schema-compiler/src/adapter/BaseDimension.ts @@ -142,6 +142,11 @@ export class BaseDimension { if (this.expression) { return `expr:${this.expressionName}`; } - return this.query.cubeEvaluator.pathFromArray(this.path() as string[]); + const path = this.path(); + if (path === null) { + // Sanity check, this should not actually happen because we checked this.expression earlier + throw new Error('Unexpected null path'); + } + return this.query.cubeEvaluator.pathFromArray(path); } } diff --git a/packages/cubejs-schema-compiler/src/adapter/BaseFilter.ts b/packages/cubejs-schema-compiler/src/adapter/BaseFilter.ts index 71a59d3ae4f08..0aba27e512e54 100644 --- a/packages/cubejs-schema-compiler/src/adapter/BaseFilter.ts +++ b/packages/cubejs-schema-compiler/src/adapter/BaseFilter.ts @@ -1,6 +1,6 @@ import inlection from 'inflection'; import moment from 'moment-timezone'; -import { contains, join, map } from 'ramda'; +import { includes, join, map } from 'ramda'; import { FROM_PARTITION_RANGE, TO_PARTITION_RANGE } from '@cubejs-backend/shared'; import { BaseDimension } from './BaseDimension'; @@ -134,7 +134,7 @@ export class BaseFilter extends BaseDimension { } public isDateOperator(): boolean { - return contains(this.camelizeOperator, DATE_OPERATORS); + return includes(this.camelizeOperator, DATE_OPERATORS); } public valuesArray() { diff --git a/packages/cubejs-schema-compiler/src/adapter/BaseMeasure.ts b/packages/cubejs-schema-compiler/src/adapter/BaseMeasure.ts index dff05deb9ff5f..2acb3bf19c0f2 100644 --- a/packages/cubejs-schema-compiler/src/adapter/BaseMeasure.ts +++ b/packages/cubejs-schema-compiler/src/adapter/BaseMeasure.ts @@ -320,17 +320,22 @@ export class BaseMeasure { return this.measureDefinition().sql; } - public path() { + public path(): Array | null { if (this.expression) { return null; } return this.query.cubeEvaluator.parsePath('measures', this.measure); } - public expressionPath() { + public expressionPath(): string { if (this.expression) { return `expr:${this.expression.expressionName}`; } - return this.query.cubeEvaluator.pathFromArray(this.path() as string[]); + const path = this.path(); + if (path === null) { + // Sanity check, this should not actually happen because we checked this.expression earlier + throw new Error('Unexpected null path'); + } + return this.query.cubeEvaluator.pathFromArray(path); } } diff --git a/packages/cubejs-schema-compiler/src/adapter/BaseQuery.js b/packages/cubejs-schema-compiler/src/adapter/BaseQuery.js index 58737882810a3..f5b2df79dd3e9 100644 --- a/packages/cubejs-schema-compiler/src/adapter/BaseQuery.js +++ b/packages/cubejs-schema-compiler/src/adapter/BaseQuery.js @@ -129,6 +129,11 @@ export class BaseQuery { /** @type {import('./BaseTimeDimension').BaseTimeDimension[]} */ timeDimensions; + /** + * @type {import('../compiler/JoinGraph').FinishedJoinTree} + */ + join; + /** * BaseQuery class constructor. * @param {Compilers|*} compilers @@ -413,7 +418,7 @@ export class BaseQuery { /** * - * @returns {Array>} + * @returns {Array>} */ get allJoinHints() { if (!this.collectedJoinHints) { @@ -2001,6 +2006,12 @@ export class BaseQuery { )); } + /** + * + * @param {string} cube + * @param {boolean} [isLeftJoinCondition] + * @returns {[string, string, string?]} + */ rewriteInlineCubeSql(cube, isLeftJoinCondition) { const sql = this.cubeSql(cube); const cubeAlias = this.cubeAlias(cube); @@ -2023,9 +2034,15 @@ export class BaseQuery { } } + /** + * + * @param {import('../compiler/JoinGraph').FinishedJoinTree} join + * @param {Array} subQueryDimensions + * @returns {string} + */ joinQuery(join, subQueryDimensions) { const subQueryDimensionsByCube = R.groupBy(d => this.cubeEvaluator.cubeNameFromPath(d), subQueryDimensions); - const joins = join.joins.map( + const joins = join.joins.flatMap( j => { const [cubeSql, cubeAlias, conditions] = this.rewriteInlineCubeSql(j.originalTo, true); return [{ @@ -2035,7 +2052,7 @@ export class BaseQuery { // TODO handle the case when sub query referenced by a foreign cube on other side of a join }].concat((subQueryDimensionsByCube[j.originalTo] || []).map(d => this.subQueryJoin(d))); } - ).reduce((a, b) => a.concat(b), []); + ); const [cubeSql, cubeAlias] = this.rewriteInlineCubeSql(join.root); @@ -2047,6 +2064,11 @@ export class BaseQuery { ]); } + /** + * + * @param {JoinChain} toJoin + * @returns {string} + */ joinSql(toJoin) { const [root, ...rest] = toJoin; const joins = rest.map( @@ -2108,6 +2130,11 @@ export class BaseQuery { return this.filtersWithoutSubQueriesValue; } + /** + * + * @param {string} dimension + * @returns {{ prefix: string, subQuery: this, cubeName: string }} + */ subQueryDescription(dimension) { const symbol = this.cubeEvaluator.dimensionByPath(dimension); const [cubeName, name] = this.cubeEvaluator.parsePath('dimensions', dimension); @@ -2152,6 +2179,12 @@ export class BaseQuery { return { prefix, subQuery, cubeName }; } + /** + * + * @param {string} cubeName + * @param {string} name + * @returns {string} + */ subQueryName(cubeName, name) { return `${cubeName}_${name}_subquery`; } @@ -2355,6 +2388,10 @@ export class BaseQuery { ); } + /** + * @param {string} cube + * @returns {unknown} + */ cubeSql(cube) { const foundPreAggregation = this.preAggregations.findPreAggregationToUseForCube(cube); if (foundPreAggregation && @@ -2449,6 +2486,13 @@ export class BaseQuery { ]; } + /** + * @template T + * @param {boolean} excludeTimeDimensions + * @param {(t: () => void) => T} fn + * @param {string | Array} methodName + * @returns {T} + */ collectFromMembers(excludeTimeDimensions, fn, methodName) { const membersToCollectFrom = this.allMembersConcat(excludeTimeDimensions) .concat(this.join ? this.join.joins.map(j => ({ @@ -2475,6 +2519,14 @@ export class BaseQuery { .concat(excludeTimeDimensions ? [] : this.timeDimensions); } + /** + * @template T + * @param {Array} membersToCollectFrom + * @param {(t: () => void) => T} fn + * @param {string | Array} methodName + * @param {unknown} [cache] + * @returns {T} + */ collectFrom(membersToCollectFrom, fn, methodName, cache) { const methodCacheKey = Array.isArray(methodName) ? methodName : [methodName]; return R.pipe( @@ -2496,6 +2548,11 @@ export class BaseQuery { ); } + /** + * + * @param {() => void} fn + * @returns {Array} + */ collectSubQueryDimensionsFor(fn) { const context = { subQueryDimensions: [] }; this.evaluateSymbolSqlWithContext( @@ -2971,6 +3028,11 @@ export class BaseQuery { return strings.join(' || '); } + /** + * + * @param {string} cubeName + * @returns {Array} + */ primaryKeyNames(cubeName) { const primaryKeys = this.cubeEvaluator.primaryKeys[cubeName]; if (!primaryKeys || !primaryKeys.length) { @@ -3106,6 +3168,12 @@ export class BaseQuery { )(context.leafMeasures); } + /** + * @template T + * @param {() => T} fn + * @param {unknown} context + * @returns {T} + */ evaluateSymbolSqlWithContext(fn, context) { const oldContext = this.evaluateSymbolContext; this.evaluateSymbolContext = oldContext ? Object.assign({}, oldContext, context) : context; @@ -3328,6 +3396,11 @@ export class BaseQuery { .map(s => `(${s})`).join(' AND '); } + /** + * @param {string} primaryKeyName + * @param {string} cubeName + * @returns {unknown} + */ primaryKeySql(primaryKeyName, cubeName) { const primaryKeyDimension = this.cubeEvaluator.dimensionByPath([cubeName, primaryKeyName]); return this.evaluateSymbolSql( @@ -3473,8 +3546,8 @@ export class BaseQuery { /** * - * @param options - * @returns {BaseQuery} + * @param {unknown} options + * @returns {this} */ newSubQuery(options) { const QueryClass = this.constructor; diff --git a/packages/cubejs-schema-compiler/src/adapter/BaseSegment.ts b/packages/cubejs-schema-compiler/src/adapter/BaseSegment.ts index 854eb8da1c8f2..88b626c72b3c6 100644 --- a/packages/cubejs-schema-compiler/src/adapter/BaseSegment.ts +++ b/packages/cubejs-schema-compiler/src/adapter/BaseSegment.ts @@ -81,17 +81,22 @@ export class BaseSegment { return this.segmentDefinition().sql; } - public path() { + public path(): Array | null { if (this.expression) { return null; } return this.query.cubeEvaluator.parsePath('segments', this.segment); } - public expressionPath() { + public expressionPath(): string { if (this.expression) { return `expr:${this.expression.expressionName}`; } - return this.query.cubeEvaluator.pathFromArray(this.path() as string[]); + const path = this.path(); + if (path === null) { + // Sanity check, this should not actually happen because we checked this.expression earlier + throw new Error('Unexpected null path'); + } + return this.query.cubeEvaluator.pathFromArray(path); } } diff --git a/packages/cubejs-schema-compiler/src/adapter/PreAggregations.ts b/packages/cubejs-schema-compiler/src/adapter/PreAggregations.ts index 0dd6fad25ec4f..4d01f9f265ca7 100644 --- a/packages/cubejs-schema-compiler/src/adapter/PreAggregations.ts +++ b/packages/cubejs-schema-compiler/src/adapter/PreAggregations.ts @@ -4,7 +4,7 @@ import { CubeSymbols } from '../compiler/CubeSymbols'; import { UserError } from '../compiler/UserError'; import { BaseQuery } from './BaseQuery'; import { - PreAggregationDefinition, PreAggregationDefinitions, + PreAggregationDefinition, PreAggregationDefinitions, PreAggregationDefinitionsExtended, PreAggregationReferences, PreAggregationTimeDimensionReference } from '../compiler/CubeEvaluator'; @@ -124,7 +124,12 @@ export class PreAggregations { if ( !isInPreAggregationQuery || isInPreAggregationQuery && this.query.options.useOriginalSqlPreAggregationsInPreAggregation) { - return R.pipe( + return R.pipe< + [string[]], + FullPreAggregationDescription[], + FullPreAggregationDescription[], + FullPreAggregationDescription[] + >( R.map(cube => { const { preAggregations } = this.collectOriginalSqlPreAggregations(() => this.query.cubeSql(cube)); return R.unnest(preAggregations.map(p => this.preAggregationDescriptionsFor(p))); @@ -138,6 +143,10 @@ export class PreAggregations { private preAggregationCubes(): string[] { const { join } = this.query; + if (!join) { + // This can happen with Tesseract, or when there's no cubes to join + throw new Error('Unexpected missing join tree for query'); + } return join.joins.map(j => j.originalTo).concat([join.root]); } @@ -371,7 +380,11 @@ export class PreAggregations { public findPreAggregationToUseForCube(cube: string): PreAggregationForCube | null { const preAggregates = this.query.cubeEvaluator.preAggregationsForCube(cube); - const originalSqlPreAggregations = R.pipe( + const originalSqlPreAggregations = R.pipe< + [Record], + [string, PreAggregationDefinitionExtended][], + [string, PreAggregationDefinitionExtended][] + >( R.toPairs, R.filter(([, a]) => a.type === 'originalSql') )(preAggregates); @@ -764,7 +777,7 @@ export class PreAggregations { if (td[1] === '*') { return R.any((tdtc: [string, string]) => tdtc[0] === td[0]); // need to match the dimension at least } else { - return R.contains(td); + return R.includes(td); } })) ) @@ -939,8 +952,13 @@ export class PreAggregations { )(query.collectCubeNames()); } - private findRollupPreAggregationsForCube(cube: string, canUsePreAggregation: CanUsePreAggregationFn, preAggregations: PreAggregationDefinitions): PreAggregationForQuery[] { - return R.pipe( + private findRollupPreAggregationsForCube(cube: string, canUsePreAggregation: CanUsePreAggregationFn, preAggregations: PreAggregationDefinitionsExtended): PreAggregationForQuery[] { + return R.pipe< + [PreAggregationDefinitions], + [string, PreAggregationDefinitionExtended][], + [string, PreAggregationDefinitionExtended][], + PreAggregationForQuery[] + >( R.toPairs, // eslint-disable-next-line no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -951,7 +969,12 @@ export class PreAggregations { public getRollupPreAggregationByName(cube, preAggregationName) { const canUsePreAggregation = () => true; - const preAggregation = R.pipe( + const preAggregation = R.pipe< + [Record], + [string, PreAggregationDefinitionExtended][], + [string, PreAggregationDefinitionExtended][], + [string, PreAggregationDefinitionExtended] | undefined + >( R.toPairs, R.filter(([_, a]) => a.type === 'rollup' || a.type === 'rollupJoin' || a.type === 'rollupLambda'), R.find(([k, _]) => k === preAggregationName) diff --git a/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts b/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts index 329bfd0ccdd68..5bcb70fcb1810 100644 --- a/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts +++ b/packages/cubejs-schema-compiler/src/compiler/CubeEvaluator.ts @@ -3,13 +3,15 @@ import R from 'ramda'; import { CubeSymbols, type ToString } from './CubeSymbols'; import { UserError } from './UserError'; -import { BaseQuery } from '../adapter'; +import { BaseQuery, PreAggregationDefinitionExtended } from '../adapter'; import type { CubeValidator } from './CubeValidator'; import type { ErrorReporter } from './ErrorReporter'; +// TODO replace Function with proper types + export type SegmentDefinition = { type: string, - sql: Function, + sql(): string, primaryKey?: true, ownedByCube: boolean, fieldType?: string, @@ -19,7 +21,7 @@ export type SegmentDefinition = { export type DimensionDefinition = { type: string, - sql: Function, + sql(): string, primaryKey?: true, ownedByCube: boolean, fieldType?: string, @@ -41,7 +43,7 @@ export type TimeShiftDefinitionReference = { export type MeasureDefinition = { type: string, - sql: Function, + sql(): string, ownedByCube: boolean, rollingWindow?: any filters?: any @@ -110,6 +112,8 @@ export type PreAggregationDefinition = { export type PreAggregationDefinitions = Record; +export type PreAggregationDefinitionsExtended = Record; + export type PreAggregationTimeDimensionReference = { dimension: string, granularity: string, @@ -135,10 +139,29 @@ export type PreAggregationInfo = { indexesReferences: unknown, }; +export type EvaluatedCubeDimensions = Record; +export type EvaluatedCubeMeasures = Record; +export type EvaluatedCubeSegments = Record; +export type EvaluatedHierarchies = unknown; + +export type EvaluatedCube = { + measures: EvaluatedCubeMeasures, + dimensions: EvaluatedCubeDimensions, + segments: EvaluatedCubeSegments, + joins: unknown, + hierarchies: unknown, + evaluatedHierarchies: EvaluatedHierarchies, + preAggregations: Record, + dataSource?: string, + folders: unknown, + sql: unknown, + sqlTable: unknown, +}; + export class CubeEvaluator extends CubeSymbols { - public evaluatedCubes: Record = {}; + public evaluatedCubes: Record = {}; - public primaryKeys: Record = {}; + public primaryKeys: Record> = {}; public byFileName: Record = {}; @@ -535,16 +558,19 @@ export class CubeEvaluator extends CubeSymbols { } public timeDimensionPathsForCube(cube: any) { - return R.compose( + return R.compose< + [EvaluatedCubeDimensions], + EvaluatedCubeDimensions, + Array, + Array + >( R.map(dimName => `${cube}.${dimName}`), R.keys, - // @ts-ignore R.filter((d: any) => d.type === 'time') - // @ts-ignore )(this.evaluatedCubes[cube].dimensions || {}); } - public measuresForCube(cube) { + public measuresForCube(cube: string): EvaluatedCubeMeasures { return this.cubeFromPath(cube).measures || {}; } @@ -555,7 +581,7 @@ export class CubeEvaluator extends CubeSymbols { ); } - public preAggregationsForCube(path: string): Record { + public preAggregationsForCube(path: string): Record { return this.cubeFromPath(path).preAggregations || {}; } @@ -659,11 +685,11 @@ export class CubeEvaluator extends CubeSymbols { return this.byPath('segments', segmentPath); } - public cubeExists(cube) { + public cubeExists(cube: string): boolean { return !!this.evaluatedCubes[cube]; } - public cubeFromPath(path: string) { + public cubeFromPath(path: string): EvaluatedCube { return this.evaluatedCubes[this.cubeNameFromPath(path)]; } @@ -697,7 +723,7 @@ export class CubeEvaluator extends CubeSymbols { throw new UserError(`Can't resolve member '${Array.isArray(path) ? path.join('.') : path}'`); } - public byPath(type: 'measures' | 'dimensions' | 'segments' | 'preAggregations', path: string | string[]) { + public byPath(type: T, path: string | string[]): EvaluatedCube[T][string] { if (!type) { throw new Error(`Type can't be undefined for '${path}'`); } @@ -707,19 +733,22 @@ export class CubeEvaluator extends CubeSymbols { } const cubeAndName = Array.isArray(path) ? path : path.split('.'); - if (!this.evaluatedCubes[cubeAndName[0]]) { + const cube = this.evaluatedCubes[cubeAndName[0]]; + if (cube === undefined) { throw new UserError(`Cube '${cubeAndName[0]}' not found for path '${path}'`); } - if (!this.evaluatedCubes[cubeAndName[0]][type]) { + const typeMembers = cube[type]; + if (typeMembers === undefined) { throw new UserError(`${type} not defined for path '${path}'`); } - if (!this.evaluatedCubes[cubeAndName[0]][type][cubeAndName[1]]) { + const member = typeMembers[cubeAndName[1]]; + if (member === undefined) { throw new UserError(`'${cubeAndName[1]}' not found for path '${path}'`); } - return this.evaluatedCubes[cubeAndName[0]][type][cubeAndName[1]]; + return member as EvaluatedCube[T][string]; } public parsePath(type: 'measures' | 'dimensions' | 'segments' | 'preAggregations', path: string): string[] { diff --git a/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts b/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts index 6c7ff8e06b16a..21305535c1e1d 100644 --- a/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts +++ b/packages/cubejs-schema-compiler/src/compiler/CubeSymbols.ts @@ -10,7 +10,14 @@ import type { ErrorReporter } from './ErrorReporter'; export type ToString = { toString(): string }; -interface CubeDefinition { +// TODO complete this type according to createCube +// TODO proper union for relationship +export type CubeJoinDefinition = { + relationship: string, + sql: string, +}; + +export interface CubeDefinition { name: string; extends?: (...args: Array) => { __cubeName: string }; sql?: string | (() => string); @@ -24,7 +31,7 @@ interface CubeDefinition { preAggregations?: Record; // eslint-disable-next-line camelcase pre_aggregations?: Record; - joins?: Record; + joins?: Record; accessPolicy?: any[]; folders?: any[]; includes?: any; @@ -84,12 +91,13 @@ export class CubeSymbols { } public compile(cubes: CubeDefinition[], errorReporter: ErrorReporter) { - // @ts-ignore - this.cubeDefinitions = R.pipe( - // @ts-ignore - R.map((c: CubeDefinition) => [c.name, c]), + this.cubeDefinitions = R.pipe< + [CubeDefinition[]], + [string, CubeDefinition][], + Record + >( + R.map(c => [c.name, c]), R.fromPairs - // @ts-ignore )(cubes); this.cubeList = cubes.map(c => (c.name ? this.getCubeDefinition(c.name) : this.createCube(c))); // TODO support actual dependency sorting to allow using views inside views @@ -756,7 +764,7 @@ export class CubeSymbols { } } - protected funcArguments(func) { + public funcArguments(func: Function): string[] { const funcDefinition = func.toString(); if (!this.funcArgumentsValues[funcDefinition]) { const match = funcDefinition.match(FunctionRegex); diff --git a/packages/cubejs-schema-compiler/src/compiler/CubeToMetaTransformer.js b/packages/cubejs-schema-compiler/src/compiler/CubeToMetaTransformer.js index 5f57a8b6a615b..1a1ff957088fb 100644 --- a/packages/cubejs-schema-compiler/src/compiler/CubeToMetaTransformer.js +++ b/packages/cubejs-schema-compiler/src/compiler/CubeToMetaTransformer.js @@ -135,7 +135,7 @@ export class CubeToMetaTransformer { // As for now context works on the cubes level return R.filter( - (query) => R.contains(query.config.name, context.contextMembers) + (query) => R.includes(query.config.name, context.contextMembers) )(this.queries); } diff --git a/packages/cubejs-schema-compiler/src/compiler/CubeValidator.ts b/packages/cubejs-schema-compiler/src/compiler/CubeValidator.ts index 0c1accf8c60d2..1612b12fa7a98 100644 --- a/packages/cubejs-schema-compiler/src/compiler/CubeValidator.ts +++ b/packages/cubejs-schema-compiler/src/compiler/CubeValidator.ts @@ -1,7 +1,7 @@ import Joi from 'joi'; import cronParser from 'cron-parser'; -import type { CubeSymbols } from './CubeSymbols'; +import type { CubeDefinition, CubeSymbols } from './CubeSymbols'; import type { ErrorReporter } from './ErrorReporter'; /* ***************************** @@ -909,13 +909,13 @@ export class CubeValidator { if (result.error != null) { errorReporter.error(formatErrorMessage(result.error), result.error); } else { - this.validCubes[cube.name] = true; + this.validCubes.set(cube.name, true); } return result; } - public isCubeValid(cube) { - return this.validCubes[cube.name] || cube.isSplitView; + public isCubeValid(cube: CubeDefinition): boolean { + return this.validCubes.get(cube.name) ?? cube.isSplitView ?? false; } } diff --git a/packages/cubejs-schema-compiler/src/compiler/JoinGraph.js b/packages/cubejs-schema-compiler/src/compiler/JoinGraph.js deleted file mode 100644 index 179ca2ac5ceba..0000000000000 --- a/packages/cubejs-schema-compiler/src/compiler/JoinGraph.js +++ /dev/null @@ -1,240 +0,0 @@ -import R from 'ramda'; -import Graph from 'node-dijkstra'; -import { UserError } from './UserError'; - -export class JoinGraph { - /** - * @param {import('./CubeValidator').CubeValidator} cubeValidator - * @param {import('./CubeEvaluator').CubeEvaluator} cubeEvaluator - */ - constructor(cubeValidator, cubeEvaluator) { - this.cubeValidator = cubeValidator; - this.cubeEvaluator = cubeEvaluator; - this.nodes = {}; - this.edges = {}; - this.builtJoins = {}; - } - - compile(cubes, errorReporter) { - this.edges = R.compose( - R.fromPairs, - R.unnest, - R.map(v => this.buildJoinEdges(v, errorReporter.inContext(`${v.name} cube`))), - R.filter(this.cubeValidator.isCubeValid.bind(this.cubeValidator)) - )(this.cubeEvaluator.cubeList); - this.nodes = R.compose( - R.map(groupedByFrom => R.fromPairs(groupedByFrom.map(join => [join.to, 1]))), - R.groupBy(join => join.from), - R.map(v => v[1]), - R.toPairs - )(this.edges); - this.undirectedNodes = R.compose( - R.map(groupedByFrom => R.fromPairs(groupedByFrom.map(join => [join.from, 1]))), - R.groupBy(join => join.to), - R.unnest, - R.map(v => [v[1], { from: v[1].to, to: v[1].from }]), - R.toPairs - )(this.edges); - this.graph = new Graph(this.nodes); - } - - buildJoinEdges(cube, errorReporter) { - return R.compose( - R.filter(R.identity), - R.map(join => { - const multipliedMeasures = R.compose( - R.filter( - m => m.sql && this.cubeEvaluator.funcArguments(m.sql).length === 0 && m.sql() === 'count(*)' || - ['sum', 'avg', 'count', 'number'].indexOf(m.type) !== -1 - ), - R.values - ); - const joinRequired = - (v) => `primary key for '${v}' is required when join is defined in order to make aggregates work properly`; - if ( - !this.cubeEvaluator.primaryKeys[join[1].from].length && - multipliedMeasures(this.cubeEvaluator.measuresForCube(join[1].from)).length > 0 - ) { - errorReporter.error(joinRequired(join[1].from)); - return null; - } - if (!this.cubeEvaluator.primaryKeys[join[1].to].length && - multipliedMeasures(this.cubeEvaluator.measuresForCube(join[1].to)).length > 0) { - errorReporter.error(joinRequired(join[1].to)); - return null; - } - return join; - }), - R.unnest, - R.map(join => [ - [`${cube.name}-${join[0]}`, { - join: join[1], - from: cube.name, - to: join[0], - originalFrom: cube.name, - originalTo: join[0] - }] - ]), - R.filter(R.identity), - R.map(join => { - if (!this.cubeEvaluator.cubeExists(join[0])) { - errorReporter.error(`Cube ${join[0]} doesn't exist`); - return undefined; - } - return join; - }), - R.toPairs - )(cube.joins || {}); - } - - buildJoinNode(cube) { - return R.compose( - R.fromPairs, - R.map(v => [v[0], 1]), - R.toPairs - )(cube.joins || {}); - } - - buildJoin(cubesToJoin) { - if (!cubesToJoin.length) { - return null; - } - const key = JSON.stringify(cubesToJoin); - if (!this.builtJoins[key]) { - const join = R.pipe( - R.map( - cube => this.buildJoinTreeForRoot(cube, R.without([cube], cubesToJoin)) - ), - R.filter(R.identity), - R.sortBy(joinTree => joinTree.joins.length) - )(cubesToJoin)[0]; - if (!join) { - throw new UserError(`Can't find join path to join ${cubesToJoin.map(v => `'${v}'`).join(', ')}`); - } - this.builtJoins[key] = Object.assign(join, { - multiplicationFactor: R.compose( - R.fromPairs, - R.map(v => [this.cubeFromPath(v), this.findMultiplicationFactorFor(this.cubeFromPath(v), join.joins)]) - )(cubesToJoin) - }); - } - return this.builtJoins[key]; - } - - cubeFromPath(cubePath) { - if (Array.isArray(cubePath)) { - return cubePath[cubePath.length - 1]; - } - return cubePath; - } - - buildJoinTreeForRoot(root, cubesToJoin) { - const self = this; - if (Array.isArray(root)) { - const [newRoot, ...additionalToJoin] = root; - if (additionalToJoin.length > 0) { - cubesToJoin = [additionalToJoin].concat(cubesToJoin); - } - root = newRoot; - } - const nodesJoined = {}; - const result = cubesToJoin.map(joinHints => { - if (!Array.isArray(joinHints)) { - joinHints = [joinHints]; - } - let prevNode = root; - return joinHints.filter(toJoin => toJoin !== prevNode).map(toJoin => { - if (nodesJoined[toJoin]) { - prevNode = toJoin; - return { joins: [] }; - } - const path = this.graph.path(prevNode, toJoin); - if (!path) { - return null; - } - const foundJoins = self.joinsByPath(path); - prevNode = toJoin; - nodesJoined[toJoin] = true; - return { cubes: path, joins: foundJoins }; - }); - }).reduce((a, b) => a.concat(b), []).reduce((joined, res) => { - if (!res || !joined) { - return null; - } - const indexedPairs = R.compose( - R.addIndex(R.map)((j, i) => [i + joined.joins.length, j]) - ); - return { - joins: joined.joins.concat(indexedPairs(res.joins)) - }; - }, { joins: [] }); - - if (!result) { - return null; - } - - const pairsSortedByIndex = - R.compose(R.uniq, R.map(indexToJoin => indexToJoin[1]), R.sortBy(indexToJoin => indexToJoin[0])); - return { - joins: pairsSortedByIndex(result.joins), - root - }; - } - - findMultiplicationFactorFor(cube, joins) { - const visited = {}; - const self = this; - function findIfMultipliedRecursive(currentCube) { - if (visited[currentCube]) { - return false; - } - visited[currentCube] = true; - function nextNode(nextJoin) { - return nextJoin.from === currentCube ? nextJoin.to : nextJoin.from; - } - const nextJoins = joins.filter(j => j.from === currentCube || j.to === currentCube); - if (nextJoins.find( - nextJoin => self.checkIfCubeMultiplied(currentCube, nextJoin) && !visited[nextNode(nextJoin)] - )) { - return true; - } - return !!nextJoins.find( - nextJoin => findIfMultipliedRecursive(nextNode(nextJoin)) - ); - } - return findIfMultipliedRecursive(cube); - } - - checkIfCubeMultiplied(cube, join) { - return join.from === cube && join.join.relationship === 'hasMany' || - join.to === cube && join.join.relationship === 'belongsTo'; - } - - joinsByPath(path) { - return R.range(0, path.length - 1).map(i => this.edges[`${path[i]}-${path[i + 1]}`]); - } - - connectedComponents() { - if (!this.cachedConnectedComponents) { - let componentId = 1; - const components = {}; - R.toPairs(this.nodes).map(nameToConnection => nameToConnection[0]).forEach(node => { - this.findConnectedComponent(componentId, node, components); - componentId += 1; - }); - this.cachedConnectedComponents = components; - } - return this.cachedConnectedComponents; - } - - findConnectedComponent(componentId, node, components) { - if (!components[node]) { - components[node] = componentId; - R.toPairs(this.undirectedNodes[node]) - .map(connectedNodeNames => connectedNodeNames[0]) - .forEach(connectedNode => { - this.findConnectedComponent(componentId, connectedNode, components); - }); - } - } -} diff --git a/packages/cubejs-schema-compiler/src/compiler/JoinGraph.ts b/packages/cubejs-schema-compiler/src/compiler/JoinGraph.ts new file mode 100644 index 0000000000000..44f50517b1b02 --- /dev/null +++ b/packages/cubejs-schema-compiler/src/compiler/JoinGraph.ts @@ -0,0 +1,363 @@ +import R from 'ramda'; +import Graph from 'node-dijkstra'; + +import type { CubeValidator } from './CubeValidator'; +import type { CubeEvaluator, EvaluatedCubeMeasures, MeasureDefinition } from './CubeEvaluator'; +import type { CubeDefinition, CubeJoinDefinition } from './CubeSymbols'; +import type { ErrorReporter } from './ErrorReporter'; +import { UserError } from './UserError'; + +type JoinEdge = { + join: CubeJoinDefinition, + from: string, + to: string, + originalFrom: string, + originalTo: string, +}; + +// edge name (string like 'from-to') -> edge +type Edges = Record; + +type JoinTreeJoins = Array; + +type JoinTree = { + root: string, + joins: JoinTreeJoins, +}; + +export type FinishedJoinTree = JoinTree & { + multiplicationFactor: Record, +}; + +export type JoinHint = string | Array; + +export type JoinHints = Array; + +function present(t: T | null): t is T { + return t !== null; +} + +export class JoinGraph { + // source node -> destination node -> weight + protected nodes: Record>; + + protected edges: Edges; + + // source node -> destination node -> weight + protected undirectedNodes: Record>; + + protected builtJoins: Record; + + protected graph: Graph | null; + + protected cachedConnectedComponents: Record | null; + + public constructor(protected cubeValidator: CubeValidator, protected cubeEvaluator: CubeEvaluator) { + this.nodes = {}; + this.edges = {}; + this.undirectedNodes = {}; + this.builtJoins = {}; + this.graph = null; + this.cachedConnectedComponents = null; + } + + public compile(cubes: unknown, errorReporter: ErrorReporter): void { + this.edges = R.compose< + [Array], + Array, + Array>, + Array<[string, JoinEdge]>, + Record + >( + R.fromPairs, + R.unnest, + R.map(v => this.buildJoinEdges(v, errorReporter.inContext(`${v.name} cube`))), + R.filter(this.cubeValidator.isCubeValid.bind(this.cubeValidator)) + )(this.cubeEvaluator.cubeList); + // This requires @types/ramda@0.29 or newer + // @ts-ignore + this.nodes = R.compose< + [Record], + Array<[string, JoinEdge]>, + Array, + Record | undefined>, + Record> + >( + // This requires @types/ramda@0.29 or newer + // @ts-ignore + R.map(groupedByFrom => R.fromPairs((groupedByFrom ?? []).map(join => [join.to, 1]))), + R.groupBy(join => join.from), + R.map(v => v[1]), + R.toPairs + )(this.edges); + // This requires @types/ramda@0.29 or newer + // @ts-ignore + this.undirectedNodes = R.compose< + [Record], + Array<[string, JoinEdge]>, + Array<[JoinEdge, {from: string, to: string }]>, + Array<{from: string, to: string }>, + Record | undefined>, + Record> + >( + // This requires @types/ramda@0.29 or newer + // @ts-ignore + R.map(groupedByFrom => R.fromPairs((groupedByFrom ?? []).map(join => [join.from, 1]))), + R.groupBy(join => join.to), + R.unnest, + R.map(v => [v[1], { from: v[1].to, to: v[1].from }]), + R.toPairs + )(this.edges); + this.graph = new Graph(this.nodes); + } + + protected buildJoinEdges(cube: CubeDefinition, errorReporter: ErrorReporter): Array<[string, JoinEdge]> { + return R.compose< + [Record], + Array<[string, CubeJoinDefinition]>, + Array<[string, CubeJoinDefinition] | null>, + Array<[string, CubeJoinDefinition]>, + Array<[string, JoinEdge]>, + Array<[string, JoinEdge] | null>, + Array<[string, JoinEdge]> + >( + R.filter< + [string, JoinEdge] | null, + [string, JoinEdge] + >(present), + R.map(join => { + const multipliedMeasures = R.compose< + [EvaluatedCubeMeasures], + Array, + Array + >( + R.filter( + m => m.sql && this.cubeEvaluator.funcArguments(m.sql).length === 0 && m.sql() === 'count(*)' || + ['sum', 'avg', 'count', 'number'].indexOf(m.type) !== -1 + ), + R.values + ); + const joinRequired = + (v) => `primary key for '${v}' is required when join is defined in order to make aggregates work properly`; + if ( + !this.cubeEvaluator.primaryKeys[join[1].from].length && + multipliedMeasures(this.cubeEvaluator.measuresForCube(join[1].from)).length > 0 + ) { + errorReporter.error(joinRequired(join[1].from)); + return null; + } + if (!this.cubeEvaluator.primaryKeys[join[1].to].length && + multipliedMeasures(this.cubeEvaluator.measuresForCube(join[1].to)).length > 0) { + errorReporter.error(joinRequired(join[1].to)); + return null; + } + return join; + }), + R.map(join => [`${cube.name}-${join[0]}`, { + join: join[1], + from: cube.name, + to: join[0], + originalFrom: cube.name, + originalTo: join[0] + }]), + R.filter< + [string, CubeJoinDefinition] | null, + [string, CubeJoinDefinition] + >(present), + R.map(join => { + if (!this.cubeEvaluator.cubeExists(join[0])) { + errorReporter.error(`Cube ${join[0]} doesn't exist`); + return null; + } + return join; + }), + R.toPairs + )(cube.joins || {}); + } + + protected buildJoinNode(cube: CubeDefinition): Record { + return R.compose< + [Record], + Array<[string, CubeJoinDefinition]>, + Array<[string, 1]>, + Record + >( + R.fromPairs, + R.map(v => [v[0], 1]), + R.toPairs + )(cube.joins || {}); + } + + public buildJoin(cubesToJoin: JoinHints): FinishedJoinTree | null { + if (!cubesToJoin.length) { + return null; + } + const key = JSON.stringify(cubesToJoin); + if (!this.builtJoins[key]) { + const join = R.pipe< + [JoinHints], + Array, + Array, + Array + >( + R.map( + cube => this.buildJoinTreeForRoot(cube, R.without([cube], cubesToJoin)) + ), + R.filter< + JoinTree | null, + JoinTree + >(present), + R.sortBy(joinTree => joinTree.joins.length) + )(cubesToJoin)[0]; + if (!join) { + throw new UserError(`Can't find join path to join ${cubesToJoin.map(v => `'${v}'`).join(', ')}`); + } + this.builtJoins[key] = { + ...join, + multiplicationFactor: R.compose< + [JoinHints], + Array<[string, boolean]>, + Record + >( + R.fromPairs, + R.map(v => [this.cubeFromPath(v), this.findMultiplicationFactorFor(this.cubeFromPath(v), join.joins)]) + )(cubesToJoin) + }; + } + return this.builtJoins[key]; + } + + protected cubeFromPath(cubePath: string | Array): string { + if (Array.isArray(cubePath)) { + return cubePath[cubePath.length - 1]; + } + return cubePath; + } + + protected buildJoinTreeForRoot(root: JoinHint, cubesToJoin: JoinHints): JoinTree | null { + const self = this; + const { graph } = this; + if (graph === null) { + // JoinGraph was not compiled + return null; + } + if (Array.isArray(root)) { + const [newRoot, ...additionalToJoin] = root; + cubesToJoin = [additionalToJoin, ...cubesToJoin]; + root = newRoot; + } + + const singleRoot = root; + const nodesJoined = {}; + const result = cubesToJoin.map(joinHints => { + if (!Array.isArray(joinHints)) { + joinHints = [joinHints]; + } + let prevNode = singleRoot; + return joinHints.filter(toJoin => toJoin !== prevNode).map(toJoin => { + if (nodesJoined[toJoin]) { + prevNode = toJoin; + return { joins: [] }; + } + const path = graph.path(prevNode, toJoin); + if (!path) { + return null; + } + if (!Array.isArray(path)) { + // Unexpected object return from graph, it should do so only when path cost was requested + return null; + } + const foundJoins = self.joinsByPath(path); + prevNode = toJoin; + nodesJoined[toJoin] = true; + return { cubes: path, joins: foundJoins }; + }); + }).flat().reduce<{joins: Array<[number, JoinEdge]>} | null>((joined, res) => { + if (res === null || joined === null) { + return null; + } + const indexedPairs = R.compose< + [Array], + Array<[number, JoinEdge]> + >( + R.addIndex(R.map)((j, i) => [i + joined.joins.length, j]) + ); + return { + joins: [...joined.joins, ...indexedPairs(res.joins)], + }; + }, { joins: [] }); + + if (!result) { + return null; + } + + const pairsSortedByIndex = + R.compose< + [Array<[number, JoinEdge]>], + Array<[number, JoinEdge]>, + Array, + Array + >(R.uniq, R.map(indexToJoin => indexToJoin[1]), R.sortBy(indexToJoin => indexToJoin[0])); + return { + joins: pairsSortedByIndex(result.joins), + root: singleRoot + }; + } + + protected findMultiplicationFactorFor(cube: string, joins: JoinTreeJoins): boolean { + const visited = {}; + const self = this; + function findIfMultipliedRecursive(currentCube: string): boolean { + if (visited[currentCube]) { + return false; + } + visited[currentCube] = true; + function nextNode(nextJoin: JoinEdge): string { + return nextJoin.from === currentCube ? nextJoin.to : nextJoin.from; + } + const nextJoins = joins.filter(j => j.from === currentCube || j.to === currentCube); + if (nextJoins.find( + nextJoin => self.checkIfCubeMultiplied(currentCube, nextJoin) && !visited[nextNode(nextJoin)] + )) { + return true; + } + return !!nextJoins.find( + nextJoin => findIfMultipliedRecursive(nextNode(nextJoin)) + ); + } + return findIfMultipliedRecursive(cube); + } + + protected checkIfCubeMultiplied(cube: string, join: JoinEdge): boolean { + return join.from === cube && join.join.relationship === 'hasMany' || + join.to === cube && join.join.relationship === 'belongsTo'; + } + + protected joinsByPath(path: Array): Array { + return R.range(0, path.length - 1).map(i => this.edges[`${path[i]}-${path[i + 1]}`]); + } + + public connectedComponents(): Record { + if (!this.cachedConnectedComponents) { + let componentId = 1; + const components = {}; + R.toPairs(this.nodes).map(nameToConnection => nameToConnection[0]).forEach(node => { + this.findConnectedComponent(componentId, node, components); + componentId += 1; + }); + this.cachedConnectedComponents = components; + } + return this.cachedConnectedComponents; + } + + protected findConnectedComponent(componentId: number, node: string, components: Record): void { + if (!components[node]) { + components[node] = componentId; + R.toPairs(this.undirectedNodes[node]) + .map(connectedNodeNames => connectedNodeNames[0]) + .forEach(connectedNode => { + this.findConnectedComponent(componentId, connectedNode, components); + }); + } + } +} diff --git a/packages/cubejs-schema-compiler/src/scaffolding/ScaffoldingSchema.ts b/packages/cubejs-schema-compiler/src/scaffolding/ScaffoldingSchema.ts index 77c7fa42a0e8e..d00abee215d23 100644 --- a/packages/cubejs-schema-compiler/src/scaffolding/ScaffoldingSchema.ts +++ b/packages/cubejs-schema-compiler/src/scaffolding/ScaffoldingSchema.ts @@ -32,7 +32,7 @@ export type JoinRelationship = 'hasOne' | 'hasMany' | 'belongsTo'; type ColumnsToJoin = { cubeToJoin: string; columnToJoin: string; - tableName: string; + tableName: TableName; }; export type CubeDescriptorMember = { @@ -112,7 +112,7 @@ export type DatabaseSchema = Record; type TableData = { schema: string, table: string, - tableName: string; + tableName: TableName; tableDefinition: ColumnData[], }; @@ -122,7 +122,7 @@ type ScaffoldingSchemaOptions = { }; export class ScaffoldingSchema { - private tableNamesToTables: { [key: string]: TableData[] } = {}; + private tableNamesToTables: Record> = {}; public constructor( private readonly dbSchema: DatabaseSchema, @@ -188,17 +188,24 @@ export class ScaffoldingSchema { } protected prepareTableNamesToTables(tableNames: TableName[]) { - this.tableNamesToTables = R.pipe( - // @ts-ignore + // This requires @types/ramda@0.29 or newer + // @ts-ignore + this.tableNamesToTables = R.pipe< + [Array>], + Array<[string, TableData]>, + Record | undefined>, + Record> + >( R.unnest, R.groupBy(n => n[0]), - R.map(groupedNameToDef => groupedNameToDef.map(nameToDef => nameToDef[1])) - )( + // This requires @types/ramda@0.29 or newer // @ts-ignore + R.map(groupedNameToDef => (groupedNameToDef ?? []).map(nameToDef => nameToDef[1])) + )( tableNames.map(tableName => { const [schema, table] = this.parseTableName(tableName); const tableDefinition = this.resolveTableDefinition(tableName); - const definition = { + const definition: TableData = { schema, table, tableDefinition, tableName }; const tableizeName = inflection.tableize(this.fixCase(table)); @@ -207,7 +214,7 @@ export class ScaffoldingSchema { const names = R.uniq([table, tableizeName].concat(tableNamesFromParts)); return names.map(n => [n, definition]); }) - ) as any; + ); } public resolveTableDefinition(tableName: TableName) { @@ -237,7 +244,7 @@ export class ScaffoldingSchema { }; } - protected parseTableName(tableName: TableName) { + protected parseTableName(tableName: TableName): [string, string] { let schemaAndTable; if (Array.isArray(tableName)) { schemaAndTable = tableName; diff --git a/yarn.lock b/yarn.lock index cb274ec5bbaf3..af508a1db617e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7904,6 +7904,11 @@ dependencies: "@types/node" "*" +"@types/node-dijkstra@^2.5.6": + version "2.5.6" + resolved "https://registry.yarnpkg.com/@types/node-dijkstra/-/node-dijkstra-2.5.6.tgz#df4621e50df10b2e98229796ab1c2a3ca74b65b8" + integrity sha512-+n0D+FdGuCLsKoH7fwX3iWfkKSAG0e4z1F96UG5gAnlE2V/1AZO6LuPLzHQd1MC2fZJDcE2cpTS2Ln1lywgUvw== + "@types/node-fetch@^2.5.7", "@types/node-fetch@^2.5.8": version "2.6.12" resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.12.tgz#8ab5c3ef8330f13100a7479e2cd56d3386830a03" @@ -7968,10 +7973,10 @@ resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== -"@types/ramda@0.27.40", "@types/ramda@^0.27.32", "@types/ramda@^0.27.34", "@types/ramda@^0.27.40": - version "0.27.40" - resolved "https://registry.yarnpkg.com/@types/ramda/-/ramda-0.27.40.tgz#99f307356fe553095ee4d3c2af2b0eb3af7a8413" - integrity sha512-V99ZfTH2tqVYdLDAlgh2uT+N074HPgqnAsMjALKSBqogYd0HbuuGMqNukJ6fk9Ml/Htaus76fsc4Yh3p7q1VdQ== +"@types/ramda@^0.27.32", "@types/ramda@^0.27.34", "@types/ramda@^0.27.40": + version "0.27.66" + resolved "https://registry.yarnpkg.com/@types/ramda/-/ramda-0.27.66.tgz#f1a23d13b0087d806a62e3ff941e5e59b3318999" + integrity sha512-i2YW+E2U6NfMt3dp0RxNcejox+bxJUNDjB7BpYuRuoHIzv5juPHkJkNgcUOu+YSQEmaWu8cnAo/8r63C0NnuVA== dependencies: ts-toolbelt "^6.15.1" @@ -24029,16 +24034,7 @@ string-length@^5.0.1: char-regex "^2.0.0" strip-ansi "^7.0.1" -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -24129,7 +24125,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -24157,13 +24153,6 @@ strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1, strip-ansi@^7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -26279,7 +26268,7 @@ workerpool@^9.2.0: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-9.2.0.tgz#f74427cbb61234708332ed8ab9cbf56dcb1c4371" integrity sha512-PKZqBOCo6CYkVOwAxWxQaSF2Fvb5Iv2fCeTP7buyWI2GiynWr46NcXSgK/idoV6e60dgCBfgYc+Un3HMvmqP8w== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -26305,15 +26294,6 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"