From 28a72a82f2dc626b6cdff04db63cffada038dbeb Mon Sep 17 00:00:00 2001 From: "trent.hashimoto" Date: Fri, 9 Jan 2026 14:08:02 -0800 Subject: [PATCH 01/17] Sync from integrations monorepo --- .eslintrc.js | 38 +- src/hypersync/DataSourceBase.ts | 6 +- src/hypersync/HypersyncApp.ts | 120 +++-- src/hypersync/ICriteriaProvider.ts | 11 +- src/hypersync/IDataSource.ts | 13 +- src/hypersync/JsonCriteriaProvider.ts | 114 ++++- src/hypersync/JsonProofProvider.ts | 258 +++++++--- src/hypersync/Paginator.ts | 80 +-- src/hypersync/ProofProviderBase.ts | 53 +- src/hypersync/ProofProviderFactory.ts | 4 +- src/hypersync/RestDataSourceBase.ts | 286 +++++++++-- src/hypersync/ServiceDataIterator.ts | 463 ++++++++++++++++++ src/hypersync/Sync.ts | 26 +- src/hypersync/common.ts | 40 +- src/hypersync/hypersyncConnector.ts | 222 +++------ src/hypersync/index.ts | 1 + src/hypersync/layout.ts | 2 +- src/hypersync/messages.ts | 11 +- src/hypersync/models.ts | 9 +- src/hypersync/time.ts | 105 +++- src/hypersync/tokens.ts | 61 +-- .../UarApplicationProofProvider.ts | 14 +- src/schema-proof/UarDirectoryProofProvider.ts | 14 +- src/schema-proof/common.ts | 2 +- 24 files changed, 1507 insertions(+), 446 deletions(-) create mode 100644 src/hypersync/ServiceDataIterator.ts diff --git a/.eslintrc.js b/.eslintrc.js index 5ec21e0..152c749 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,32 +1,20 @@ +/* eslint-env node */ + module.exports = { - env: { - browser: false, - commonjs: true, - es2021: true, - node: true - }, - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'prettier' - ], - parserOptions: { - ecmaVersion: 'latest' - }, - plugins: ['@typescript-eslint'], - rules: { - '@typescript-eslint/no-non-null-assertion': 0, - '@typescript-eslint/ban-types': 0, - '@typescript-eslint/explicit-module-boundary-types': ['off'], - '@typescript-eslint/no-explicit-any': ['off'] - }, + ignorePatterns: ['src/**/*.test.ts'], overrides: [ { - files: ['*.js'], + files: ['*.ts'], + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaVersion: 'latest', + project: `${__dirname}/tsconfig.json` + }, rules: { - '@typescript-eslint/no-var-requires': 'off' + 'max-lines-per-function': ['error', { max: 570, skipBlankLines: true }], + 'max-depth': ['error', 6], + complexity: ['error', 25] } } - ], - ignorePatterns: ['**/build/*'] + ] }; diff --git a/src/hypersync/DataSourceBase.ts b/src/hypersync/DataSourceBase.ts index 73803b4..64023ba 100644 --- a/src/hypersync/DataSourceBase.ts +++ b/src/hypersync/DataSourceBase.ts @@ -6,8 +6,8 @@ import { SyncMetadata } from './IDataSource'; -import { DataObject, DataValueMap } from '@hyperproof/hypersync-models'; -import { IHyperproofUser } from '@hyperproof/integration-sdk'; +import { DataObject, DataValueMap } from '@hyperproof-int/hypersync-models'; +import { ILocalizable } from '@hyperproof-int/integration-sdk'; /** * Abstract base class for a data source object. Provides convenient @@ -27,7 +27,7 @@ export abstract class DataSourceBase implements IDataSource { params?: DataValueMap, page?: string, metadata?: SyncMetadata, - hyperproofUser?: IHyperproofUser + organization?: ILocalizable ): Promise>; /** diff --git a/src/hypersync/HypersyncApp.ts b/src/hypersync/HypersyncApp.ts index 8d64527..2c7a624 100644 --- a/src/hypersync/HypersyncApp.ts +++ b/src/hypersync/HypersyncApp.ts @@ -1,14 +1,16 @@ import { formatHypersyncError, StringMap } from './common'; -import { - createHypersync, - IValidateCredentialsResponse -} from './hypersyncConnector'; +import { createHypersync } from './hypersyncConnector'; import { ICriteriaMetadata, ICriteriaPage, ICriteriaProvider } from './ICriteriaProvider'; -import { DataSetResultStatus, IDataSource, SyncMetadata } from './IDataSource'; +import { + DataSetResultStatus, + IDataSource, + isRestDataSourceBase, + SyncMetadata +} from './IDataSource'; import { JsonCriteriaProvider } from './JsonCriteriaProvider'; import { MESSAGES } from './messages'; import { IHypersync } from './models'; @@ -19,7 +21,8 @@ import { } from './ProofProviderBase'; import { IProofTypeConfig, ProofProviderFactory } from './ProofProviderFactory'; import { RestDataSourceBase } from './RestDataSourceBase'; -import { IGetProofDataResponse } from './Sync'; +import { IterableObject } from './ServiceDataIterator'; +import { IGetProofDataResponse, IHypersyncSyncPlanResponse } from './Sync'; import { DataValueMap, @@ -27,31 +30,33 @@ import { HypersyncCriteriaFieldType, HypersyncPeriod, ICriteriaFieldConfig, + ICriteriaSearchInput, IDataSet, IHypersyncDefinition, SchemaCategory, ValueLookup -} from '@hyperproof/hypersync-models'; +} from '@hyperproof-int/hypersync-models'; import { AuthorizationType, createApp, createOAuthConnector, CustomAuthCredentials, ExternalAPIError, + getAgent, IAuthorizationConfigBase, ICheckConnectionHealthInvocationPayload, ICredentialsMetadata, - IHyperproofUser, IHyperproofUserContext, + ILocalizable, IntegrationContext, + IValidateCredentialsResponse, ListStorageResult, Logger, OAuthConnector, OAuthTokenResponse, ObjectType, - RecordingManager, UserContext -} from '@hyperproof/integration-sdk'; +} from '@hyperproof-int/integration-sdk'; import express from 'express'; import fs from 'fs'; import createHttpError from 'http-errors'; @@ -93,7 +98,7 @@ interface ICustomProofType extends IProofTypeConfig { */ type CustomProofTypeMap = { [proofType: string]: ICustomProofType }; -class HypersyncAppConnector extends createHypersync(OAuthConnector) { +export class HypersyncAppConnector extends createHypersync(OAuthConnector) { private credentialsMetadata?: ICredentialsMetadata; private hypersyncApp: HypersyncApp; @@ -679,7 +684,7 @@ class HypersyncAppConnector extends createHypersync(OAuthConnector) { orgId: string, vendorUserId: string, criteria: HypersyncCriteria, - search?: string, + search?: string | ICriteriaSearchInput, schemaCategory?: SchemaCategory ) { const { messages, dataSource, criteriaProvider, proofProviderFactory } = @@ -712,6 +717,33 @@ class HypersyncAppConnector extends createHypersync(OAuthConnector) { ); } + public async generateSyncPlan( + integrationContext: IntegrationContext, + orgId: string, + vendorUserId: string, + criteria: HypersyncCriteria, + metadata?: SyncMetadata, + retryCount?: number + ) { + const userContext = await this.getUser(integrationContext, vendorUserId); + if (!userContext) { + throw createHttpError( + StatusCodes.UNAUTHORIZED, + this.getUserNotFoundMessage(vendorUserId) + ); + } + const { dataSource, criteriaProvider, proofProviderFactory } = + await this.createResources(integrationContext, orgId, vendorUserId); + return this.hypersyncApp.generateSyncPlan( + dataSource, + criteriaProvider, + proofProviderFactory, + criteria, + metadata, + retryCount + ); + } + public async syncNow( integrationContext: IntegrationContext, orgId: string, @@ -719,10 +751,11 @@ class HypersyncAppConnector extends createHypersync(OAuthConnector) { objectId: string, hypersync: IHypersync, syncStartDate: string, - hyperproofUser: IHyperproofUser, + organization: ILocalizable, page?: string, metadata?: SyncMetadata, - retryCount?: number + retryCount?: number, + iterableSlice?: IterableObject[] ) { const vendorUserId = hypersync.settings.vendorUserId; const userContext = await this.getUser(integrationContext, vendorUserId); @@ -740,29 +773,26 @@ class HypersyncAppConnector extends createHypersync(OAuthConnector) { this.getUserNotFoundMessage(vendorUserId) ); } - const recordingManager = new RecordingManager(integrationContext); - recordingManager.start(); try { const data = await this.hypersyncApp.getProofData( dataSource, criteriaProvider, proofProviderFactory, hypersync, - hyperproofUser, + organization, userContext.vendorUserProfile, syncStartDate, page, metadata, - retryCount + retryCount, + iterableSlice ); - await recordingManager.stop(); return Array.isArray(data) ? { data } : data; } catch (err) { - await recordingManager.stop(); if ( err instanceof ExternalAPIError && err?.computeRetry && @@ -953,7 +983,7 @@ class HypersyncAppConnector extends createHypersync(OAuthConnector) { orgId, vendorUserId ); - if (!(dataSource instanceof RestDataSourceBase)) { + if (!isRestDataSourceBase(dataSource)) { throw createHttpError( StatusCodes.BAD_REQUEST, 'Hypersync does not support customization.' @@ -1537,6 +1567,7 @@ export class HypersyncApp { ): Promise { await Logger.debug('Retrieving OAuth access token.'); const response = await Superagent.post(configuration.oauth_token_url) + .agent(getAgent(configuration.oauth_token_url)) .type('form') .send({ grant_type: 'authorization_code', @@ -1564,6 +1595,7 @@ export class HypersyncApp { await Logger.debug('Refreshing OAuth access token.'); const currentRefreshToken = tokenContext.refresh_token; const response = await Superagent.post(configuration.oauth_token_url) + .agent(getAgent(configuration.oauth_token_url)) .type('form') .send({ grant_type: 'refresh_token', @@ -1617,7 +1649,7 @@ export class HypersyncApp { * Refreshes credentials provided by the user in a custom auth application. * Returns updated credentials or undefined if unneeded * - * @param {CustomAuthCredentails} credentials User's current credentials. + * @param {CustomAuthCredentials} credentials User's current credentials. */ public async refreshCredentials( // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -1727,8 +1759,7 @@ export class HypersyncApp { proofProviderFactory: ProofProviderFactory, criteria: HypersyncCriteria, pages: ICriteriaPage[], - // eslint-disable-next-line @typescript-eslint/no-unused-vars - search?: string, + search?: string | ICriteriaSearchInput, schemaCategory?: SchemaCategory ): Promise { await Logger.debug('Generating criteria metadata.'); @@ -1823,7 +1854,7 @@ export class HypersyncApp { criteriaProvider ); - return provider.generateCriteriaMetadata(criteria, pages); + return provider.generateCriteriaMetadata(criteria, pages, search); } /** @@ -1852,6 +1883,32 @@ export class HypersyncApp { return provider.generateSchema(criteria); } + /** + * Returns the sync plan of the proof that will be generated. + * + * @param dataSource IDataSource instance used to retrieve data. + * @param criteriaProvider ICriteriaProvider instance used to generate criteria metadata. + * @param proofProviderFactory Factory object that provides ProofProviderBase objects. + * @param criteria Set of proof criterion selected by the user. + * @param metadata Arbitrary synchronization state associated with the sync. Optional. + * @param retryCount Current retry count of sync. Optional. + */ + public async generateSyncPlan( + dataSource: IDataSource, + criteriaProvider: ICriteriaProvider, + proofProviderFactory: ProofProviderFactory, + criteria: HypersyncCriteria, + metadata?: SyncMetadata, + retryCount?: number + ): Promise { + const provider = proofProviderFactory.createProofProvider( + criteria.proofType!, + dataSource, + criteriaProvider + ); + return provider.generateSyncPlan(criteria, metadata, retryCount); + } + /** * Retrieves data from the external source and formats it for rendering. * @@ -1859,7 +1916,6 @@ export class HypersyncApp { * @param criteriaProvider ICriteriaProvider instance used to generate criteria metadata. * @param proofProviderFactory Factory object that provides ProofProviderBase objects. * @param hypersync The Hypersync that is synchronizing. - * @param hyperproofUser The Hyperproof user who initiated the sync. * @param userProfile Profile object returned by getUserProfile. * @param syncStartDate Date and time at which the sync operation was initiated. * @param page Current proof page number being synchronized. Optional. @@ -1871,12 +1927,13 @@ export class HypersyncApp { criteriaProvider: ICriteriaProvider, proofProviderFactory: ProofProviderFactory, hypersync: IHypersync, - hyperproofUser: IHyperproofUser, + organization: ILocalizable, userProfile: TUserProfile, syncStartDate: string, page?: string, metadata?: SyncMetadata, - retryCount?: number + retryCount?: number, + iterableSlice?: IterableObject[] ): Promise { await Logger.debug( `Retrieving data for proof type '${hypersync.settings.criteria.proofType}'.` @@ -1888,12 +1945,13 @@ export class HypersyncApp { ); return provider.getProofData( hypersync, - hyperproofUser, + organization, this.getUserAccountName(userProfile), new Date(syncStartDate), page, metadata, - retryCount + retryCount, + iterableSlice ); } @@ -1922,7 +1980,7 @@ export class HypersyncApp { messages: StringMap ): Promise { const providersPath = path.resolve(this.appRootDir, 'proof-providers'); - let providers: typeof ProofProviderBase[] = []; + let providers: (typeof ProofProviderBase)[] = []; if (fs.existsSync(providersPath)) { const exportedProviders = await import( path.resolve(this.appRootDir, 'proof-providers') diff --git a/src/hypersync/ICriteriaProvider.ts b/src/hypersync/ICriteriaProvider.ts index 41e6e47..bb3c205 100644 --- a/src/hypersync/ICriteriaProvider.ts +++ b/src/hypersync/ICriteriaProvider.ts @@ -1,16 +1,18 @@ import { TokenContext } from './tokens'; import { + DataValueMap, HypersyncCriteria, HypersyncCriteriaFieldType, HypersyncCriteriaValue, HypersyncPeriod, + ICriteriaSearchInput, IProofCriterionRef, ISelectOption, IValidation, SchemaCategory -} from '@hyperproof/hypersync-models'; -import { CriteriaPageMessageLevel } from '@hyperproof/integration-sdk'; +} from '@hyperproof-int/hypersync-models'; +import { CriteriaPageMessageLevel } from '@hyperproof-int/integration-sdk'; /** * Information needed to render a criteria field used in the configuration @@ -28,6 +30,7 @@ export interface ICriteriaField { noOptionsMessage?: string; isMulti?: boolean; validation?: IValidation; + savedCriteriaSettings?: DataValueMap; } /** @@ -65,6 +68,7 @@ export interface IProofCriterionValue { label: string; value: HypersyncCriteriaValue; validation?: IValidation; + savedCriteriaSettings?: DataValueMap; } /** @@ -100,7 +104,8 @@ export interface ICriteriaProvider { proofCriteria: IProofCriterionRef[], criteriaValues: HypersyncCriteria, tokenContext: TokenContext, - pages: ICriteriaPage[] + pages: ICriteriaPage[], + search?: string | ICriteriaSearchInput ): Promise; /** diff --git a/src/hypersync/IDataSource.ts b/src/hypersync/IDataSource.ts index 7c7c726..789be4e 100644 --- a/src/hypersync/IDataSource.ts +++ b/src/hypersync/IDataSource.ts @@ -1,8 +1,9 @@ import { IErrorInfo } from './models'; +import { RestDataSourceBase } from './RestDataSourceBase'; import { TokenContext } from './tokens'; -import { DataObject, DataValueMap } from '@hyperproof/hypersync-models'; -import { IHyperproofUser } from '@hyperproof/integration-sdk'; +import { DataObject, DataValueMap } from '@hyperproof-int/hypersync-models'; +import { ILocalizable } from '@hyperproof-int/integration-sdk'; export enum DataSetResultStatus { Complete = 'complete', @@ -55,6 +56,12 @@ export interface IDataSource { params?: DataValueMap, page?: string, metadata?: SyncMetadata, - hyperproofUser?: IHyperproofUser + organization?: ILocalizable ): Promise>; } + +export function isRestDataSourceBase( + dataSource: IDataSource +): dataSource is RestDataSourceBase { + return (dataSource as RestDataSourceBase).getConfig() !== undefined; +} diff --git a/src/hypersync/JsonCriteriaProvider.ts b/src/hypersync/JsonCriteriaProvider.ts index 4e304d7..bb6e352 100644 --- a/src/hypersync/JsonCriteriaProvider.ts +++ b/src/hypersync/JsonCriteriaProvider.ts @@ -8,14 +8,16 @@ import { DataSetResultStatus, IDataSource } from './IDataSource'; import { resolveTokens, TokenContext } from './tokens'; import { + DataValueMap, HypersyncCriteria, HypersyncCriteriaFieldType, ICriteriaConfig, ICriteriaFieldConfig, + ICriteriaSearchInput, IProofCriterionRef, ISelectOption -} from '@hyperproof/hypersync-models'; -import { compareValues, Logger } from '@hyperproof/integration-sdk'; +} from '@hyperproof-int/hypersync-models'; +import { compareValues, Logger } from '@hyperproof-int/integration-sdk'; import fs from 'fs'; import path from 'path'; @@ -78,7 +80,8 @@ export class JsonCriteriaProvider implements ICriteriaProvider { proofCriteria: IProofCriterionRef[], criteriaValues: HypersyncCriteria, tokenContext: TokenContext, - pages: ICriteriaPage[] + pages: ICriteriaPage[], + search?: string | ICriteriaSearchInput ) { let lastConfig; let pageNumber: number; @@ -88,22 +91,39 @@ export class JsonCriteriaProvider implements ICriteriaProvider { return; } + let hasSearchField = false; for (const criterion of proofCriteria) { // Look up the criterion in the set of all criteria for the connector.. - const config = this.criteriaFields[criterion.name]; + const config: ICriteriaFieldConfig = this.criteriaFields[criterion.name]; if (!config) { throw new Error(`Unable to find criterion named ${criterion.name}`); } if ( config.type !== HypersyncCriteriaFieldType.Select && - config.type !== HypersyncCriteriaFieldType.Text + config.type !== HypersyncCriteriaFieldType.Text && + config.type !== HypersyncCriteriaFieldType.SelectSavedCriteria && + config.type !== HypersyncCriteriaFieldType.Search ) { throw new Error( `Unrecognized or unsupported criteria field type: ${config.type}` ); } + if (config.type === HypersyncCriteriaFieldType.Search) { + if (hasSearchField) { + throw new Error( + `Only one search criteria field is supported per proof` + ); + } + hasSearchField = true; + if (config.isMulti) { + throw new Error( + `Multi-select is not allowed for search criteria fields` + ); + } + } + // If the previous criteria field config doesn't have a value, then // this criteria field cannot be edited. const isDisabled = @@ -121,7 +141,8 @@ export class JsonCriteriaProvider implements ICriteriaProvider { config, criteriaValues, tokenContext, - isDisabled + isDisabled, + search ) ); @@ -160,10 +181,11 @@ export class JsonCriteriaProvider implements ICriteriaProvider { }); continue; } - // Otherwise behavior differs based on the type of field switch (field.type) { case HypersyncCriteriaFieldType.Select: + case HypersyncCriteriaFieldType.SelectSavedCriteria: + case HypersyncCriteriaFieldType.Search: { const options = await this.getCriteriaFieldOptions( field, @@ -181,11 +203,20 @@ export class JsonCriteriaProvider implements ICriteriaProvider { o => o.value === criteriaValue )?.label; } - criteria.push({ - name: field.property, - label: resolveTokens(field.label, tokenContext), - value: displayedValue - }); + if (field.type === HypersyncCriteriaFieldType.SelectSavedCriteria) { + criteria.push({ + name: field.property, + label: resolveTokens(field.label, tokenContext), + value: displayedValue, + savedCriteriaSettings: field.savedCriteriaSettings + }); + } else { + criteria.push({ + name: field.property, + label: resolveTokens(field.label, tokenContext), + value: displayedValue + }); + } } break; @@ -198,7 +229,6 @@ export class JsonCriteriaProvider implements ICriteriaProvider { }); } break; - default: throw new Error( `Unrecognized or unsupported criteria field type: ${field.type}` @@ -216,20 +246,31 @@ export class JsonCriteriaProvider implements ICriteriaProvider { config: ICriteriaFieldConfig, criteriaValues: HypersyncCriteria, tokenContext: TokenContext, - isDisabled?: boolean + isDisabled?: boolean, + search?: string | ICriteriaSearchInput ): Promise { return { name: config.property, type: config.type, label: resolveTokens(config.label, tokenContext), isRequired: config.isRequired, + savedCriteriaSettings: + config.type === HypersyncCriteriaFieldType.SelectSavedCriteria + ? config.savedCriteriaSettings + : undefined, options: - isDisabled || config.type !== HypersyncCriteriaFieldType.Select + isDisabled || + ![ + HypersyncCriteriaFieldType.Select, + HypersyncCriteriaFieldType.SelectSavedCriteria, + HypersyncCriteriaFieldType.Search + ].includes(config.type) ? [] : await this.getCriteriaFieldOptions( config, criteriaValues, - tokenContext + tokenContext, + search ), value: criteriaValues[config.property] as string | number, placeholder: config.placeholder @@ -247,19 +288,51 @@ export class JsonCriteriaProvider implements ICriteriaProvider { }; } + /** + * Merges search input values from a criteria search field into the parameters map. + * Extracts the 'value' property from each search field and adds it to the params. + * Does not handle offset or lazy loading. + */ + private mergeSearchWithParams( + search: string | ICriteriaSearchInput, + params?: DataValueMap + ) { + const mergedParams = { ...params }; + if (typeof search !== 'object') { + throw new Error( + 'Invalid search input. Search must be provided as an object.' + ); + } + for (const key in search) { + if ( + search[key] !== null && + typeof search[key] === 'object' && + 'value' in search[key] + ) { + mergedParams[key] = search[key].value; + } else { + throw new Error( + `Error encountered parsing search input: ${JSON.stringify(search)}` + ); + } + } + return mergedParams; + } + /** * Helper method that adds the select control options to a criteria metadata field. */ private async getCriteriaFieldOptions( config: ICriteriaFieldConfig, criteria: HypersyncCriteria, - tokenContext: TokenContext + tokenContext: TokenContext, + search?: string | ICriteriaSearchInput ) { let data: ISelectOption[] = []; if (config.dataSet && config.valueProperty && config.labelProperty) { // If there are parameters, resolve any embedded tokens. - const params = config.dataSetParams; + let params = config.dataSetParams; if (params) { for (const key of Object.keys(params)) { const value = params[key]; @@ -271,6 +344,9 @@ export class JsonCriteriaProvider implements ICriteriaProvider { } } let nextPage = undefined; + if (search) { + params = this.mergeSearchWithParams(search, params); + } do { // Fetch the data from the service. const result: any = await this.dataSource.getData( @@ -281,7 +357,7 @@ export class JsonCriteriaProvider implements ICriteriaProvider { if (result.status !== DataSetResultStatus.Complete) { throw new Error( - `Pending response received for critiera field data set: ${config.dataSet}` + `Pending response received for critera field data set: ${config.dataSet}` ); } diff --git a/src/hypersync/JsonProofProvider.ts b/src/hypersync/JsonProofProvider.ts index 4d55117..51f9ae1 100644 --- a/src/hypersync/JsonProofProvider.ts +++ b/src/hypersync/JsonProofProvider.ts @@ -1,32 +1,46 @@ -import { ID_ALL, ID_ANY, ID_NONE, StringMap } from './common'; +import { ID_ALL, ID_ANY, ID_NONE, ID_UNDEFINED, StringMap } from './common'; import { HypersyncTemplate } from './enums'; import { ICriteriaPage, ICriteriaProvider } from './ICriteriaProvider'; -import { DataSetResultStatus, IDataSource, SyncMetadata } from './IDataSource'; +import { + DataSetResultStatus, + IDataSource, + isRestDataSourceBase, + SyncMetadata +} from './IDataSource'; import { calcLayoutInfo } from './layout'; +import { MESSAGES } from './messages'; import { IHypersync } from './models'; import { IHypersyncProofField, IProofFile, ProofProviderBase } from './ProofProviderBase'; -import { IGetProofDataResponse } from './Sync'; +import { IterableObject } from './ServiceDataIterator'; +import { IGetProofDataResponse, IHypersyncSyncPlanResponse } from './Sync'; import { dateToLocalizedString } from './time'; import { resolveTokens, TokenContext } from './tokens'; import { DataObject, + DataValueMap, HypersyncCriteria, HypersyncFieldFormat, HypersyncFieldType, HypersyncPeriod, + ICriteriaSearchInput, IHypersyncDefinition, IHypersyncField, - IProofSpec -} from '@hyperproof/hypersync-models'; -import { IHyperproofUser, Logger } from '@hyperproof/integration-sdk'; + IProofSpec, + IteratorSource +} from '@hyperproof-int/hypersync-models'; +import { ILocalizable, Logger } from '@hyperproof-int/integration-sdk'; +import createHttpError from 'http-errors'; +import { StatusCodes } from 'http-status-codes'; import { validateDataSchema } from '../schema-proof/common'; +const SAVED_CRITERIA_SUFFIX = 'SavedCriterion'; + /** * Provides methods for working with proof type definitions stored * in a JSON file. @@ -55,7 +69,8 @@ export class JsonProofProvider extends ProofProviderBase { public async generateCriteriaMetadata( criteriaValues: HypersyncCriteria, - pages: ICriteriaPage[] + pages: ICriteriaPage[], + search?: string | ICriteriaSearchInput ) { const definition = await this.getDefinition(); const tokenContext = this.initTokenContext(criteriaValues); @@ -64,7 +79,8 @@ export class JsonProofProvider extends ProofProviderBase { definition.criteria.map(c => ({ name: c.name, page: c.page })), criteriaValues, tokenContext, - pages + pages, + search ); // If all of the criteria have been specified, build the proof spec @@ -113,45 +129,126 @@ export class JsonProofProvider extends ProofProviderBase { }; } - // eslint-disable-next-line @typescript-eslint/no-unused-vars + public async generateSyncPlan( + criteriaValues: HypersyncCriteria, + metadata?: SyncMetadata, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + retryCount?: number + ): Promise { + await Logger.info(`Generating sync plan for proof ${this.proofType}`); + const definition = await this.getDefinition(); + const tokenContext = this.initTokenContext(criteriaValues); + const proofSpec: IProofSpec = this.buildProofSpec(definition, tokenContext); + + if (proofSpec.dataSetIterator) { + if (!isRestDataSourceBase(this.dataSource)) { + throw createHttpError( + StatusCodes.BAD_REQUEST, + 'Hypersync does not support data source iteration.' + ); + } + await this.fetchLookups(proofSpec, tokenContext); + + const dataSetParams = proofSpec.dataSetParams ?? {}; + if (dataSetParams) { + this.resolveTokensForParams(dataSetParams, tokenContext); + } + this.addSavedCriteriaToParams(dataSetParams, criteriaValues); + + let iteratorParams = {}; + // Resolve tokens for principal iterator params + for (const iterator of proofSpec.dataSetIterator) { + if ( + iterator.layer === 1 && + iterator.source === IteratorSource.DataSet + ) { + iteratorParams = iterator.dataSetParams ?? {}; + this.resolveTokensForParams(iteratorParams, tokenContext); + } + } + + const response = await this.dataSource.generateIteratorPlan( + this.proofType, + proofSpec.dataSetIterator, + dataSetParams, + iteratorParams, + metadata + ); + if (response.status !== DataSetResultStatus.Complete) { + return { + ...response, + syncPlan: {} + }; + } + const { data: iteratorPlan } = response; + return { + syncPlan: { + iteratorPlan, + combine: true + } + }; + } + + return { + syncPlan: { + combine: true + } + }; + } + public async getProofData( hypersync: IHypersync, - hyperproofUser: IHyperproofUser, + organization: ILocalizable, authorizedUser: string, - // eslint-disable-next-line @typescript-eslint/no-unused-vars syncStartDate: Date, - // eslint-disable-next-line @typescript-eslint/no-unused-vars page?: string, + metadata?: SyncMetadata, // eslint-disable-next-line @typescript-eslint/no-unused-vars - metadata?: SyncMetadata + retryCount?: number, + iterableSlice?: IterableObject[] ): Promise { await Logger.info(`Generating declarative proof type ${this.proofType}`); - const combine = true; const settings = hypersync.settings; const criteriaValues = settings.criteria; const definition = await this.getDefinition(); const tokenContext = this.initTokenContext(criteriaValues); - const proofSpec = this.buildProofSpec(definition, tokenContext); + const proofSpec: IProofSpec = this.buildProofSpec(definition, tokenContext); await this.fetchLookups(proofSpec, tokenContext); const params = proofSpec.dataSetParams; if (params) { - for (const key of Object.keys(params)) { - const value = params[key]; - if (typeof value === 'string') { - params[key] = resolveTokens(value, tokenContext); - } + this.resolveTokensForParams(params, tokenContext); + } + this.addSavedCriteriaToParams(params, criteriaValues); + + let response; + if (proofSpec.dataSetIterator) { + if (!isRestDataSourceBase(this.dataSource)) { + throw createHttpError( + StatusCodes.BAD_REQUEST, + 'Hypersync does not support data source iteration.' + ); } + response = await this.dataSource.iterateDataFlow( + this.proofType, + proofSpec.dataSet, + proofSpec.dataSetIterator, + iterableSlice || [], + params, + page, + metadata, + organization + ); + } else { + response = await this.dataSource.getData( + proofSpec.dataSet, + params, + page, + metadata, + organization + ); } - const response = await this.dataSource.getData( - proofSpec.dataSet, - params, - page, - metadata, - hyperproofUser - ); - if (response.status !== DataSetResultStatus.Complete) { return { ...response, @@ -180,15 +277,10 @@ export class JsonProofProvider extends ProofProviderBase { if (dateFields.length || numberFields.length) { if (Array.isArray(data)) { data.forEach(row => { - this.addFormattedValues( - row, - dateFields, - numberFields, - hyperproofUser - ); + this.addFormattedValues(row, dateFields, numberFields, organization); }); } else { - this.addFormattedValues(data, dateFields, numberFields, hyperproofUser); + this.addFormattedValues(data, dateFields, numberFields, organization); } } @@ -212,7 +304,7 @@ export class JsonProofProvider extends ProofProviderBase { // Since Job Engine seeks the first job-level page, assign // empty list for subsequent pages to optimize performance. let displayProofCriteria = true; - if (combine === true && page !== undefined) { + if (page !== undefined) { displayProofCriteria = false; } const criteria = displayProofCriteria @@ -244,7 +336,7 @@ export class JsonProofProvider extends ProofProviderBase { webPageUrl: resolveTokens(proofSpec.webPageUrl, tokenContext) }), orientation: proofSpec.orientation, - userTimeZone: hyperproofUser.timeZone, + userTimeZone: organization.timeZone, criteria, proofFormat: settings.proofFormat, template: HypersyncTemplate.UNIVERSAL, @@ -261,17 +353,16 @@ export class JsonProofProvider extends ProofProviderBase { collector: this.connectorName, collectedOn: dateToLocalizedString( syncStartDate, - hyperproofUser.timeZone, - hyperproofUser.language, - hyperproofUser.locale + organization.timeZone, + organization.language, + organization.locale )!, errorInfo, zoom } } ], - nextPage, - combine + nextPage }; } @@ -287,7 +378,8 @@ export class JsonProofProvider extends ProofProviderBase { constants: { ID_ALL: ID_ALL, ID_ANY: ID_ANY, - ID_NONE: ID_NONE + ID_NONE: ID_NONE, + ID_UNDEFINED: ID_UNDEFINED }, criteria, lookups: {} @@ -305,21 +397,40 @@ export class JsonProofProvider extends ProofProviderBase { let proofSpec = definition.proofSpec; if (definition.overrides) { for (const override of definition.overrides) { + const operand = resolveTokens( + override.condition.criteria, + tokenContext + ); + const isUndefinedEval = operand === ID_UNDEFINED; // evaluate for undefined criteria value const conditionValue = resolveTokens( override.condition.value, - tokenContext + tokenContext, + false, + isUndefinedEval ); if ( - conditionValue === - resolveTokens(override.condition.criteria, tokenContext) + conditionValue === operand || + (isUndefinedEval && typeof conditionValue === 'undefined') ) { + const shouldOverrideDataSetParams = + override.proofSpec?.dataSetParams ?? false; proofSpec = { ...proofSpec, - ...override.proofSpec + ...override.proofSpec, + ...(shouldOverrideDataSetParams && { + dataSetParams: { + ...proofSpec.dataSetParams, + ...override.proofSpec.dataSetParams + } + }) }; } } } + Logger.debug('Using proof specification', JSON.stringify(proofSpec)); + if (!proofSpec.noResultsMessage) { + proofSpec.noResultsMessage = MESSAGES.Default.NoResultsMessage; + } return proofSpec; } @@ -336,12 +447,7 @@ export class JsonProofProvider extends ProofProviderBase { for (const lookup of proofSpec.lookups) { const params = lookup.dataSetParams; if (params) { - for (const key of Object.keys(params)) { - const value = params[key]; - if (typeof value === 'string') { - params[key] = resolveTokens(value, tokenContext); - } - } + this.resolveTokensForParams(params, tokenContext); } const response = await this.dataSource.getData( lookup.dataSet, @@ -358,6 +464,18 @@ export class JsonProofProvider extends ProofProviderBase { } } + private resolveTokensForParams( + params: DataValueMap, + tokenContext: TokenContext + ) { + for (const key of Object.keys(params)) { + const value = params[key]; + if (typeof value === 'string') { + params[key] = resolveTokens(value, tokenContext); + } + } + } + /** * Performs reverse lookup of criteria labels for hypersync * suggested name. @@ -384,10 +502,10 @@ export class JsonProofProvider extends ProofProviderBase { proofRow: DataObject, dateFields: IHypersyncField[], numberFields: IHypersyncField[], - hyperproofUser: IHyperproofUser + organization: ILocalizable ) { if (dateFields.length) { - this.addFormattedDates(proofRow, dateFields, hyperproofUser); + this.addFormattedDates(proofRow, dateFields, organization); } if (numberFields.length) { this.addFormattedNumbers(proofRow, numberFields); @@ -400,7 +518,7 @@ export class JsonProofProvider extends ProofProviderBase { private addFormattedDates( proofRow: DataObject, dateFields: IHypersyncField[], - hyperproofUser: IHyperproofUser + organization: ILocalizable ) { for (const dateField of dateFields) { if (proofRow[dateField.property + 'Formatted']) { @@ -410,9 +528,9 @@ export class JsonProofProvider extends ProofProviderBase { if (dateValue instanceof Date || typeof dateValue === 'string') { proofRow[dateField.property + 'Formatted'] = dateToLocalizedString( dateValue, - hyperproofUser.timeZone, - hyperproofUser.language, - hyperproofUser.locale + organization.timeZone, + organization.language, + organization.locale )!; } } @@ -451,4 +569,26 @@ export class JsonProofProvider extends ProofProviderBase { return value.toString(); } + + private addSavedCriteriaToParams( + params: DataValueMap | undefined, + criteriaValues: HypersyncCriteria + ) { + if (params && this.criteriaValuesHaveSavedCriterion(criteriaValues)) { + for (const key in criteriaValues) { + if (key.endsWith(SAVED_CRITERIA_SUFFIX)) { + params[key] = (criteriaValues[key] as any).data; + } + } + } + } + + private criteriaValuesHaveSavedCriterion(criteriaValues: HypersyncCriteria) { + for (const key in criteriaValues) { + if (key.endsWith(SAVED_CRITERIA_SUFFIX)) { + return true; + } + } + return false; + } } diff --git a/src/hypersync/Paginator.ts b/src/hypersync/Paginator.ts index 40a580b..f636078 100644 --- a/src/hypersync/Paginator.ts +++ b/src/hypersync/Paginator.ts @@ -13,12 +13,20 @@ import { PageUntilCondition, PagingScheme, PagingType -} from '@hyperproof/hypersync-models'; +} from '@hyperproof-int/hypersync-models'; import jsonata from 'jsonata'; +import set from 'lodash/set'; const HEADER_PREFIX = 'header:'; // Prefix indicating property found in response header const PROPERTY_ACCESSORS = ['@odata.nextLink']; // Do not evaluate as jsonata expression +export enum PagingState { + IterationPlan = 'iterationPlan', + SingleIteration = 'singleIteration', + BatchedIteration = 'batchedIteration', + None = 'none' +} + export abstract class Paginator { currentPage?: number | string; @@ -208,7 +216,7 @@ export abstract class Paginator { * @param dataSet Data set for which data has been retrieved. */ protected ensureDataSetArray(dataSet: IDataSet): void { - if (dataSet.result !== 'array') { + if (dataSet.result !== 'array' && !dataSet.filter) { throw new Error( `Paginator: Expected result for paginated requests must be of type array.` ); @@ -221,8 +229,8 @@ export abstract class Paginator { * until token is no longer found in response. */ export class NextTokenPaginator extends Paginator { - private limitParameter: string; - private limitValue: number; + private limitParameter?: string; + private limitValue?: number; private tokenType: NextTokenType; private tokenParameter?: string; @@ -236,7 +244,9 @@ export class NextTokenPaginator extends Paginator { this.tokenType = pagingScheme.tokenType; const request = pagingScheme.request as NextTokenRequest; this.limitParameter = request.limitParameter; - this.limitValue = Number(request.limitValue); + this.limitValue = request.limitValue + ? Number(request.limitValue) + : undefined; this.tokenParameter = request.tokenParameter; } @@ -248,6 +258,9 @@ export class NextTokenPaginator extends Paginator { this.currentPage = page; const delimiter = this.calcUrlDelimiter(relativeUrl, baseUrl); if (this.isFirstPage(page)) { + if (!this.limitParameter) { + return `${relativeUrl}`; + } return `${relativeUrl}${delimiter}${this.limitParameter}=${this.limitValue}`; } @@ -255,6 +268,10 @@ export class NextTokenPaginator extends Paginator { case NextTokenType.Url: return page as string; case NextTokenType.Token: + page = encodeURIComponent(page as string); + if (!this.limitParameter) { + return `${relativeUrl}${delimiter}${this.tokenParameter!}=${page}`; + } return `${relativeUrl}${delimiter}${this.limitParameter}=${ this.limitValue }&${this.tokenParameter!}=${page}`; @@ -282,17 +299,19 @@ export class NextTokenPaginator extends Paginator { `Paginator: POST method pagination does not support ${this.tokenType}. Type must be token or search array.` ); } - const pagedMessageBody = { - ...messageBody, - [this.limitParameter]: this.limitValue - }; + if (this.limitParameter) { + set(messageBody, this.limitParameter, this.limitValue); + } if (!this.isFirstPage(page)) { - pagedMessageBody[this.tokenParameter!] = + set( + messageBody, + this.tokenParameter!, this.tokenType === NextTokenType.SearchArray ? this.formatSearchArrayToken(page!) - : page; + : page + ); } - return pagedMessageBody; + return messageBody; } public getNextPage( @@ -338,12 +357,21 @@ export class NextTokenPaginator extends Paginator { pagingScheme: INextTokenScheme ): string | undefined { const request = pagingScheme.request as NextTokenRequest; - if (!request['limitParameter']) { - return `Request parameters must be defined for ${pagingScheme.type} schemes.`; - } - if (isNaN(+request.limitValue) || +request.limitValue <= 0) { - // Guard against a non-incrementing loop - return `Limit value ${this.limitValue} must be a positive integer for ${pagingScheme.type} schemes.`; + if (request['limitParameter'] || request['limitValue']) { + // if either is defined, then + if (!request['limitParameter']) { + // limit parameter cannot be undefined if limit value is defined + return `Request parameters must be defined for ${pagingScheme.type} schemes.`; + } + if (!request['limitValue']) { + // limit value cannot be undefined if limit parameter is defined + return `Limit value must be defined if request parameters are defined for ${pagingScheme.type} schemes.`; + } + if (isNaN(+request.limitValue) || +request.limitValue <= 0) { + // limit value must be be a positive integer + // Guard against a non-incrementing loop + return `Limit value ${this.limitValue} must be a positive integer for ${pagingScheme.type} schemes.`; + } } if ( pagingScheme.pageUntil === PageUntilCondition.NoNextToken && @@ -442,11 +470,9 @@ export class PageBasedPaginator extends Paginator { `Paginator: Invalid POST request body: ${JSON.stringify(messageBody)}` ); } - return { - ...messageBody, - [this.pageParameter]: this.currentPage, - [this.limitParameter]: this.limitValue - }; + set(messageBody, this.pageParameter, this.currentPage); + set(messageBody, this.limitParameter, this.limitValue); + return messageBody; } public getNextPage( @@ -573,11 +599,9 @@ export class OffsetAndLimitPaginator extends Paginator { `Paginator: Invalid POST request body: ${JSON.stringify(messageBody)}` ); } - return { - ...messageBody, - [this.offsetParameter]: this.currentPage, - [this.limitParameter]: this.limitValue - }; + set(messageBody, this.offsetParameter, this.currentPage); + set(messageBody, this.limitParameter, this.limitValue); + return messageBody; } public getNextPage( diff --git a/src/hypersync/ProofProviderBase.ts b/src/hypersync/ProofProviderBase.ts index 8cc9537..ca9d480 100644 --- a/src/hypersync/ProofProviderBase.ts +++ b/src/hypersync/ProofProviderBase.ts @@ -7,7 +7,8 @@ import { } from './ICriteriaProvider'; import { SyncMetadata } from './IDataSource'; import { IErrorInfo, IHypersync } from './models'; -import { IGetProofDataResponse } from './Sync'; +import { IterableObject } from './ServiceDataIterator'; +import { IGetProofDataResponse, IHypersyncSyncPlanResponse } from './Sync'; /* eslint-disable @typescript-eslint/no-unused-vars */ import { @@ -18,11 +19,10 @@ import { HypersyncFieldType, HypersyncPageOrientation, SchemaCategory -} from '@hyperproof/hypersync-models'; -import { - IHyperproofUser, - IntegrationContext -} from '@hyperproof/integration-sdk'; +} from '@hyperproof-int/hypersync-models'; +import { ILocalizable } from '@hyperproof-int/integration-sdk'; +import createHttpError from 'http-errors'; +import { StatusCodes } from 'http-status-codes'; /** * Field information that is used in the layout of a generated proof document. @@ -53,6 +53,19 @@ export interface IHypersyncSchema { format: HypersyncDataFormat; isHierarchical: boolean; fields: IHypersyncSchemaField[]; + throwTypeCheckingExceptions?: boolean; +} + +/** + * Sync plan generated from a configured Hypersync. Used by the + * multipaging feature. + */ +export interface IHypersyncSyncPlan { + combine?: boolean; + iteratorPlan?: { + iterableArray: IterableObject[]; + subArraySize?: number; + }; } /** @@ -98,6 +111,7 @@ export interface IHypersyncContents { collector: string; collectedOn: string; errorInfo?: IErrorInfo; + sourceTimeZone?: string; } /** @@ -163,16 +177,36 @@ export class ProofProviderBase { throw new Error('generateSchema must be implemented by derived class.'); } + /** + * Generates the sync plan for the proof type. This sync plan is used in the + * multipaging functionality. + * + * @param {*} criteriaValues Criteria values chosen by the user. + * @param {*} metadata Additional metadata associated with the sync. Optional. + * @param {number} retryCount Current retry count of sync. Optional. + */ + async generateSyncPlan( + criteriaValues: HypersyncCriteria, + metadata?: SyncMetadata, + retryCount?: number + ): Promise { + throw createHttpError( + StatusCodes.METHOD_NOT_ALLOWED, + 'generateSyncPlan must be implemented by derived class.' + ); + } + /** * Retrieves the data needed to generate proof files for the proof type. * * @param {*} hypersync The Hypersync that is being synced. - * @param {*} hyperproofUser The Hyperproof user who created the Hypersync. + * @param {*} organization The localization data for the user. * @param {string} authorizedUser User name, email or other unique identifer for the external user. * @param {*} syncStartDate Date and time at which the sync started. * @param {*} page The current page in the sync. Optional. * @param {*} metadata Additional metadata associated with the sync. Optional. * @param {number} retryCount Current retry count of sync. Optional. + * @param {*} iterableSlice If the sync plan includes an iterator, this is the slice of the iterable. Optional. * * @returns An array of objects which can be used to generate the actual proof * files. Each element in the array corresponds to one proof file that should @@ -182,12 +216,13 @@ export class ProofProviderBase { */ async getProofData( hypersync: IHypersync, - hyperproofUser: IHyperproofUser, + organization: ILocalizable, authorizedUser: string, syncStartDate: Date, page?: string, metadata?: SyncMetadata, - retryCount?: number + retryCount?: number, + iterableSlice?: IterableObject[] ): Promise { throw new Error('getProofData must be implemented by derived class.'); } diff --git a/src/hypersync/ProofProviderFactory.ts b/src/hypersync/ProofProviderFactory.ts index 98af817..8fbdd02 100644 --- a/src/hypersync/ProofProviderFactory.ts +++ b/src/hypersync/ProofProviderFactory.ts @@ -10,8 +10,8 @@ import { IProofType, IProofTypeMap, SchemaCategory -} from '@hyperproof/hypersync-models'; -import { compareValues } from '@hyperproof/integration-sdk'; +} from '@hyperproof-int/hypersync-models'; +import { compareValues } from '@hyperproof-int/integration-sdk'; import fs from 'fs'; import createHttpError from 'http-errors'; import { StatusCodes } from 'http-status-codes'; diff --git a/src/hypersync/RestDataSourceBase.ts b/src/hypersync/RestDataSourceBase.ts index 4e4ef98..ed92284 100644 --- a/src/hypersync/RestDataSourceBase.ts +++ b/src/hypersync/RestDataSourceBase.ts @@ -7,12 +7,19 @@ import { SyncMetadata } from './IDataSource'; import { IErrorInfo } from './models'; -import { Paginator } from './Paginator'; +import { Paginator, PagingState } from './Paginator'; +import { + IterableObject, + IteratorPlanDataSetResult, + ServiceDataIterator +} from './ServiceDataIterator'; import { resolveTokens, TokenContext } from './tokens'; import { DataObject, + DataSetIteratorDefinition, DataSetMethod, + DataSetMethodsWithBody, DataValue, DataValueMap, IDataSet, @@ -20,17 +27,18 @@ import { PagingLevel, Transform, ValueLookup -} from '@hyperproof/hypersync-models'; +} from '@hyperproof-int/hypersync-models'; import { ApiClient, compareValues, IApiClientResponse, - IHyperproofUser, + ILocalizable, Logger -} from '@hyperproof/integration-sdk'; +} from '@hyperproof-int/integration-sdk'; import createHttpError from 'http-errors'; import { StatusCodes } from 'http-status-codes'; import jsonata from 'jsonata'; +import set from 'lodash/set'; import { HeadersInit, Response } from 'node-fetch'; import queryString from 'query-string'; @@ -64,7 +72,7 @@ interface IPredicateClause { * file in JSON format. * * Connectors should override this class and add support for service-specific - * functionality like paging. + * functionality. */ export class RestDataSourceBase< TDataSet extends IDataSet = IDataSet @@ -73,7 +81,9 @@ export class RestDataSourceBase< protected apiClient: ApiClient; protected messages: StringMap; protected headers: HeadersInit; - constructor( + protected pagingState: PagingState = PagingState.None; + + public constructor( config: IRestDataSourceConfig, messages: StringMap, headers: HeadersInit, @@ -91,6 +101,26 @@ export class RestDataSourceBase< this.apiClient = apiClient; } + public setPagingState(pagingState: PagingState) { + if (this.pagingState !== PagingState.None) { + throw new Error('Paging state can only be set once.'); + } + this.pagingState = pagingState; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public async setBaseUrlFromHost(hostUrl?: string): Promise { + if (!hostUrl) { + return; + } + this.setBaseUrl(hostUrl); + } + + public async setBaseUrl(baseUrl: string): Promise { + this.config.baseUrl = baseUrl; + this.apiClient.setBaseUrl(baseUrl); + } + /** * Explicitly set the retry count on the DataSource's ApiClient's ThrottleManager */ @@ -136,7 +166,7 @@ export class RestDataSourceBase< this.config.valueLookups[name] = valueLookup; } - public async getNonProcessedResponse( + public async getUnprocessedResponse( dataSetName: string, params?: DataValueMap ): Promise { @@ -146,13 +176,53 @@ export class RestDataSourceBase< } // Resolve tokens in the URL and query string. - const { relativeUrl } = this.resolveUrlTokens(params, dataSet); + const { resolvedUrl } = this.resolveUrlTokens(params, dataSet); await Logger.info( - `RestDataSourceBase: Retrieving RAW response from URL '${relativeUrl}'` + `RestDataSourceBase: Retrieving RAW response from URL '${resolvedUrl}'` + ); + + return this.apiClient.getUnprocessedResponse( + resolvedUrl, + undefined, + dataSet.isAbsoluteUrl ); + } + + public async generateIteratorPlan( + proofType: string, + dataSetIterator: DataSetIteratorDefinition[], + dataSetParams: DataValueMap, + iteratorParams: DataValueMap, + metadata?: SyncMetadata + ): Promise { + const iterator = new ServiceDataIterator(this, dataSetIterator, proofType); + return iterator.generateIteratorPlan( + dataSetParams, + iteratorParams, + metadata + ); + } - return this.apiClient.getNonProcessedResponse(relativeUrl); + public async iterateDataFlow( + proofType: string, + dataSetName: string, + dataSetIterator: DataSetIteratorDefinition[], + iterableSlice: IterableObject[], + params?: DataValueMap, + page?: string, + metadata?: SyncMetadata, + organization?: ILocalizable + ): Promise> { + const iterator = new ServiceDataIterator(this, dataSetIterator, proofType); + return iterator.iterateDataFlow( + dataSetName, + iterableSlice, + params, + page, + metadata, + organization + ); } /** @@ -168,7 +238,7 @@ export class RestDataSourceBase< params?: DataValueMap, page?: string, metadata?: SyncMetadata, - hyperproofUser?: IHyperproofUser + organization?: ILocalizable ): Promise> { await Logger.debug( `RestDataSourceBase: Retrieving Hypersync service data for data set '${dataSetName}'` @@ -179,13 +249,13 @@ export class RestDataSourceBase< } // Resolve tokens in the URL and query string. - const { relativeUrl, tokenContext } = this.resolveUrlTokens( + const { resolvedUrl: relativeUrl, tokenContext } = this.resolveUrlTokens( params, dataSet ); let requestBody; - if (dataSet.method && ['POST', 'PATCH'].includes(dataSet.method)) { + if (dataSet.method && DataSetMethodsWithBody.includes(dataSet.method)) { requestBody = this.generateRequestBody( dataSetName, tokenContext, @@ -207,7 +277,7 @@ export class RestDataSourceBase< dataSet.method, requestBody, dataSet.headers, - hyperproofUser + organization ); } else { // Fetch the data from the service. @@ -221,7 +291,7 @@ export class RestDataSourceBase< dataSet.method, requestBody, dataSet.headers, - hyperproofUser + organization ); } @@ -244,15 +314,30 @@ export class RestDataSourceBase< dataSet: TDataSet ) { const tokenContext = this.initTokenContext(params); - let relativeUrl = resolveTokens(dataSet.url, tokenContext); + let resolvedUrl = resolveTokens(dataSet.url, tokenContext); const query = { ...dataSet.query }; if (Object.keys(query).length) { for (const key of Object.keys(query)) { query[key] = resolveTokens(query[key], tokenContext); } - relativeUrl = `${relativeUrl}?${queryString.stringify(query)}`; + + // Only include query parameters that have a non-empty value. + const filteredQuery = Object.keys(query).reduce( + (acc: DataValueMap, key) => { + if (query[key] && query[key].length > 0) { + acc[key] = query[key]; + } + return acc; + }, + {} + ); + + if (Object.keys(filteredQuery).length !== 0) { + resolvedUrl = `${resolvedUrl}?${queryString.stringify(filteredQuery)}`; + } } - return { relativeUrl, tokenContext }; + + return { resolvedUrl, tokenContext }; } /** @@ -275,12 +360,8 @@ export class RestDataSourceBase< metadata?: SyncMetadata ): Promise> { let data: any = response.data; - // The `property` attribute can be used to select data out of the response. - const isConnectorPaged: boolean = - dataSet.pagingScheme?.level === PagingLevel.Connector; - if (dataSet.property && !isConnectorPaged) { - // Connector pagination previously applied expression from `property` + if (dataSet.property) { await Logger.info( `RestDataSourceBase: Extracting data from '${dataSet.property}' property.` ); @@ -330,12 +411,17 @@ export class RestDataSourceBase< if (this.isArrayResult(dataSet) !== isDataArray) { if (isDataArray && data.length === 1) { data = data[0]; - } else if (this.isArrayResult(dataSet) && data === null) { + } else if ( + this.isArrayResult(dataSet) && + [null, undefined].includes(data) + ) { // Allow for offset pagination beyond end of record set data = []; } else { throw new Error( - `Data returned does not match expected ${dataSet.result} result.` + `Data returned from ${dataSetName} response ${ + dataSet.property ? `'.${dataSet.property}' property` : 'body' + } does not match expected ${dataSet.result} result.` ); } } @@ -385,6 +471,55 @@ export class RestDataSourceBase< return response as IRestDataSetComplete; } + /** + * Alias for getDataObject that only permits dataSets with PUT method + */ + public async putDataObject( + dataSetName: string, + params?: DataValueMap + ): Promise> { + this.validateRequestMethod(dataSetName, DataSetMethod.PUT); + const response = await super.getDataObject(dataSetName, params); + return response as IRestDataSetComplete; + } + + /** + * Alias for getDataObject that only permits dataSets with POST method + */ + public async postDataObject( + dataSetName: string, + params?: DataValueMap + ): Promise> { + this.validateRequestMethod(dataSetName, DataSetMethod.POST); + const response = await super.getDataObject(dataSetName, params); + return response as IRestDataSetComplete; + } + + /** + * Alias for getDataObject that only permits dataSets with PATCH method + */ + public async patchDataObject( + dataSetName: string, + params?: DataValueMap + ): Promise> { + this.validateRequestMethod(dataSetName, DataSetMethod.PATCH); + const response = await super.getDataObject(dataSetName, params); + return response as IRestDataSetComplete; + } + + private validateRequestMethod( + dataSetName: string, + method: DataSetMethod + ): void { + const dataSet = this.config.dataSets[dataSetName]; + if (dataSet?.method !== method) { + throw createHttpError( + StatusCodes.BAD_REQUEST, + `Called ${method}DataObject with a dataSet that does not use ${method} method` + ); + } + } + /** * Retrieves a data object collection from the service. * @@ -411,7 +546,7 @@ export class RestDataSourceBase< * @param {DataSetMethod} method REST HTTP Method. Optional. * @param {object} requestBody Request body of the HTTP request. Optional. * @param {object} requestHeaders Additional headers to be included in the HTTP request. Optional. - * @param {*} hyperproofUser The Hyperproof user who initiated the sync. Optional. + * @param {*} organization The localization data to be used for formatting. Optional. * * @returns RestDataSetResult */ @@ -429,7 +564,7 @@ export class RestDataSourceBase< requestBody?: any, requestHeaders?: { [key: string]: string }, // eslint-disable-next-line @typescript-eslint/no-unused-vars - hyperproofUser?: IHyperproofUser + organization?: ILocalizable ): Promise> { await Logger.info( `RestDataSourceBase: Retrieving data from URL '${relativeUrl}'` @@ -437,21 +572,29 @@ export class RestDataSourceBase< let response: IApiClientResponse; switch (method) { - case 'PATCH': + case DataSetMethod.PATCH: response = await this.apiClient.patchJson( relativeUrl, requestBody, requestHeaders ); break; - case 'POST': + case DataSetMethod.POST: response = await this.apiClient.postJson( relativeUrl, requestBody, requestHeaders ); + break; - case 'GET': + case DataSetMethod.PUT: + response = await this.apiClient.putJson( + relativeUrl, + requestBody, + requestHeaders + ); + break; + case DataSetMethod.GET: case undefined: response = await this.apiClient.getJson(relativeUrl, requestHeaders); break; @@ -463,7 +606,6 @@ export class RestDataSourceBase< } const { json: data, source, headers } = response; - this.validateResponse(dataSetName, data, source, headers); return { @@ -487,11 +629,11 @@ export class RestDataSourceBase< * @param {DataSetMethod} method REST HTTP Method. Optional. * @param {object} requestBody Body of the HTTP request. Optional. * @param {object} requestHeaders Additional headers to be included in the HTTP request. Optional. - * @param {*} hyperproofUser The Hyperproof user who initiated the sync. Optional. + * @param {*} organization The localization data to be used for formatting. Optional. * * @returns RestDataSetResult */ - protected async pageDataFromUrl( + private async pageDataFromUrl( dataSetName: string, dataSet: IDataSet, relativeUrl: string, @@ -501,12 +643,12 @@ export class RestDataSourceBase< method?: DataSetMethod, requestBody?: any, requestHeaders?: { [key: string]: string }, - hyperproofUser?: IHyperproofUser + organization?: ILocalizable ): Promise> { const baseUrl = this.config.baseUrl; const paginator = Paginator.createPaginator(dataSet.pagingScheme!, method); - if (dataSet.pagingScheme?.level === PagingLevel.Connector) { + if (this.isConnectorLevelPaging(dataSet)) { const results: any = []; let connectorPage: string | undefined; let response; @@ -529,7 +671,7 @@ export class RestDataSourceBase< method, pagedMessageBody, requestHeaders, - hyperproofUser + organization ); if (response.status !== DataSetResultStatus.Complete) { @@ -558,7 +700,7 @@ export class RestDataSourceBase< source: baseUrl ? new URL(relativeUrl, baseUrl).toString() : relativeUrl, - data: results + data: dataSet.property ? set({}, dataSet.property, results) : results }; } else { // Default to job level paging @@ -579,7 +721,7 @@ export class RestDataSourceBase< method, pagedMessageBody, requestHeaders, - hyperproofUser + organization ); if (response.status !== DataSetResultStatus.Complete) { @@ -698,6 +840,12 @@ export class RestDataSourceBase< const result = Array.isArray(data) ? data : [data]; for (const lookup of lookups) { for (const dataObject of result) { + if (!dataObject || typeof dataObject !== 'object') { + await Logger.warn( + `RestDataSourceBase: Skipping invalid data object in lookup of type: ${typeof dataObject}` + ); + continue; + } const params = { ...lookup.dataSetParams }; if (params) { tokenContext['source'] = dataObject; @@ -708,19 +856,41 @@ export class RestDataSourceBase< } } } + if (lookup.delaySeconds) { + const { delaySeconds } = lookup; + if (isNaN(delaySeconds) || delaySeconds < 0 || delaySeconds > 1) { + throw new Error( + `Invalid delay seconds number: ${delaySeconds} for data set: ${dataSetName}. Must be less than or equal to 1.` + ); + } + await new Promise(resolve => + setTimeout(resolve, delaySeconds * 1000) + ); + } - const response = await this.getData( - lookup.dataSet, - params, - undefined, // Paging is not supported for lookups - metadata - ); - if (response.status !== DataSetResultStatus.Complete) { - throw new Error( - `Invalid response received for data set: ${dataSetName}` + try { + const response = await this.getData( + lookup.dataSet, + params, + undefined, // Paging is not supported for lookups + metadata ); + if (response.status !== DataSetResultStatus.Complete) { + throw new Error( + `Invalid response received for data set: ${dataSetName}` + ); + } + dataObject[lookup.alias] = response.data; + } catch (error) { + if (lookup.continueOnError === true) { + await Logger.warn( + `Continuing upon error performing lookup for data set: ${lookup.dataSet}`, + JSON.stringify(error) + ); + continue; + } + throw error; } - dataObject[lookup.alias] = response.data; } } @@ -1022,10 +1192,10 @@ export class RestDataSourceBase< // eslint-disable-next-line @typescript-eslint/no-unused-vars params?: DataValueMap ): string | object | undefined { - if (body) { - return resolveTokens(body as object | string, tokenContext); + if (!body) { + return body; } - return body; + return resolveTokens(body as object | string, tokenContext); } /** @@ -1049,4 +1219,18 @@ export class RestDataSourceBase< ): void { // No validation applied by default } + + protected isConnectorLevelPaging(dataSet: IDataSet): boolean { + if (dataSet.pagingScheme?.level === PagingLevel.Connector) { + return true; + } else if ( + dataSet.pagingScheme && + [PagingState.IterationPlan, PagingState.BatchedIteration].includes( + this.pagingState + ) + ) { + return true; + } + return false; + } } diff --git a/src/hypersync/ServiceDataIterator.ts b/src/hypersync/ServiceDataIterator.ts new file mode 100644 index 0000000..1ea96a9 --- /dev/null +++ b/src/hypersync/ServiceDataIterator.ts @@ -0,0 +1,463 @@ +import { + DataSetResultStatus, + IDataSetResultPending, + IDataSource, + isRestDataSourceBase, + SyncMetadata +} from './IDataSource'; +import { PagingState } from './Paginator'; +import { + IRestDataSetComplete, + RestDataSetResult, + RestDataSourceBase +} from './RestDataSourceBase'; + +import { + CriteriaSourcedIterator, + DataObject, + DataSetIteratorDefinition, + DataSetSourcedIterator, + DataValue, + DataValueMap, + IProofSpec, + IteratorSource +} from '@hyperproof-int/hypersync-models'; +import { ILocalizable } from '@hyperproof-int/integration-sdk'; + +export interface IIteratorPlanComplete { + status: DataSetResultStatus.Complete; + data: { + iterableArray: IterableObject[]; + subArraySize: number; + }; +} + +export type IterableObject = { + [key: string]: DataValue; +}; + +export type IteratorPlanDataSetResult = + | IIteratorPlanComplete + | IDataSetResultPending; + +export type CriteriaTransformers = { + [key: string]: (data: any, iterandKey: string) => IterableObject[]; +}; + +export const criteriaTransformers: CriteriaTransformers = { + csvToIterable: (data: any, iterandKey: string) => { + if (typeof data !== 'string') { + throw new Error( + 'ServiceDataIterator: CSV criteria data must be a string' + ); + } + return data + .split(',') + .map((s: string) => s.trim()) + .map((item: string) => ({ [iterandKey]: item })); + } +}; + +const MAX_ALLOWABLE_PROPERTIES = 1; +const MAX_ITERABLE_ARRAY_SIZE = 10000; +const MAX_VALIDATION_ITERATIONS = 3; // Number of records validated during sync plan generation + +/** + * Manages iteration over service data for hypersyncs, supporting both dataset and criteria-sourced iteration plans. + * + * The `ServiceDataIterator` is responsible for generating iteration plans, controlling iterative data flow, and validating + * iterable arrays and elements. It interacts with a REST data source to fetch data according to the specified iterator definition. + */ +export class ServiceDataIterator { + protected restDataSource: RestDataSourceBase; + protected proofType: string; + protected principalIterator: DataSetIteratorDefinition; + protected principalArraySize: number; + + constructor( + restDataSource: IDataSource, + dataSetIterator: DataSetIteratorDefinition[], + proofType: string + ) { + const errMessage = this.validateIterator( + ServiceDataIterator.extractIteratorLayer(dataSetIterator, 1) + ); + if (errMessage) { + throw new Error(`Iterator: ${errMessage}`); + } + if (!isRestDataSourceBase(restDataSource)) { + throw new Error( + 'ServiceDataIterator: DataSource must be an instance of RestDataSourceBase' + ); + } + this.restDataSource = restDataSource; + this.proofType = proofType; + this.principalIterator = ServiceDataIterator.extractIteratorLayer( + dataSetIterator, + 1 + ); + this.principalArraySize = this.principalIterator.subArraySize ?? 1; + } + + /** + * Extracts the target layer from a JSON dataSetIterator array. + */ + public static extractIteratorLayer( + dataSetIterator: IProofSpec['dataSetIterator'], + layer: number + ) { + if (!dataSetIterator || dataSetIterator.length === 0) { + throw new Error( + 'ServiceDataIterator: dataSetIterator is empty or undefined.' + ); + } + + const iterator = dataSetIterator.find( + (i: DataSetIteratorDefinition) => i.layer === layer + ); + if (!iterator) { + throw new Error( + `ServiceDataIterator: No matching iterator found for layer: ${layer}` + ); + } + return iterator; + } + + /** + * Merges the specified key from an iterable object into a parameters map. + * Supports replacing a given `dataSetParam` value for the benefit + * of token reuse and extensibility of JSON datasets. + */ + public static mergeIterandWithParams( + iterand: IterableObject, + iterandKey: string, + params?: DataValueMap + ) { + return { + ...params, + [iterandKey]: iterand[iterandKey] + }; + } + + public async generateIteratorPlan( + dataSetParams: DataValueMap, + iteratorParams: DataValueMap, + metadata?: SyncMetadata + ): Promise { + let iterableArray: IterableObject[] = []; + this.restDataSource.setPagingState(PagingState.IterationPlan); + switch (this.principalIterator.source) { + case IteratorSource.DataSet: { + const response = await this.generateArrayFromDataset( + iteratorParams, + metadata + ); + if (response.status !== DataSetResultStatus.Complete) { + return response as IDataSetResultPending; + } + iterableArray = response.data; + break; + } + case IteratorSource.Criteria: { + iterableArray = this.generateArrayFromCriteria( + dataSetParams, + this.principalIterator.criteriaTransformer + ); + break; + } + default: + throw new Error('ServiceDataIterator: Unsupported iterator source.'); + } + + const errMessage = this.validateIterableArray(iterableArray); + if (errMessage) { + throw new Error(`ServiceDataIterator: ${errMessage}`); + } + + return { + status: DataSetResultStatus.Complete, + data: { + iterableArray, + subArraySize: this.principalArraySize + } + }; + } + + public async iterateDataFlow( + dataSetName: string, + iterableSlice: IterableObject[], + params?: DataValueMap, + page?: string, + metadata?: SyncMetadata, + organization?: ILocalizable + ): Promise> { + this.restDataSource.setPagingState( + this.isSingleIteration() + ? PagingState.SingleIteration + : PagingState.BatchedIteration + ); + return this.controlIterativeDataFlow( + dataSetName, + iterableSlice, + params, + page, + metadata, + organization + ); + } + + protected generateArrayFromCriteria( + params: DataValueMap, + transformer?: string + ) { + if (!params) { + throw new Error( + 'ServiceDataIterator: Params are missing. Unable to generate iterable array from criteria.' + ); + } + const criteriaProperty = (this.principalIterator as CriteriaSourcedIterator) + .criteriaProperty; + + const sourceCriteria = params[criteriaProperty]; + if (!sourceCriteria) { + throw new Error( + `ServiceDataIterator: Missing param value matching criteria property '${criteriaProperty}'` + ); + } + if (!transformer) { + throw new Error( + `ServiceDataIterator: Invalid criteria transformer: ${transformer}` + ); + } + if ( + !Object.prototype.hasOwnProperty.call( + criteriaTransformers, + transformer + ) || + typeof criteriaTransformers[transformer] !== 'function' + ) { + throw new Error( + `ServiceDataIterator: Criteria transformer '${transformer}' does not exist or is not a function.` + ); + } + + const iterableArray = criteriaTransformers[ + transformer as keyof CriteriaTransformers + ](sourceCriteria, this.principalIterator.iterandKey); + return iterableArray; + } + + protected async generateArrayFromDataset( + params: DataValueMap, + metadata?: SyncMetadata + ) { + const dataSet = (this.principalIterator as DataSetSourcedIterator).dataSet; + const response = await this.restDataSource.getData( + dataSet!, + params, + undefined, + metadata + ); + + if (response.status !== DataSetResultStatus.Complete) { + return response; + } + + const { data: iterableArray } = response; + + if (!Array.isArray(iterableArray)) { + throw new Error( + `ServiceDataIterator: Iterable array of type ${typeof iterableArray} must be an array.` + ); + } + + return { + data: iterableArray, + status: DataSetResultStatus.Complete + }; + } + + protected async controlIterativeDataFlow( + dataSetName: string, + iterableSlice: IterableObject[], + params?: DataValueMap, + page?: string, + metadata?: SyncMetadata, + organization?: ILocalizable + ): Promise> { + const accumulator: IRestDataSetComplete = { + status: DataSetResultStatus.Complete, + data: [], + headers: {}, + source: undefined, + nextPage: undefined, + context: undefined + }; + + for (const iteration of iterableSlice) { + const mergedParams = ServiceDataIterator.mergeIterandWithParams( + iteration, + this.principalIterator.iterandKey, + params + ); + + let response = await this.restDataSource.getData( + dataSetName, + mergedParams, + page, + metadata, + organization + ); + + if (response.status !== DataSetResultStatus.Complete) { + return response; + } + + if (!Array.isArray(response.data)) { + throw new Error( + `ServiceDataIterator: Expected data to be an array, but received: ${typeof response.data}` + ); + } + + response = await this.handleDataSetIteration(iteration, response); + + if (response.status !== DataSetResultStatus.Complete) { + return response; + } + + this.updateResponseFields(response, accumulator); + } + + return accumulator; + } + + protected async handleDataSetIteration( + iteration: IterableObject, + response: IRestDataSetComplete + ): Promise> { + return response; + } + + protected updateResponseFields( + response: IRestDataSetComplete, + accumulator: IRestDataSetComplete + ): void { + if (response.data) accumulator.data.push(...response.data); + if (response.headers) accumulator.headers = response.headers; + if (response.source) accumulator.source = response.source; + if (response.nextPage && this.isSingleIteration()) + accumulator.nextPage = response.nextPage; + if (response.context) accumulator.context = response.context; + } + + /** + * Performs validation on the provided JSON iterator configuration. + * Returns an error message string if validation fails, otherwise undefined. + */ + protected validateIterator( + iterator: DataSetIteratorDefinition + ): string | undefined { + if (iterator.subArraySize !== undefined && iterator.subArraySize <= 0) { + return 'Sub-array size must be greater than 0'; + } + if (!iterator.iterandKey || iterator.iterandKey.trim() === '') { + return 'Iterator must specify a non-empty iterandKey property'; + } + switch (iterator.source) { + case IteratorSource.DataSet: + if ( + !(iterator as DataSetSourcedIterator).dataSet || + (iterator as DataSetSourcedIterator).dataSet.trim() === '' + ) { + return 'Dataset iterator must specify a non-empty dataSet property'; + } + break; + case IteratorSource.Criteria: + if ( + !(iterator as CriteriaSourcedIterator).criteriaProperty || + (iterator as CriteriaSourcedIterator).criteriaProperty.trim() === '' + ) { + return 'Criteria iterator must specify a non-empty criteriaProperty property'; + } + if ( + !(iterator as CriteriaSourcedIterator).criteriaTransformer || + (iterator as CriteriaSourcedIterator).criteriaTransformer.trim() === + '' + ) { + return 'Criteria iterator must specify a non-empty criteriaTransformer property'; + } + if ( + !( + (iterator as CriteriaSourcedIterator).criteriaTransformer in + criteriaTransformers + ) + ) { + return `Criteria iterator specifies unknown criteriaTransformer: '${ + (iterator as CriteriaSourcedIterator).criteriaTransformer + }'`; + } + break; + default: + return `Invalid iterator source: ${ + (iterator as DataSetSourcedIterator | CriteriaSourcedIterator).source + }`; + } + return; + } + + /** + * Performs validation on the principal array to be returned and orchestrated at the Job level. + * The origin of this array argument may be criteria selection or async call. + * Returns an error message string if validation fails, otherwise undefined. + */ + protected validateIterableArray( + iterableArray: IterableObject[] + ): string | undefined { + if (iterableArray.length === 0) { + return 'Invalid iterableArray. Must be a non-empty array'; + } + if (iterableArray.length > MAX_ITERABLE_ARRAY_SIZE) { + return `Invalid iterableArray. Length exceeds maximum of ${MAX_ITERABLE_ARRAY_SIZE}. Found ${iterableArray.length} elements.`; + } + for (const [index, element] of iterableArray.entries()) { + if (index >= MAX_VALIDATION_ITERATIONS) { + break; + } + const error = this.validateIterableElement(element); + if (error) { + return `Invalid iterableArray. Element at index ${index}: ${error}`; + } + } + return; + } + + /** + * Inspects individual elements of the principal array. + * Returns an error message string if validation fails, otherwise undefined. + */ + protected validateIterableElement( + element: IterableObject + ): string | undefined { + if (typeof element !== 'object' || element === null) { + return 'Must be a JSON object'; + } + if (!(this.principalIterator.iterandKey in element)) { + return `Missing iterand key: '${this.principalIterator.iterandKey}'`; + } + if (Object.keys(element).length > MAX_ALLOWABLE_PROPERTIES) { + return `Only ${MAX_ALLOWABLE_PROPERTIES} property is allowed to be present in an iterable element. Found ${ + Object.keys(element).length + } properties. + Use declarative transforms to reshape data.`; + } + return; + } + + /** + * Determines if current sync iteration is single (meaning an iterable slice size of one) + * or batched (with a size greater than one). This influences paging state and nextPage handling. + */ + protected isSingleIteration(): boolean { + return this.principalArraySize === 1; + } +} diff --git a/src/hypersync/Sync.ts b/src/hypersync/Sync.ts index 91fbf30..c550691 100644 --- a/src/hypersync/Sync.ts +++ b/src/hypersync/Sync.ts @@ -1,9 +1,13 @@ import { HypersyncResult } from './enums'; import { SyncMetadata } from './IDataSource'; import { IHypersync } from './models'; -import { IHypersyncContents, IProofFile } from './ProofProviderBase'; +import { + IHypersyncContents, + IHypersyncSyncPlan, + IProofFile +} from './ProofProviderBase'; -import { IHyperproofUser } from '@hyperproof/integration-sdk'; +import { ILocalizable } from '@hyperproof-int/integration-sdk'; /** * Detailed response object that may be returned from getProofData. @@ -11,7 +15,17 @@ import { IHyperproofUser } from '@hyperproof/integration-sdk'; export interface IGetProofDataResponse { data: IProofFile[]; nextPage?: string; - combine?: boolean; + metadata?: SyncMetadata; + delay?: number; + retry?: boolean; + maxRetry?: number; + publishResult?: boolean; + syncResult?: HypersyncResult; + failureMessage?: string; +} + +export interface IHypersyncSyncPlanResponse { + syncPlan: IHypersyncSyncPlan; metadata?: SyncMetadata; delay?: number; retry?: boolean; @@ -25,7 +39,7 @@ export class Sync { public userContext: object; public hypersync: IHypersync; public syncStartDate: Date; - public hyperproofUser: IHyperproofUser; + public organization: ILocalizable; public page?: number; public metadata?: SyncMetadata; @@ -41,14 +55,14 @@ export class Sync { userContext: object, hypersync: IHypersync, syncStartDate: string, - hyperproofUser: IHyperproofUser, + organization: ILocalizable, page?: number, metadata?: SyncMetadata ) { this.userContext = userContext; this.hypersync = hypersync; this.syncStartDate = syncStartDate ? new Date(syncStartDate) : new Date(); - this.hyperproofUser = hyperproofUser; + this.organization = organization; this.page = page; this.metadata = metadata; } diff --git a/src/hypersync/common.ts b/src/hypersync/common.ts index 44be036..60d8055 100644 --- a/src/hypersync/common.ts +++ b/src/hypersync/common.ts @@ -7,14 +7,16 @@ import { import { HypersyncFieldType, IHypersyncField -} from '@hyperproof/hypersync-models'; +} from '@hyperproof-int/hypersync-models'; import createHttpError from 'http-errors'; import { StatusCodes } from 'http-status-codes'; +import queryString from 'query-string'; // Magic constants that are used to allow the user to choose All, Any or None from a set. export const ID_ALL = 'fbd4bc94-23b7-40fa-b3bc-b3a0e7faf173'; export const ID_ANY = '3cc42087-3b95-4b7e-82e4-d2d8466daa8f'; export const ID_NONE = 'deeee249-c2eb-4598-8824-3db79927c7a6'; +export const ID_UNDEFINED = 'a9e2b542-33b8-4dde-93d6-7eb97d395c08'; // Unselected optional value /** * Used to map one string value to another. @@ -179,3 +181,39 @@ export class LayoutFields { this._fields = fields; } } + +/** + * Uses query-string library determine if query parameters exist in URL. + * Returns the appropriate delimiter based on resulting condition. + */ +export const parseUrlDelimiter = (url: string) => { + const parsedUrl = queryString.parseUrl(url); + if (Object.keys(parsedUrl.query).length > 0) { + return '&'; + } + return '?'; +}; + +/** + * Utility function to convert input to base64. + * @param input to convert to base64 string + * @returns base64 string + */ +export const toBase64 = ( + input: WithImplicitCoercion +) => { + return Buffer.from(input).toString('base64'); +}; + +/** + * Simple function to convert a string to a boolean value. + * @param value string + * @returns boolean + */ +export const stringToBoolean = (value: string | undefined | null): boolean => { + if (value === undefined || value === null) { + return false; + } + const lowerValue = value.toLowerCase(); + return lowerValue === 'true' || lowerValue === '1'; +}; diff --git a/src/hypersync/hypersyncConnector.ts b/src/hypersync/hypersyncConnector.ts index 796ec91..b6279b9 100644 --- a/src/hypersync/hypersyncConnector.ts +++ b/src/hypersync/hypersyncConnector.ts @@ -4,29 +4,28 @@ import { formatHypersyncError } from './common'; import { HypersyncStage } from './enums'; import { ICriteriaMetadata } from './ICriteriaProvider'; import { SyncMetadata } from './IDataSource'; -import { formatMessage, MESSAGES } from './messages'; import { IHypersync } from './models'; import { IHypersyncSchema } from './ProofProviderBase'; -import { IGetProofDataResponse } from './Sync'; +import { IterableObject } from './ServiceDataIterator'; +import { IGetProofDataResponse, IHypersyncSyncPlanResponse } from './Sync'; import { HypersyncCriteria, + ICriteriaSearchInput, SchemaCategory -} from '@hyperproof/hypersync-models'; +} from '@hyperproof-int/hypersync-models'; import { - AuthorizationType, createConnector, CustomAuthCredentials, ExternalAPIError, formatUserKey, - HealthStatus, HttpHeader, ICheckConnectionHealthInvocationPayload, - IConnectionHealth, - IHyperproofUser, IHyperproofUserContext, + ILocalizable, IntegrationContext, IRetryResponse, + IValidateCredentialsResponse, LogContextKey, Logger, mapObjectTypesParamToType, @@ -34,26 +33,13 @@ import { ObjectType, StorageItem, UserContext -} from '@hyperproof/integration-sdk'; +} from '@hyperproof-int/integration-sdk'; import express from 'express'; import fs from 'fs'; import createHttpError from 'http-errors'; import { StatusCodes } from 'http-status-codes'; import { ParsedQs } from 'qs'; -/** - * Object returned from the validateCredentials method. - * - * Ideally all validateCredentials implementers would return only the vendorUserId - * and vendorUserProfile members. But for historical reasons we also allow connectors - * to return other, arbitrary values which will be blended into the persisted user context. - */ -export interface IValidateCredentialsResponse { - vendorUserId: string; - vendorUserProfile?: object; - [key: string]: any; -} - /** * * @param {*} superclass class that extends OAuthConnector - this allows us to dynamically extend a class which enables mixing inheritance @@ -162,7 +148,10 @@ export function createHypersync(superclass: typeof OAuthConnector) { * that allows the user to specify proof criteria. */ app.put( - '/organizations/:orgId/users/:userId/generatecriteriametadata', + [ + '/organizations/:orgId/users/:userId/generatecriteriametadata', + '/organizations/:orgId/generatecriteriametadata' + ], async (req, res) => { try { const result = await this.generateCriteriaMetadata( @@ -193,7 +182,10 @@ export function createHypersync(superclass: typeof OAuthConnector) { * Returns the schema of the proof that will be generated. */ app.put( - '/organizations/:orgId/users/:userId/generateschema', + [ + '/organizations/:orgId/users/:userId/generateschema', + '/organizations/:orgId/generateschema' + ], async (req, res) => { try { const result = await this.generateSchema( @@ -212,6 +204,34 @@ export function createHypersync(superclass: typeof OAuthConnector) { } ); + /** + * Returns the sync plan of the proof that will be generated. + */ + app.put( + [ + '/organizations/:orgId/users/:userId/generatesyncplan', + '/organizations/:orgId/generatesyncplan' + ], + async (req, res) => { + try { + const result = await this.generateSyncPlan( + req.fusebit, + req.params.orgId, + req.body.hypersync.settings.vendorUserId, + req.body.hypersync.settings.criteria, + req.body.metadata, + req.body.retryCount + ); + res.json(result); + } catch (err: any) { + await Logger.error('Failed to generate sync plan', err); + res + .status(err.status || StatusCodes.INTERNAL_SERVER_ERROR) + .json({ message: err.message }); + } + } + ); + app.post( '/organizations/:orgId/:objectType/:objectId/invoke', this.checkAuthorized(), @@ -229,10 +249,11 @@ export function createHypersync(superclass: typeof OAuthConnector) { objectId, req.body.hypersync, req.body.syncStartDate, - req.body.user, + req.body.organization, req.body.page, req.body.metadata, - req.body.retryCount + req.body.retryCount, + req.body.iterableSlice ); res.json(data); break; @@ -404,88 +425,6 @@ export function createHypersync(superclass: typeof OAuthConnector) { throw new Error('Not implemented'); } - override async checkConnectionHealth( - integrationContext: IntegrationContext, - orgId: string, - userId: string, - vendorUserId: string, - body?: ICheckConnectionHealthInvocationPayload - ): Promise { - try { - const userContext = await this.getHyperproofUserContext( - integrationContext, - vendorUserId - ); - - // we'll need to allow JiraHS to find its userContext using hostUrl - if (!userContext) { - throw createHttpError( - StatusCodes.NOT_FOUND, - this.getUserNotFoundMessage(vendorUserId) - ); - } - - if (this.authorizationType === AuthorizationType.CUSTOM) { - // NOTE: calling this will not save any token retrieved from the corresponding service to the vendor-user. - // In the case of AWS, the temporary token for cross-account role auth is saved in vendor-user for each sync, but not saved by validateCredentials itself. - // Since the token is temporary they will expire in an hour and way before AWS scheduled syncs are run and thus no reason to save it in this step. - await this.validateCredentials( - userContext.keys!, - integrationContext, - userId - ); - } else { - const tokenResponse = await this.ensureAccessToken( - integrationContext, - userContext - ); - - await this.validateAccessToken( - integrationContext, - userContext, - tokenResponse.access_token, - body - ); - } - } catch (e: any) { - // The connector can customize the health result status and message here. - // However custom connectors' validateCredentials may have already handled the - // error and threw a different error/status code. - const healthResult = this.handleHealthError(e); - - if (healthResult.healthStatus === HealthStatus.NotImplemented) { - await Logger.info( - `Connection health check found the connector did not implement validate credentials function.` - ); - } else if (healthResult.healthStatus === HealthStatus.Unhealthy) { - if (healthResult.statusCode === StatusCodes.NOT_FOUND) { - await Logger.info( - `Connection health check returned an unhealthy response: ${this.getUserNotFoundMessage( - vendorUserId - )}` - ); - } else { - await Logger.info( - `Connection health check returned an unhealthy response: ${healthResult.message}` - ); - } - } else if (healthResult.healthStatus === HealthStatus.Unknown) { - await Logger.warn( - `Connection health check returned an unknown error: ${healthResult.message}` - ); - } - - return healthResult; - } - - return { - healthStatus: HealthStatus.Healthy, - message: undefined, - details: undefined, - statusCode: StatusCodes.OK - }; - } - /** * Validates custom auth credentials that are provided when creating a new * new user connection. Designed to be overridden. @@ -514,47 +453,6 @@ export function createHypersync(superclass: typeof OAuthConnector) { throw createHttpError(StatusCodes.NOT_IMPLEMENTED, 'Not Implemented'); } - /** - * This can be overriden by the connector in order to provide better health check result with more information or to process/filter errors. - */ - handleHealthError(error: any): IConnectionHealth { - const errorMessage = error.message; - const extendedErrorMessage = error[LogContextKey.ExtendedMessage]; - const healthResult: Readonly = { - healthStatus: HealthStatus.Unknown, - message: `Error: ${ - extendedErrorMessage ? extendedErrorMessage : errorMessage - }`, - details: undefined, - statusCode: error.statusCode || StatusCodes.INTERNAL_SERVER_ERROR - }; - switch (error.statusCode) { - case StatusCodes.NOT_FOUND: - return { - ...healthResult, - healthStatus: HealthStatus.Unhealthy, - message: formatMessage(MESSAGES.NoAccountFound, { - [MESSAGES.App]: this.connectorName - }) - }; - case StatusCodes.UNAUTHORIZED: - case StatusCodes.FORBIDDEN: - return { - ...healthResult, - healthStatus: HealthStatus.Unhealthy, - message: extendedErrorMessage ? extendedErrorMessage : errorMessage - }; - case StatusCodes.NOT_IMPLEMENTED: - return { - ...healthResult, - healthStatus: HealthStatus.NotImplemented, - message: 'The function for validating token is not implemented.' - }; - } - - return healthResult; - } - /** * Returns a human readable string which identifies the vendor user's account. * This string is displayed in Hypersync's Connected Accounts page to help the @@ -577,10 +475,11 @@ export function createHypersync(superclass: typeof OAuthConnector) { objectId: string, hypersync: IHypersync, syncStartDate: string, - hyperproofUser: IHyperproofUser, + organization: ILocalizable, page?: string, metadata?: SyncMetadata, - retryCount?: number + retryCount?: number, + iterableSlice?: IterableObject[] ): Promise { throw Error('Not implemented'); } @@ -593,7 +492,7 @@ export function createHypersync(superclass: typeof OAuthConnector) { orgId: string, vendorUserId: string, criteria: HypersyncCriteria, - search?: string, + search?: string | ICriteriaSearchInput, schemaCategory?: SchemaCategory ): Promise { throw Error('Not implemented'); @@ -614,6 +513,23 @@ export function createHypersync(superclass: typeof OAuthConnector) { ); } + /** + * Returns the sync plan of the proof that will be generated. + */ + async generateSyncPlan( + integrationContext: IntegrationContext, + orgId: string, + vendorUserId: string, + criteria: HypersyncCriteria, + metadata?: SyncMetadata, + retryCount?: number + ): Promise { + throw createHttpError( + StatusCodes.METHOD_NOT_ALLOWED, + 'Sync plan generation is not implemented.' + ); + } + /** * Returns the file names that constitute the app's definition */ @@ -623,10 +539,6 @@ export function createHypersync(superclass: typeof OAuthConnector) { return [...images, ...metadata]; } - getUserNotFoundMessage(vendorUserId: string) { - return `No ${this.connectorName} account found with id ${vendorUserId}. The connection may have been deleted.`; - } - /** * Keeps an access token alive. Designed to be overridden in connectors * that have tokens that expire. Behavior varies by connector. diff --git a/src/hypersync/index.ts b/src/hypersync/index.ts index 0833d04..81374a6 100644 --- a/src/hypersync/index.ts +++ b/src/hypersync/index.ts @@ -2,6 +2,7 @@ export * from './DataSourceBase'; export * from './HypersyncApp'; export * from './ICriteriaProvider'; export * from './IDataSource'; +export * from './ServiceDataIterator'; export * from './JsonCriteriaProvider'; export * from './JsonProofProvider'; export * from './ProofProviderBase'; diff --git a/src/hypersync/layout.ts b/src/hypersync/layout.ts index d872e65..e82ae2e 100644 --- a/src/hypersync/layout.ts +++ b/src/hypersync/layout.ts @@ -1,6 +1,6 @@ import { IHypersyncProofField } from './ProofProviderBase'; -import { HypersyncPageOrientation } from '@hyperproof/hypersync-models'; +import { HypersyncPageOrientation } from '@hyperproof-int/hypersync-models'; const DEFAULT_A4_WIDTH_PIXELS = 794; const DEFAULT_A4_LENGTH_PIXELS = 1123; diff --git a/src/hypersync/messages.ts b/src/hypersync/messages.ts index b0aadde..9d956e4 100644 --- a/src/hypersync/messages.ts +++ b/src/hypersync/messages.ts @@ -1,5 +1,8 @@ export const MESSAGES = { App: 'App', + Default: { + NoResultsMessage: 'This Hypersync returned no results.' + }, FormatBooleanYesNo: 'Yes;No', FormatBooleanTrueFalse: 'True;False', ImportTimeLessThan1Hour: 'This import will take {minutes} minutes', @@ -29,6 +32,12 @@ export const MESSAGES = { LabelOwner: 'Owner', LabelRole: 'Role', LabelUserName: 'Username' + }, + AgeString: { + Years: '{years} years ', + Days: '{days} days ', + Hours: '{hours} hours ', + Minutes: '{minutes} minutes' } }; @@ -45,7 +54,7 @@ const MESSAGE_KEYS = { export const formatMessage = ( messageKey: string, messageVals: { [key: string]: string }, - messageKeysOverride?: { [key: string]: string } + messageKeysOverride?: { [key: string]: object } ) => { let message = messageKey; diff --git a/src/hypersync/models.ts b/src/hypersync/models.ts index 1564154..dacc656 100644 --- a/src/hypersync/models.ts +++ b/src/hypersync/models.ts @@ -5,12 +5,12 @@ import { HypersyncCriteria, HypersyncPeriod, SchemaCategory -} from '@hyperproof/hypersync-models'; +} from '@hyperproof-int/hypersync-models'; import { IIntegration, IIntegrationSettingsBase, IntegrationSettingsClass -} from '@hyperproof/integration-sdk'; +} from '@hyperproof-int/integration-sdk'; /** * Settings that are saved with a Hypersync integration. @@ -42,6 +42,11 @@ export interface IHypersync schemaCategory?: SchemaCategory; } +export interface IHypersyncSavedCriteria { + label: string; + data: string[]; +} + /** * Information about errors encountered during proof generation. */ diff --git a/src/hypersync/time.ts b/src/hypersync/time.ts index 89db7cc..6d13734 100644 --- a/src/hypersync/time.ts +++ b/src/hypersync/time.ts @@ -1,11 +1,13 @@ import '@js-joda/timezone'; +import { formatMessage, MESSAGES } from './messages'; -import { HypersyncPeriod } from '@hyperproof/hypersync-models'; +import { HypersyncPeriod } from '@hyperproof-int/hypersync-models'; import { ChronoUnit, convert, DateTimeFormatter, DayOfWeek, + Duration, Instant, LocalDateTime, TemporalAdjusters, @@ -16,7 +18,12 @@ import { const fallbackLocale = 'en-US'; const fallbackTimeZone = 'UTC'; - +const MESSAGE_KEYS = { + [MESSAGES.AgeString.Years]: { years: 'years' }, + [MESSAGES.AgeString.Days]: { days: 'days' }, + [MESSAGES.AgeString.Hours]: { hours: 'hours' }, + [MESSAGES.AgeString.Minutes]: { minutes: 'minutes' } +}; /** * @typedef {Object} PeriodRange * @property {ZonedDateTime} from @@ -163,6 +170,35 @@ export const dateToLocalizedString = ( }); }; +/** + * Convert ISO Date to formatted Date String using caller's prefrences, without the time component + * + * @param {Date | string} date The date to convert. + * @param {string} timeZone The time zone to use for conversion, defaults to UTC + * @param {string} lang The language used for string formatting, defaults to 'en' + * @param {string} locale The locale used for string formatting, defaults to 'US' + */ +export const dateToLocalizedDateString = ( + date: Date | string, + timeZone: string, + lang: string, + locale: string +) => { + if (!date) { + return undefined; + } + + const localDate = new Date(date); + const locales = combineLangLocale(lang, locale); + + return localDate.toLocaleString(locales, { + year: 'numeric', + month: 'short', + day: 'numeric', + timeZone: timeZone ? timeZone : fallbackTimeZone + }); +}; + /** * Convert JsJoda LocalDateTime object and HypersyncPeriod to a user-friendly date string * @@ -217,3 +253,68 @@ export const zonedDateTimeToISOString = (zdt: ZonedDateTime) => { const combineLangLocale = (lang: string, locale: string) => { return lang ? (locale ? `${lang}-${locale}` : lang) : fallbackLocale; }; + +/** + * Implements time delay + * @param secondsToWait duration to sleep in seconds + */ +export const sleepDuration = async (secondsToWait: number) => { + await new Promise(res => setTimeout(res, Math.max(secondsToWait * 1000, 0))); +}; + +// build readable string from js-joda Duration object +export const buildAgeString = (age: Duration): string => { + let milliseconds = age.toMillis(); + + const secondsInMs = 1000; + const minutesInMs = secondsInMs * 60; + const hoursInMs = minutesInMs * 60; + const daysInMs = hoursInMs * 24; + const yearsInMs = daysInMs * 365.25; + + const years = Math.floor(milliseconds / yearsInMs); + milliseconds -= years * yearsInMs; + + const days = Math.floor(milliseconds / daysInMs); + milliseconds -= days * daysInMs; + + const hours = Math.floor(milliseconds / hoursInMs); + milliseconds -= hours * hoursInMs; + + const minutes = Math.floor(milliseconds / minutesInMs); + milliseconds -= minutes * minutesInMs; + + const ageYearsString = + years === 0 + ? '' + : formatMessage( + MESSAGES.AgeString.Years, + { years: years.toString() }, + MESSAGE_KEYS + ); + const ageDaysString = + days === 0 + ? '' + : formatMessage( + MESSAGES.AgeString.Days, + { days: days.toString() }, + MESSAGE_KEYS + ); + const ageHoursString = + hours === 0 + ? '' + : formatMessage( + MESSAGES.AgeString.Hours, + { hours: hours.toString() }, + MESSAGE_KEYS + ); + const ageMinutesString = + minutes === 0 + ? '' + : formatMessage( + MESSAGES.AgeString.Minutes, + { minutes: minutes.toString() }, + MESSAGE_KEYS + ); + return `${ageYearsString}${ageDaysString}${ageHoursString}${ageMinutesString}`; +}; diff --git a/src/hypersync/tokens.ts b/src/hypersync/tokens.ts index 80b539e..73c9628 100644 --- a/src/hypersync/tokens.ts +++ b/src/hypersync/tokens.ts @@ -3,7 +3,7 @@ import { DataValue, HypersyncCriteria, HypersyncCriteriaValue -} from '@hyperproof/hypersync-models'; +} from '@hyperproof-int/hypersync-models'; /** * Context object that is used when resolving placeholder tokens in a string. @@ -17,14 +17,6 @@ export type TokenContext = { | TokenContext; }; -export const resolveTokensWithUndefinedDefault = ( - value: string, - context: TokenContext, - suppressErrors = false -): string | undefined => { - return executeResolveTokens(value, context, suppressErrors, undefined); -}; - /** * Replaces tokens found in strings at all levels in a JSON object according * to `executeResolveTokens`. Returns a new object, leaving the original @@ -35,22 +27,25 @@ export const resolveTokensWithUndefinedDefault = ( * * @param value A valid JSON value, potentially containing strings at arbitrary depth with tokens. * @param context Dictionary of values to use in place of tokens. - * @param suppressErrors Whether or not to supress errors when they are detected. + * @param suppressErrors Whether or not to suppress errors when they are detected. Default false * @param missingDefaultValue The value to use when a token resolves to a falsy value */ export const resolveTokens = ( value: T, context: TokenContext, - suppressErrors = false + suppressErrors = false, + undefinedOnMissingValue = false ): T => { return executeResolveTokensInJsonValue( value, context, suppressErrors, - '' + undefinedOnMissingValue ) as T; }; +const tokenRegEx = /\{\{.*?\}\}/g; + /** * Replaces tokens in a string value by looking up the token value * in a provided context object. Tokens are of the form: @@ -58,26 +53,22 @@ export const resolveTokens = ( * * @param value Tokenized string. * @param context Dictionary of values to use in place of tokens. - * @param suppressErrors Whether or not to supress errors when they are detected. - * @param missingDefaultValue The value to use when a token resolves to a falsy value + * @param suppressErrors Whether or not to suppress errors when they are detected. + * @param undefinedOnMissingValue If true, set the value to undefined if the token resolves to a falsy value. Otherwise, defaults to an empty string */ const executeResolveTokens = ( value: string, context: TokenContext, suppressErrors = false, - missingDefaultValue: '' | undefined + undefinedOnMissingValue = false ): string | undefined => { let output: string | undefined = value; - const regEx = /\{\{.*?\}\}/g; - let tokens = output.match(regEx); + let tokens = output.match(tokenRegEx); let foundError = false; + while (tokens && !foundError) { for (const token of tokens) { - const variable = token.substring(2, token.length - 2).trim(); - if (!variable || variable.length === 0) { - throw new Error(`Invalid token: ${token}`); - } - const parts = variable.split('.'); + const parts = splitToken(token); let value: | DataValue | DataObject @@ -117,6 +108,7 @@ const executeResolveTokens = ( } // For multiselect fields, we need to join the values together. + // HYP-47577: We should use a more flexible method of resolving multiselect field values if (Array.isArray(value)) { value = value.join(','); } @@ -138,31 +130,40 @@ const executeResolveTokens = ( } if (value === undefined || value === null) { - output = missingDefaultValue; + output = undefinedOnMissingValue ? undefined : ''; } else { output = output!.replace(token, value.toString()); } } // Resolving tokens may have introduced more tokens. - tokens = output?.match(regEx) ?? null; + tokens = output?.match(tokenRegEx) ?? null; } return output; }; +const splitToken = (token: string): string[] => { + const variable = token.substring(2, token.length - 2).trim(); + if (!variable || variable.length === 0) { + throw new Error(`Invalid token: ${token}`); + } + const parts = variable.split('.'); + return parts; +}; + const executeResolveTokensInJsonValue = ( value: object | string, context: TokenContext, suppressErrors = false, - missingDefaultValue: '' | undefined + undefinedOnMissingValue = false ): object | string | undefined => { if (typeof value === 'string') { return executeResolveTokens( value, context, suppressErrors, - missingDefaultValue + undefinedOnMissingValue ); } else { const out = JSON.parse(JSON.stringify(value)); // deep clone the original object @@ -170,7 +171,7 @@ const executeResolveTokensInJsonValue = ( out, context, suppressErrors, - missingDefaultValue + undefinedOnMissingValue ); return out; } @@ -180,7 +181,7 @@ const innerResolveTokensInJsonValue = ( value: { [key: string]: any }, context: TokenContext, suppressErrors = false, - missingDefaultValue: '' | undefined + undefinedOnMissingValue = false ) => { if (value === null) { return; @@ -191,14 +192,14 @@ const innerResolveTokensInJsonValue = ( value[key], context, suppressErrors, - missingDefaultValue + undefinedOnMissingValue ); } else if (typeof value[key] === 'object') { innerResolveTokensInJsonValue( value[key], context, suppressErrors, - missingDefaultValue + undefinedOnMissingValue ); } } diff --git a/src/schema-proof/UarApplicationProofProvider.ts b/src/schema-proof/UarApplicationProofProvider.ts index 5141e77..2da6f5b 100644 --- a/src/schema-proof/UarApplicationProofProvider.ts +++ b/src/schema-proof/UarApplicationProofProvider.ts @@ -5,8 +5,8 @@ import { HypersyncDataFormat, HypersyncFieldType, SchemaCategory -} from '@hyperproof/hypersync-models'; -import { IHyperproofUser } from '@hyperproof/integration-sdk'; +} from '@hyperproof-int/hypersync-models'; +import { ILocalizable } from '@hyperproof-int/integration-sdk'; import { date, InferType, object, string } from 'yup'; import { DataSourceBase } from '../hypersync'; @@ -58,7 +58,7 @@ export abstract class UarApplicationProofProvider< public async getProofData( hypersync: IHypersync, - hyperproofUser: IHyperproofUser, + organization: ILocalizable, authorizedUser: string, syncStartDate: Date ): Promise { @@ -80,7 +80,7 @@ export abstract class UarApplicationProofProvider< type: process.env.integration_type!, title: MESSAGES.LabelAccessReview, subtitle: MESSAGES.LabelAccessReview, - userTimeZone: hyperproofUser.timeZone, + userTimeZone: organization.timeZone, criteria: [], proofFormat: hypersync.settings.proofFormat, template: HypersyncTemplate.UNIVERSAL, @@ -90,9 +90,9 @@ export abstract class UarApplicationProofProvider< collector: this.connectorName, collectedOn: dateToLocalizedString( syncStartDate, - hyperproofUser.timeZone, - hyperproofUser.language, - hyperproofUser.locale + organization.timeZone, + organization.language, + organization.locale )! } } diff --git a/src/schema-proof/UarDirectoryProofProvider.ts b/src/schema-proof/UarDirectoryProofProvider.ts index d3dc8a2..f9e8cc3 100644 --- a/src/schema-proof/UarDirectoryProofProvider.ts +++ b/src/schema-proof/UarDirectoryProofProvider.ts @@ -5,8 +5,8 @@ import { HypersyncDataFormat, HypersyncFieldType, SchemaCategory -} from '@hyperproof/hypersync-models'; -import { IHyperproofUser } from '@hyperproof/integration-sdk'; +} from '@hyperproof-int/hypersync-models'; +import { ILocalizable } from '@hyperproof-int/integration-sdk'; import { date, InferType, object, string } from 'yup'; import { DataSourceBase } from '../hypersync/DataSourceBase'; @@ -57,7 +57,7 @@ export abstract class UarDirectoryProofProvider extends ProofProviderBase { public async getProofData( hypersync: IHypersync, - hyperproofUser: IHyperproofUser, + organization: ILocalizable, authorizedUser: string, syncStartDate: Date ): Promise { @@ -79,7 +79,7 @@ export abstract class UarDirectoryProofProvider extends ProofProviderBase { type: process.env.integration_type!, title: MESSAGES.LabelAccessReview, subtitle: MESSAGES.LabelAccessReview, - userTimeZone: hyperproofUser.timeZone, + userTimeZone: organization.timeZone, criteria: [], proofFormat: hypersync.settings.proofFormat, template: HypersyncTemplate.UNIVERSAL, @@ -89,9 +89,9 @@ export abstract class UarDirectoryProofProvider extends ProofProviderBase { collector: this.connectorName, collectedOn: dateToLocalizedString( syncStartDate, - hyperproofUser.timeZone, - hyperproofUser.language, - hyperproofUser.locale + organization.timeZone, + organization.language, + organization.locale )! } } diff --git a/src/schema-proof/common.ts b/src/schema-proof/common.ts index 8d294ed..73979aa 100644 --- a/src/schema-proof/common.ts +++ b/src/schema-proof/common.ts @@ -1,7 +1,7 @@ import { uarApplicationSchema } from './UarApplicationProofProvider'; import { uarDirectorySchema } from './UarDirectoryProofProvider'; -import { DataObject, SchemaCategory } from '@hyperproof/hypersync-models'; +import { DataObject, SchemaCategory } from '@hyperproof-int/hypersync-models'; import { IHypersync } from '../hypersync'; From a34a2a7b042e945b352a82b9b1b3d35a073a4e5e Mon Sep 17 00:00:00 2001 From: "trent.hashimoto" Date: Fri, 9 Jan 2026 14:55:07 -0800 Subject: [PATCH 02/17] README and package update --- README.md | 11 +++++++++++ package.json | 18 +++++++++--------- src/hypersync/DataSourceBase.ts | 4 ++-- src/hypersync/HypersyncApp.ts | 4 ++-- src/hypersync/ICriteriaProvider.ts | 4 ++-- src/hypersync/IDataSource.ts | 4 ++-- src/hypersync/JsonCriteriaProvider.ts | 4 ++-- src/hypersync/JsonProofProvider.ts | 4 ++-- src/hypersync/Paginator.ts | 2 +- src/hypersync/ProofProviderBase.ts | 4 ++-- src/hypersync/ProofProviderFactory.ts | 4 ++-- src/hypersync/RestDataSourceBase.ts | 4 ++-- src/hypersync/ServiceDataIterator.ts | 4 ++-- src/hypersync/Sync.ts | 2 +- src/hypersync/common.ts | 2 +- src/hypersync/hypersyncConnector.ts | 4 ++-- src/hypersync/layout.ts | 2 +- src/hypersync/models.ts | 4 ++-- src/hypersync/time.ts | 2 +- src/hypersync/tokens.ts | 2 +- .../UarApplicationProofProvider.ts | 4 ++-- src/schema-proof/UarDirectoryProofProvider.ts | 4 ++-- src/schema-proof/common.ts | 2 +- 23 files changed, 55 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index e879a3e..53cdec3 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,17 @@ To get started with the Hypersync SDK hop on over to the [SDK documentation](doc ## Release Notes +### 4.0.0 + +- **Breaking:** Updated to Node.js 22 +- **Breaking:** Updated to `@hyperproof/hypersync-models` 6.0.0 and `@hyperproof/integration-sdk` 2.0.0 +- Added `ServiceDataIterator` class for iterative data fetching with support for dataset and criteria-sourced iteration +- Enhanced `JsonProofProvider` with iterator support for generating proofs from iterable data +- Improved `JsonCriteriaProvider` with saved criteria settings and search input handling +- Enhanced `RestDataSourceBase` with improved pagination and error handling +- Added `continueOnError` support in data set lookups +- Plus other bug fixes and performance enhancements + ### 3.2.0 - Integrate latest Integration SDK which contains reliability improvements and bug fixes. diff --git a/package.json b/package.json index 83cacce..5250141 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hyperproof/hypersync-sdk", - "version": "3.2.0", + "version": "4.0.0", "description": "Hypersync SDK", "license": "MIT", "repository": { @@ -10,24 +10,25 @@ "main": "lib/index.js", "types": "lib/index.d.ts", "scripts": { - "build": "tsc && copyfiles -u 1 \"src/**/*.d.ts\" lib", + "build": "tsc", "lint": "./node_modules/eslint/bin/eslint.js src/**/*.ts" }, "engines": { - "node": "^16.19.1 || ^18.17.1" + "node": "22" }, "dependencies": { - "@hyperproof/hypersync-models": "5.1.0", - "@hyperproof/integration-sdk": "1.2.0", + "@hyperproof/hypersync-models": "6.0.0", + "@hyperproof/integration-sdk": "2.0.0", "@js-joda/core": "3.2.0", "@js-joda/timezone": "2.5.0", "abort-controller": "3.0.0", - "express": "4.21.0", + "express": "4.21.2", "html-entities": "2.5.2", "http-errors": "2.0.0", "http-status-codes": "2.3.0", "jsonata": "1.8.7", "jsonwebtoken": "9.0.2", + "lodash": "4.17.21", "mime": "3.0.0", "node-fetch": "2.7.0", "query-string": "7.1.3", @@ -40,13 +41,12 @@ "@types/express": "^4.17.21", "@types/http-errors": "^2.0.0", "@types/jsonwebtoken": "^9.0.0", + "@types/lodash": "^4.14.202", "@types/mime": "^3.0.4", - "@types/node": "^22.7.5", - "@types/node-fetch": "^2.6.11", + "@types/node-fetch": "^2.6.13", "@types/superagent": "^8.1.9", "@typescript-eslint/eslint-plugin": "8.7.0", "@typescript-eslint/parser": "8.7.0", - "copyfiles": "^2.4.1", "eslint": "8.57.1", "eslint-config-prettier": "^8.5.0", "prettier": "^2.6.1", diff --git a/src/hypersync/DataSourceBase.ts b/src/hypersync/DataSourceBase.ts index 64023ba..0541a51 100644 --- a/src/hypersync/DataSourceBase.ts +++ b/src/hypersync/DataSourceBase.ts @@ -6,8 +6,8 @@ import { SyncMetadata } from './IDataSource'; -import { DataObject, DataValueMap } from '@hyperproof-int/hypersync-models'; -import { ILocalizable } from '@hyperproof-int/integration-sdk'; +import { DataObject, DataValueMap } from '@hyperproof/hypersync-models'; +import { ILocalizable } from '@hyperproof/integration-sdk'; /** * Abstract base class for a data source object. Provides convenient diff --git a/src/hypersync/HypersyncApp.ts b/src/hypersync/HypersyncApp.ts index 2c7a624..ef2e57c 100644 --- a/src/hypersync/HypersyncApp.ts +++ b/src/hypersync/HypersyncApp.ts @@ -35,7 +35,7 @@ import { IHypersyncDefinition, SchemaCategory, ValueLookup -} from '@hyperproof-int/hypersync-models'; +} from '@hyperproof/hypersync-models'; import { AuthorizationType, createApp, @@ -56,7 +56,7 @@ import { OAuthTokenResponse, ObjectType, UserContext -} from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/integration-sdk'; import express from 'express'; import fs from 'fs'; import createHttpError from 'http-errors'; diff --git a/src/hypersync/ICriteriaProvider.ts b/src/hypersync/ICriteriaProvider.ts index bb3c205..ca69ce2 100644 --- a/src/hypersync/ICriteriaProvider.ts +++ b/src/hypersync/ICriteriaProvider.ts @@ -11,8 +11,8 @@ import { ISelectOption, IValidation, SchemaCategory -} from '@hyperproof-int/hypersync-models'; -import { CriteriaPageMessageLevel } from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/hypersync-models'; +import { CriteriaPageMessageLevel } from '@hyperproof/integration-sdk'; /** * Information needed to render a criteria field used in the configuration diff --git a/src/hypersync/IDataSource.ts b/src/hypersync/IDataSource.ts index 789be4e..1c983c9 100644 --- a/src/hypersync/IDataSource.ts +++ b/src/hypersync/IDataSource.ts @@ -2,8 +2,8 @@ import { IErrorInfo } from './models'; import { RestDataSourceBase } from './RestDataSourceBase'; import { TokenContext } from './tokens'; -import { DataObject, DataValueMap } from '@hyperproof-int/hypersync-models'; -import { ILocalizable } from '@hyperproof-int/integration-sdk'; +import { DataObject, DataValueMap } from '@hyperproof/hypersync-models'; +import { ILocalizable } from '@hyperproof/integration-sdk'; export enum DataSetResultStatus { Complete = 'complete', diff --git a/src/hypersync/JsonCriteriaProvider.ts b/src/hypersync/JsonCriteriaProvider.ts index bb6e352..d481e59 100644 --- a/src/hypersync/JsonCriteriaProvider.ts +++ b/src/hypersync/JsonCriteriaProvider.ts @@ -16,8 +16,8 @@ import { ICriteriaSearchInput, IProofCriterionRef, ISelectOption -} from '@hyperproof-int/hypersync-models'; -import { compareValues, Logger } from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/hypersync-models'; +import { compareValues, Logger } from '@hyperproof/integration-sdk'; import fs from 'fs'; import path from 'path'; diff --git a/src/hypersync/JsonProofProvider.ts b/src/hypersync/JsonProofProvider.ts index 51f9ae1..047e19c 100644 --- a/src/hypersync/JsonProofProvider.ts +++ b/src/hypersync/JsonProofProvider.ts @@ -32,8 +32,8 @@ import { IHypersyncField, IProofSpec, IteratorSource -} from '@hyperproof-int/hypersync-models'; -import { ILocalizable, Logger } from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/hypersync-models'; +import { ILocalizable, Logger } from '@hyperproof/integration-sdk'; import createHttpError from 'http-errors'; import { StatusCodes } from 'http-status-codes'; diff --git a/src/hypersync/Paginator.ts b/src/hypersync/Paginator.ts index f636078..170a67f 100644 --- a/src/hypersync/Paginator.ts +++ b/src/hypersync/Paginator.ts @@ -13,7 +13,7 @@ import { PageUntilCondition, PagingScheme, PagingType -} from '@hyperproof-int/hypersync-models'; +} from '@hyperproof/hypersync-models'; import jsonata from 'jsonata'; import set from 'lodash/set'; diff --git a/src/hypersync/ProofProviderBase.ts b/src/hypersync/ProofProviderBase.ts index ca9d480..ea8091f 100644 --- a/src/hypersync/ProofProviderBase.ts +++ b/src/hypersync/ProofProviderBase.ts @@ -19,8 +19,8 @@ import { HypersyncFieldType, HypersyncPageOrientation, SchemaCategory -} from '@hyperproof-int/hypersync-models'; -import { ILocalizable } from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/hypersync-models'; +import { ILocalizable } from '@hyperproof/integration-sdk'; import createHttpError from 'http-errors'; import { StatusCodes } from 'http-status-codes'; diff --git a/src/hypersync/ProofProviderFactory.ts b/src/hypersync/ProofProviderFactory.ts index 8fbdd02..98af817 100644 --- a/src/hypersync/ProofProviderFactory.ts +++ b/src/hypersync/ProofProviderFactory.ts @@ -10,8 +10,8 @@ import { IProofType, IProofTypeMap, SchemaCategory -} from '@hyperproof-int/hypersync-models'; -import { compareValues } from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/hypersync-models'; +import { compareValues } from '@hyperproof/integration-sdk'; import fs from 'fs'; import createHttpError from 'http-errors'; import { StatusCodes } from 'http-status-codes'; diff --git a/src/hypersync/RestDataSourceBase.ts b/src/hypersync/RestDataSourceBase.ts index ed92284..190d0dc 100644 --- a/src/hypersync/RestDataSourceBase.ts +++ b/src/hypersync/RestDataSourceBase.ts @@ -27,14 +27,14 @@ import { PagingLevel, Transform, ValueLookup -} from '@hyperproof-int/hypersync-models'; +} from '@hyperproof/hypersync-models'; import { ApiClient, compareValues, IApiClientResponse, ILocalizable, Logger -} from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/integration-sdk'; import createHttpError from 'http-errors'; import { StatusCodes } from 'http-status-codes'; import jsonata from 'jsonata'; diff --git a/src/hypersync/ServiceDataIterator.ts b/src/hypersync/ServiceDataIterator.ts index 1ea96a9..37b3fd5 100644 --- a/src/hypersync/ServiceDataIterator.ts +++ b/src/hypersync/ServiceDataIterator.ts @@ -21,8 +21,8 @@ import { DataValueMap, IProofSpec, IteratorSource -} from '@hyperproof-int/hypersync-models'; -import { ILocalizable } from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/hypersync-models'; +import { ILocalizable } from '@hyperproof/integration-sdk'; export interface IIteratorPlanComplete { status: DataSetResultStatus.Complete; diff --git a/src/hypersync/Sync.ts b/src/hypersync/Sync.ts index c550691..39e6f7c 100644 --- a/src/hypersync/Sync.ts +++ b/src/hypersync/Sync.ts @@ -7,7 +7,7 @@ import { IProofFile } from './ProofProviderBase'; -import { ILocalizable } from '@hyperproof-int/integration-sdk'; +import { ILocalizable } from '@hyperproof/integration-sdk'; /** * Detailed response object that may be returned from getProofData. diff --git a/src/hypersync/common.ts b/src/hypersync/common.ts index 60d8055..4897b4d 100644 --- a/src/hypersync/common.ts +++ b/src/hypersync/common.ts @@ -7,7 +7,7 @@ import { import { HypersyncFieldType, IHypersyncField -} from '@hyperproof-int/hypersync-models'; +} from '@hyperproof/hypersync-models'; import createHttpError from 'http-errors'; import { StatusCodes } from 'http-status-codes'; import queryString from 'query-string'; diff --git a/src/hypersync/hypersyncConnector.ts b/src/hypersync/hypersyncConnector.ts index b6279b9..544a5c8 100644 --- a/src/hypersync/hypersyncConnector.ts +++ b/src/hypersync/hypersyncConnector.ts @@ -13,7 +13,7 @@ import { HypersyncCriteria, ICriteriaSearchInput, SchemaCategory -} from '@hyperproof-int/hypersync-models'; +} from '@hyperproof/hypersync-models'; import { createConnector, CustomAuthCredentials, @@ -33,7 +33,7 @@ import { ObjectType, StorageItem, UserContext -} from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/integration-sdk'; import express from 'express'; import fs from 'fs'; import createHttpError from 'http-errors'; diff --git a/src/hypersync/layout.ts b/src/hypersync/layout.ts index e82ae2e..d872e65 100644 --- a/src/hypersync/layout.ts +++ b/src/hypersync/layout.ts @@ -1,6 +1,6 @@ import { IHypersyncProofField } from './ProofProviderBase'; -import { HypersyncPageOrientation } from '@hyperproof-int/hypersync-models'; +import { HypersyncPageOrientation } from '@hyperproof/hypersync-models'; const DEFAULT_A4_WIDTH_PIXELS = 794; const DEFAULT_A4_LENGTH_PIXELS = 1123; diff --git a/src/hypersync/models.ts b/src/hypersync/models.ts index dacc656..94a44dc 100644 --- a/src/hypersync/models.ts +++ b/src/hypersync/models.ts @@ -5,12 +5,12 @@ import { HypersyncCriteria, HypersyncPeriod, SchemaCategory -} from '@hyperproof-int/hypersync-models'; +} from '@hyperproof/hypersync-models'; import { IIntegration, IIntegrationSettingsBase, IntegrationSettingsClass -} from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/integration-sdk'; /** * Settings that are saved with a Hypersync integration. diff --git a/src/hypersync/time.ts b/src/hypersync/time.ts index 6d13734..d7e7d95 100644 --- a/src/hypersync/time.ts +++ b/src/hypersync/time.ts @@ -1,7 +1,7 @@ import '@js-joda/timezone'; import { formatMessage, MESSAGES } from './messages'; -import { HypersyncPeriod } from '@hyperproof-int/hypersync-models'; +import { HypersyncPeriod } from '@hyperproof/hypersync-models'; import { ChronoUnit, convert, diff --git a/src/hypersync/tokens.ts b/src/hypersync/tokens.ts index 73c9628..ba2199a 100644 --- a/src/hypersync/tokens.ts +++ b/src/hypersync/tokens.ts @@ -3,7 +3,7 @@ import { DataValue, HypersyncCriteria, HypersyncCriteriaValue -} from '@hyperproof-int/hypersync-models'; +} from '@hyperproof/hypersync-models'; /** * Context object that is used when resolving placeholder tokens in a string. diff --git a/src/schema-proof/UarApplicationProofProvider.ts b/src/schema-proof/UarApplicationProofProvider.ts index 2da6f5b..9b66ac5 100644 --- a/src/schema-proof/UarApplicationProofProvider.ts +++ b/src/schema-proof/UarApplicationProofProvider.ts @@ -5,8 +5,8 @@ import { HypersyncDataFormat, HypersyncFieldType, SchemaCategory -} from '@hyperproof-int/hypersync-models'; -import { ILocalizable } from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/hypersync-models'; +import { ILocalizable } from '@hyperproof/integration-sdk'; import { date, InferType, object, string } from 'yup'; import { DataSourceBase } from '../hypersync'; diff --git a/src/schema-proof/UarDirectoryProofProvider.ts b/src/schema-proof/UarDirectoryProofProvider.ts index f9e8cc3..edc7d0b 100644 --- a/src/schema-proof/UarDirectoryProofProvider.ts +++ b/src/schema-proof/UarDirectoryProofProvider.ts @@ -5,8 +5,8 @@ import { HypersyncDataFormat, HypersyncFieldType, SchemaCategory -} from '@hyperproof-int/hypersync-models'; -import { ILocalizable } from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/hypersync-models'; +import { ILocalizable } from '@hyperproof/integration-sdk'; import { date, InferType, object, string } from 'yup'; import { DataSourceBase } from '../hypersync/DataSourceBase'; diff --git a/src/schema-proof/common.ts b/src/schema-proof/common.ts index 73979aa..8d294ed 100644 --- a/src/schema-proof/common.ts +++ b/src/schema-proof/common.ts @@ -1,7 +1,7 @@ import { uarApplicationSchema } from './UarApplicationProofProvider'; import { uarDirectorySchema } from './UarDirectoryProofProvider'; -import { DataObject, SchemaCategory } from '@hyperproof-int/hypersync-models'; +import { DataObject, SchemaCategory } from '@hyperproof/hypersync-models'; import { IHypersync } from '../hypersync'; From 6118afd491f3f5a9baa2fcf7b4d447828f7fe76c Mon Sep 17 00:00:00 2001 From: Trent Hashimoto Date: Fri, 9 Jan 2026 15:10:24 -0800 Subject: [PATCH 03/17] Mention ILocalizable change --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 53cdec3..2a5073c 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ To get started with the Hypersync SDK hop on over to the [SDK documentation](doc - **Breaking:** Updated to Node.js 22 - **Breaking:** Updated to `@hyperproof/hypersync-models` 6.0.0 and `@hyperproof/integration-sdk` 2.0.0 +- Replaced `hyperproofUser` with `organization` object of type `ILocalizable` that can be used for date localization - Added `ServiceDataIterator` class for iterative data fetching with support for dataset and criteria-sourced iteration - Enhanced `JsonProofProvider` with iterator support for generating proofs from iterable data - Improved `JsonCriteriaProvider` with saved criteria settings and search input handling From ac2b64acaf58aa6cc4a232de22fed2d826e46f06 Mon Sep 17 00:00:00 2001 From: "trent.hashimoto" Date: Fri, 9 Jan 2026 17:06:41 -0800 Subject: [PATCH 04/17] Sync from integrations monorepo --- .github/workflows/build.yml | 26 + doc/001-getting-started.md | 1 - doc/002-dev-workflow.md | 22 +- doc/004-connections.md | 5 +- doc/005-data-sources.md | 22 +- doc/006-proof-types.md | 20 +- doc/030-package-json-reference.md | 3 +- doc/052-data-source-json.md | 13 +- doc/054-proof-types-json.md | 2 +- doc/migration.md | 2 +- schema/proofType.schema.json | 65 +- schema/restDataSource.schema.json | 358 ++-- src/hypersync/DataSourceBase.ts | 4 +- src/hypersync/HypersyncApp.test.ts | 915 ++++++++++ src/hypersync/HypersyncApp.ts | 4 +- src/hypersync/ICriteriaProvider.ts | 4 +- src/hypersync/IDataSource.ts | 4 +- src/hypersync/JsonCriteriaProvider.test.ts | 1362 ++++++++++++++ src/hypersync/JsonCriteriaProvider.ts | 4 +- src/hypersync/JsonProofProvider.test.ts | 1580 +++++++++++++++++ src/hypersync/JsonProofProvider.ts | 4 +- src/hypersync/Paginator.test.ts | 753 ++++++++ src/hypersync/Paginator.ts | 2 +- src/hypersync/ProofProviderBase.ts | 4 +- src/hypersync/ProofProviderFactory.ts | 4 +- src/hypersync/RestDataSourceBase.test.ts | 575 ++++++ src/hypersync/RestDataSourceBase.ts | 4 +- src/hypersync/ServiceDataIterator.test.ts | 93 + src/hypersync/ServiceDataIterator.ts | 4 +- src/hypersync/Sync.ts | 2 +- src/hypersync/common.test.ts | 151 ++ src/hypersync/common.ts | 2 +- src/hypersync/hypersyncConnector.ts | 4 +- src/hypersync/layout.ts | 2 +- src/hypersync/models.ts | 4 +- src/hypersync/time.ts | 2 +- src/hypersync/tokens.ts | 2 +- .../UarApplicationProofProvider.ts | 4 +- src/schema-proof/UarDirectoryProofProvider.ts | 4 +- src/schema-proof/common.ts | 2 +- 40 files changed, 5738 insertions(+), 300 deletions(-) create mode 100644 .github/workflows/build.yml create mode 100644 src/hypersync/HypersyncApp.test.ts create mode 100644 src/hypersync/JsonCriteriaProvider.test.ts create mode 100644 src/hypersync/JsonProofProvider.test.ts create mode 100644 src/hypersync/Paginator.test.ts create mode 100644 src/hypersync/RestDataSourceBase.test.ts create mode 100644 src/hypersync/ServiceDataIterator.test.ts create mode 100644 src/hypersync/common.test.ts diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..e197add --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,26 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '22' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Build + run: npm run build diff --git a/doc/001-getting-started.md b/doc/001-getting-started.md index a593d41..8291854 100644 --- a/doc/001-getting-started.md +++ b/doc/001-getting-started.md @@ -30,7 +30,6 @@ Finally, we recommend cloning the [Hypersync SDK Samples GitHub repository](http For development purposes it is recommended to have a separate 'sandbox' organization in Hyperproof. This will allow you to develop and test your custom Hypersync apps in isolation without affecting the users in your production Hyperproof organization. To create a 'sandbox' organization, please contact your Hyperproof Customer Service Manager. - ## Next Steps Once you have installed the prerequisites and determined the organization that you will use for development, you are ready to install your first custom Hypersync app. Follow the guidance in [App Development Workflow](./002-dev-workflow.md) to install, test and update one of the pre-built sample apps. diff --git a/doc/002-dev-workflow.md b/doc/002-dev-workflow.md index 24b0835..58a1993 100644 --- a/doc/002-dev-workflow.md +++ b/doc/002-dev-workflow.md @@ -15,16 +15,20 @@ After installing the CLI, you will first need to sign in using this command: ``` hp signin ``` -> NOTE: For customers using Hyperproof EU or Hyperproof GOV, please use the following commands: + +> NOTE: For customers using Hyperproof EU or Hyperproof GOV, please use the following commands: +> +> ``` +> hp signin --domain hyperproof.eu +> ``` +> +> or +> +> ``` +> hp signin --domain hyperproofgov.app +> ``` > ->``` ->hp signin --domain hyperproof.eu ->``` ->or ->``` ->hp signin --domain hyperproofgov.app ->``` ->For additional information use 'hp signin -help' +> For additional information use 'hp signin -help' This command will launch a new browser window and allow you to sign in (if you are not already signed in) and then authorize CLI access to your Hyperproof organization. diff --git a/doc/004-connections.md b/doc/004-connections.md index a6ecaa3..dbed46d 100644 --- a/doc/004-connections.md +++ b/doc/004-connections.md @@ -24,8 +24,9 @@ In a Hypersync app, there are two types connections: [OAuth connections](#oauth- > a [custom authentication connection](#custom-authentication) should be used. > **NOTE: API Permissions** ->- Always use the minimum permissions necessary for proofs. Hypersyncs are read-only so use read-only permissions when possible. ->- Use minimum scopes when using OAuth. The Hypersync author is going to be asked to authorize one or more scopes. You only want to ask for what you need. +> +> - Always use the minimum permissions necessary for proofs. Hypersyncs are read-only so use read-only permissions when possible. +> - Use minimum scopes when using OAuth. The Hypersync author is going to be asked to authorize one or more scopes. You only want to ask for what you need. ## OAuth Authorization diff --git a/doc/005-data-sources.md b/doc/005-data-sources.md index c3814a9..e5ecf4e 100644 --- a/doc/005-data-sources.md +++ b/doc/005-data-sources.md @@ -6,7 +6,7 @@ Data sources can retrieve different data sets from the external service. Each da Data set names are used in other components like proof types to identify the specific data element or elements needed by that component. -> **NOTE**: Data sets can and *should* be re-used across multiple proof types, and can also be used in non-proof scenarios such as collecting user data during the `validateCredentials` process. +> **NOTE**: Data sets can and _should_ be re-used across multiple proof types, and can also be used in non-proof scenarios such as collecting user data during the `validateCredentials` process. ## REST Data Sources @@ -66,9 +66,9 @@ For more information on configuring the data sets in your data source using `dat Many REST APIs use a paging mechanism to allow data to be retrieved in chunks. For example, some APIs take a `pageSize` and `pageNumber` argument which specify how many items to return, and the page number to start reading from, respectively. -Four paging styles are supported: __Page Based__, __Offset And Limit__, __Next Token__, and __GraphQL Connections__. As a default, query string parameters will be programatically added to an API url. If POST is designated as the data source HTTP method, paging parameters are added to the body of the request. +Four paging styles are supported: **Page Based**, **Offset And Limit**, **Next Token**, and **GraphQL Connections**. As a default, query string parameters will be programatically added to an API url. If POST is designated as the data source HTTP method, paging parameters are added to the body of the request. -1. __Page Based.__ Begin paging at a starting value and increment the page value by 1 after each iteration (1, 2, 3, etc). Return at most `limitValue` items per page. +1. **Page Based.** Begin paging at a starting value and increment the page value by 1 after each iteration (1, 2, 3, etc). Return at most `limitValue` items per page. ```json "pagingScheme": { @@ -83,9 +83,9 @@ Four paging styles are supported: __Page Based__, __Offset And Limit__, __Next T } ``` -The mandatory `request` property in the paging scheme constructs the paged query string. The query string of the first API call from the above example will be: `?pageNumber=1&pageSize=100`. Each paging scheme must include a `pageUntil` property which defines the point at which pagination stops. If `reachTotalCount` condition is applied, `totalCount` must be defined in the response object, which represents the path to the total combined number of items in the data returned from the external service.* +The mandatory `request` property in the paging scheme constructs the paged query string. The query string of the first API call from the above example will be: `?pageNumber=1&pageSize=100`. Each paging scheme must include a `pageUntil` property which defines the point at which pagination stops. If `reachTotalCount` condition is applied, `totalCount` must be defined in the response object, which represents the path to the total combined number of items in the data returned from the external service.\* -2. __Offset And Limit.__ Begin paging at a starting value and increment the offset by the number of elements in a full page (0, 100, 200, 300, etc). Return at most `limitValue` items per page. +2. **Offset And Limit.** Begin paging at a starting value and increment the offset by the number of elements in a full page (0, 100, 200, 300, etc). Return at most `limitValue` items per page. ```json "pagingScheme": { @@ -103,9 +103,9 @@ The mandatory `request` property in the paging scheme constructs the paged query } ``` -The mandatory `request` property in the paging scheme constructs the paged query string. The query string of the first API call from the above example will be: `?offset=0&limit=100`. Each paging scheme must include a `pageUntil` property which defines the point at which pagination stops. If `reachTotalCount` condition is applied, `totalCount` must be defined in the response object. This string value represents the path to the total combined number of items in the data returned from the external service.* +The mandatory `request` property in the paging scheme constructs the paged query string. The query string of the first API call from the above example will be: `?offset=0&limit=100`. Each paging scheme must include a `pageUntil` property which defines the point at which pagination stops. If `reachTotalCount` condition is applied, `totalCount` must be defined in the response object. This string value represents the path to the total combined number of items in the data returned from the external service.\* -3. __Next Token.__ Begin paging and continue until `nextToken` is no longer provided. Return at most `limitValue` items per page. Tokens may be a unique string returned from the external service or a url. +3. **Next Token.** Begin paging and continue until `nextToken` is no longer provided. Return at most `limitValue` items per page. Tokens may be a unique string returned from the external service or a url. ```json "pagingScheme": { @@ -123,9 +123,9 @@ The mandatory `request` property in the paging scheme constructs the paged query } ``` -The mandatory `request` property in the paging scheme constructs the paged query string. The query string of the first API call from the above example will be: `?size=20`. Each successive call will be structured in the pattern: `?size=20&token=891b629672384d04`. Each paging scheme must include a `pageUntil` property which defines the point at which pagination stops. When `noNextToken` condition is applied, `nextToken` must be included in the response object. This string value represents the path to the expected value in the data returned from the external service.* +The mandatory `request` property in the paging scheme constructs the paged query string. The query string of the first API call from the above example will be: `?size=20`. Each successive call will be structured in the pattern: `?size=20&token=891b629672384d04`. Each paging scheme must include a `pageUntil` property which defines the point at which pagination stops. When `noNextToken` condition is applied, `nextToken` must be included in the response object. This string value represents the path to the expected value in the data returned from the external service.\* -4. __GraphQL Connections.__ Following the GraphQL [Connections](https://graphql.org/learn/pagination/#connection-specification) specification, continue paging until `hasNextPage` is false. Return at most `limitValue` items per page. Supports forward, non-nested pagination. +4. **GraphQL Connections.** Following the GraphQL [Connections](https://graphql.org/learn/pagination/#connection-specification) specification, continue paging until `hasNextPage` is false. Return at most `limitValue` items per page. Supports forward, non-nested pagination. ```json "body": { @@ -149,9 +149,9 @@ The mandatory `request` property in the paging scheme constructs the paged query } ``` -The paging scheme dynamically adds the `first` and `after` variables to the body of a request. The `after` variable is defined using the `endCursor` string from the preceding response. `pageInfo` must be included in the paging scheme response object. This string value represents the path to the `pageInfo` object in the data returned from the external service. +The paging scheme dynamically adds the `first` and `after` variables to the body of a request. The `after` variable is defined using the `endCursor` string from the preceding response. `pageInfo` must be included in the paging scheme response object. This string value represents the path to the `pageInfo` object in the data returned from the external service. -*If values are to be found in the response header, apply the `header:` prefix. +\*If values are to be found in the response header, apply the `header:` prefix. ## Custom Data Sources diff --git a/doc/006-proof-types.md b/doc/006-proof-types.md index 85d0dc1..67669e2 100644 --- a/doc/006-proof-types.md +++ b/doc/006-proof-types.md @@ -12,15 +12,17 @@ Once the user selects a proof type, they may be asked for one or more criteria v Proof types retrieve data from the app's [data source](./005-data-sources.md) by name. The proof type may also provide parameter values to the data source to ensure that the right data is returned. -> **IMPORTANT: Planning Your Proof Types and Fields** ->>- Define different kinds of proof - Users, Groups, etc... ->>- Define key fields of information needed in each proof type. Make sure your API calls can return these vital elements. ->>- Define how to identify the right resource(s) - This information can be unique to each proof type and will be needed to configure `criteriafields.json` You’ll want to ask the user for these when they author the Hypersync. For example if you want to get configuration information from Azure you might need to know the tenant and subscription and resource group. ->> -> **Tips for finding this info**: ->>- Look at console pages and dashboards. They usually contain all key information by default and are a great starting point. ->>- Look at other similar proof types in other apps. ->>- Always include proof for users and their permissions. See User Access Reviews proof for more information. +> **IMPORTANT: Planning Your Proof Types and Fields** +> +> > - Define different kinds of proof - Users, Groups, etc... +> > - Define key fields of information needed in each proof type. Make sure your API calls can return these vital elements. +> > - Define how to identify the right resource(s) - This information can be unique to each proof type and will be needed to configure `criteriafields.json` You’ll want to ask the user for these when they author the Hypersync. For example if you want to get configuration information from Azure you might need to know the tenant and subscription and resource group. +> > +> > **Tips for finding this info**: +> > +> > - Look at console pages and dashboards. They usually contain all key information by default and are a great starting point. +> > - Look at other similar proof types in other apps. +> > - Always include proof for users and their permissions. See User Access Reviews proof for more information. ### Layouts diff --git a/doc/030-package-json-reference.md b/doc/030-package-json-reference.md index 614bdef..cfdea23 100644 --- a/doc/030-package-json-reference.md +++ b/doc/030-package-json-reference.md @@ -23,6 +23,7 @@ The `package.json` file for a custom Hypersync app must contain the values shown ... } ``` + NOTE: `schemaCategories` array is optional and only required for Hypersyncs that need to utilize the Access Review module. NOTE: When attempting to upgrade existing Hypersyncs to the `schemaCategories` for Access Review proofs, you must first delete the existing Hypersync using the CLI command `hp customapps delete`. This ensures that the new `schemaCategories` definition is added to your Hypersync. @@ -48,7 +49,7 @@ More information on the version attribute can be found [here](https://docs.npmjs The `app_hyperproof` value in `package.json` is an object that contains values that Hyperproof uses to properly expose and run your custom Hypersync app. The object must contain the values in the table below. | Attribute | Description | -| ----------------------- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | name | The name of your application as shown in the Hyperproof UI. | | appType | The type of custom app. Must be set to `hypersync`. | | authType | The type of authentication/authorization used by your application. Acceptable values are `custom` or `oauth`. | diff --git a/doc/052-data-source-json.md b/doc/052-data-source-json.md index e3a22cb..c160917 100644 --- a/doc/052-data-source-json.md +++ b/doc/052-data-source-json.md @@ -14,7 +14,7 @@ If the APIs in your data sets do not share a common root, omit the `baseUrl` pro A "data set" is a named object used to identify a data object or a data collection (i.e. data array) that can be retrieved from a data source. -`dataSets` can and *should* be re-used across multiple proof types, and can also be used in non-proof scenarios such as collecting user data during the `validateCredentials` process. +`dataSets` can and _should_ be re-used across multiple proof types, and can also be used in non-proof scenarios such as collecting user data during the `validateCredentials` process. There should be one property in the `dataSets` object for each data set that is used by your custom Hypersync app. @@ -39,19 +39,19 @@ Each data set object contains the following properties: Often objects returned from a REST API contain property values that need to be formatted before they are presented to the user. -The `transform` property is where we define the fields that we wish to return in the dataset. The property name is the field name that will be available in the dataset. The value of the property refers to the property in the Json returned from the API call that we will display. +The `transform` property is where we define the fields that we wish to return in the dataset. The property name is the field name that will be available in the dataset. The value of the property refers to the property in the Json returned from the API call that we will display. -The simplest transformation is the ability to rename the property we wish to display as described above. In the example included in the JSONata discussion below, we can see that the dataset field `realname` is renamed from the JSON API result property `full_name`. `realname` is the name of the field to be referenced in your proof.json +The simplest transformation is the ability to rename the property we wish to display as described above. In the example included in the JSONata discussion below, we can see that the dataset field `realname` is renamed from the JSON API result property `full_name`. `realname` is the name of the field to be referenced in your proof.json ## Transform with JSONata -Sometimes the data returned for your API needs to be transformed in some way before it can be used in your dataset. The Hypersync SDK makes use of the query and transformation language JSONata. This allows the use of an extensive library of expressions to transform your data. +Sometimes the data returned for your API needs to be transformed in some way before it can be used in your dataset. The Hypersync SDK makes use of the query and transformation language JSONata. This allows the use of an extensive library of expressions to transform your data. Here are a few examples. -- In our final dataset, we need to have a property named `owner` that contains the value of the API result property `full_name`. If the value of `full_name` is empty, we use JSONata to replace it with the value from `username` +- In our final dataset, we need to have a property named `owner` that contains the value of the API result property `full_name`. If the value of `full_name` is empty, we use JSONata to replace it with the value from `username` - For our `role` property, we use JSONata to force this value to contain a concatenated string of values from the `roles` array which is returned from the API result. -- The API result value for `last_login_time` is a unix timestamp in seconds. We use JSONata to convert this value into milliseconds and then into a readable timestamp. +- The API result value for `last_login_time` is a unix timestamp in seconds. We use JSONata to convert this value into milliseconds and then into a readable timestamp. The full JSONata documentation of operators and functions can be found here. @@ -82,6 +82,7 @@ https://docs.jsonata.org/overview } } ``` + ## Transform with Value Lookups Often, an API returns a value or set of values that need to be mapped to alternate values for readability, consistency, or other reasons. For example, an API that returns user information may return a `status` property with values `active` and `deactivated`. In this case it may be desirable to map these values to the strings "Active" and "Inactive" respectively. The `valueLookups` object in `dataSource.json` makes this sort of mapping possible without writing code. diff --git a/doc/054-proof-types-json.md b/doc/054-proof-types-json.md index 9fe7322..e4f05bb 100644 --- a/doc/054-proof-types-json.md +++ b/doc/054-proof-types-json.md @@ -34,6 +34,6 @@ An additional optional property 'schemaCategory' is required if the Hypersync is "listOfUsersApplication": { "label": "{{messages.PROOF_TYPE_USER_LIST}}", "schemaCategory": "uarApplication" - } + } } ``` diff --git a/doc/migration.md b/doc/migration.md index 1aca69a..76f2f9a 100644 --- a/doc/migration.md +++ b/doc/migration.md @@ -16,7 +16,7 @@ The Hypersync SDK functionality is distributed via three Node packages: | @hyperproof/integration-sdk | 1.0.2 | Common integration elements | All of the functionality needed to develop Hypersync applications is now -exported from `@hyperproof/hypersync-sdk`. There is no longer a need to +exported from `@hyperproof/hypersync-sdk`. There is no longer a need to include dependencies to `@hyperproof/hypersync-models` or `@hyperproof/integration-sdk`. The `dependencies` section of your app's `package.json` file should be updated as follows: diff --git a/schema/proofType.schema.json b/schema/proofType.schema.json index 9380e37..0def759 100644 --- a/schema/proofType.schema.json +++ b/schema/proofType.schema.json @@ -55,11 +55,7 @@ } } }, - "required": [ - "description", - "criteria", - "proofSpec" - ], + "required": ["description", "criteria", "proofSpec"], "$defs": { "overrideSpec": { "$schema": "https://json-schema.org/draft/2020-12/schema#", @@ -69,13 +65,7 @@ "properties": { "period": { "type": "string", - "enum": [ - "daily", - "weekly", - "monthly", - "quarterly", - "yearly" - ] + "enum": ["daily", "weekly", "monthly", "quarterly", "yearly"] }, "useVersioning": { "type": "boolean" @@ -85,18 +75,11 @@ }, "format": { "type": "string", - "enum": [ - "stacked", - "tabular", - "custom" - ] + "enum": ["stacked", "tabular", "custom"] }, "orientation": { "type": "string", - "enum": [ - "portrait", - "landscape" - ] + "enum": ["portrait", "landscape"] }, "title": { "type": "string" @@ -129,10 +112,7 @@ }, "source": { "type": "string", - "enum": [ - "dataSet", - "criteria" - ] + "enum": ["dataSet", "criteria"] }, "iterandKey": { "type": "string" @@ -155,11 +135,7 @@ } }, "additionalProperties": false, - "required": [ - "layer", - "source", - "iterandKey" - ] + "required": ["layer", "source", "iterandKey"] }, { "if": { @@ -170,10 +146,7 @@ } }, "then": { - "required": [ - "dataSet", - "dataSetParams" - ] + "required": ["dataSet", "dataSetParams"] } }, { @@ -185,10 +158,7 @@ } }, "then": { - "required": [ - "criteriaProperty", - "criteriaTransformer" - ] + "required": ["criteriaProperty", "criteriaTransformer"] } } ] @@ -211,18 +181,10 @@ }, "type": { "type": "string", - "enum": [ - "text", - "number", - "date", - "boolean" - ] + "enum": ["text", "number", "date", "boolean"] } }, - "required": [ - "property", - "label" - ] + "required": ["property", "label"] } }, "lookups": { @@ -241,10 +203,7 @@ "$ref": "dataSetParams.schema.json" } }, - "required": [ - "name", - "dataSet" - ] + "required": ["name", "dataSet"] } }, "webPageUrl": { @@ -267,4 +226,4 @@ ] } } -} \ No newline at end of file +} diff --git a/schema/restDataSource.schema.json b/schema/restDataSource.schema.json index bb641d7..da515e6 100644 --- a/schema/restDataSource.schema.json +++ b/schema/restDataSource.schema.json @@ -35,199 +35,215 @@ }, "pagingScheme": { "type": "object", - "oneOf": [ - { - "properties": { - "type": { - "type": "string", - "enum": ["nextToken"] - }, - "request": { - "type": "object", - "properties":{ - "tokenParameter": { - "type": "string" - }, - "limitParameter": { - "type": "string" - }, - "limitValue": { - "type": "number" - } + "oneOf": [ + { + "properties": { + "type": { + "type": "string", + "enum": ["nextToken"] + }, + "request": { + "type": "object", + "properties": { + "tokenParameter": { + "type": "string" }, - "required": ["limitParameter", "limitValue"], - "additionalProperties": false - }, - "response": { - "type": "object", - "properties":{ - "nextToken": { - "type": "string" - } + "limitParameter": { + "type": "string" }, - "required": ["nextToken"], - "additionalProperties": false - }, - "pageUntil": { - "type": "string", - "enum": ["noNextToken"] + "limitValue": { + "type": "number" + } }, - "tokenType": { - "type": "string", - "enum": ["token", "url", "searchArray"] + "required": ["limitParameter", "limitValue"], + "additionalProperties": false + }, + "response": { + "type": "object", + "properties": { + "nextToken": { + "type": "string" + } }, - "level": { - "type": "string", - "enum": ["connector", "job"] - } + "required": ["nextToken"], + "additionalProperties": false + }, + "pageUntil": { + "type": "string", + "enum": ["noNextToken"] }, - "required": ["type", "request", "response", "pageUntil", "tokenType"], - "additionalProperties": false + "tokenType": { + "type": "string", + "enum": ["token", "url", "searchArray"] + }, + "level": { + "type": "string", + "enum": ["connector", "job"] + } }, - { - "properties": { - "type": { - "type": "string", - "enum": ["pageBased"] - }, - "request": { - "type": "object", - "properties":{ - "pageParameter": { - "type": "string" - }, - "pageStartingValue": { - "type": "number" - }, - "limitParameter": { - "type": "string" - }, - "limitValue": { - "type": "number" - } + "required": [ + "type", + "request", + "response", + "pageUntil", + "tokenType" + ], + "additionalProperties": false + }, + { + "properties": { + "type": { + "type": "string", + "enum": ["pageBased"] + }, + "request": { + "type": "object", + "properties": { + "pageParameter": { + "type": "string" }, - "required": ["pageParameter", "pageStartingValue", "limitParameter", "limitValue"], - "additionalProperties": false - }, - "response": { - "type": "object", - "properties":{ - "totalCount": { - "type": "string" - } + "pageStartingValue": { + "type": "number" + }, + "limitParameter": { + "type": "string" }, - "required": ["totalCount"], - "additionalProperties": false + "limitValue": { + "type": "number" + } }, - "pageUntil": { - "type": "string", - "enum": ["noDataLeft", "reachTotalCount"] + "required": [ + "pageParameter", + "pageStartingValue", + "limitParameter", + "limitValue" + ], + "additionalProperties": false + }, + "response": { + "type": "object", + "properties": { + "totalCount": { + "type": "string" + } }, - "level": { - "type": "string", - "enum": ["connector", "job"] - } + "required": ["totalCount"], + "additionalProperties": false + }, + "pageUntil": { + "type": "string", + "enum": ["noDataLeft", "reachTotalCount"] }, - "required": ["type", "request", "pageUntil"], - "additionalProperties": false + "level": { + "type": "string", + "enum": ["connector", "job"] + } }, - { - "properties": { - "type": { - "type": "string", - "enum": ["offsetAndLimit"] - }, - "request": { - "type": "object", - "properties":{ - "offsetParameter": { - "type": "string" - }, - "offsetStartingValue": { - "type": "number" - }, - "limitParameter": { - "type": "string" - }, - "limitValue": { - "type": "number" - } + "required": ["type", "request", "pageUntil"], + "additionalProperties": false + }, + { + "properties": { + "type": { + "type": "string", + "enum": ["offsetAndLimit"] + }, + "request": { + "type": "object", + "properties": { + "offsetParameter": { + "type": "string" }, - "required": ["offsetParameter", "offsetStartingValue", "limitParameter", "limitValue"], - "additionalProperties": false - }, - "response": { - "type": "object", - "properties":{ - "totalCount": { - "type": "string" - } + "offsetStartingValue": { + "type": "number" }, - "required": ["totalCount"], - "additionalProperties": false + "limitParameter": { + "type": "string" + }, + "limitValue": { + "type": "number" + } }, - "pageUntil": { - "type": "string", - "enum": ["noDataLeft", "reachTotalCount"] + "required": [ + "offsetParameter", + "offsetStartingValue", + "limitParameter", + "limitValue" + ], + "additionalProperties": false + }, + "response": { + "type": "object", + "properties": { + "totalCount": { + "type": "string" + } }, - "level": { - "type": "string", - "enum": ["connector", "job"] - } + "required": ["totalCount"], + "additionalProperties": false }, - "required": ["type", "request", "pageUntil"], - "additionalProperties": false + "pageUntil": { + "type": "string", + "enum": ["noDataLeft", "reachTotalCount"] + }, + "level": { + "type": "string", + "enum": ["connector", "job"] + } }, - { - "properties": { - "type": { - "type": "string", - "enum": ["graphqlConnections"] - }, - "request": { - "type": "object", - "properties":{ - "limitParameter": { - "anyOf": [ - { - "type": "string", - "enum": ["first"] - }, - { - "type": "string" - } - ] - }, - "limitValue": { - "type": "number" - } - }, - "required": ["limitParameter", "limitValue"], - "additionalProperties": false - }, - "response": { - "type": "object", - "properties":{ - "pageInfo": { - "type": "string" - } + "required": ["type", "request", "pageUntil"], + "additionalProperties": false + }, + { + "properties": { + "type": { + "type": "string", + "enum": ["graphqlConnections"] + }, + "request": { + "type": "object", + "properties": { + "limitParameter": { + "anyOf": [ + { + "type": "string", + "enum": ["first"] + }, + { + "type": "string" + } + ] }, - "required": ["pageInfo"], - "additionalProperties": false + "limitValue": { + "type": "number" + } }, - "pageUntil": { - "type": "string", - "enum": ["noNextPage"] + "required": ["limitParameter", "limitValue"], + "additionalProperties": false + }, + "response": { + "type": "object", + "properties": { + "pageInfo": { + "type": "string" + } }, - "level": { - "type": "string", - "enum": ["connector", "job"] - } + "required": ["pageInfo"], + "additionalProperties": false }, - "required": ["type", "request", "response", "pageUntil"], - "additionalProperties": false - } - ] + "pageUntil": { + "type": "string", + "enum": ["noNextPage"] + }, + "level": { + "type": "string", + "enum": ["connector", "job"] + } + }, + "required": ["type", "request", "response", "pageUntil"], + "additionalProperties": false + } + ] }, "body": { "type": ["object", "string"] diff --git a/src/hypersync/DataSourceBase.ts b/src/hypersync/DataSourceBase.ts index 0541a51..64023ba 100644 --- a/src/hypersync/DataSourceBase.ts +++ b/src/hypersync/DataSourceBase.ts @@ -6,8 +6,8 @@ import { SyncMetadata } from './IDataSource'; -import { DataObject, DataValueMap } from '@hyperproof/hypersync-models'; -import { ILocalizable } from '@hyperproof/integration-sdk'; +import { DataObject, DataValueMap } from '@hyperproof-int/hypersync-models'; +import { ILocalizable } from '@hyperproof-int/integration-sdk'; /** * Abstract base class for a data source object. Provides convenient diff --git a/src/hypersync/HypersyncApp.test.ts b/src/hypersync/HypersyncApp.test.ts new file mode 100644 index 0000000..2c4d92a --- /dev/null +++ b/src/hypersync/HypersyncApp.test.ts @@ -0,0 +1,915 @@ +import { + HypersyncApp, + HypersyncAppConnector, + IHypersyncAppConfig +} from './HypersyncApp'; +import { ICriteriaPage, ICriteriaProvider } from './ICriteriaProvider'; +import { IDataSource } from './IDataSource'; +import { JsonProofProvider } from './JsonProofProvider'; +import { IHypersync } from './models'; +import { ProofProviderBase } from './ProofProviderBase'; +import { ProofProviderFactory } from './ProofProviderFactory'; + +import { + IntegrationContext, + ObjectType, + UserContext +} from '@hyperproof-int/integration-sdk/lib'; +import { ILocalizable } from '@hyperproof-int/integration-sdk/src'; + +describe('HypersyncApp.ts', () => { + let app: any; + let mockProofProviderFactory: any; + let datasource: any; + let criteria: any; + let pages: any; + let messages: any; + beforeAll(() => { + process.env.integration_type = 'testConnector'; + }); + beforeEach(() => { + app = new HypersyncApp({ + appRootDir: 'testDir', + connectorName: 'testConnector', + messages: {}, + credentialsMetadata: {} + } as IHypersyncAppConfig); + }); + describe('HypersyncApp', () => { + beforeEach(() => { + mockProofProviderFactory = { + getProofTypeOptions: jest.fn(), + createProofProvider: jest.fn(), + getCustomProofTypeCategories: jest + .fn() + .mockReturnValue(new Set()) + }; + + datasource = { + dataSetName: 'testDatasource', + params: {} + } as unknown as IDataSource; + + criteria = {}; + + pages = [ + { + fields: [], + isValid: true + } + ] as ICriteriaPage[]; + + messages = {}; + }); + describe('generateCriteriaMetadata()', () => { + describe('category field', () => { + it('should call generateProofCategoryfield', async () => { + // Arrange + const mockCriteriaProvider = { + generateProofCategoryField: jest.fn() + }; + + let proofProviderFactory = + mockProofProviderFactory as unknown as ProofProviderFactory; + let criteriaProvider = + mockCriteriaProvider as unknown as ICriteriaProvider; + + // Act + await app.generateCriteriaMetadata( + messages, + datasource, + criteriaProvider, + proofProviderFactory, + criteria, + pages + ); + + // Assert + expect( + criteriaProvider.generateProofCategoryField + ).toHaveBeenCalled(); + }); + + it('should add categoryField to fields array when valid', async () => { + // Arrange + const validCategoryField = { + name: 'category', + type: 'select', + label: 'Category', + options: [{ value: 'test', label: 'Test' }], + value: 'test' + }; + + const mockCriteriaProvider = { + generateProofCategoryField: jest + .fn() + .mockResolvedValue(validCategoryField) + }; + + let proofProviderFactory = + mockProofProviderFactory as unknown as ProofProviderFactory; + let criteriaProvider = + mockCriteriaProvider as unknown as ICriteriaProvider; + + // Act + await app.generateCriteriaMetadata( + messages, + datasource, + criteriaProvider, + proofProviderFactory, + criteria, + pages + ); + + // Assert + expect(pages[0].fields).toContainEqual(validCategoryField); + }); + + it('should throw error when categoryField type is not "select"', async () => { + // Arrange + const invalidCategoryField = { + name: 'category', + type: 'text', // Wrong type + label: 'Category', + options: [{ value: 'test', label: 'Test' }], + value: 'test' + }; + + const mockCriteriaProvider = { + generateProofCategoryField: jest + .fn() + .mockResolvedValue(invalidCategoryField) + }; + + let proofProviderFactory = + mockProofProviderFactory as unknown as ProofProviderFactory; + let criteriaProvider = + mockCriteriaProvider as unknown as ICriteriaProvider; + + // Act & Assert + await expect( + app.generateCriteriaMetadata( + messages, + datasource, + criteriaProvider, + proofProviderFactory, + criteria, + pages + ) + ).rejects.toThrow('Invalid proof category field.'); + }); + + it('should throw error when categoryField options array is missing', async () => { + // Arrange + const invalidCategoryField = { + name: 'category', + type: 'select', + label: 'Category', + value: 'test' + // Missing options array + }; + + const mockCriteriaProvider = { + generateProofCategoryField: jest + .fn() + .mockResolvedValue(invalidCategoryField) + }; + + let proofProviderFactory = + mockProofProviderFactory as unknown as ProofProviderFactory; + let criteriaProvider = + mockCriteriaProvider as unknown as ICriteriaProvider; + + // Act & Assert + await expect( + app.generateCriteriaMetadata( + messages, + datasource, + criteriaProvider, + proofProviderFactory, + criteria, + pages + ) + ).rejects.toThrow('Invalid proof category field.'); + }); + + it('should throw error when categoryField options array has length <= 0', async () => { + // Arrange + const invalidCategoryField = { + name: 'category', + type: 'select', + label: 'Category', + value: 'test', + options: [] // Empty array + }; + + const mockCriteriaProvider = { + generateProofCategoryField: jest + .fn() + .mockResolvedValue(invalidCategoryField) + }; + + let proofProviderFactory = + mockProofProviderFactory as unknown as ProofProviderFactory; + let criteriaProvider = + mockCriteriaProvider as unknown as ICriteriaProvider; + + // Act & Assert + await expect( + app.generateCriteriaMetadata( + messages, + datasource, + criteriaProvider, + proofProviderFactory, + criteria, + pages + ) + ).rejects.toThrow('Invalid proof category field.'); + }); + + it('should throw error when categoryField options[0].value is not a string', async () => { + // Arrange + const invalidCategoryField = { + name: 'category', + type: 'select', + label: 'Category', + value: 'test', + options: [{ value: 1, label: 'Test' }] // Value is not a string + }; + + const mockCriteriaProvider = { + generateProofCategoryField: jest + .fn() + .mockResolvedValue(invalidCategoryField) + }; + + let proofProviderFactory = + mockProofProviderFactory as unknown as ProofProviderFactory; + let criteriaProvider = + mockCriteriaProvider as unknown as ICriteriaProvider; + + // Act & Assert + await expect( + app.generateCriteriaMetadata( + messages, + datasource, + criteriaProvider, + proofProviderFactory, + criteria, + pages + ) + ).rejects.toThrow('Invalid proof category field.'); + }); + + // ensure custom proof type is picked up in customprooftypecategory + it('should add custom proof type categories to categoryField options', async () => { + // Arrange + const validCategoryField = { + name: 'category', + type: 'select', + label: 'Category', + options: [{ value: 'test', label: 'Test' }], + value: 'test' + }; + + const mockCriteriaProvider = { + generateProofCategoryField: jest + .fn() + .mockResolvedValue(validCategoryField) + }; + + const customCategories = new Set(['other', 'customCategory2']); + + const mockProofProviderFactory = { + getProofTypeOptions: jest.fn(), + createProofProvider: jest.fn(), + getCustomProofTypeCategories: jest + .fn() + .mockReturnValue(customCategories) + }; + + let proofProviderFactory = + mockProofProviderFactory as unknown as ProofProviderFactory; + let criteriaProvider = + mockCriteriaProvider as unknown as ICriteriaProvider; + + // Act + await app.generateCriteriaMetadata( + messages, + datasource, + criteriaProvider, + proofProviderFactory, + criteria, + pages + ); + + // Assert + const categoryFieldOptions = pages[0].fields.find( + f => f.name === 'category' + )?.options; + expect(categoryFieldOptions).toContainEqual({ + value: 'other', + label: 'Other' + }); + }); + }); + + describe('proof types', () => { + it('should validate proofTypeField', async () => { + // Arrange + const categoryField = { + name: 'category', + type: 'select', + label: 'Category', + options: [{ value: 'test', label: 'Test' }], + value: 'test' + }; + + const mockCriteriaProvider = { + generateProofCategoryField: jest + .fn() + .mockResolvedValue(categoryField) + }; + + const validProofTypes = [ + { value: 'type1', label: 'Type 1' }, + { value: 'type2', label: 'Type 2' } + ]; + + const mockProofProviderFactory = { + getProofTypeOptions: jest.fn().mockReturnValue(validProofTypes), + createProofProvider: jest.fn(), + getCustomProofTypeCategories: jest + .fn() + .mockReturnValue(new Set()) + }; + + const invalidCriteria = { + proofType: 'invalidType' // Type that doesn't exist in options + }; + + let proofProviderFactory = + mockProofProviderFactory as unknown as ProofProviderFactory; + let criteriaProvider = + mockCriteriaProvider as unknown as ICriteriaProvider; + + // Act + await app.generateCriteriaMetadata( + messages, + datasource, + criteriaProvider, + proofProviderFactory, + invalidCriteria, + pages + ); + + // Assert + // Verify proofType was cleared since it was invalid + expect(invalidCriteria.proofType).toBeUndefined(); + + // Verify proofType field was added with correct options + const proofTypeField = pages[0].fields.find( + f => f.name === 'proofType' + ); + expect(proofTypeField?.options).toEqual(validProofTypes); + expect(proofTypeField?.isRequired).toBeTruthy(); + expect(proofTypeField?.value).toBeUndefined(); + }); + + it('should add proofType field when category is selected and valid proof type exists', async () => { + // Arrange + const categoryField = { + name: 'category', + type: 'select', + label: 'Category', + options: [{ value: 'test', label: 'Test' }], + value: 'test' + }; + + const mockCriteriaProvider = { + generateProofCategoryField: jest + .fn() + .mockResolvedValue(categoryField) + }; + + const validProofTypes = [ + { value: 'type1', label: 'Type 1' }, + { value: 'type2', label: 'Type 2' } + ]; + + const mockProofProvider = { + generateCriteriaMetadata: jest.fn() + }; + + const mockProofProviderFactory = { + getProofTypeOptions: jest.fn().mockReturnValue(validProofTypes), + createProofProvider: jest.fn().mockReturnValue(mockProofProvider), + getCustomProofTypeCategories: jest + .fn() + .mockReturnValue(new Set()) + }; + + const criteria = { + proofType: 'type1' // Valid proof type that exists in options + }; + + let proofProviderFactory = + mockProofProviderFactory as unknown as ProofProviderFactory; + let criteriaProvider = + mockCriteriaProvider as unknown as ICriteriaProvider; + let provider = mockProofProvider as unknown as + | ProofProviderBase + | JsonProofProvider; + + // Act + await app.generateCriteriaMetadata( + messages, + datasource, + criteriaProvider, + proofProviderFactory, + criteria, + pages + ); + + // Assert + const proofTypeField = pages[0].fields.find( + f => f.name === 'proofType' + ); + expect(proofTypeField?.options).toEqual(validProofTypes); + expect(proofTypeField?.value).toBe('type1'); + expect(proofTypeField?.isRequired).toBeTruthy(); + expect(proofTypeField?.isDisabled).toBeFalsy(); + expect(provider.generateCriteriaMetadata).toHaveBeenCalledWith( + criteria, + pages, + undefined + ); + }); + + it('should add proofType field with correct disabled state', async () => { + // Arrange + const categoryField = { + name: 'category', + type: 'select', + label: 'Category', + options: [{ value: 'test', label: 'Test' }], + value: null // No value selected + }; + + const mockCriteriaProvider = { + generateProofCategoryField: jest + .fn() + .mockResolvedValue(categoryField) + }; + + let proofProviderFactory = + mockProofProviderFactory as unknown as ProofProviderFactory; + let criteriaProvider = + mockCriteriaProvider as unknown as ICriteriaProvider; + + // Act + await app.generateCriteriaMetadata( + messages, + datasource, + criteriaProvider, + proofProviderFactory, + criteria, + pages + ); + + // Assert + const proofTypeField = pages[0].fields.find( + f => f.name === 'proofType' + ); + expect(proofTypeField?.isDisabled).toBeTruthy(); + }); + + it('should filter proof types based on selected category', async () => { + // Arrange + const categoryField = { + name: 'category', + type: 'select', + label: 'Category', + options: [{ value: 'test', label: 'Test' }], + value: 'test' + }; + + const mockCriteriaProvider = { + generateProofCategoryField: jest + .fn() + .mockResolvedValue(categoryField) + }; + + const expectedProofTypes = [ + { value: 'type1', label: 'Type 1' }, + { value: 'type2', label: 'Type 2' } + ]; + + const mockProofProviderFactory = { + getProofTypeOptions: jest.fn().mockReturnValue(expectedProofTypes), + createProofProvider: jest.fn(), + getCustomProofTypeCategories: jest + .fn() + .mockReturnValue(new Set()) + }; + + let proofProviderFactory = + mockProofProviderFactory as unknown as ProofProviderFactory; + let criteriaProvider = + mockCriteriaProvider as unknown as ICriteriaProvider; + + // Act + await app.generateCriteriaMetadata( + messages, + datasource, + criteriaProvider, + proofProviderFactory, + criteria, + pages + ); + + // Assert + expect( + mockProofProviderFactory.getProofTypeOptions + ).toHaveBeenCalledWith('test', undefined); + + const proofTypeField = pages[0].fields.find( + f => f.name === 'proofType' + ); + expect(proofTypeField?.options).toEqual(expectedProofTypes); + expect(proofTypeField?.isDisabled).toBeFalsy(); + }); + + it('should clear proofType if not found in filtered options', async () => { + // Arrange + const categoryField = { + name: 'category', + type: 'select', + label: 'Category', + options: [{ value: 'test', label: 'Test' }], + value: 'test' + }; + + const mockCriteriaProvider = { + generateProofCategoryField: jest + .fn() + .mockResolvedValue(categoryField) + }; + + const mockProofProviderFactory = { + getProofTypeOptions: jest + .fn() + .mockReturnValue([{ value: 'newType', label: 'New Type' }]), + createProofProvider: jest.fn(), + getCustomProofTypeCategories: jest + .fn() + .mockReturnValue(new Set()) + }; + + const criteria = { + proofType: 'oldType' // This type doesn't exist in new options + }; + + let proofProviderFactory = + mockProofProviderFactory as unknown as ProofProviderFactory; + let criteriaProvider = + mockCriteriaProvider as unknown as ICriteriaProvider; + + // Act + await app.generateCriteriaMetadata( + messages, + datasource, + criteriaProvider, + proofProviderFactory, + criteria, + pages + ); + + // Assert + expect(criteria.proofType).toBeUndefined(); + }); + }); + + describe('proof provider', () => { + it('should create proof provider when valid proof type is selected', async () => { + // Arrange + const categoryField = { + name: 'category', + type: 'select', + label: 'Category', + options: [{ value: 'test', label: 'Test' }], + value: 'test' + }; + + const mockCriteriaProvider = { + generateProofCategoryField: jest + .fn() + .mockResolvedValue(categoryField) + }; + + const mockProofProvider = { + generateCriteriaMetadata: jest.fn().mockReturnValue({}) + }; + + const mockProofProviderFactory = { + getProofTypeOptions: jest + .fn() + .mockReturnValue([{ value: 'validType', label: 'Valid Type' }]), + createProofProvider: jest.fn().mockReturnValue(mockProofProvider), + getCustomProofTypeCategories: jest + .fn() + .mockReturnValue(new Set()) + }; + + const criteria = { + proofType: 'validType' + }; + + let proofProviderFactory = + mockProofProviderFactory as unknown as ProofProviderFactory; + let criteriaProvider = + mockCriteriaProvider as unknown as ICriteriaProvider; + + // Act + await app.generateCriteriaMetadata( + messages, + datasource, + criteriaProvider, + proofProviderFactory, + criteria, + pages + ); + + // Assert + expect( + mockProofProviderFactory.createProofProvider + ).toHaveBeenCalledWith('validType', datasource, criteriaProvider); + expect( + mockProofProvider.generateCriteriaMetadata + ).toHaveBeenCalledWith(criteria, pages, undefined); + }); + }); + }); + describe('createdataSource()', () => {}); + }); + + describe('HypersyncAppConnector', () => { + let mockConnector: HypersyncAppConnector; + let mockApp: HypersyncApp; + + beforeAll(() => { + process.env.integration_type = 'testAppConnector'; + }); + + beforeEach(() => { + mockApp = { + validateCredentials: jest.fn(), + getMessages: jest.fn().mockReturnValue({}), + createDataSource: jest.fn().mockResolvedValue({}), + createCriteriaProvider: jest.fn().mockResolvedValue({}), + getProofProviderFactory: jest.fn().mockResolvedValue({}), + getProofData: jest.fn().mockResolvedValue([]) + } as Partial as HypersyncApp; + + mockConnector = new HypersyncAppConnector('testAppConnector', mockApp); + }); + + describe('syncNow()', () => { + describe('user context', () => { + it('should throw error when user context is not found', async () => { + // Arrange + + const mockIntegrationContext = { + storage: { + list: jest.fn().mockResolvedValue({ items: [] }), + get: jest.fn(), + put: jest.fn(), + delete: jest.fn() + }, + logger: { + info: jest.fn(), + error: jest.fn() + } + } as unknown as IntegrationContext; + + const hypersync = { + settings: { + vendorUserId: 'testUser' + } + } as unknown as IHypersync; + + jest + .spyOn(mockConnector, 'getUser') + .mockImplementation() + .mockResolvedValue(undefined); + + // Act & Assert + await expect( + mockConnector.syncNow( + mockIntegrationContext, + 'orgId', + ObjectType.LABEL, + 'objectId', + hypersync, + 'syncStartDate' + ) + ).rejects.toThrow(/The connection may have been deleted/); + }); + + it('should call app.getProofData with correct parameters', async () => { + // Arrange + + const mockIntegrationContext = { + storage: { + list: jest.fn().mockResolvedValue({ items: [] }), + get: jest.fn(), + put: jest.fn(), + delete: jest.fn() + }, + logger: { + info: jest.fn(), + error: jest.fn() + } + } as unknown as IntegrationContext; + + const mockUserContext = { + vendorUserId: 'testUser', + vendorUserProfile: { id: 'testProfile' } + } as unknown as UserContext; + + const hypersync = { + settings: { + vendorUserId: 'testUser' + } + } as unknown as IHypersync; + + const organization: ILocalizable = { + language: 'en', + locale: 'US', + timeZone: 'America/Los_Angeles' + }; + + jest + .spyOn(mockConnector as any, 'getUser') + .mockResolvedValue(mockUserContext); + jest + .spyOn(mockConnector as any, 'createResources') + .mockResolvedValue({ + messages: {}, + dataSource: {}, + criteriaProvider: {}, + proofProviderFactory: {} + }); + + // Act + await mockConnector.syncNow( + mockIntegrationContext, + 'orgId', + ObjectType.LABEL, + 'objectId', + hypersync, + 'syncStartDate', + organization, + 'page1', + { metadata: 'test' }, + 3, + undefined + ); + + // Assert + expect(mockApp.getProofData).toHaveBeenCalledWith( + {}, + {}, + {}, + hypersync, + organization, + { id: 'testProfile' }, + 'syncStartDate', + 'page1', + { metadata: 'test' }, + 3, + undefined + ); + }); + + it('should return results from getProofData', async () => { + // Arrange + const expectedResults = { + data: [ + { + proof: 'data' + } + ] + }; + (mockApp.getProofData as jest.Mock).mockResolvedValue( + expectedResults + ); + + const mockIntegrationContext = { + storage: { + list: jest.fn().mockResolvedValue({ items: [] }) + } + } as unknown as IntegrationContext; + + const mockUserContext = { + vendorUserId: 'testUser', + vendorUserProfile: {} + } as unknown as UserContext; + + const hypersync = { + settings: { + vendorUserId: 'testUser' + } + } as unknown as IHypersync; + + jest + .spyOn(mockConnector as any, 'getUser') + .mockResolvedValue(mockUserContext); + jest + .spyOn(mockConnector as any, 'createResources') + .mockResolvedValue({ + messages: {}, + dataSource: {}, + criteriaProvider: {}, + proofProviderFactory: {} + }); + + // Act + const result = await mockConnector.syncNow( + mockIntegrationContext, + 'orgId', + ObjectType.LABEL, + 'objectId', + hypersync, + 'syncStartDate' + ); + + // Assert + expect(result).toEqual(expectedResults); + }); + }); + }); + + describe('validateCredentials()', () => { + it('should throw error when userId does not match userIdPattern', async () => { + // Arrange + const userIdPattern = /^user_\d+$/; + (mockApp.validateCredentials as jest.Mock).mockResolvedValue({ + userId: 'invalidUserId', + profile: { userName: 'Test User' }, + userIdPattern: userIdPattern + }); + + const credentials = { username: 'testuser', password: 'testpass' }; + const mockIntegrationContext = { + storage: { + list: jest.fn().mockResolvedValue({ items: [] }) + } + } as unknown as IntegrationContext; + + // Act & Assert + await expect( + mockConnector.validateCredentials( + credentials, + mockIntegrationContext, + 'hyperproofUserId123' + ) + ).rejects.toThrow(); + }); + + it('should pass validation when userId matches userIdPattern', async () => { + // Arrange + const userIdPattern = /^user_\d+$/; + (mockApp.validateCredentials as jest.Mock).mockResolvedValue({ + userId: 'user_12345', // This matches the pattern + profile: { userName: 'Test User' }, + userIdPattern: userIdPattern + }); + + const credentials = { + username: 'testuser', + password: 'testpass' + }; + + const mockIntegrationContext = { + storage: { + list: jest.fn().mockResolvedValue({ items: [] }) + } + } as unknown as IntegrationContext; + + // Act + const result = await mockConnector.validateCredentials( + credentials, + mockIntegrationContext, + 'hyperproofUserId123' + ); + + // Assert + expect(result).toEqual({ + vendorUserId: 'user_12345', + vendorUserProfile: { userName: 'Test User' } + }); + }); + }); + }); +}); diff --git a/src/hypersync/HypersyncApp.ts b/src/hypersync/HypersyncApp.ts index ef2e57c..2c7a624 100644 --- a/src/hypersync/HypersyncApp.ts +++ b/src/hypersync/HypersyncApp.ts @@ -35,7 +35,7 @@ import { IHypersyncDefinition, SchemaCategory, ValueLookup -} from '@hyperproof/hypersync-models'; +} from '@hyperproof-int/hypersync-models'; import { AuthorizationType, createApp, @@ -56,7 +56,7 @@ import { OAuthTokenResponse, ObjectType, UserContext -} from '@hyperproof/integration-sdk'; +} from '@hyperproof-int/integration-sdk'; import express from 'express'; import fs from 'fs'; import createHttpError from 'http-errors'; diff --git a/src/hypersync/ICriteriaProvider.ts b/src/hypersync/ICriteriaProvider.ts index ca69ce2..bb3c205 100644 --- a/src/hypersync/ICriteriaProvider.ts +++ b/src/hypersync/ICriteriaProvider.ts @@ -11,8 +11,8 @@ import { ISelectOption, IValidation, SchemaCategory -} from '@hyperproof/hypersync-models'; -import { CriteriaPageMessageLevel } from '@hyperproof/integration-sdk'; +} from '@hyperproof-int/hypersync-models'; +import { CriteriaPageMessageLevel } from '@hyperproof-int/integration-sdk'; /** * Information needed to render a criteria field used in the configuration diff --git a/src/hypersync/IDataSource.ts b/src/hypersync/IDataSource.ts index 1c983c9..789be4e 100644 --- a/src/hypersync/IDataSource.ts +++ b/src/hypersync/IDataSource.ts @@ -2,8 +2,8 @@ import { IErrorInfo } from './models'; import { RestDataSourceBase } from './RestDataSourceBase'; import { TokenContext } from './tokens'; -import { DataObject, DataValueMap } from '@hyperproof/hypersync-models'; -import { ILocalizable } from '@hyperproof/integration-sdk'; +import { DataObject, DataValueMap } from '@hyperproof-int/hypersync-models'; +import { ILocalizable } from '@hyperproof-int/integration-sdk'; export enum DataSetResultStatus { Complete = 'complete', diff --git a/src/hypersync/JsonCriteriaProvider.test.ts b/src/hypersync/JsonCriteriaProvider.test.ts new file mode 100644 index 0000000..ac875e2 --- /dev/null +++ b/src/hypersync/JsonCriteriaProvider.test.ts @@ -0,0 +1,1362 @@ +import { DataSetResultStatus, IDataSource } from './IDataSource'; +import { JsonCriteriaProvider } from './JsonCriteriaProvider'; + +import { + HypersyncCriteriaFieldType, + ValidationTypes +} from '@hyperproof-int/hypersync-models'; +import { Logger } from '@hyperproof-int/integration-sdk'; +import fs from 'fs'; +import path from 'path'; + +jest.mock('fs'); +jest.mock('path', () => { + return { + resolve: jest.fn((...args) => { + return args[args.length - 2]; + }) + }; +}); + +describe('JsonCriteriaProvider', () => { + const appRootDir = 'appRootDir'; + const initialFieldName = 'initialField'; + const propertyValue = 'propertyValue'; + const propertyLabel = 'propertyLabel'; + + const initialField = { + type: HypersyncCriteriaFieldType.Text, + property: 'initialProperty', + label: 'initialLabel', + isRequired: false + }; + const fileContents = { + [initialFieldName]: initialField + }; + const fileJson = JSON.stringify(fileContents); + const dataSet = { + [propertyValue]: 'dataSetValue', + [propertyLabel]: 'dataSetLabel' + }; + const otherSet = { + [propertyValue]: 'otherSetValue', + [propertyLabel]: 'otherSetLabel' + }; + + let dataSource: IDataSource; + + beforeEach(() => { + Logger.info = jest.fn(); + fs.existsSync = jest.fn().mockReturnValue(true); + fs.readFileSync = jest.fn().mockReturnValue(fileJson); + dataSource = { + getData: jest.fn().mockResolvedValue({ + status: DataSetResultStatus.Complete, + data: [ + { + [propertyValue]: dataSet.propertyValue, + [propertyLabel]: dataSet.propertyLabel + }, + { + [propertyValue]: otherSet.propertyValue, + [propertyLabel]: otherSet.propertyLabel + } + ] + }) + }; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + test('initializes correctly', () => { + // Arrange & Act + const provider = new JsonCriteriaProvider(appRootDir, dataSource); + + // Assert + expect(path.resolve).toHaveBeenCalledWith(appRootDir, expect.any(String)); + expect(fs.existsSync).toHaveBeenCalledWith(appRootDir); // path.resolve was mocked to return appRootDir + expect(fs.readFileSync).toHaveBeenCalledWith(appRootDir, 'utf8'); + expect(provider).toBeDefined(); + }); + + describe('is initialized and', () => { + let provider: JsonCriteriaProvider; + beforeEach(() => { + provider = new JsonCriteriaProvider(appRootDir, dataSource); + }); + + describe('getConfig', () => { + test('returns the config', () => { + // Arrange + + // Act + const config = provider.getConfig(); + + // Assert + expect(config).toBeDefined(); + expect(config).toEqual(fileContents); + }); + + test('with no file returns empty object', () => { + // Arrange + fs.existsSync = jest.fn().mockReturnValue(false); + const provider = new JsonCriteriaProvider(appRootDir, dataSource); + + // Act + const config = provider.getConfig(); + + // Assert + expect(config).toBeDefined(); + expect(config).toEqual({}); + }); + }); + + describe('addCriteriaField', () => { + test('adds a new field', () => { + // Arrange + const fieldName = 'fieldName'; + const fieldValue = { + type: HypersyncCriteriaFieldType.Text, + property: 'property', + label: 'label', + isRequired: true + }; + + // Act + provider.addCriteriaField(fieldName, fieldValue); + + // // Assert + const config = provider.getConfig(); + expect(config).toBeDefined(); + expect(config[fieldName]).toBe(fieldValue); + }); + + test('throws an error when adding a existing field', () => { + // Arrange + const newFieldValue = { + type: HypersyncCriteriaFieldType.Text, + property: 'rejected', + label: 'NO', + isRequired: false + }; + + // Act + expect(() => + provider.addCriteriaField(initialFieldName, newFieldValue) + ).toThrow('A criteria field with that name already exists.'); + + // Assert + const config = provider.getConfig(); + expect(config).toBeDefined(); + expect(config[initialFieldName]).toBeDefined(); + expect(config[initialFieldName]).toEqual(initialField); + }); + }); + + describe('generateProofCategoryField', () => { + test('generates the fields when criteriaFields contains hp_proofCategory', async () => { + // Arrange + const fileContents = { + hp_proofCategory: initialField + }; + const fileJson = JSON.stringify(fileContents); + fs.readFileSync = jest.fn().mockReturnValue(fileJson); + const provider = new JsonCriteriaProvider(appRootDir, dataSource); + + const criteriaValues = {}; + const tokenContext = {}; + + const criteraFieldReturn = { + name: initialField.property, + type: initialField.type, + label: initialField.label + }; + provider['buildCriteriaField'] = jest + .fn() + .mockResolvedValue(criteraFieldReturn); + + const expectedField = { + name: criteraFieldReturn.name, + type: criteraFieldReturn.type, + label: criteraFieldReturn.label + }; + + // Act + const field = await provider.generateProofCategoryField( + criteriaValues, + tokenContext + ); + + expect(field).not.toBeNull(); + expect(field).toEqual(expectedField); + }); + + test('generates the field when criteriaFields contains proofCategory', async () => { + // Arrange + const fileContents = { + proofCategory: initialField + }; + const fileJson = JSON.stringify(fileContents); + fs.readFileSync = jest.fn().mockReturnValue(fileJson); + const provider = new JsonCriteriaProvider(appRootDir, dataSource); + + const criteriaValues = {}; + const tokenContext = {}; + + const criteraFieldReturn = { + name: initialField.property, + type: initialField.type, + label: initialField.label + }; + provider['buildCriteriaField'] = jest + .fn() + .mockResolvedValue(criteraFieldReturn); + + const expectedField = { + name: initialField.property, + type: initialField.type, + label: initialField.label + }; + + // Act + const field = await provider.generateProofCategoryField( + criteriaValues, + tokenContext + ); + + expect(field).not.toBeNull(); + expect(field).toEqual(expectedField); + }); + + test('returns null when no proofCategory fields', async () => { + // Arrange + const criteriaValues = {}; + const tokenContext = {}; + + // Act + const field = await provider.generateProofCategoryField( + criteriaValues, + tokenContext + ); + + // Assert + expect(field).toBeNull(); + }); + }); + + describe('generateCriteriaFields', () => { + test('returns valid last page when no proofCriteria is passed in', async () => { + // Arrange + const proofCriteria = []; + const criteriaValues = {}; + const tokenContext = {}; + const pages = [ + { + fields: [], + isValid: false + } + ]; + const pagesLength = pages.length; + const pageFields = pages[0].fields; + + // Act + await provider.generateCriteriaFields( + proofCriteria, + criteriaValues, + tokenContext, + pages + ); + + // Assert + expect(pages.length).toBe(pagesLength); + const page = pages[pagesLength - 1]; + expect(page.isValid).toBeTruthy(); + expect(page.fields).toBe(pageFields); + }); + + test('throws error when proofCriteria is not in criteriaFields', async () => { + // Arrange + const proofCriteria = [ + { + name: 'criteriaName', + page: 0 + } + ]; + const criteriaValues = {}; + const tokenContext = {}; + const pages = []; + const pagesLength = pages.length; + + // Act + // Due to async function throwing error, can't just use expect().toThrow() + try { + await provider.generateCriteriaFields( + proofCriteria, + criteriaValues, + tokenContext, + pages + ); + // We shouldn't reach here + expect(false).toBeTruthy(); + } catch (error) { + expect(error.message).toBe( + `Unable to find criterion named ${proofCriteria[0].name}` + ); + } + + // Assert + expect(pages.length).toBe(pagesLength); + }); + + test('throws error if criteriaField is not appropriate type', async () => { + // Arrange + const initialField = { + type: HypersyncCriteriaFieldType.Radio, + property: 'initialProperty', + label: 'initialLabel', + isRequired: true + }; + const fileContents = { + initialField: initialField + }; + const fileJson = JSON.stringify(fileContents); + fs.readFileSync = jest.fn().mockReturnValue(fileJson); + const provider = new JsonCriteriaProvider(appRootDir, dataSource); + + const proofCriteria = [ + { + name: initialFieldName, + page: 0 + } + ]; + const criteriaValues = {}; + const tokenContext = {}; + const pages = []; + const pagesLength = pages.length; + + // Act + // Due to async function throwing error, can't just use expect().toThrow() + try { + await provider.generateCriteriaFields( + proofCriteria, + criteriaValues, + tokenContext, + pages + ); + expect(false).toBeTruthy(); + } catch (error) { + expect(error.message).toBe( + `Unrecognized or unsupported criteria field type: ${initialField.type}` + ); + } + + // Assert + expect(pages.length).toBe(pagesLength); + }); + + test('returns appropriate field values', async () => { + // Arrange + const proofCriteria = [ + { + name: initialFieldName, + page: 0 + } + ]; + const criteriaValues = {}; + const tokenContext = {}; + const pages = []; + const pagesLength = pages.length; + + const expectedCriteriaFieldReturn = { + name: initialField.property, + type: initialField.type, + label: initialField.label, + isRequired: initialField.isRequired, + options: [] + }; + provider['buildCriteriaField'] = jest + .fn() + .mockResolvedValue(expectedCriteriaFieldReturn); + + const expectedPages = [ + { + fields: [expectedCriteriaFieldReturn], + isValid: true + } + ]; + + // Act + await provider.generateCriteriaFields( + proofCriteria, + criteriaValues, + tokenContext, + pages + ); + + // Assert + expect(pages.length).toBe(pagesLength + 1); + expect(pages).toEqual(expectedPages); + }); + + test('returns appropriate fields with empty fields if extra pages', async () => { + // Arrange + const proofCriteria = [ + { + name: initialFieldName, + page: 1 + } + ]; + const criteriaValues = {}; + const tokenContext = {}; + const pages = []; + const pagesLength = pages.length; + + const expectedCriteriaFieldReturn = { + name: initialField.property, + type: initialField.type, + label: initialField.label, + isReadable: initialField.isRequired, + options: [] + }; + provider['buildCriteriaField'] = jest + .fn() + .mockResolvedValue(expectedCriteriaFieldReturn); + + const expectedPages = [ + // Add blank page to cover missing page 0 + { isValid: false, fields: [] }, + { + fields: [expectedCriteriaFieldReturn], + isValid: true + } + ]; + + // Act + await provider.generateCriteriaFields( + proofCriteria, + criteriaValues, + tokenContext, + pages + ); + + // Assert + expect(pages.length).toBe(pagesLength + 2); + expect(pages).toEqual(expectedPages); + }); + }); + + describe('generateProofCriteria', () => { + test('with no proofCriteria returns emptpy array', async () => { + // Arrange + const proofCriteria = []; + const criteriaValues = {}; + const tokenContext = {}; + + // Act + const criteria = await provider.generateProofCriteria( + proofCriteria, + criteriaValues, + tokenContext + ); + + // Assert + expect(criteria).toBeDefined(); + expect(criteria).toEqual([]); + }); + + test('with empty non-required proof criteria value returns default display name', async () => { + // Arrange + const initialField = { + type: HypersyncCriteriaFieldType.Text, + property: 'initialProperty', + label: 'initialLabel', + isRequired: false, + defaultDisplayValue: 'defaultName' + }; + const fileContents = { + initialField: initialField + }; + const fileJson = JSON.stringify(fileContents); + fs.readFileSync = jest.fn().mockReturnValue(fileJson); + const provider = new JsonCriteriaProvider(appRootDir, dataSource); + + const proofCriteria = [ + { + name: initialFieldName, + page: 0 + } + ]; + const criteriaValues = { + [initialField.property]: undefined + }; + const tokenContext = {}; + + const expectedCriteria = [ + { + name: initialField.property, + label: initialField.label, + value: initialField.defaultDisplayValue + } + ]; + + // Act + const criteria = await provider.generateProofCriteria( + proofCriteria, + criteriaValues, + tokenContext + ); + + // Assert + expect(criteria).toBeDefined(); + expect(criteria).toEqual(expectedCriteria); + }); + + test('with empty non-required proof criteria array returns default display name', async () => { + // Arrange + const initialField = { + type: HypersyncCriteriaFieldType.Text, + property: 'initialProperty', + label: 'initialLabel', + isRequired: false, + defaultDisplayValue: 'defaultName' + }; + const fileContents = { + initialField: initialField + }; + const fileJson = JSON.stringify(fileContents); + fs.readFileSync = jest.fn().mockReturnValue(fileJson); + const provider = new JsonCriteriaProvider(appRootDir, dataSource); + + const proofCriteria = [ + { + name: initialFieldName, + page: 0 + } + ]; + const criteriaValues = { + [initialField.property]: [] + }; + const tokenContext = {}; + + const expectedCriteria = [ + { + name: initialField.property, + label: initialField.label, + value: initialField.defaultDisplayValue + } + ]; + + // Act + const criteria = await provider.generateProofCriteria( + proofCriteria, + criteriaValues, + tokenContext + ); + + // Assert + expect(criteria).toBeDefined(); + expect(criteria).toEqual(expectedCriteria); + }); + + test('with field type text returns criteria', async () => { + // Arrange + const proofCriteria = [ + { + name: initialFieldName, + page: 0 + } + ]; + const criteriaString = 'criteriaValues'; + const criteriaValues = { + [initialField.property]: criteriaString + }; + const tokenContext = {}; + + const expectedCriteria = [ + { + name: initialField.property, + label: initialField.label, + value: criteriaString + } + ]; + + // Act + const criteria = await provider.generateProofCriteria( + proofCriteria, + criteriaValues, + tokenContext + ); + + // Assert + expect(criteria).toBeDefined(); + expect(criteria).toEqual(expectedCriteria); + }); + + test('with field type select returns criteria', async () => { + // Arrange + const initialField = { + type: HypersyncCriteriaFieldType.Select, + property: 'initialProperty', + label: 'initialLabel', + isRequired: false, + dataSet: [], + valueProperty: propertyValue, + labelProperty: propertyLabel + }; + const fileContents = { + initialField: initialField + }; + const fileJson = JSON.stringify(fileContents); + fs.readFileSync = jest.fn().mockReturnValue(fileJson); + const provider = new JsonCriteriaProvider(appRootDir, dataSource); + + provider['getCriteriaFieldOptions'] = jest.fn().mockReturnValue([ + { value: dataSet.propertyValue, label: dataSet.propertyLabel }, + { value: otherSet.propertyValue, label: otherSet.propertyLabel } + ]); + + const proofCriteria = [ + { + name: initialFieldName, + page: 0 + } + ]; + const criteriaValues = { + [initialField.property]: dataSet.propertyValue + }; + const tokenContext = {}; + + const expectedCriteria = [ + { + name: initialField.property, + label: initialField.label, + value: dataSet.propertyLabel + } + ]; + + // Act + const criteria = await provider.generateProofCriteria( + proofCriteria, + criteriaValues, + tokenContext + ); + + // Assert + expect(criteria).toBeDefined(); + expect(criteria).toEqual(expectedCriteria); + }); + + test('with field type select with array returns criteria', async () => { + // Arrange + const initialField = { + type: HypersyncCriteriaFieldType.Select, + property: 'initialProperty', + label: 'initialLabel', + isRequired: false, + dataSet: [], + valueProperty: propertyValue, + labelProperty: propertyLabel + }; + const fileContents = { + initialField: initialField + }; + const fileJson = JSON.stringify(fileContents); + fs.readFileSync = jest.fn().mockReturnValue(fileJson); + const provider = new JsonCriteriaProvider(appRootDir, dataSource); + + provider['getCriteriaFieldOptions'] = jest.fn().mockReturnValue([ + { value: dataSet.propertyValue, label: dataSet.propertyLabel }, + { value: otherSet.propertyValue, label: otherSet.propertyLabel } + ]); + + const proofCriteria = [ + { + name: initialFieldName, + page: 0 + } + ]; + const criteriaValues = { + [initialField.property]: [ + dataSet.propertyValue, + otherSet.propertyValue + ] + }; + const tokenContext = {}; + + const expectedValue = + dataSet.propertyLabel + ', ' + otherSet.propertyLabel; + const expectedCriteria = [ + { + name: initialField.property, + label: initialField.label, + value: expectedValue + } + ]; + + // Act + const criteria = await provider.generateProofCriteria( + proofCriteria, + criteriaValues, + tokenContext + ); + + // Assert + expect(criteria).toBeDefined(); + expect(criteria).toEqual(expectedCriteria); + }); + }); + + describe('buildCriteriaField (private)', () => { + test('with isDisabled true returns empty array for options', async () => { + // Arrange + const property = 'property'; + const label = 'label'; + + const fieldConfig = { + type: HypersyncCriteriaFieldType.Select, + property: property, + label: label, + isRequired: true + }; + const criteriaValues = {}; + const tokenContext = {}; + const isDisabled = true; + + const criteraFieldOptionReturn = { + value: 'optionValue', + label: 'optionLabel' + }; + provider['getCriteriaFieldOptions'] = jest + .fn() + .mockResolvedValue([criteraFieldOptionReturn]); + + const expectedResponse = { + name: fieldConfig.property, + type: fieldConfig.type, + label: fieldConfig.label, + isRequired: fieldConfig.isRequired, + options: [], + isDisabled: isDisabled + }; + + // Act + const criteriaField = await provider['buildCriteriaField']( + fieldConfig, + criteriaValues, + tokenContext, + isDisabled + ); + + // Assert + expect(criteriaField).toBeDefined(); + expect(criteriaField).toEqual(expectedResponse); + }); + + test('with field type text returns empty array for options', async () => { + // Arrange + const property = 'property'; + const label = 'label'; + + const fieldConfig = { + type: HypersyncCriteriaFieldType.Text, + property: property, + label: label, + isRequired: true + }; + const criteriaValues = {}; + const tokenContext = {}; + const isDisabled = false; + + const criteraFieldOptionReturn = { + value: 'optionValue', + label: 'optionLabel' + }; + provider['getCriteriaFieldOptions'] = jest + .fn() + .mockResolvedValue([criteraFieldOptionReturn]); + + const expectedResponse = { + name: fieldConfig.property, + type: fieldConfig.type, + label: fieldConfig.label, + isRequired: fieldConfig.isRequired, + options: [], + isDisabled: isDisabled + }; + + // Act + const criteriaField = await provider['buildCriteriaField']( + fieldConfig, + criteriaValues, + tokenContext, + isDisabled + ); + + // Assert + expect(criteriaField).toBeDefined(); + expect(criteriaField).toEqual(expectedResponse); + }); + + test('returns criteria fields', async () => { + // Arrange + const property = 'property'; + const label = 'label'; + const placeholder = 'placeholderValue'; + const criteriaValue = 'criteriaValue'; + + const fieldConfig = { + type: HypersyncCriteriaFieldType.Select, + property: property, + label: label, + isRequired: true, + placeholder: placeholder, + isMulti: false, + validation: { + type: ValidationTypes.alphaNumeric + } + }; + const criteriaValues = { + [property]: criteriaValue + }; + const tokenContext = {}; + const isDisabled = false; + + const criteraFieldOptionReturn = { + value: 'optionValue', + label: 'optionLabel' + }; + provider['getCriteriaFieldOptions'] = jest + .fn() + .mockResolvedValue([criteraFieldOptionReturn]); + + const expectedResponse = { + name: fieldConfig.property, + type: fieldConfig.type, + label: fieldConfig.label, + isRequired: fieldConfig.isRequired, + options: [criteraFieldOptionReturn], + value: criteriaValues[property], + placeholder: fieldConfig.placeholder, + isDisabled: isDisabled, + isMulti: fieldConfig.isMulti, + validation: { + type: fieldConfig.validation.type, + errorMessage: '' + } + }; + + // Act + const criteriaField = await provider['buildCriteriaField']( + fieldConfig, + criteriaValues, + tokenContext, + isDisabled + ); + + // Assert + expect(criteriaField).toBeDefined(); + expect(criteriaField).toEqual(expectedResponse); + }); + }); + + describe('getCriteriaFieldOptions (private)', () => { + test('throws an error if getData returns pending', async () => { + // Arrange + const dataSource = { + getData: jest.fn().mockResolvedValue({ + status: DataSetResultStatus.Pending, + data: [] + }) + }; + const provider = new JsonCriteriaProvider(appRootDir, dataSource); + + const property = 'property'; + const label = 'label'; + const dataSet = 'dataset'; + const valueProperty = 'valueProp'; + const labelProperty = 'labelProp'; + + const fieldConfig = { + type: HypersyncCriteriaFieldType.Select, + property: property, + label: label, + isRequired: true, + dataSet: dataSet, + valueProperty: valueProperty, + labelProperty: labelProperty + }; + const criteriaValues = {}; + const tokenContext = {}; + + // Act + // Due to async function throwing error, can't just use expect().toThrow() + let criteriaFieldOptions; + try { + criteriaFieldOptions = await provider['getCriteriaFieldOptions']( + fieldConfig, + criteriaValues, + tokenContext + ); + expect(false).toBeTruthy(); + } catch (error) { + expect(error.message).toBe( + `Pending response received for critera field data set: ${dataSet}` + ); + } + + // Assert + expect(criteriaFieldOptions).toBeUndefined(); + }); + + test('throws an error if getData returns a non-array data set', async () => { + // Arrange + const dataSource = { + getData: jest.fn().mockResolvedValue({ + status: DataSetResultStatus.Complete, + data: '' + }) + }; + const provider = new JsonCriteriaProvider(appRootDir, dataSource); + + const property = 'property'; + const label = 'label'; + const dataSet = 'dataset'; + const valueProperty = 'valueProp'; + const labelProperty = 'labelProp'; + + const fieldConfig = { + type: HypersyncCriteriaFieldType.Select, + property: property, + label: label, + isRequired: true, + dataSet: dataSet, + valueProperty: valueProperty, + labelProperty: labelProperty + }; + const criteriaValues = {}; + const tokenContext = {}; + + // Act + // Due to async function throwing error, can't just use expect().toThrow() + let criteriaFieldOptions; + try { + criteriaFieldOptions = await provider['getCriteriaFieldOptions']( + fieldConfig, + criteriaValues, + tokenContext + ); + expect(false).toBeTruthy(); + } catch (error) { + expect(error.message).toBe( + `Invalid criteria field data set: ${dataSet}` + ); + } + + // Assert + expect(criteriaFieldOptions).toBeUndefined(); + }); + + test('returns empty array if no dataSet or fixedValues', async () => { + // Arrange + const property = 'property'; + const label = 'label'; + const valueProperty = 'valueProp'; + const labelProperty = 'labelProp'; + + const fieldConfig = { + type: HypersyncCriteriaFieldType.Select, + property: property, + label: label, + isRequired: true, + valueProperty: valueProperty, + labelProperty: labelProperty + }; + const criteriaValues = {}; + const tokenContext = {}; + + const expectedResponse = []; + + // Act + const criteriaFieldOptions = await provider['getCriteriaFieldOptions']( + fieldConfig, + criteriaValues, + tokenContext + ); + + // Assert + expect(criteriaFieldOptions).toBeDefined(); + expect(criteriaFieldOptions).toEqual(expectedResponse); + }); + + test('returns empty array if no valueProperty or fixedValue', async () => { + // Arrange + const property = 'property'; + const label = 'label'; + const dataSet = 'dataset'; + const labelProperty = 'labelProp'; + + const fieldConfig = { + type: HypersyncCriteriaFieldType.Select, + property: property, + label: label, + isRequired: true, + dataSet: dataSet, + labelProperty: labelProperty + }; + const criteriaValues = {}; + const tokenContext = {}; + + const expectedResponse = []; + + // Act + const criteriaFieldOptions = await provider['getCriteriaFieldOptions']( + fieldConfig, + criteriaValues, + tokenContext + ); + + // Assert + expect(criteriaFieldOptions).toBeDefined(); + expect(criteriaFieldOptions).toEqual(expectedResponse); + }); + + test('returns empty array if no labelProperty or fixedValue', async () => { + // Arrange + const property = 'property'; + const label = 'label'; + const dataSet = 'dataset'; + const valueProperty = 'valueProp'; + + const fieldConfig = { + type: HypersyncCriteriaFieldType.Select, + property: property, + label: label, + isRequired: true, + dataSet: dataSet, + valueProperty: valueProperty + }; + const criteriaValues = {}; + const tokenContext = {}; + + const expectedResponse = []; + + // Act + const criteriaFieldOptions = await provider['getCriteriaFieldOptions']( + fieldConfig, + criteriaValues, + tokenContext + ); + + // Assert + expect(criteriaFieldOptions).toBeDefined(); + expect(criteriaFieldOptions).toEqual(expectedResponse); + }); + + test('returns appropriate JSON if getData returns non-empty array', async () => { + // Arrange + const firstLabel = 'firstLabel'; + const firstValue = 'firstValue'; + const secondLabel = 'secondLabel'; + const secondValue = 'secondValue'; + + const dataSource = { + getData: jest.fn().mockResolvedValue({ + status: DataSetResultStatus.Complete, + data: [ + { + labelProp: firstLabel, + valueProp: firstValue + }, + { + labelProp: secondLabel, + valueProp: secondValue + } + ] + }) + }; + const provider = new JsonCriteriaProvider(appRootDir, dataSource); + + const property = 'property'; + const label = 'label'; + const dataSet = 'dataset'; + const valueProperty = 'valueProp'; + const labelProperty = 'labelProp'; + + const fieldConfig = { + type: HypersyncCriteriaFieldType.Select, + property: property, + label: label, + isRequired: true, + dataSet: dataSet, + valueProperty: valueProperty, + labelProperty: labelProperty + }; + const criteriaValues = {}; + const tokenContext = {}; + + const expectedResponse = [ + { + label: firstLabel, + value: firstValue + }, + { + label: secondLabel, + value: secondValue + } + ]; + + // Act + const criteriaFieldOptions = await provider['getCriteriaFieldOptions']( + fieldConfig, + criteriaValues, + tokenContext + ); + + // Assert + expect(criteriaFieldOptions).toBeDefined(); + expect(criteriaFieldOptions).toEqual(expectedResponse); + }); + + test('returns appropriate JSON if only fixedValue is populated', async () => { + // Arrange + const property = 'property'; + const label = 'label'; + const stringValue = { + label: 'string', + value: 'value' + }; + const numberValue = { + label: 'number', + value: 1 + }; + + const fieldConfig = { + type: HypersyncCriteriaFieldType.Select, + property: property, + label: label, + isRequired: true, + fixedValues: [stringValue, numberValue] + }; + const criteriaValues = {}; + const tokenContext = {}; + + const expectedResponse = [stringValue, numberValue]; + + // Act + const criteriaFieldOptions = await provider['getCriteriaFieldOptions']( + fieldConfig, + criteriaValues, + tokenContext + ); + + // Assert + expect(criteriaFieldOptions).toBeDefined(); + expect(criteriaFieldOptions).toEqual(expectedResponse); + }); + + test('returns appropriate JSON if getData and fixedValue are populated', async () => { + // Arrange + const firstLabel = 'firstLabel'; + const firstValue = 'firstValue'; + const secondLabel = 'secondLabel'; + const secondValue = 'secondValue'; + + const dataSource = { + getData: jest.fn().mockResolvedValue({ + status: DataSetResultStatus.Complete, + data: [ + { + labelProp: firstLabel, + valueProp: firstValue + }, + { + labelProp: secondLabel, + valueProp: secondValue + } + ] + }) + }; + const provider = new JsonCriteriaProvider(appRootDir, dataSource); + + const property = 'property'; + const label = 'label'; + const dataSet = 'dataset'; + const valueProperty = 'valueProp'; + const labelProperty = 'labelProp'; + const stringValue = { + label: 'string', + value: 'value' + }; + const numberValue = { + label: 'number', + value: 1 + }; + + const fieldConfig = { + type: HypersyncCriteriaFieldType.Select, + property: property, + label: label, + isRequired: true, + dataSet: dataSet, + valueProperty: valueProperty, + labelProperty: labelProperty, + fixedValues: [stringValue, numberValue] + }; + const criteriaValues = {}; + const tokenContext = {}; + + const expectedResponse = [ + stringValue, + numberValue, + { + label: firstLabel, + value: firstValue + }, + { + label: secondLabel, + value: secondValue + } + ]; + + // Act + const criteriaFieldOptions = await provider['getCriteriaFieldOptions']( + fieldConfig, + criteriaValues, + tokenContext + ); + + // Assert + expect(criteriaFieldOptions).toBeDefined(); + expect(criteriaFieldOptions).toEqual(expectedResponse); + }); + + test('returns appropriate JSON if getData has multiple pages', async () => { + // Arrange + const firstLabel = 'firstLabel'; + const firstValue = 'firstValue'; + const secondLabel = 'secondLabel'; + const secondValue = 'secondValue'; + + const mockGetData = jest.fn(); + mockGetData.mockResolvedValueOnce({ + status: DataSetResultStatus.Complete, + data: [ + { + labelProp: firstLabel, + valueProp: firstValue + } + ], + nextPage: 'true' + }); + mockGetData.mockResolvedValue({ + status: DataSetResultStatus.Complete, + data: [ + { + labelProp: secondLabel, + valueProp: secondValue + } + ] + }); + + const dataSource = { + getData: mockGetData + }; + const provider = new JsonCriteriaProvider(appRootDir, dataSource); + + const property = 'property'; + const label = 'label'; + const dataSet = 'dataset'; + const valueProperty = 'valueProp'; + const labelProperty = 'labelProp'; + const stringValue = { + label: 'string', + value: 'value' + }; + const numberValue = { + label: 'number', + value: 1 + }; + + const fieldConfig = { + type: HypersyncCriteriaFieldType.Select, + property: property, + label: label, + isRequired: true, + dataSet: dataSet, + valueProperty: valueProperty, + labelProperty: labelProperty, + fixedValues: [stringValue, numberValue] + }; + const criteriaValues = {}; + const tokenContext = {}; + + const expectedResponse = [ + stringValue, + numberValue, + { + label: firstLabel, + value: firstValue + }, + { + label: secondLabel, + value: secondValue + } + ]; + + // Act + const criteriaFieldOptions = await provider['getCriteriaFieldOptions']( + fieldConfig, + criteriaValues, + tokenContext + ); + + // Assert + expect(criteriaFieldOptions).toBeDefined(); + expect(criteriaFieldOptions).toEqual(expectedResponse); + }); + + test('getData is called with search input for criteria search field type', async () => { + // Arrange + const fieldConfig = { + type: HypersyncCriteriaFieldType.Search, + property: 'property', + label: 'label', + isRequired: true, + dataSet: 'dataset', + valueProperty: 'id', + labelProperty: 'name' + }; + const criteriaValues = {}; + const tokenContext = {}; + const searchInput = { + searchKey: { value: 'searchValue', offset: '0' } + }; + + const mockGetData = jest.fn().mockResolvedValue({ + status: DataSetResultStatus.Complete, + data: [] + }); + const provider = new JsonCriteriaProvider(appRootDir, { + getData: mockGetData + }); + + // Act + await provider['getCriteriaFieldOptions']( + fieldConfig, + criteriaValues, + tokenContext, + searchInput + ); + + // Assert + expect(mockGetData).toHaveBeenCalledWith( + 'dataset', + expect.objectContaining({ searchKey: 'searchValue' }), + undefined + ); + }); + }); + }); +}); diff --git a/src/hypersync/JsonCriteriaProvider.ts b/src/hypersync/JsonCriteriaProvider.ts index d481e59..bb6e352 100644 --- a/src/hypersync/JsonCriteriaProvider.ts +++ b/src/hypersync/JsonCriteriaProvider.ts @@ -16,8 +16,8 @@ import { ICriteriaSearchInput, IProofCriterionRef, ISelectOption -} from '@hyperproof/hypersync-models'; -import { compareValues, Logger } from '@hyperproof/integration-sdk'; +} from '@hyperproof-int/hypersync-models'; +import { compareValues, Logger } from '@hyperproof-int/integration-sdk'; import fs from 'fs'; import path from 'path'; diff --git a/src/hypersync/JsonProofProvider.test.ts b/src/hypersync/JsonProofProvider.test.ts new file mode 100644 index 0000000..333c262 --- /dev/null +++ b/src/hypersync/JsonProofProvider.test.ts @@ -0,0 +1,1580 @@ +import { ID_ALL, ID_ANY, ID_NONE, ID_UNDEFINED } from './common'; +import { ICriteriaProvider } from './ICriteriaProvider'; +import { DataSetResultStatus, IDataSource } from './IDataSource'; +import { JsonProofProvider } from './JsonProofProvider'; +import { MESSAGES } from './messages'; +import { dateToLocalizedString } from './time'; + +import { + HypersyncCriteriaFieldType, + HypersyncDataFormat, + HypersyncFieldFormat, + HypersyncFieldType, + HypersyncPageOrientation, + HypersyncPeriod, + IProofSpec +} from '@hyperproof-int/hypersync-models'; +import { + HypersyncProofFormat, + HypersyncTemplate, + IHypersync +} from '@hyperproof-int/hypersync-sdk/lib'; +import { + ILocalizable, + IntegrationSettingsClass, + ObjectType +} from '@hyperproof-int/integration-sdk'; + +const CONNECTOR_NAME = 'testConnector'; +const PROOF_TYPE = 'testProofType'; +const JSON_MESSAGES = {}; +const DESCRIPTION = 'test description'; +const PERIOD = HypersyncPeriod.Daily; +const TEST_NAME = 'testName'; +const TEST_FORMAT = HypersyncDataFormat.Tabular; +const TEST_TITLE = 'testTitle'; +const TEST_SUBTITLE = 'testSubtitle'; +const TEST_DATASET = 'testDataSet'; +const NO_RESULTS_MESSAGE = 'No results message'; +const FIELD_PROPERTY = 'propertyValue'; +const FIELD_LABEL = 'labelValue'; +const FIELD_TYPE = HypersyncFieldType.Number; + +const FIELDS = [ + { + property: FIELD_PROPERTY, + label: FIELD_LABEL, + type: FIELD_TYPE + } +]; + +const PROOF_SPEC = { + period: PERIOD, + useVersioning: false, + suggestedName: TEST_NAME, + format: TEST_FORMAT, + title: TEST_TITLE, + subtitle: TEST_SUBTITLE, + dataSet: TEST_DATASET, + noResultsMessage: NO_RESULTS_MESSAGE, + fields: FIELDS +}; + +const CRITERIA = [ + { + name: 'testCriterion', + page: 0 + } +]; + +const DEFINITION = { + description: DESCRIPTION, + criteria: CRITERIA, + proofSpec: PROOF_SPEC, + overrides: [] +}; + +const GET_DATA = { + dataProperty: 'dataProperty', + dataLabel: 'dataLabel' +}; + +const PROOF_CRITERIA = [ + { + label: 'Criterion Label', + name: 'Criterion Name', + value: 'Criterion Value' + } +]; + +const GET_DEFINITION = jest.fn().mockResolvedValue(DEFINITION); + +const getHyperproofOrganization = (): ILocalizable => { + return { + language: 'en', + locale: 'US', + timeZone: 'America/Los_Angeles' + }; +}; + +describe('JsonProofProvider', () => { + let criteriaProvider: ICriteriaProvider; + let dataSource: IDataSource; + + beforeEach(() => { + dataSource = { + getData: jest.fn().mockResolvedValue({ + status: DataSetResultStatus.Complete, + data: [GET_DATA] + }) + }; + + criteriaProvider = { + generateProofCategoryField: jest.fn().mockResolvedValue({}), + generateCriteriaFields: jest.fn().mockResolvedValue(undefined), + generateProofCriteria: jest.fn().mockResolvedValue(PROOF_CRITERIA) + }; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('initializes correctly', () => { + // Arrange + // Act + const provider = new JsonProofProvider( + CONNECTOR_NAME, + PROOF_TYPE, + dataSource, + criteriaProvider, + JSON_MESSAGES, + GET_DEFINITION + ); + + // Assert + expect(provider).toBeDefined(); + expect(provider['connectorName']).toEqual(CONNECTOR_NAME); + expect(provider['proofType']).toEqual(PROOF_TYPE); + expect(provider['dataSource']).toEqual(dataSource); + expect(provider['criteriaProvider']).toEqual(criteriaProvider); + expect(provider['messages']).toEqual(JSON_MESSAGES); + expect(provider['getDefinition']).toEqual(GET_DEFINITION); + }); + + describe('is initialized and', () => { + const FORMATTED = 'Formatted'; + let provider: JsonProofProvider; + + beforeEach(() => { + provider = new JsonProofProvider( + CONNECTOR_NAME, + PROOF_TYPE, + dataSource, + criteriaProvider, + JSON_MESSAGES, + GET_DEFINITION + ); + }); + + describe('generateCriteriaMetadata', () => { + beforeEach(() => { + provider['initTokenContext'] = jest.fn().mockReturnValue({}); + provider['buildProofSpec'] = jest.fn().mockReturnValue(PROOF_SPEC); + provider['findCriteriaLabels'] = jest.fn().mockReturnValue({}); + }); + + it('returns pages with default values when last page is not valid', async () => { + // Arrange + const criteriaValues = {}; + const pages = [ + { + fields: [], + isValid: true + }, + { + fields: [], + isValid: false + } + ]; + + const expectedMetadata = { + pages: pages, + period: HypersyncPeriod.Monthly, + useVersioning: false, + suggestedName: '', + description: DESCRIPTION, + enableExcelOutput: true + }; + + // Act + const criteriaMetadata = await provider.generateCriteriaMetadata( + criteriaValues, + pages + ); + + // Assert + expect(criteriaMetadata).toBeDefined(); + expect(criteriaMetadata).toEqual(expectedMetadata); + }); + + it('returns page with specified values when last page is valid', async () => { + // Arrange + const criteriaValues = {}; + const pages = [ + { + fields: [], + isValid: false + }, + { + fields: [], + isValid: true + } + ]; + + const expectedMetadata = { + pages: pages, + period: PERIOD, + useVersioning: false, + suggestedName: TEST_NAME, + description: DESCRIPTION, + enableExcelOutput: true + }; + + // Act + const criteriaMetadata = await provider.generateCriteriaMetadata( + criteriaValues, + pages + ); + + // Assert + expect(criteriaMetadata).toBeDefined(); + expect(criteriaMetadata).toEqual(expectedMetadata); + }); + }); + + describe('generateSchema', () => { + const criteriaValues = {}; + + beforeEach(() => { + provider['initTokenContext'] = jest.fn().mockReturnValue({}); + }); + + it('returns appropriate values from proof spec', async () => { + // Arrange + provider['buildProofSpec'] = jest.fn().mockReturnValue(PROOF_SPEC); + + const expectedSchema = { + format: PROOF_SPEC.format, + isHierarchical: false, + fields: [ + { + property: FIELD_PROPERTY, + label: FIELD_LABEL, + type: FIELD_TYPE + } + ] + }; + + // Act + const schema = await provider.generateSchema(criteriaValues); + + // Assert + expect(schema).toBeDefined(); + expect(schema).toEqual(expectedSchema); + }); + + it('returns appropriate values from proof spec but no field type', async () => { + // Arrange + const proofSpec = { + ...PROOF_SPEC, + fields: [ + { + property: FIELD_PROPERTY, + label: FIELD_LABEL + } + ] + }; + + provider['buildProofSpec'] = jest.fn().mockReturnValue(proofSpec); + + const expectedSchema = { + format: PROOF_SPEC.format, + isHierarchical: false, + fields: [ + { + property: FIELD_PROPERTY, + label: FIELD_LABEL, + type: HypersyncFieldType.Text + } + ] + }; + + // Act + const schema = await provider.generateSchema(criteriaValues); + + // Assert + expect(schema).toBeDefined(); + expect(schema).toEqual(expectedSchema); + }); + }); + + describe('getProofData', () => { + const field = { + property: FIELD_PROPERTY, + label: FIELD_LABEL, + type: HypersyncFieldType.Number, + width: '100px' + }; + const proofSpec = { + ...PROOF_SPEC, + orientation: HypersyncPageOrientation.Landscape, + fields: [field] + }; + + const hypersync = buildHypersync(); + const organization = getHyperproofOrganization(); + const authorizedUser = 'authorizedUser'; + const syncStartDate = new Date(); + + beforeEach(() => { + provider['getDefinition'] = jest.fn().mockResolvedValue(DEFINITION); + provider['initTokenContext'] = jest.fn().mockReturnValue({}); + provider['buildProofSpec'] = jest.fn().mockReturnValue(proofSpec); + provider['fetchLookups'] = jest.fn(); + provider['addFormattedDates'] = jest.fn(); + provider['addFormattedNumbers'] = jest.fn(); + }); + + it('returns empty array and getData status if getData does not complete', async () => { + // Arrange + dataSource = { + getData: jest.fn().mockResolvedValue({ + status: DataSetResultStatus.Pending + }) + }; + const provider = new JsonProofProvider( + CONNECTOR_NAME, + PROOF_TYPE, + dataSource, + criteriaProvider, + JSON_MESSAGES, + GET_DEFINITION + ); + provider['getDefinition'] = jest.fn().mockResolvedValue({}); + provider['initTokenContext'] = jest.fn().mockReturnValue({}); + provider['buildProofSpec'] = jest.fn().mockReturnValue({}); + provider['fetchLookups'] = jest.fn(); + + const expectedProofData = { + status: DataSetResultStatus.Pending, + data: [] + }; + + // Act + const proofData = await provider.getProofData( + hypersync, + organization, + authorizedUser, + syncStartDate + ); + + // Assert + expect(proofData).toBeDefined(); + expect(proofData).toEqual(expectedProofData); + expect(provider['getDefinition']).toHaveBeenCalled(); + expect(provider['initTokenContext']).toHaveBeenCalled(); + expect(provider['buildProofSpec']).toHaveBeenCalled(); + expect(provider['fetchLookups']).toHaveBeenCalled(); + }); + + it('calls addFormattedNumbers if field includes numbers', async () => { + // Arrange + const expectedProofData = buildExpectedProofData( + proofSpec, + hypersync, + organization, + syncStartDate, + authorizedUser + ); + + // Act + const proofData = await provider.getProofData( + hypersync, + organization, + authorizedUser, + syncStartDate + ); + + // Assert + expect(proofData).toBeDefined(); + expect(proofData).toEqual(expectedProofData); + + expect(provider['getDefinition']).toHaveBeenCalled(); + expect(provider['initTokenContext']).toHaveBeenCalled(); + expect(provider['buildProofSpec']).toHaveBeenCalled(); + expect(provider['fetchLookups']).toHaveBeenCalled(); + expect(provider['addFormattedDates']).not.toHaveBeenCalled(); + expect(provider['addFormattedNumbers']).toHaveBeenCalled(); + }); + + it('calls addFormattedDates if field includes dates', async () => { + // Arrange + const field = { + property: FIELD_PROPERTY, + label: FIELD_LABEL, + type: HypersyncFieldType.Date, + width: '100px' + }; + const proofSpec = { + ...PROOF_SPEC, + orientation: HypersyncPageOrientation.Landscape, + fields: [field] + }; + provider['buildProofSpec'] = jest.fn().mockReturnValue(proofSpec); + + const expectedProofData = buildExpectedProofData( + proofSpec, + hypersync, + organization, + syncStartDate, + authorizedUser + ); + + // Act + const proofData = await provider.getProofData( + hypersync, + organization, + authorizedUser, + syncStartDate + ); + + // Assert + expect(proofData).toBeDefined(); + expect(proofData).toEqual(expectedProofData); + + expect(provider['getDefinition']).toHaveBeenCalled(); + expect(provider['initTokenContext']).toHaveBeenCalled(); + expect(provider['buildProofSpec']).toHaveBeenCalled(); + expect(provider['fetchLookups']).toHaveBeenCalled(); + expect(provider['addFormattedDates']).toHaveBeenCalled(); + expect(provider['addFormattedNumbers']).not.toHaveBeenCalled(); + }); + + it('calls both addFormattedNumbers and addFormattedDates if field includes both', async () => { + // Arrange + const field = [ + { + property: FIELD_PROPERTY, + label: FIELD_LABEL, + type: HypersyncFieldType.Number, + width: '100px' + }, + { + property: FIELD_PROPERTY, + label: FIELD_LABEL, + type: HypersyncFieldType.Date, + width: '100px' + } + ]; + const proofSpec = { + ...PROOF_SPEC, + orientation: HypersyncPageOrientation.Landscape, + fields: field + }; + provider['buildProofSpec'] = jest.fn().mockReturnValue(proofSpec); + + const expectedProofData = buildExpectedProofData( + proofSpec, + hypersync, + organization, + syncStartDate, + authorizedUser + ); + + // Act + const proofData = await provider.getProofData( + hypersync, + organization, + authorizedUser, + syncStartDate + ); + + // Assert + expect(proofData).toBeDefined(); + expect(proofData).toEqual(expectedProofData); + + expect(provider['getDefinition']).toHaveBeenCalled(); + expect(provider['initTokenContext']).toHaveBeenCalled(); + expect(provider['buildProofSpec']).toHaveBeenCalled(); + expect(provider['fetchLookups']).toHaveBeenCalled(); + expect(provider['addFormattedDates']).toHaveBeenCalled(); + expect(provider['addFormattedNumbers']).toHaveBeenCalled(); + }); + + it('calls neither addFormatedNumbers and addFormattedDates if field includes neither', async () => { + // Arrange + const field = { + property: FIELD_PROPERTY, + label: FIELD_LABEL, + type: HypersyncFieldType.Boolean, + width: '100px' + }; + const proofSpec = { + ...PROOF_SPEC, + orientation: HypersyncPageOrientation.Landscape, + fields: [field] + }; + provider['buildProofSpec'] = jest.fn().mockReturnValue(proofSpec); + + const expectedProofData = buildExpectedProofData( + proofSpec, + hypersync, + organization, + syncStartDate, + authorizedUser + ); + + // Act + const proofData = await provider.getProofData( + hypersync, + organization, + authorizedUser, + syncStartDate + ); + + // Assert + expect(proofData).toBeDefined(); + expect(proofData).toEqual(expectedProofData); + + expect(provider['getDefinition']).toHaveBeenCalled(); + expect(provider['initTokenContext']).toHaveBeenCalled(); + expect(provider['buildProofSpec']).toHaveBeenCalled(); + expect(provider['fetchLookups']).toHaveBeenCalled(); + expect(provider['addFormattedDates']).not.toHaveBeenCalled(); + expect(provider['addFormattedNumbers']).not.toHaveBeenCalled(); + }); + + it('returns empty array for criteria if page is included', async () => { + // Arrange + const page = 'page'; + + const builtProofData = buildExpectedProofData( + proofSpec, + hypersync, + organization, + syncStartDate, + authorizedUser + ); + const expectedProofData = { + ...builtProofData, + data: [ + { + ...builtProofData.data[0], + contents: { + ...builtProofData.data[0].contents, + criteria: [] + } + } + ] + }; + + // Act + const proofData = await provider.getProofData( + hypersync, + organization, + authorizedUser, + syncStartDate, + page + ); + + // Assert + expect(proofData).toBeDefined(); + expect(proofData).toEqual(expectedProofData); + + expect(provider['getDefinition']).toHaveBeenCalled(); + expect(provider['initTokenContext']).toHaveBeenCalled(); + expect(provider['buildProofSpec']).toHaveBeenCalled(); + expect(provider['fetchLookups']).toHaveBeenCalled(); + }); + + it('returns proof as array even when getData is not an array', async () => { + // Arrange + const getDataData = { + dataProperty: 'dataProperty', + dataLabel: 'dataLabel' + }; + dataSource = { + getData: jest.fn().mockResolvedValue({ + status: DataSetResultStatus.Complete, + data: getDataData + }) + }; + const provider = new JsonProofProvider( + CONNECTOR_NAME, + PROOF_TYPE, + dataSource, + criteriaProvider, + JSON_MESSAGES, + GET_DEFINITION + ); + provider['getDefinition'] = jest.fn().mockResolvedValue(DEFINITION); + provider['initTokenContext'] = jest.fn().mockReturnValue({}); + provider['buildProofSpec'] = jest.fn().mockReturnValue(proofSpec); + provider['fetchLookups'] = jest.fn(); + + const expectedProofData = buildExpectedProofData( + proofSpec, + hypersync, + organization, + syncStartDate, + authorizedUser + ); + + // Act + const proofData = await provider.getProofData( + hypersync, + organization, + authorizedUser, + syncStartDate + ); + + // Assert + expect(proofData).toBeDefined(); + expect(proofData).toEqual(expectedProofData); + + expect(provider['getDefinition']).toHaveBeenCalled(); + expect(provider['initTokenContext']).toHaveBeenCalled(); + expect(provider['buildProofSpec']).toHaveBeenCalled(); + expect(provider['fetchLookups']).toHaveBeenCalled(); + }); + + it('returns an empty proof and no result message if data is empty array', async () => { + // Arrange + dataSource = { + getData: jest.fn().mockResolvedValue({ + status: DataSetResultStatus.Complete, + data: [] + }) + }; + const provider = new JsonProofProvider( + CONNECTOR_NAME, + PROOF_TYPE, + dataSource, + criteriaProvider, + JSON_MESSAGES, + GET_DEFINITION + ); + provider['getDefinition'] = jest.fn().mockResolvedValue(DEFINITION); + provider['initTokenContext'] = jest.fn().mockReturnValue({}); + provider['buildProofSpec'] = jest.fn().mockReturnValue(proofSpec); + provider['fetchLookups'] = jest.fn(); + + const builtProofData = buildExpectedProofData( + proofSpec, + hypersync, + organization, + syncStartDate, + authorizedUser + ); + const expectedProofData = { + ...builtProofData, + data: [ + { + ...builtProofData.data[0], + contents: { + ...builtProofData.data[0].contents, + layout: { + ...builtProofData.data[0].contents.layout, + noResultsMessage: NO_RESULTS_MESSAGE + }, + proof: [] + } + } + ] + }; + + // Act + const proofData = await provider.getProofData( + hypersync, + organization, + authorizedUser, + syncStartDate + ); + + // Assert + expect(proofData).toBeDefined(); + expect(proofData).toEqual(expectedProofData); + + expect(provider['getDefinition']).toHaveBeenCalled(); + expect(provider['initTokenContext']).toHaveBeenCalled(); + expect(provider['buildProofSpec']).toHaveBeenCalled(); + expect(provider['fetchLookups']).toHaveBeenCalled(); + }); + + function buildHypersync(): IHypersync { + const hypersyncSettingsClass: IntegrationSettingsClass.Hypersync = + IntegrationSettingsClass.Hypersync; + return { + id: 'hypersyncId', + appId: 'appId', + objectId: 'objectId', + objectType: ObjectType.LABEL, + orgId: 'orgId', + createdBy: 'hypersyncAuthor', + createdOn: '2020-01-01T00:00:00Z', + updatedBy: 'otherAuthor', + updatedOn: '2021-01-01T00:00:00Z', + settings: { + class: hypersyncSettingsClass, + vendorUserId: 'vendorUserId', + name: 'hypersyncName', + criteria: {}, + isAutomatic: false, + period: HypersyncPeriod.Monthly, + useVersioning: false, + proofFormat: HypersyncProofFormat.PDF, + isEnabled: true + } + }; + } + + function buildExpectedProofData( + proofSpec: IProofSpec, + hypersync: IHypersync, + organization: ILocalizable, + syncStartDate: Date, + authorizedUser: string + ) { + return { + nextPage: undefined, + data: [ + { + filename: hypersync.settings.name, + contents: { + type: process.env.integration_type!, + title: proofSpec.title, + subtitle: proofSpec.subtitle, + source: undefined, + orientation: proofSpec.orientation, + userTimeZone: organization.timeZone, + criteria: PROOF_CRITERIA, + proofFormat: hypersync.settings.proofFormat, + template: HypersyncTemplate.UNIVERSAL, + layout: { + format: proofSpec.format, + noResultsMessage: '', + fields: proofSpec.fields + }, + proof: [GET_DATA], + authorizedUser, + collector: CONNECTOR_NAME, + collectedOn: dateToLocalizedString( + syncStartDate, + organization.timeZone, + organization.language, + organization.locale + )!, + errorInfo: undefined, + zoom: 1 + } + } + ] + }; + } + }); + + describe('initTokenContext', () => { + // There is no logic in this function, so just testing that we get the correct data back + it('returns appropriate values', () => { + // Arrange + const criteriaValues = { + proofType: PROOF_TYPE, + name: 'criterionValue' + }; + + const expectedContext = { + messages: JSON_MESSAGES, + constants: { + ID_ALL: ID_ALL, + ID_ANY: ID_ANY, + ID_NONE: ID_NONE, + ID_UNDEFINED: ID_UNDEFINED + }, + criteria: criteriaValues, + lookups: {} + }; + + // Act + const tokenContext = provider['initTokenContext'](criteriaValues); + + // Assert + expect(tokenContext).toBeDefined(); + expect(tokenContext).toEqual(expectedContext); + }); + }); + + describe('buildProofSpec', () => { + const tokenContext = {}; + + it('returns proof spec when no override', () => { + // Arrange + const expectedProofSpec = { + ...PROOF_SPEC + }; + + // Act + const proofSpec = provider['buildProofSpec'](DEFINITION, tokenContext); + + // Assert + expect(proofSpec).toBeDefined(); + expect(proofSpec).toEqual(expectedProofSpec); + }); + + it('returns proof spec with appropriate overridden values', () => { + // Arrange + const overrideString = 'override'; + const overrides = [ + { + condition: { + value: overrideString, + criteria: overrideString + }, + proofSpec: { + useVersioning: !PROOF_SPEC.useVersioning + } + } + ]; + const definition = { + ...DEFINITION, + overrides: overrides + }; + + const expectedProofSpec = { + ...PROOF_SPEC, + useVersioning: !PROOF_SPEC.useVersioning + }; + + // Act + const proofSpec = provider['buildProofSpec'](definition, tokenContext); + + // Assert + expect(proofSpec).toBeDefined(); + expect(proofSpec).toEqual(expectedProofSpec); + }); + + it('returns appropriate message when no noResultsMessage given', () => { + // Arrange + const definitionProofSpec = { + ...PROOF_SPEC, + noResultsMessage: undefined + }; + const definition = { + ...DEFINITION, + proofSpec: definitionProofSpec + }; + + const expectedProofSpec = { + ...PROOF_SPEC, + noResultsMessage: MESSAGES.Default.NoResultsMessage + }; + + // Act + const proofSpec = provider['buildProofSpec'](definition, tokenContext); + + // Assert + expect(proofSpec).toBeDefined(); + expect(proofSpec).toEqual(expectedProofSpec); + }); + + it('returns proof spec with appropriate overridden dataSetParams', () => { + // Arrange + const overrideString = 'override'; + const overrides = [ + { + condition: { + value: overrideString, + criteria: overrideString + }, + proofSpec: { + dataSetParams: { + param1: 'value1' + } + } + } + ]; + const definition = { + ...DEFINITION, + proofSpec: { + ...DEFINITION.proofSpec, + dataSetParams: { + param1: 'oldValue', + param2: 'value2' + } + }, + overrides: overrides + }; + + const expectedProofSpec = { + ...PROOF_SPEC, + dataSetParams: { + param1: 'value1', + param2: 'value2' + } + }; + + // Act + const proofSpec = provider['buildProofSpec'](definition, tokenContext); + + // Assert + expect(proofSpec).toBeDefined(); + expect(proofSpec).toEqual(expectedProofSpec); + }); + }); + + describe('fetchLookups', () => { + const tokenContext = {}; + + it('does not change the tokenContext if there are no lookups', async () => { + // Arrange + const expectedTokenContext = {}; + + // Act + await provider['fetchLookups'](PROOF_SPEC, tokenContext); + + // Assert + expect(tokenContext).toEqual(expectedTokenContext); + }); + + it('does not change the tokenContext if lookups is an empty array', async () => { + // Arrange + const proofSpec = { + ...PROOF_SPEC, + lookups: [] + }; + + const expectedTokenContext = {}; + + // Act + await provider['fetchLookups'](proofSpec, tokenContext); + + // Assert + expect(tokenContext).toEqual(expectedTokenContext); + }); + + it('throws an error if getData does not complete', async () => { + // Arrange + const proofSpec = { + ...PROOF_SPEC, + lookups: [ + { + name: 'lookupName', + dataSet: 'dataSetName' + } + ] + }; + + dataSource = { + getData: jest.fn().mockResolvedValue({ + status: DataSetResultStatus.Pending, + data: [] + }) + }; + provider = new JsonProofProvider( + CONNECTOR_NAME, + PROOF_TYPE, + dataSource, + criteriaProvider, + JSON_MESSAGES, + GET_DEFINITION + ); + + const expectedTokenContext = {}; + + // Act + try { + await provider['fetchLookups'](proofSpec, tokenContext); + expect(false).toBeTruthy(); + } catch (error) { + expect(error.message).toEqual( + `Pending response received for proof specification lookup data set: ${proofSpec.lookups[0].dataSet}` + ); + } + + // Assert + expect(tokenContext).toEqual(expectedTokenContext); + }); + + it('adds values from getData lookups to tokenContext lookups', async () => { + // Arrange + const lookupFromGetData = { + name: 'dataName', + dataSet: 'dataDataSet' + }; + const lookupFromSpec = { + name: 'specName', + dataSet: 'specDataSet' + }; + const lookupExisting = { + name: 'existingName', + dataSet: 'existingDataSet' + }; + dataSource = { + getData: jest.fn().mockResolvedValue({ + status: DataSetResultStatus.Complete, + data: [lookupFromGetData] + }) + }; + provider = new JsonProofProvider( + CONNECTOR_NAME, + PROOF_TYPE, + dataSource, + criteriaProvider, + JSON_MESSAGES, + GET_DEFINITION + ); + + const tokenContext = { + lookups: { + existing: [lookupExisting] + } + }; + const proofSpec = { + ...PROOF_SPEC, + lookups: [lookupFromSpec] + }; + + const expectedTokenContext = { + lookups: { + existing: [lookupExisting], + [lookupFromSpec.name]: [lookupFromGetData] + } + }; + + // Act + await provider['fetchLookups'](proofSpec, tokenContext); + + // Assert + expect(tokenContext).toEqual(expectedTokenContext); + }); + }); + + describe('findCriteriaLabels', () => { + const page = { + fields: [ + { + name: 'page', + type: HypersyncCriteriaFieldType.Text, + label: 'pageLabel', + options: [ + { + value: 'optionValue', + label: 'optionLabel' + } + ] + } + ], + isValid: true + }; + const pages = [page]; + + it('returns an empty object if no criteria are passed in', () => { + // Arrange + const expectedCriteriaLabels = {}; + + // Act + const criteriaLabels = provider['findCriteriaLabels'](pages); + + // Assert + expect(criteriaLabels).toBeDefined(); + expect(criteriaLabels).toEqual(expectedCriteriaLabels); + }); + + it('returns an empty object if an empty criteria are passed in', () => { + // Arrange + const criteria = {}; + + const expectedCriteriaLabels = {}; + + // Act + const criteriaLabels = provider['findCriteriaLabels'](pages, criteria); + + // Assert + expect(criteriaLabels).toBeDefined(); + expect(criteriaLabels).toEqual(expectedCriteriaLabels); + }); + + it('returns an empty object if no field matches the criteria', () => { + // Arrange + const criteria = { + mismatchedPage: 'optionValue' + }; + + const expectedCriteriaLabels = {}; + + // Act + const criteriaLabels = provider['findCriteriaLabels'](pages, criteria); + + // Assert + expect(criteriaLabels).toBeDefined(); + expect(criteriaLabels).toEqual(expectedCriteriaLabels); + }); + + it('returns an empty object if matching page has no options', () => { + // Arrange + const expectedCriteriaName = 'page'; + + const page = { + fields: [ + { + name: expectedCriteriaName, + type: HypersyncCriteriaFieldType.Text, + label: 'pageLabel' + } + ], + isValid: true + }; + const pages = [page]; + + const criteria = { + [expectedCriteriaName]: 'pageOptionValue' + }; + + const expectedCriteriaLabels = {}; + + // Act + const criteriaLabels = provider['findCriteriaLabels'](pages, criteria); + + // Assert + expect(criteriaLabels).toBeDefined(); + expect(criteriaLabels).toEqual(expectedCriteriaLabels); + }); + + it('returns appropriate values if good criteria are passed in', () => { + // Arrange + const expectedCriteriaName = 'page'; + const criteriaLookupName = 'pageOptionValue'; + const expectedCriteriaValue = 'pageOptionLabel'; + + const page = { + fields: [ + { + name: expectedCriteriaName, + type: HypersyncCriteriaFieldType.Text, + label: 'pageLabel', + options: [ + { + value: criteriaLookupName, + label: expectedCriteriaValue + } + ] + } + ], + isValid: true + }; + const pages = [page]; + + const criteria = { + [expectedCriteriaName]: criteriaLookupName + }; + + const expectedCriteriaLabels = { + [expectedCriteriaName]: expectedCriteriaValue + }; + + // Act + const criteriaLabels = provider['findCriteriaLabels'](pages, criteria); + + // Assert + expect(criteriaLabels).toBeDefined(); + expect(criteriaLabels).toEqual(expectedCriteriaLabels); + }); + }); + + describe('addFormattedValues', () => { + const organization = getHyperproofOrganization(); + const proofRow = { + row: 'value' + }; + const dateFields = []; + const numberFields = []; + const addFormattedDates = jest.fn(); + const addFormattedNumbers = jest.fn(); + + beforeEach(() => { + provider['addFormattedDates'] = addFormattedDates; + provider['addFormattedNumbers'] = addFormattedNumbers; + }); + + it('does nothing if no dates or numbers are passed in', () => { + // Arrange + // Act + provider['addFormattedValues']( + proofRow, + dateFields, + numberFields, + organization + ); + + // Assert + expect(addFormattedDates).not.toHaveBeenCalled(); + expect(addFormattedNumbers).not.toHaveBeenCalled(); + }); + + it('calls addFormattedDates if dates are passed in', () => { + // Arrange + const dateFields = [ + { + property: 'dateField', + label: 'Date Field' + } + ]; + + // Act + provider['addFormattedValues']( + proofRow, + dateFields, + numberFields, + organization + ); + + // Assert + expect(addFormattedDates).toHaveBeenCalledWith( + proofRow, + dateFields, + organization + ); + expect(addFormattedNumbers).not.toHaveBeenCalled(); + }); + + it('calls addFormattedNumbers if numbers are passed in', () => { + // Arrange + const numberFields = [ + { + property: 'numberField', + label: 'Number Field' + } + ]; + + // Act + provider['addFormattedValues']( + proofRow, + dateFields, + numberFields, + organization + ); + + // Assert + expect(addFormattedDates).not.toHaveBeenCalled(); + expect(addFormattedNumbers).toHaveBeenCalledWith( + proofRow, + numberFields + ); + }); + + it('calls both if dates and numbers are passed in', () => { + // Arrange + const dateFields = [ + { + property: 'dateField', + label: 'Date Field' + } + ]; + const numberFields = [ + { + property: 'numberField', + label: 'Number Field' + } + ]; + + // Act + provider['addFormattedValues']( + proofRow, + dateFields, + numberFields, + organization + ); + + // Assert + expect(addFormattedDates).toHaveBeenCalledWith( + proofRow, + dateFields, + organization + ); + expect(addFormattedNumbers).toHaveBeenCalledWith( + proofRow, + numberFields + ); + }); + }); + + describe('addFormattedDates', () => { + const organization = getHyperproofOrganization(); + const rowName = 'rowName'; + const dateFields = [ + { + property: rowName, + label: 'Date Field' + } + ]; + + it('does nothing if proof row is already formatted', () => { + // Arrange + const rowValue = '2020-01-01T00:00:00Z'; + const proofRow = { + [rowName + FORMATTED]: rowValue + }; + + const expectedProofRow = { + [rowName + FORMATTED]: rowValue + }; + + // Act + provider['addFormattedDates'](proofRow, dateFields, organization); + + // Assert + expect(proofRow).toEqual(expectedProofRow); + }); + + it('does nothing if proof row value is not a date or string', () => { + // Arrange + const rowValue = 123; + const proofRow = { + [rowName]: rowValue + }; + + const expectedProofRow = { + [rowName]: rowValue + }; + + // Act + provider['addFormattedDates'](proofRow, dateFields, organization); + + // Assert + expect(proofRow).toEqual(expectedProofRow); + }); + + it('formats date if it is a string', () => { + // Arrange + const rowValue = '2020-01-01T00:00:00Z'; + const proofRow = { + [rowName]: rowValue + }; + + const expectedDate = dateToLocalizedString( + new Date(rowValue), + organization.timeZone, + organization.language, + organization.locale + ); + const expectedProofRow = { + [rowName]: rowValue, + [rowName + FORMATTED]: expectedDate + }; + + // Act + provider['addFormattedDates'](proofRow, dateFields, organization); + + // Assert + expect(proofRow).toEqual(expectedProofRow); + }); + + it('formats date if it is Date object', () => { + // Arrange + const rowValue = new Date('2020-01-01T00:00:00Z'); + const proofRow = { + [rowName]: rowValue + }; + + const expectedDate = dateToLocalizedString( + new Date(rowValue), + organization.timeZone, + organization.language, + organization.locale + ); + const expectedProofRow = { + [rowName]: rowValue, + [rowName + FORMATTED]: expectedDate + }; + + // Act + provider['addFormattedDates'](proofRow, dateFields, organization); + + // Assert + expect(proofRow).toEqual(expectedProofRow); + }); + }); + + describe('addFormattedNumbers', () => { + const rowName = 'rowName'; + const numberFields = [ + { + property: rowName, + label: 'Number Field' + } + ]; + + it('does nothing if proof row is already formatted', () => { + // Arrange + const rowValue = 123; + const proofRow = { + [rowName + FORMATTED]: rowValue + }; + + const formatNumber = jest.fn(); + provider['formatNumber'] = formatNumber; + + const expectedProofRow = { + [rowName + FORMATTED]: rowValue + }; + + // Act + provider['addFormattedNumbers'](proofRow, numberFields); + + // Assert + expect(proofRow).toEqual(expectedProofRow); + expect(formatNumber).not.toHaveBeenCalled(); + }); + + it('does nothing if proof row value is not a number', () => { + // Arrange + const rowValue = '123'; + const proofRow = { + [rowName]: rowValue + }; + + const formatNumber = jest.fn().mockReturnValue(rowValue); + provider['formatNumber'] = formatNumber; + + const expectedProofRow = { + [rowName]: rowValue + }; + + // Act + provider['addFormattedNumbers'](proofRow, numberFields); + + // Assert + expect(proofRow).toEqual(expectedProofRow); + expect(formatNumber).not.toHaveBeenCalled(); + }); + + it('formats number to a string if it is a number', () => { + // Arrange + const rowValue = 123; + const proofRow = { + [rowName]: rowValue + }; + + const formatNumber = jest.fn().mockReturnValue(rowValue.toString()); + provider['formatNumber'] = formatNumber; + + const expectedProofRow = { + [rowName]: rowValue, + [rowName + FORMATTED]: rowValue.toString() + }; + + // Act + provider['addFormattedNumbers'](proofRow, numberFields); + + // Assert + expect(formatNumber).toHaveBeenCalledWith(rowValue, numberFields[0]); + expect(proofRow).toEqual(expectedProofRow); + }); + }); + + describe('formatNumber', () => { + it('returns number as a string', () => { + // Arrange + const value = 123; + const numberField = { + property: 'numberField', + label: 'Number Field' + }; + + const expectedValue = value.toString(); + + // Act + const returnedValue = provider['formatNumber'](value, numberField); + + // Assert + expect(returnedValue).toBeDefined(); + expect(returnedValue).toEqual(expectedValue); + }); + + it('returns number as a two-digit percent if format is percent', () => { + // Arrange + const value = 12; + const numberField = { + property: 'numberField', + label: 'Number Field', + format: HypersyncFieldFormat.Percent + }; + + const expectedValue = '12.00%'; + + // Act + const returnedValue = provider['formatNumber'](value, numberField); + + // Assert + expect(returnedValue).toBeDefined(); + expect(returnedValue).toEqual(expectedValue); + }); + + it('returns number as a two-digit percent if format is percent', () => { + // Arrange + const value = 12.34; + const numberField = { + property: 'numberField', + label: 'Number Field', + format: HypersyncFieldFormat.Percent + }; + + const expectedValue = '12.34%'; + + // Act + const returnedValue = provider['formatNumber'](value, numberField); + + // Assert + expect(returnedValue).toBeDefined(); + expect(returnedValue).toEqual(expectedValue); + }); + + it('returns number as rounded two-digit percent if format is percent', () => { + // Arrange + const value = 12.349; + const numberField = { + property: 'numberField', + label: 'Number Field', + format: HypersyncFieldFormat.Percent + }; + + const expectedValue = '12.35%'; + + // Act + const returnedValue = provider['formatNumber'](value, numberField); + + // Assert + expect(returnedValue).toBeDefined(); + expect(returnedValue).toEqual(expectedValue); + }); + + it('returns 0% if number is 0 and format is percent', () => { + // Arrange + const value = 0; + const numberField = { + property: 'numberField', + label: 'Number Field', + format: HypersyncFieldFormat.Percent + }; + + const expectedValue = '0%'; + + // Act + const returnedValue = provider['formatNumber'](value, numberField); + + // Assert + expect(returnedValue).toBeDefined(); + expect(returnedValue).toEqual(expectedValue); + }); + + it('returns empty string if number is not actually a number and format is percent', () => { + // Arrange + const value = undefined; + const numberField = { + property: 'numberField', + label: 'Number Field', + format: HypersyncFieldFormat.Percent + }; + + const expectedValue = ''; + + // Act + const returnedValue = provider['formatNumber'](value, numberField); + + // Assert + expect(returnedValue).toBeDefined(); + expect(returnedValue).toEqual(expectedValue); + }); + }); + }); +}); diff --git a/src/hypersync/JsonProofProvider.ts b/src/hypersync/JsonProofProvider.ts index 047e19c..51f9ae1 100644 --- a/src/hypersync/JsonProofProvider.ts +++ b/src/hypersync/JsonProofProvider.ts @@ -32,8 +32,8 @@ import { IHypersyncField, IProofSpec, IteratorSource -} from '@hyperproof/hypersync-models'; -import { ILocalizable, Logger } from '@hyperproof/integration-sdk'; +} from '@hyperproof-int/hypersync-models'; +import { ILocalizable, Logger } from '@hyperproof-int/integration-sdk'; import createHttpError from 'http-errors'; import { StatusCodes } from 'http-status-codes'; diff --git a/src/hypersync/Paginator.test.ts b/src/hypersync/Paginator.test.ts new file mode 100644 index 0000000..144916e --- /dev/null +++ b/src/hypersync/Paginator.test.ts @@ -0,0 +1,753 @@ +import { + GraphQLConnectionsPaginator, + NextTokenPaginator, + OffsetAndLimitPaginator, + PageBasedPaginator, + Paginator +} from './Paginator'; + +import { + DataSetMethod, + INextTokenScheme, + IOffsetAndLimitScheme, + IPageBasedScheme, + NextTokenType, + PageUntilCondition, + PagingType +} from '@hyperproof-int/hypersync-models'; +import { IGraphQLConnectionsScheme } from '@hyperproof-int/hypersync-models/src/dataSource'; + +describe('Paginator', () => { + describe('createPaginator', () => { + it('should create a NextTokenPaginator with limit parameter and limit value', () => { + const pagingScheme = { + type: PagingType.NextToken, + tokenType: NextTokenType.Token, + request: { + limitParameter: '$top', + limitValue: 10, + tokenParameter: 'nextToken' + } + } as INextTokenScheme; + const paginator = Paginator.createPaginator(pagingScheme); + expect(paginator).toBeInstanceOf(NextTokenPaginator); + }); + + it('should not create a NextTokenPaginator if limit value is not a positive integer', () => { + const pagingScheme = { + type: PagingType.NextToken, + tokenType: NextTokenType.Token, + request: { + limitParameter: '$top', + limitValue: -10, + tokenParameter: 'nextToken' + } + } as INextTokenScheme; + expect(() => Paginator.createPaginator(pagingScheme)).toThrow( + 'Paginator: Limit value undefined must be a positive integer for nextToken schemes.' + ); + }); + + it('should create a NextTokenPaginator with no limit parameter and no limit value', () => { + const pagingScheme = { + type: PagingType.NextToken, + tokenType: NextTokenType.Token, + request: { + tokenParameter: 'nextToken' + } + } as INextTokenScheme; + const paginator = Paginator.createPaginator(pagingScheme); + expect(paginator).toBeInstanceOf(NextTokenPaginator); + }); + + it('should not create a NextTokenPaginator if on limit parameter or limit value in undefined', () => { + const pagingSchemeWithNoLimitValue = { + type: PagingType.NextToken, + tokenType: NextTokenType.Token, + request: { + limitParameter: '$top', + tokenParameter: 'nextToken' + } + } as INextTokenScheme; + expect(() => + Paginator.createPaginator(pagingSchemeWithNoLimitValue) + ).toThrow( + 'Paginator: Limit value must be defined if request parameters are defined for nextToken schemes.' + ); + }); + + it('should create a PageBasedPaginator', () => { + const pagingScheme = { + type: PagingType.PageBased, + request: { + pageParameter: 'page', + pageStartingValue: 1, + limitParameter: 'limit', + limitValue: 10 + } + } as IPageBasedScheme; + const paginator = Paginator.createPaginator(pagingScheme); + expect(paginator).toBeInstanceOf(PageBasedPaginator); + }); + + it('should not create a PageBasedPaginator if limit parameter is undefined', () => { + const pagingSchemeWithNoLimitParameter = { + type: PagingType.PageBased, + request: { + pageParameter: 'page', + pageStartingValue: 1, + limitValue: 10 + } + } as IPageBasedScheme; + expect(() => + Paginator.createPaginator(pagingSchemeWithNoLimitParameter) + ).toThrow( + 'Paginator: Request parameters must be defined for pageBased schemes.' + ); + }); + + it('should not create a PageBasedPaginator if page parameter is undefined', () => { + const pagingSchemeWithNoPageParameter = { + type: PagingType.PageBased, + request: { + pageStartingValue: 1, + limitValue: 10, + limitParameter: 'limit' + } + } as IPageBasedScheme; + expect(() => + Paginator.createPaginator(pagingSchemeWithNoPageParameter) + ).toThrow( + 'Paginator: Request parameters must be defined for pageBased schemes.' + ); + }); + + it('should not create a PageBasedPaginator if pageStartingValue is undefined', () => { + const pagingSchemeWithNoPageStartingValue = { + type: PagingType.PageBased, + request: { + pageParameter: 'page', + limitParameter: 'limit', + limitValue: 10 + } + } as IPageBasedScheme; + expect(() => + Paginator.createPaginator(pagingSchemeWithNoPageStartingValue) + ).toThrow( + 'Paginator: Request parameters must be defined for pageBased schemes.' + ); + }); + + it('shouldHYP-63898 not create a PageBasedPaginator if limit parameter is not a positive integer', () => { + const pagingScheme = { + type: PagingType.PageBased, + request: { + pageParameter: 'page', + pageStartingValue: 1, + limitParameter: 'limit', + limitValue: -10 + } + } as IPageBasedScheme; + expect(() => Paginator.createPaginator(pagingScheme)).toThrow( + 'Paginator: Limit value undefined must be a positive integer for page-based schemes.' + ); + }); + + it('should not create a PageBasedPaginator if paging condition is ReachTotalCount and total count is not defined', () => { + const pagingScheme = { + type: PagingType.PageBased, + request: { + pageParameter: 'page', + pageStartingValue: 1, + limitParameter: 'limit', + limitValue: 10 + }, + pageUntil: PageUntilCondition.ReachTotalCount + } as IPageBasedScheme; + expect(() => Paginator.createPaginator(pagingScheme)).toThrow( + 'Paginator: totalCount must be defined for paging condition: reachTotalCount.' + ); + }); + + it('should create an OffsetAndLimitPaginator', () => { + const pagingScheme = { + type: PagingType.OffsetAndLimit, + request: { + offsetParameter: 'skip', + offsetStartingValue: 0, + limitParameter: 'resultsPerPage', + limitValue: 10 + }, + pageUntil: PageUntilCondition.NoDataLeft + } as IOffsetAndLimitScheme; + const paginator = Paginator.createPaginator(pagingScheme); + expect(paginator).toBeInstanceOf(OffsetAndLimitPaginator); + }); + + it('should not create an OffsetAndLimitPaginator if offsetParameter is undefined', () => { + const pagingSchemewithNoOffestParameter = { + type: PagingType.OffsetAndLimit, + request: { + offsetStartingValue: 0, + limitParameter: 'resultsPerPage', + limitValue: 10 + }, + pageUntil: PageUntilCondition.NoDataLeft + } as IOffsetAndLimitScheme; + expect(() => + Paginator.createPaginator(pagingSchemewithNoOffestParameter) + ).toThrow( + 'Paginator: Request parameters must be defined for offsetAndLimit schemes.' + ); + }); + + it('should not create an OffsetAndLimitPaginator if offsetStartingValue is undefined', () => { + const pagingSchemeWithNoOffsetStartingValue = { + type: PagingType.OffsetAndLimit, + request: { + offsetParameter: 'skip', + limitParameter: 'resultsPerPage', + limitValue: 10 + }, + pageUntil: PageUntilCondition.NoDataLeft + } as IOffsetAndLimitScheme; + expect(() => + Paginator.createPaginator(pagingSchemeWithNoOffsetStartingValue) + ).toThrow( + 'Paginator: Request parameters must be defined for offsetAndLimit schemes.' + ); + }); + + it('should not create an OffsetAndLimitPaginator if limitParameter is undefined', () => { + const pagingSchemeWithNoLimitParameter = { + type: PagingType.OffsetAndLimit, + request: { + offsetParameter: 'skip', + offsetStartingValue: 0, + limitValue: 10 + }, + pageUntil: PageUntilCondition.NoDataLeft + } as IOffsetAndLimitScheme; + expect(() => + Paginator.createPaginator(pagingSchemeWithNoLimitParameter) + ).toThrow( + 'Paginator: Request parameters must be defined for offsetAndLimit schemes.' + ); + }); + + it('should not create an OffsetAndLimitPaginator if limit parameter is not a positive integer', () => { + const pagingScheme = { + type: PagingType.OffsetAndLimit, + request: { + offsetParameter: 'skip', + offsetStartingValue: 0, + limitParameter: 'resultsPerPage', + limitValue: -10 + }, + pageUntil: PageUntilCondition.NoDataLeft + } as IOffsetAndLimitScheme; + expect(() => Paginator.createPaginator(pagingScheme)).toThrow( + 'Paginator: Limit value undefined must be a positive integer for offset-and-limit schemes.' + ); + }); + + it('should not create a OffsetAndLimitPaginator if paging condition is ReachTotalCount and total count is not defined', () => { + const pagingScheme = { + type: PagingType.OffsetAndLimit, + request: { + offsetParameter: 'skip', + offsetStartingValue: 0, + limitParameter: 'resultsPerPage', + limitValue: 10 + }, + pageUntil: PageUntilCondition.ReachTotalCount + } as IOffsetAndLimitScheme; + expect(() => Paginator.createPaginator(pagingScheme)).toThrow( + 'Paginator: totalCount must be defined for paging condition: reachTotalCount.' + ); + }); + + it('should create a GraphQLConnectionsPaginator', () => { + const pagingScheme = { + type: PagingType.GraphQLConnections, + request: { limitParameter: '$top', limitValue: 10 }, + response: { pageInfo: JSON.stringify({ totalCount: 'totalCount' }) }, + pageUntil: PageUntilCondition.NoNextPage + } as IGraphQLConnectionsScheme; + const paginator = Paginator.createPaginator( + pagingScheme, + DataSetMethod.POST + ); + + expect(paginator).toBeInstanceOf(GraphQLConnectionsPaginator); + }); + + it('should not create a GraphQLConnectionsPaginator if limitParamter is undefined', () => { + const pagingScheme = { + type: PagingType.GraphQLConnections, + request: { limitValue: 10 }, + response: { pageInfo: JSON.stringify({ totalCount: 'totalCount' }) }, + pageUntil: PageUntilCondition.NoNextPage + } as IGraphQLConnectionsScheme; + + expect(() => + Paginator.createPaginator(pagingScheme, DataSetMethod.POST) + ).toThrow( + 'Paginator: Request parameters must be defined for graphqlConnections schemes.' + ); + }); + + it('should not create a GraphQLConnectionsPaginator if limitValue is undefined', () => { + const pagingSchemeWithNoLimitValue = { + type: PagingType.GraphQLConnections, + request: { limitParameter: '$top' }, + response: { pageInfo: JSON.stringify({ totalCount: 'totalCount' }) }, + pageUntil: PageUntilCondition.NoNextPage + } as IGraphQLConnectionsScheme; + + expect(() => + Paginator.createPaginator( + pagingSchemeWithNoLimitValue, + DataSetMethod.POST + ) + ).toThrow( + 'Paginator: Limit value undefined must be a positive integer for graphqlConnections schemes.' + ); + }); + + it('should not create a GraphQLConnectionsPaginator if limitValue is not a positive integer', () => { + const pagingSchemeWithNonPositiveLimitValue = { + type: PagingType.GraphQLConnections, + request: { limitParameter: '$top', limitValue: -10 }, + response: { pageInfo: JSON.stringify({ totalCount: 'totalCount' }) }, + pageUntil: PageUntilCondition.NoNextPage + } as IGraphQLConnectionsScheme; + + expect(() => + Paginator.createPaginator( + pagingSchemeWithNonPositiveLimitValue, + DataSetMethod.POST + ) + ).toThrow( + 'Paginator: Limit value undefined must be a positive integer for graphqlConnections schemes.' + ); + }); + + it('should not create a GraphQLConnectionsPaginator if limitParamter is undefined', () => { + const pagingScheme = { + type: PagingType.GraphQLConnections, + request: { limitParameter: '$top', limitValue: 10 }, + response: { pageInfo: JSON.stringify({ totalCount: 'totalCount' }) }, + pageUntil: PageUntilCondition.NoNextPage + } as IGraphQLConnectionsScheme; + + expect(() => Paginator.createPaginator(pagingScheme)).toThrow( + 'Paginator: GraphQL pagination scheme graphqlConnections supports POST method.' + ); + }); + + it('should throw an error for an invalid PagingType', () => { + const pagingScheme = { type: 'InvalidType' }; + expect(() => Paginator.createPaginator(pagingScheme as any)).toThrow( + 'Paginator: Invalid paging scheme: InvalidType' + ); + }); + }); + + describe('NextTokenPaginator', () => { + it('tokenType token should paginate request', () => { + const pagingScheme = { + type: PagingType.NextToken, + tokenType: NextTokenType.Token, + request: { + limitParameter: '$top', + limitValue: 10, + tokenParameter: 'nextToken' + } + } as INextTokenScheme; + + const paginator = Paginator.createPaginator(pagingScheme); + + const relativeUrl = '/api/data'; + const baseUrl = 'https://example.com'; + const messageBody = undefined; + let page = undefined; + + let result = paginator.paginateRequest( + relativeUrl, + baseUrl, + messageBody, + DataSetMethod.GET, + page + ); + + expect(result.pagedRelativeUrl).toBe( + `${relativeUrl}?${pagingScheme.request.limitParameter}=${pagingScheme.request.limitValue}` + ); + + // pretending we found the next token + page = 'testToken'; + + result = paginator.paginateRequest( + relativeUrl, + baseUrl, + messageBody, + DataSetMethod.GET, + page + ); + + expect(result.pagedRelativeUrl).toBe( + `${relativeUrl}?${pagingScheme.request.limitParameter}=${pagingScheme.request.limitValue}&${pagingScheme.request.tokenParameter}=${page}` + ); + }); + + it('tokenType url should paginate request', () => { + const pagingScheme = { + type: PagingType.NextToken, + tokenType: NextTokenType.Url, + request: { + limitParameter: '$top', + limitValue: 10, + tokenParameter: 'nextToken' + } + } as INextTokenScheme; + + const paginator = Paginator.createPaginator(pagingScheme); + + const relativeUrl = '/api/data'; + const baseUrl = 'https://example.com'; + const messageBody = undefined; + let page = undefined; + + let result = paginator.paginateRequest( + relativeUrl, + baseUrl, + messageBody, + DataSetMethod.GET, + page + ); + + expect(result.pagedRelativeUrl).toBe( + `${relativeUrl}?${pagingScheme.request.limitParameter}=${pagingScheme.request.limitValue}` + ); + + // pretending we found the next token + page = `${baseUrl}/testToken`; + + result = paginator.paginateRequest( + relativeUrl, + baseUrl, + messageBody, + DataSetMethod.GET, + page + ); + + expect(result.pagedRelativeUrl).toBe(page); + }); + + it('should paginate request without limit parameters', () => { + const pagingScheme = { + type: PagingType.NextToken, + tokenType: NextTokenType.Token, + request: { + tokenParameter: 'nextToken' + } + } as INextTokenScheme; + const paginator = Paginator.createPaginator(pagingScheme); + + const relativeUrl = '/api/data'; + const baseUrl = 'https://example.com'; + const messageBody = undefined; + let page = undefined; + + let result = paginator.paginateRequest( + relativeUrl, + baseUrl, + messageBody, + DataSetMethod.GET, + page + ); + + expect(result.pagedRelativeUrl).toBe(relativeUrl); + + // pretending we found the next token + page = 'testToken'; + + result = paginator.paginateRequest( + relativeUrl, + baseUrl, + messageBody, + DataSetMethod.GET, + page + ); + + expect(result.pagedRelativeUrl).toBe( + `${relativeUrl}?${pagingScheme.request.tokenParameter}=${page}` + ); + }); + + it('should paginate POST message body', () => { + const pagingScheme = { + type: PagingType.NextToken, + tokenType: NextTokenType.Token, + request: { + tokenParameter: 'variables.token' + }, + response: { + nextToken: 'pagination.token' + }, + pageUntil: PageUntilCondition.NoNextToken + } as INextTokenScheme; + const paginator = Paginator.createPaginator(pagingScheme); + const relativeUrl = '/graphql'; + const baseUrl = 'https://example.com'; + const messageBody = { + query: + 'query($token: String) { attributes(token: $token) { nodes { id name } pagination { token } } }' + }; + let page = '9f6a4b24af'; + let result = paginator.paginateRequest( + relativeUrl, + baseUrl, + messageBody, + DataSetMethod.POST, + page + ); + expect(result.pagedRelativeUrl).toBe(relativeUrl); + expect(result.pagedMessageBody).toEqual({ + ...messageBody, + variables: { + token: page + } + }); + }); + }); + + describe('PageBasedPaginator', () => { + it('should paginate request', () => { + const pagingScheme = { + type: PagingType.PageBased, + request: { + pageParameter: 'page', + pageStartingValue: 1, + limitParameter: '$top', + limitValue: 10 + } + } as IPageBasedScheme; + const paginator = Paginator.createPaginator(pagingScheme); + + const relativeUrl = '/api/data'; + const baseUrl = 'https://example.com'; + const messageBody = undefined; + let page = undefined; + + let result = paginator.paginateRequest( + relativeUrl, + baseUrl, + messageBody, + DataSetMethod.GET, + page + ); + + expect(result.pagedRelativeUrl).toBe( + `${relativeUrl}?${pagingScheme.request.pageParameter}=${pagingScheme.request.pageStartingValue}&${pagingScheme.request.limitParameter}=${pagingScheme.request.limitValue}` + ); + + // pretending we found the next token + page = '2'; + + result = paginator.paginateRequest( + relativeUrl, + baseUrl, + messageBody, + DataSetMethod.GET, + page + ); + expect(result.pagedRelativeUrl).toBe( + `${relativeUrl}?page=${page}&${pagingScheme.request.limitParameter}=${pagingScheme.request.limitValue}` + ); + }); + + it('should paginate POST message body', () => { + const pagingScheme = { + type: PagingType.PageBased, + request: { + pageParameter: 'variables.page', + pageStartingValue: 1, + limitParameter: 'variables.limit', + limitValue: 10 + }, + pageUntil: PageUntilCondition.NoDataLeft + } as IPageBasedScheme; + const paginator = Paginator.createPaginator(pagingScheme); + const relativeUrl = '/graphql'; + const baseUrl = 'https://example.com'; + const messageBody = { + query: + 'query($page: Int, $limit: Int) { attributes(page: $page, limit: $limit) { nodes { id name } pagination { page } } }' + }; + let page = undefined; + let result = paginator.paginateRequest( + relativeUrl, + baseUrl, + messageBody, + DataSetMethod.POST, + page + ); + expect(result.pagedRelativeUrl).toBe(relativeUrl); + expect(result.pagedMessageBody).toEqual({ + ...messageBody, + variables: { + page: 1, + limit: 10 + } + }); + }); + }); + + describe('OffsetAndLimitPaginator', () => { + it('should paginate request', () => { + const pagingScheme = { + type: PagingType.OffsetAndLimit, + request: { + offsetParameter: 'skip', + offsetStartingValue: 0, + limitParameter: '$top', + limitValue: 10 + }, + pageUntil: PageUntilCondition.NoDataLeft + } as IOffsetAndLimitScheme; + const paginator = Paginator.createPaginator(pagingScheme); + + const relativeUrl = '/api/data'; + const baseUrl = 'https://example.com'; + const messageBody = undefined; + let page = undefined; + + let result = paginator.paginateRequest( + relativeUrl, + baseUrl, + messageBody, + DataSetMethod.GET, + page + ); + + expect(result.pagedRelativeUrl).toBe( + `${relativeUrl}?${pagingScheme.request.offsetParameter}=${pagingScheme.request.offsetStartingValue}&${pagingScheme.request.limitParameter}=${pagingScheme.request.limitValue}` + ); + + // pretending we found the next token + page = '10'; + + result = paginator.paginateRequest( + relativeUrl, + baseUrl, + messageBody, + DataSetMethod.GET, + page + ); + expect(result.pagedRelativeUrl).toBe( + `${relativeUrl}?${pagingScheme.request.offsetParameter}=${page}&${pagingScheme.request.limitParameter}=${pagingScheme.request.limitValue}` + ); + }); + + it('should paginate POST message body', () => { + const pagingScheme = { + type: PagingType.OffsetAndLimit, + request: { + offsetParameter: 'variables.offset', + offsetStartingValue: 0, + limitParameter: 'variables.limit', + limitValue: 10 + }, + response: { + totalCount: 'pagination.totalCount' + }, + pageUntil: PageUntilCondition.ReachTotalCount + } as IOffsetAndLimitScheme; + const paginator = Paginator.createPaginator(pagingScheme); + const relativeUrl = '/graphql'; + const baseUrl = 'https://example.com'; + const messageBody = { + query: + 'query($offset: Int, $limit: Int) { attributes(offset: $offset, limit: $limit) { nodes { id name } pagination { totalCount } } }' + }; + let page = undefined; + let result = paginator.paginateRequest( + relativeUrl, + baseUrl, + messageBody, + DataSetMethod.POST, + page + ); + expect(result.pagedRelativeUrl).toBe(relativeUrl); + expect(result.pagedMessageBody).toEqual({ + ...messageBody, + variables: { + offset: 0, + limit: 10 + } + }); + }); + }); + + describe('GraphQLConnectionsPaginator', () => { + it('should not mutate the supplied url', () => { + const pagingScheme = { + type: PagingType.GraphQLConnections, + request: { limitParameter: '$top', limitValue: 10 }, + response: { pageInfo: JSON.stringify({ totalCount: 'totalCount' }) }, + pageUntil: PageUntilCondition.NoNextPage + } as IGraphQLConnectionsScheme; + const paginator = Paginator.createPaginator( + pagingScheme, + DataSetMethod.POST + ); + + const relativeUrl = '/api/data'; + const baseUrl = 'https://example.com'; + const messageBody = { query: 'test' }; + let page = undefined; + + let result = paginator.paginateRequest( + relativeUrl, + baseUrl, + messageBody, + DataSetMethod.POST, + page + ); + + expect(result.pagedRelativeUrl).toBe(relativeUrl); + }); + + it('should paginate message body', () => { + const pagingScheme = { + type: PagingType.GraphQLConnections, + request: { limitParameter: '$top', limitValue: 10 }, + response: { pageInfo: JSON.stringify({ totalCount: 'totalCount' }) }, + pageUntil: PageUntilCondition.NoNextPage + } as IGraphQLConnectionsScheme; + const paginator = Paginator.createPaginator( + pagingScheme, + DataSetMethod.POST + ); + + const relativeUrl = '/api/data'; + const baseUrl = 'https://example.com'; + const messageBody = { query: 'test' }; + let page = undefined; + + let result = paginator.paginateRequest( + relativeUrl, + baseUrl, + messageBody, + DataSetMethod.POST, + page + ); + + expect( + result.pagedMessageBody.variables[pagingScheme.request.limitParameter] + ).toBe(pagingScheme.request.limitValue); + }); + }); +}); diff --git a/src/hypersync/Paginator.ts b/src/hypersync/Paginator.ts index 170a67f..f636078 100644 --- a/src/hypersync/Paginator.ts +++ b/src/hypersync/Paginator.ts @@ -13,7 +13,7 @@ import { PageUntilCondition, PagingScheme, PagingType -} from '@hyperproof/hypersync-models'; +} from '@hyperproof-int/hypersync-models'; import jsonata from 'jsonata'; import set from 'lodash/set'; diff --git a/src/hypersync/ProofProviderBase.ts b/src/hypersync/ProofProviderBase.ts index ea8091f..ca9d480 100644 --- a/src/hypersync/ProofProviderBase.ts +++ b/src/hypersync/ProofProviderBase.ts @@ -19,8 +19,8 @@ import { HypersyncFieldType, HypersyncPageOrientation, SchemaCategory -} from '@hyperproof/hypersync-models'; -import { ILocalizable } from '@hyperproof/integration-sdk'; +} from '@hyperproof-int/hypersync-models'; +import { ILocalizable } from '@hyperproof-int/integration-sdk'; import createHttpError from 'http-errors'; import { StatusCodes } from 'http-status-codes'; diff --git a/src/hypersync/ProofProviderFactory.ts b/src/hypersync/ProofProviderFactory.ts index 98af817..8fbdd02 100644 --- a/src/hypersync/ProofProviderFactory.ts +++ b/src/hypersync/ProofProviderFactory.ts @@ -10,8 +10,8 @@ import { IProofType, IProofTypeMap, SchemaCategory -} from '@hyperproof/hypersync-models'; -import { compareValues } from '@hyperproof/integration-sdk'; +} from '@hyperproof-int/hypersync-models'; +import { compareValues } from '@hyperproof-int/integration-sdk'; import fs from 'fs'; import createHttpError from 'http-errors'; import { StatusCodes } from 'http-status-codes'; diff --git a/src/hypersync/RestDataSourceBase.test.ts b/src/hypersync/RestDataSourceBase.test.ts new file mode 100644 index 0000000..bdfd138 --- /dev/null +++ b/src/hypersync/RestDataSourceBase.test.ts @@ -0,0 +1,575 @@ +import { RestDataSourceBase } from './RestDataSourceBase'; + +import { + IDataSet, + IRestDataSourceConfig +} from '@hyperproof-int/hypersync-models'; +import { DataSetResultStatus } from '@hyperproof-int/hypersync-sdk'; +import { ApiClient } from '@hyperproof-int/integration-sdk'; +import { HeadersInit } from 'node-fetch'; + +// Mock Logger +jest.mock('@hyperproof-int/integration-sdk', () => { + return { + ...jest.requireActual('@hyperproof-int/integration-sdk'), + Logger: { + log: jest.fn(), + error: jest.fn(), + info: jest.fn(), + warn: jest.fn(), + debug: jest.fn() + } + }; +}); + +describe('RestDataSourceBase', () => { + let config: IRestDataSourceConfig; + let messages: { [key: string]: string }; + let headers: HeadersInit; + let restDataSourceBase: RestDataSourceBase; + + const mockApiClient = { + setRetryCount: jest.fn(), + getUnprocessedResponse: jest.fn(), + getJson: jest.fn(), + postJson: jest.fn(), + patchJson: jest.fn() + }; + + let apiClient = mockApiClient as unknown as ApiClient; + + beforeEach(() => { + config = { + baseUrl: 'http://testbaseurl.hyperproof.io', + dataSets: { + testDataSet: { + url: 'testUrl', + method: 'GET', + headers: {}, + body: {}, + valueLookups: {}, + description: 'testDescription', + result: 'object' + }, + testDataSetArray: { + url: 'testUrl', + method: 'GET', + headers: {}, + body: {}, + valueLookups: {}, + description: 'testDescription', + result: 'array' + }, + testDataSetPageBased: { + url: 'pageBasedUrl', + method: 'GET', + pagingScheme: { + type: 'pageBased', + request: { + pageParameter: 'page', + pageStartingValue: 0, + limitParameter: 'size', + limitValue: 5 + }, + pageUntil: 'noDataLeft', + level: 'connector' + }, + headers: {}, + body: {}, + valueLookups: {}, + description: 'testDescription', + result: 'array' + }, + testDataSetTransforms: { + url: 'testUrl', + method: 'GET', + headers: {}, + body: {}, + valueLookups: {}, + description: 'testDescription', + result: 'object', + transform: { + transformedData: 'data' + } + }, + testDataSetLookups: { + url: 'testUrl', + method: 'GET', + headers: {}, + body: {}, + lookups: [ + { + alias: 'lookupData', + dataSet: 'lookupDataSet', + dataSetParams: { + param1: '{{source.id}}' + }, + continueOnError: true + } + ], + valueLookups: {}, + description: 'testDescription', + result: 'object' + } + }, + valueLookups: {} + } as IRestDataSourceConfig; + messages = { testMessage: 'This is a test message' }; + headers = { Authorization: 'Bearer token' }; + restDataSourceBase = new RestDataSourceBase( + config, + messages, + headers, + apiClient + ); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it('should create RestDataSourceBase', () => { + expect(restDataSourceBase).toBeDefined(); + }); + + it('should call ApiClient.setRetryCount when setRetryCount is called', () => { + restDataSourceBase.setRetryCount(3); + expect(apiClient.setRetryCount).toHaveBeenCalledWith(3); + }); + + it('should call getConfig and return config', async () => { + expect(restDataSourceBase.getConfig()).toStrictEqual(config); + }); + + it('should call overwriteBaseUrlAndHeaders and update config and headers', () => { + const newUrl = 'newTestUrl'; + const newHeaders = { Authorization: 'Bearer newToken' }; + restDataSourceBase.overwriteBaseUrlAndHeaders(newUrl, newHeaders); + expect(restDataSourceBase.getConfig().baseUrl).toBe(newUrl); + expect(restDataSourceBase.apiClient.headers).toBe(newHeaders); + }); + + it('should add a new data set when addDataSet is called', () => { + const newDataSet = { + url: 'newTestUrl', + method: 'GET', + headers: {}, + body: {}, + valueLookups: {}, + description: 'newTestDescription', + result: 'object' + } as IDataSet; + restDataSourceBase.addDataSet('newDataSet', newDataSet); + expect(restDataSourceBase.getConfig().dataSets['newDataSet']).toBe( + newDataSet + ); + }); + + it('should add a new value lookup when addValueLookup is called', () => { + const newValueLookup = { + url: 'newLookupUrl', + method: 'GET', + headers: {}, + body: {}, + description: 'newLookupDescription', + result: 'object' + }; + restDataSourceBase.addValueLookup('newValueLookup', newValueLookup); + expect(restDataSourceBase.getConfig().valueLookups['newValueLookup']).toBe( + newValueLookup + ); + }); + + it('should throw when getUnprocessedResponse is called with an invalid data set', async () => { + try { + await restDataSourceBase.getUnprocessedResponse('invalid', { + testMessage: 'message' + }); + } catch (err) { + expect((err as Error).message).toBe('Invalid data set name: invalid'); + } + + expect(apiClient.getUnprocessedResponse).not.toHaveBeenCalled(); + }); + + it('should call ApiClient.getUnprocessedResponse when getUnprocessedResponse is called with a valid data set', async () => { + await restDataSourceBase.getUnprocessedResponse('testDataSet', { + testMessage: 'message' + }); + expect(apiClient.getUnprocessedResponse).toHaveBeenCalledWith( + 'testUrl', + undefined, + undefined + ); + }); + + it('should call ApiClient.getUnprocessedResponse with resolved URL', async () => { + const params = { testParam: 'paramValue' }; + const resolvedUrl = 'resolvedTestUrl'; + jest + .spyOn(restDataSourceBase as any, 'resolveUrlTokens') + .mockReturnValue({ resolvedUrl }); + await restDataSourceBase.getUnprocessedResponse('testDataSet', params); + expect(apiClient.getUnprocessedResponse).toHaveBeenCalledWith( + resolvedUrl, + undefined, + undefined + ); + }); + + it('should call processResponse when getData is called with a valid data set', async () => { + const params = { testParam: 'paramValue' }; + const resolvedUrl = 'resolvedTestUrl'; + const tokenContext = {}; + const response = { + status: DataSetResultStatus.Complete, + data: {} + }; + jest + .spyOn(restDataSourceBase as any, 'resolveUrlTokens') + .mockReturnValue({ relativeUrl: resolvedUrl, tokenContext }); + jest + .spyOn(restDataSourceBase, 'getDataFromUrl') + .mockResolvedValue(response); + const processResponseSpy = jest + .spyOn(restDataSourceBase as any, 'processResponse') + .mockResolvedValue(response); + await restDataSourceBase.getData('testDataSet', params); + expect(processResponseSpy).toHaveBeenCalledWith( + 'testDataSet', + config.dataSets['testDataSet'], + tokenContext, + response, + params, + undefined + ); + }); + + it('should call setHeaders and update headers', () => { + const newHeaders = { Authorization: 'Bearer newToken' }; + restDataSourceBase.setHeaders(newHeaders); + expect(restDataSourceBase.apiClient.headers).toBe(newHeaders); + }); + + it('should call getDataObject and return data object', async () => { + const params = { testParam: 'paramValue' }; + const resolvedUrl = 'resolvedTestUrl'; + const tokenContext = {}; + const response = { status: DataSetResultStatus.Complete, data: {} }; + jest + .spyOn(restDataSourceBase as any, 'resolveUrlTokens') + .mockReturnValue({ relativeUrl: resolvedUrl, tokenContext }); + jest + .spyOn(restDataSourceBase, 'getDataFromUrl') + .mockResolvedValue(response); + const processResponseSpy = jest + .spyOn(restDataSourceBase as any, 'processResponse') + .mockResolvedValue(response); + const result = await restDataSourceBase.getDataObject( + 'testDataSet', + params + ); + expect(result).toEqual(response); + }); + + it('should call getDataObjectArray and return data object array', async () => { + const params = { testParam: 'paramValue' }; + const resolvedUrl = 'resolvedTestUrl'; + const tokenContext = {}; + const response = { status: DataSetResultStatus.Complete, data: [] }; + jest + .spyOn(restDataSourceBase as any, 'resolveUrlTokens') + .mockReturnValue({ relativeUrl: resolvedUrl, tokenContext }); + jest + .spyOn(restDataSourceBase, 'getDataFromUrl') + .mockResolvedValue(response); + + const result = await restDataSourceBase.getDataObjectArray( + 'testDataSetArray', + params + ); + expect(result).toEqual(response); + }); + + it('should call getDataFromUrl and return data', async () => { + const params = { testParam: 'paramValue' }; + const resolvedUrl = 'resolvedTestUrl'; + const tokenContext = {}; + // we need to use the two individual response shapes here since we can't just mock getDataFromUrl + // in the test for that method + const getJsonResponse = { status: DataSetResultStatus.Complete, json: {} }; + const getDataFromUrlResponse = { + status: DataSetResultStatus.Complete, + data: {} + }; + jest + .spyOn(restDataSourceBase as any, 'resolveUrlTokens') + .mockReturnValue({ relativeUrl: resolvedUrl, tokenContext }); + jest.spyOn(apiClient, 'getJson').mockResolvedValue(getJsonResponse); + const result = await restDataSourceBase.getDataFromUrl( + 'testDataSet', + config.dataSets['testDataSet'], + resolvedUrl, + params + ); + expect(result).toEqual(getDataFromUrlResponse); + }); + + it('should call getDataFromUrl with POST method and return data', async () => { + const params = { testParam: 'paramValue' }; + const resolvedUrl = 'resolvedTestUrl'; + const tokenContext = {}; + // we need to use the two individual response shapes here since we can't just mock getDataFromUrl + // in the test for that method + const postJsonResponse = { status: DataSetResultStatus.Complete, json: {} }; + const getDataFromUrlResponse = { + status: DataSetResultStatus.Complete, + data: {} + }; + const requestBody = { key: 'value' }; + jest + .spyOn(restDataSourceBase as any, 'resolveUrlTokens') + .mockReturnValue({ relativeUrl: resolvedUrl, tokenContext }); + jest.spyOn(apiClient, 'postJson').mockResolvedValue(postJsonResponse); + const result = await restDataSourceBase.getDataFromUrl( + 'testDataSet', + config.dataSets['testDataSet'], + resolvedUrl, + params, + undefined, + undefined, + 'POST', + requestBody + ); + expect(result).toEqual(getDataFromUrlResponse); + }); + + it('should call getDataFromUrl with PATCH method and return data', async () => { + const params = { testParam: 'paramValue' }; + const resolvedUrl = 'resolvedTestUrl'; + const tokenContext = {}; + // we need to use the two individual response shapes here since we can't just mock getDataFromUrl + // in the test for that method + const patchJsonResponse = { + status: DataSetResultStatus.Complete, + json: {} + }; + const getDataFromUrlResponse = { + status: DataSetResultStatus.Complete, + data: {} + }; + const requestBody = { key: 'value' }; + jest + .spyOn(restDataSourceBase as any, 'resolveUrlTokens') + .mockReturnValue({ relativeUrl: resolvedUrl, tokenContext }); + jest.spyOn(apiClient, 'patchJson').mockResolvedValue(patchJsonResponse); + const result = await restDataSourceBase.getDataFromUrl( + 'testDataSet', + config.dataSets['testDataSet'], + resolvedUrl, + params, + undefined, + undefined, + 'PATCH', + requestBody + ); + expect(result).toEqual(getDataFromUrlResponse); + }); + + it('should call isArrayResult and return true for array result', () => { + const isArrayResultSpy = jest + .spyOn(restDataSourceBase as any, 'isArrayResult') + .mockReturnValue(true); + const result = restDataSourceBase['isArrayResult']( + config.dataSets['testDataSet'] + ); + expect(isArrayResultSpy).toHaveBeenCalledWith( + config.dataSets['testDataSet'] + ); + expect(result).toBe(true); + }); + + it('should call isArrayResult and return false for object result', () => { + config.dataSets['testDataSet'].result = 'object'; + const isArrayResultSpy = jest + .spyOn(restDataSourceBase as any, 'isArrayResult') + .mockReturnValue(false); + const result = restDataSourceBase['isArrayResult']( + config.dataSets['testDataSet'] + ); + expect(isArrayResultSpy).toHaveBeenCalledWith( + config.dataSets['testDataSet'] + ); + expect(result).toBe(false); + }); + + it('should call transformObject when transformObject is called', () => { + const transform = { transformed: 'data' }; + const dataObject = {}; + const transformObjectSpy = jest.spyOn( + restDataSourceBase as any, + 'transformObject' + ); + restDataSourceBase['transformObject'](transform, dataObject); + expect(transformObjectSpy).toHaveBeenCalledWith(transform, dataObject); + }); + + it('should call initTokenContext and return token context', () => { + const params = { testParam: 'paramValue' }; + const initTokenContextSpy = jest + .spyOn(restDataSourceBase as any, 'initTokenContext') + .mockReturnValue({}); + const tokenContext = restDataSourceBase['initTokenContext'](params); + expect(initTokenContextSpy).toHaveBeenCalledWith(params); + expect(tokenContext).toEqual({}); + }); + + it('should call getPropertyValue and return property value', () => { + const dataObject = { key: { nestedKey: 'value' } }; + const getPropertyValueSpy = jest + .spyOn(restDataSourceBase as any, 'getPropertyValue') + .mockReturnValue('value'); + const propertyValue = restDataSourceBase['getPropertyValue']( + dataObject, + 'key.nestedKey' + ); + expect(getPropertyValueSpy).toHaveBeenCalledWith( + dataObject, + 'key.nestedKey' + ); + expect(propertyValue).toBe('value'); + }); + + it('should call isPredicateMatch and return true for matching predicate', () => { + const left = { key: 'value' }; + const right = { key: 'value' }; + const predicate = [ + { + leftExpression: { evaluate: () => 'value' }, + rightExpression: { evaluate: () => 'value' } + } + ]; + const isPredicateMatchSpy = jest + .spyOn(restDataSourceBase as any, 'isPredicateMatch') + .mockReturnValue(true); + const result = restDataSourceBase['isPredicateMatch']( + left, + right, + predicate + ); + expect(isPredicateMatchSpy).toHaveBeenCalledWith(left, right, predicate); + expect(result).toBe(true); + }); + + it('should call isPredicateMatch and return false for non-matching predicate', () => { + const left = { key: 'value' }; + const right = { key: 'differentValue' }; + const predicate = [ + { + leftExpression: { evaluate: () => 'value' }, + rightExpression: { evaluate: () => 'differentValue' } + } + ]; + const isPredicateMatchSpy = jest + .spyOn(restDataSourceBase as any, 'isPredicateMatch') + .mockReturnValue(false); + const result = restDataSourceBase['isPredicateMatch']( + left, + right, + predicate + ); + expect(isPredicateMatchSpy).toHaveBeenCalledWith(left, right, predicate); + expect(result).toBe(false); + }); + + it('should call generateRequestBody and return request body', () => { + const tokenContext = {}; + const body = { key: 'value' }; + const generateRequestBodySpy = jest + .spyOn(restDataSourceBase as any, 'generateRequestBody') + .mockReturnValue(body); + const requestBody = restDataSourceBase['generateRequestBody']( + 'testDataSet', + tokenContext, + body + ); + expect(generateRequestBodySpy).toHaveBeenCalledWith( + 'testDataSet', + tokenContext, + body + ); + expect(requestBody).toBe(body); + }); + + it('should call validateResponse when validateResponse is called', () => { + const data = {}; + const source = 'testSource'; + const headers = { 'Content-Type': ['application/json'] }; + const validateResponseSpy = jest.spyOn( + restDataSourceBase as any, + 'validateResponse' + ); + restDataSourceBase['validateResponse']( + 'testDataSet', + data, + source, + headers + ); + expect(validateResponseSpy).toHaveBeenCalledWith( + 'testDataSet', + data, + source, + headers + ); + }); + + it('should call validateResponse and not throw for valid response', () => { + const data = {}; + const source = 'testSource'; + const headers = { 'Content-Type': ['application/json'] }; + expect(() => + restDataSourceBase['validateResponse']( + 'testDataSet', + data, + source, + headers + ) + ).not.toThrow(); + }); + + it('should call validateResponse and throw for invalid response', () => { + const data = { error: 'Invalid response' }; + const source = 'testSource'; + const headers = { 'Content-Type': ['application/json'] }; + jest + .spyOn(restDataSourceBase as any, 'validateResponse') + .mockImplementation(() => { + throw new Error('Invalid response'); + }); + expect(() => + restDataSourceBase['validateResponse']( + 'testDataSet', + data, + source, + headers + ) + ).toThrow('Invalid response'); + }); + + it('should call applyLookups with null data and continue without token resolution error', async () => { + const dataSetName = 'testDataSetLookups'; + const dataSet = config.dataSets[dataSetName]; + const tokenContext = {}; + const data = null; + const metadata = undefined; + + const result = await restDataSourceBase['applyLookups']( + dataSetName, + dataSet, + tokenContext, + data, + metadata + ); + expect(result).toEqual(null); + }); +}); diff --git a/src/hypersync/RestDataSourceBase.ts b/src/hypersync/RestDataSourceBase.ts index 190d0dc..ed92284 100644 --- a/src/hypersync/RestDataSourceBase.ts +++ b/src/hypersync/RestDataSourceBase.ts @@ -27,14 +27,14 @@ import { PagingLevel, Transform, ValueLookup -} from '@hyperproof/hypersync-models'; +} from '@hyperproof-int/hypersync-models'; import { ApiClient, compareValues, IApiClientResponse, ILocalizable, Logger -} from '@hyperproof/integration-sdk'; +} from '@hyperproof-int/integration-sdk'; import createHttpError from 'http-errors'; import { StatusCodes } from 'http-status-codes'; import jsonata from 'jsonata'; diff --git a/src/hypersync/ServiceDataIterator.test.ts b/src/hypersync/ServiceDataIterator.test.ts new file mode 100644 index 0000000..b5744c4 --- /dev/null +++ b/src/hypersync/ServiceDataIterator.test.ts @@ -0,0 +1,93 @@ +import { DataSetResultStatus } from './IDataSource'; +import { IterableObject, ServiceDataIterator } from './ServiceDataIterator'; + +import { + DataSetIteratorDefinition, + IteratorSource +} from '@hyperproof-int/hypersync-models'; + +describe('ServiceDataIterator', () => { + const mockRestDataSource = { + setPagingState: jest.fn(), + getData: jest.fn(), + iterateData: jest.fn(), + getConfig: jest.fn(() => ({})) + }; + + const iteratorDef: DataSetIteratorDefinition = { + layer: 1, + source: IteratorSource.DataSet, + dataSet: 'planets', + dataSetParams: { system: 'solar' }, + iterandKey: 'id', + subArraySize: 1 + }; + + const dataSetIterator: DataSetIteratorDefinition[] = [iteratorDef]; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should throw if iterator definition is invalid', () => { + const badDef = { ...iteratorDef, subArraySize: 0 }; + expect( + () => + new ServiceDataIterator( + mockRestDataSource, + [{ ...badDef }], + 'testProof' + ) + ).toThrow(); + }); + + it('extractIteratorLayer returns correct layer', () => { + expect( + ServiceDataIterator.extractIteratorLayer(dataSetIterator, 1) + ).toEqual(iteratorDef); + }); + + it('mergeIterandWithParams merges params and iterand', () => { + const iterand = { id: 123 }; + const params = { hyper: 'proof', foo: 'bar' }; + expect( + ServiceDataIterator.mergeIterandWithParams(iterand, 'id', params) + ).toEqual({ + hyper: 'proof', + foo: 'bar', + id: 123 + }); + }); + + it('generateIteratorPlan throws if iterableArray is not array', async () => { + mockRestDataSource.getData.mockResolvedValue({ + status: DataSetResultStatus.Complete, + data: { id: 1, name: 'Mercury' }, + source: 'test' + }); + const iterator = new ServiceDataIterator( + mockRestDataSource, + dataSetIterator, + 'testProof' + ); + await expect(iterator.generateIteratorPlan({}, {})).rejects.toThrow( + /must be an array/ + ); + }); + + it('validateIterableArray returns undefined for valid array', () => { + class TestIterator extends ServiceDataIterator { + public callValidateIterableArray(iterableArray: IterableObject[]) { + return this.validateIterableArray(iterableArray); + } + } + const iterator = new TestIterator( + mockRestDataSource, + dataSetIterator, + 'testProof' + ); + expect( + iterator.callValidateIterableArray([{ id: 1 }, { id: 2 }, { id: 3 }]) + ).toBeUndefined(); + }); +}); diff --git a/src/hypersync/ServiceDataIterator.ts b/src/hypersync/ServiceDataIterator.ts index 37b3fd5..1ea96a9 100644 --- a/src/hypersync/ServiceDataIterator.ts +++ b/src/hypersync/ServiceDataIterator.ts @@ -21,8 +21,8 @@ import { DataValueMap, IProofSpec, IteratorSource -} from '@hyperproof/hypersync-models'; -import { ILocalizable } from '@hyperproof/integration-sdk'; +} from '@hyperproof-int/hypersync-models'; +import { ILocalizable } from '@hyperproof-int/integration-sdk'; export interface IIteratorPlanComplete { status: DataSetResultStatus.Complete; diff --git a/src/hypersync/Sync.ts b/src/hypersync/Sync.ts index 39e6f7c..c550691 100644 --- a/src/hypersync/Sync.ts +++ b/src/hypersync/Sync.ts @@ -7,7 +7,7 @@ import { IProofFile } from './ProofProviderBase'; -import { ILocalizable } from '@hyperproof/integration-sdk'; +import { ILocalizable } from '@hyperproof-int/integration-sdk'; /** * Detailed response object that may be returned from getProofData. diff --git a/src/hypersync/common.test.ts b/src/hypersync/common.test.ts new file mode 100644 index 0000000..3a299e6 --- /dev/null +++ b/src/hypersync/common.test.ts @@ -0,0 +1,151 @@ +import { paginate } from './common'; + +describe('paginate', () => { + it('should paginate first page with default page 0', () => { + const entities = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + const result = paginate(entities, 0, 3); + + expect(result.currentPage).toEqual([1, 2, 3]); + expect(result.nextPage).toBe('1'); + expect(result.startIndex).toBe(0); + expect(result.endIndex).toBe(3); + }); + + it('should paginate first page when page parameter is omitted', () => { + const entities = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + const result = paginate(entities, undefined, 3); + + expect(result.currentPage).toEqual([1, 2, 3]); + expect(result.nextPage).toBe('1'); + expect(result.startIndex).toBe(0); + expect(result.endIndex).toBe(3); + }); + + it('should paginate middle page', () => { + const entities = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + const result = paginate(entities, 1, 3); + + expect(result.currentPage).toEqual([4, 5, 6]); + expect(result.nextPage).toBe('2'); + expect(result.startIndex).toBe(3); + expect(result.endIndex).toBe(6); + }); + + it('should paginate last full page', () => { + const entities = [1, 2, 3, 4, 5, 6, 7, 8, 9]; + const result = paginate(entities, 2, 3); + + expect(result.currentPage).toEqual([7, 8, 9]); + expect(result.nextPage).toBeUndefined(); + expect(result.startIndex).toBe(6); + expect(result.endIndex).toBe(9); + }); + + it('should paginate last partial page', () => { + const entities = [1, 2, 3, 4, 5, 6, 7]; + const result = paginate(entities, 2, 3); + + expect(result.currentPage).toEqual([7]); + expect(result.nextPage).toBeUndefined(); + expect(result.startIndex).toBe(6); + expect(result.endIndex).toBe(9); + }); + + it('should handle single item per page', () => { + const entities = [1, 2, 3, 4, 5]; + const result = paginate(entities, 2, 1); + + expect(result.currentPage).toEqual([3]); + expect(result.nextPage).toBe('3'); + expect(result.startIndex).toBe(2); + expect(result.endIndex).toBe(3); + }); + + it('should handle page size larger than array', () => { + const entities = [1, 2, 3]; + const result = paginate(entities, 0, 10); + + expect(result.currentPage).toEqual([1, 2, 3]); + expect(result.nextPage).toBeUndefined(); + expect(result.startIndex).toBe(0); + expect(result.endIndex).toBe(10); + }); + + it('should handle empty array', () => { + const entities: any[] = []; + const result = paginate(entities, 0, 3); + + expect(result.currentPage).toEqual([]); + expect(result.nextPage).toBeUndefined(); + expect(result.startIndex).toBe(0); + expect(result.endIndex).toBe(3); + }); + + it('should handle page beyond array length', () => { + const entities = [1, 2, 3]; + const result = paginate(entities, 5, 3); + + expect(result.currentPage).toEqual([]); + expect(result.nextPage).toBeUndefined(); + expect(result.startIndex).toBe(15); + expect(result.endIndex).toBe(18); + }); + + it('should handle single element array', () => { + const entities = [1]; + const result = paginate(entities, 0, 3); + + expect(result.currentPage).toEqual([1]); + expect(result.nextPage).toBeUndefined(); + expect(result.startIndex).toBe(0); + expect(result.endIndex).toBe(3); + }); + + it('should handle array with exactly pageSize elements', () => { + const entities = [1, 2, 3]; + const result = paginate(entities, 0, 3); + + expect(result.currentPage).toEqual([1, 2, 3]); + expect(result.nextPage).toBeUndefined(); + expect(result.startIndex).toBe(0); + expect(result.endIndex).toBe(3); + }); + + it('should handle array with pageSize + 1 elements', () => { + const entities = [1, 2, 3, 4]; + const result = paginate(entities, 0, 3); + + expect(result.currentPage).toEqual([1, 2, 3]); + expect(result.nextPage).toBe('1'); + expect(result.startIndex).toBe(0); + expect(result.endIndex).toBe(3); + }); + + it('should handle array of objects', () => { + const entities = [ + { id: 1, name: 'Alice' }, + { id: 2, name: 'Bob' }, + { id: 3, name: 'Charlie' }, + { id: 4, name: 'David' } + ]; + const result = paginate(entities, 1, 2); + + expect(result.currentPage).toEqual([ + { id: 3, name: 'Charlie' }, + { id: 4, name: 'David' } + ]); + expect(result.nextPage).toBeUndefined(); + expect(result.startIndex).toBe(2); + expect(result.endIndex).toBe(4); + }); + + it('should handle zero page with multiple pages available', () => { + const entities = [1, 2, 3, 4, 5, 6]; + const result = paginate(entities, 0, 2); + + expect(result.currentPage).toEqual([1, 2]); + expect(result.nextPage).toBe('1'); + expect(result.startIndex).toBe(0); + expect(result.endIndex).toBe(2); + }); +}); diff --git a/src/hypersync/common.ts b/src/hypersync/common.ts index 4897b4d..60d8055 100644 --- a/src/hypersync/common.ts +++ b/src/hypersync/common.ts @@ -7,7 +7,7 @@ import { import { HypersyncFieldType, IHypersyncField -} from '@hyperproof/hypersync-models'; +} from '@hyperproof-int/hypersync-models'; import createHttpError from 'http-errors'; import { StatusCodes } from 'http-status-codes'; import queryString from 'query-string'; diff --git a/src/hypersync/hypersyncConnector.ts b/src/hypersync/hypersyncConnector.ts index 544a5c8..b6279b9 100644 --- a/src/hypersync/hypersyncConnector.ts +++ b/src/hypersync/hypersyncConnector.ts @@ -13,7 +13,7 @@ import { HypersyncCriteria, ICriteriaSearchInput, SchemaCategory -} from '@hyperproof/hypersync-models'; +} from '@hyperproof-int/hypersync-models'; import { createConnector, CustomAuthCredentials, @@ -33,7 +33,7 @@ import { ObjectType, StorageItem, UserContext -} from '@hyperproof/integration-sdk'; +} from '@hyperproof-int/integration-sdk'; import express from 'express'; import fs from 'fs'; import createHttpError from 'http-errors'; diff --git a/src/hypersync/layout.ts b/src/hypersync/layout.ts index d872e65..e82ae2e 100644 --- a/src/hypersync/layout.ts +++ b/src/hypersync/layout.ts @@ -1,6 +1,6 @@ import { IHypersyncProofField } from './ProofProviderBase'; -import { HypersyncPageOrientation } from '@hyperproof/hypersync-models'; +import { HypersyncPageOrientation } from '@hyperproof-int/hypersync-models'; const DEFAULT_A4_WIDTH_PIXELS = 794; const DEFAULT_A4_LENGTH_PIXELS = 1123; diff --git a/src/hypersync/models.ts b/src/hypersync/models.ts index 94a44dc..dacc656 100644 --- a/src/hypersync/models.ts +++ b/src/hypersync/models.ts @@ -5,12 +5,12 @@ import { HypersyncCriteria, HypersyncPeriod, SchemaCategory -} from '@hyperproof/hypersync-models'; +} from '@hyperproof-int/hypersync-models'; import { IIntegration, IIntegrationSettingsBase, IntegrationSettingsClass -} from '@hyperproof/integration-sdk'; +} from '@hyperproof-int/integration-sdk'; /** * Settings that are saved with a Hypersync integration. diff --git a/src/hypersync/time.ts b/src/hypersync/time.ts index d7e7d95..6d13734 100644 --- a/src/hypersync/time.ts +++ b/src/hypersync/time.ts @@ -1,7 +1,7 @@ import '@js-joda/timezone'; import { formatMessage, MESSAGES } from './messages'; -import { HypersyncPeriod } from '@hyperproof/hypersync-models'; +import { HypersyncPeriod } from '@hyperproof-int/hypersync-models'; import { ChronoUnit, convert, diff --git a/src/hypersync/tokens.ts b/src/hypersync/tokens.ts index ba2199a..73c9628 100644 --- a/src/hypersync/tokens.ts +++ b/src/hypersync/tokens.ts @@ -3,7 +3,7 @@ import { DataValue, HypersyncCriteria, HypersyncCriteriaValue -} from '@hyperproof/hypersync-models'; +} from '@hyperproof-int/hypersync-models'; /** * Context object that is used when resolving placeholder tokens in a string. diff --git a/src/schema-proof/UarApplicationProofProvider.ts b/src/schema-proof/UarApplicationProofProvider.ts index 9b66ac5..2da6f5b 100644 --- a/src/schema-proof/UarApplicationProofProvider.ts +++ b/src/schema-proof/UarApplicationProofProvider.ts @@ -5,8 +5,8 @@ import { HypersyncDataFormat, HypersyncFieldType, SchemaCategory -} from '@hyperproof/hypersync-models'; -import { ILocalizable } from '@hyperproof/integration-sdk'; +} from '@hyperproof-int/hypersync-models'; +import { ILocalizable } from '@hyperproof-int/integration-sdk'; import { date, InferType, object, string } from 'yup'; import { DataSourceBase } from '../hypersync'; diff --git a/src/schema-proof/UarDirectoryProofProvider.ts b/src/schema-proof/UarDirectoryProofProvider.ts index edc7d0b..f9e8cc3 100644 --- a/src/schema-proof/UarDirectoryProofProvider.ts +++ b/src/schema-proof/UarDirectoryProofProvider.ts @@ -5,8 +5,8 @@ import { HypersyncDataFormat, HypersyncFieldType, SchemaCategory -} from '@hyperproof/hypersync-models'; -import { ILocalizable } from '@hyperproof/integration-sdk'; +} from '@hyperproof-int/hypersync-models'; +import { ILocalizable } from '@hyperproof-int/integration-sdk'; import { date, InferType, object, string } from 'yup'; import { DataSourceBase } from '../hypersync/DataSourceBase'; diff --git a/src/schema-proof/common.ts b/src/schema-proof/common.ts index 8d294ed..73979aa 100644 --- a/src/schema-proof/common.ts +++ b/src/schema-proof/common.ts @@ -1,7 +1,7 @@ import { uarApplicationSchema } from './UarApplicationProofProvider'; import { uarDirectorySchema } from './UarDirectoryProofProvider'; -import { DataObject, SchemaCategory } from '@hyperproof/hypersync-models'; +import { DataObject, SchemaCategory } from '@hyperproof-int/hypersync-models'; import { IHypersync } from '../hypersync'; From 17dea231d8d7f60a2abc6f94d64d65007886c458 Mon Sep 17 00:00:00 2001 From: "trent.hashimoto" Date: Fri, 9 Jan 2026 17:10:48 -0800 Subject: [PATCH 05/17] Sync from integrations monorepo --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e197add..54c982d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,10 +17,10 @@ jobs: uses: actions/setup-node@v4 with: node-version: '22' - cache: 'npm' + cache: 'yarn' - name: Install dependencies - run: npm ci + run: yarn install --frozen-lockfile - name: Build - run: npm run build + run: yarn build From 9cbe475762e17592f58bd714f87454cafed00ba5 Mon Sep 17 00:00:00 2001 From: Trent Hashimoto Date: Mon, 12 Jan 2026 14:53:56 -0800 Subject: [PATCH 06/17] Update README with release notes for version 6.0.0 Updated release notes to version 6.0.0 with breaking changes and new features. --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2a5073c..f8e0fc1 100644 --- a/README.md +++ b/README.md @@ -8,8 +8,9 @@ To get started with the Hypersync SDK hop on over to the [SDK documentation](doc ## Release Notes -### 4.0.0 +### 6.0.0 +- Bumped version to 6 to match hypersync-models package. All packages versions will be kept in sync from now on. - **Breaking:** Updated to Node.js 22 - **Breaking:** Updated to `@hyperproof/hypersync-models` 6.0.0 and `@hyperproof/integration-sdk` 2.0.0 - Replaced `hyperproofUser` with `organization` object of type `ILocalizable` that can be used for date localization From 3b29d805c69511e3aa09a61809363ead61934669 Mon Sep 17 00:00:00 2001 From: Trent Hashimoto Date: Mon, 12 Jan 2026 14:54:19 -0800 Subject: [PATCH 07/17] Bump version and update dependencies in package.json --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 5250141..b705af7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hyperproof/hypersync-sdk", - "version": "4.0.0", + "version": "6.0.0", "description": "Hypersync SDK", "license": "MIT", "repository": { @@ -18,7 +18,7 @@ }, "dependencies": { "@hyperproof/hypersync-models": "6.0.0", - "@hyperproof/integration-sdk": "2.0.0", + "@hyperproof/integration-sdk": "6.0.0", "@js-joda/core": "3.2.0", "@js-joda/timezone": "2.5.0", "abort-controller": "3.0.0", From 560e4994962ad3a68feae7342fbcff3400debb3f Mon Sep 17 00:00:00 2001 From: "trent.hashimoto" Date: Mon, 12 Jan 2026 15:00:22 -0800 Subject: [PATCH 08/17] Remove hyperproofgov from documentation --- doc/002-dev-workflow.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/doc/002-dev-workflow.md b/doc/002-dev-workflow.md index 58a1993..4e985ea 100644 --- a/doc/002-dev-workflow.md +++ b/doc/002-dev-workflow.md @@ -16,18 +16,12 @@ After installing the CLI, you will first need to sign in using this command: hp signin ``` -> NOTE: For customers using Hyperproof EU or Hyperproof GOV, please use the following commands: +> NOTE: For customers using Hyperproof EU, please use the following command: > > ``` > hp signin --domain hyperproof.eu > ``` > -> or -> -> ``` -> hp signin --domain hyperproofgov.app -> ``` -> > For additional information use 'hp signin -help' This command will launch a new browser window and allow you to sign in (if you are not already signed in) and then authorize CLI access to your Hyperproof organization. From 836c92ff1b0f802a6b418aafa8f0b22c1c4f94f2 Mon Sep 17 00:00:00 2001 From: "trent.hashimoto" Date: Mon, 12 Jan 2026 15:14:02 -0800 Subject: [PATCH 09/17] Convert hypersync-sdk imports @hyperproof-int/ -> @hyperproof/ --- src/hypersync/DataSourceBase.ts | 4 ++-- src/hypersync/HypersyncApp.test.ts | 4 ++-- src/hypersync/HypersyncApp.ts | 4 ++-- src/hypersync/ICriteriaProvider.ts | 4 ++-- src/hypersync/IDataSource.ts | 4 ++-- src/hypersync/JsonCriteriaProvider.test.ts | 4 ++-- src/hypersync/JsonCriteriaProvider.ts | 4 ++-- src/hypersync/JsonProofProvider.test.ts | 6 +++--- src/hypersync/JsonProofProvider.ts | 4 ++-- src/hypersync/Paginator.test.ts | 4 ++-- src/hypersync/Paginator.ts | 2 +- src/hypersync/ProofProviderBase.ts | 4 ++-- src/hypersync/ProofProviderFactory.ts | 4 ++-- src/hypersync/RestDataSourceBase.test.ts | 10 +++++----- src/hypersync/RestDataSourceBase.ts | 4 ++-- src/hypersync/ServiceDataIterator.test.ts | 2 +- src/hypersync/ServiceDataIterator.ts | 4 ++-- src/hypersync/Sync.ts | 2 +- src/hypersync/common.ts | 2 +- src/hypersync/hypersyncConnector.ts | 4 ++-- src/hypersync/layout.ts | 2 +- src/hypersync/models.ts | 4 ++-- src/hypersync/time.ts | 2 +- src/hypersync/tokens.ts | 2 +- src/schema-proof/UarApplicationProofProvider.ts | 4 ++-- src/schema-proof/UarDirectoryProofProvider.ts | 4 ++-- src/schema-proof/common.ts | 2 +- 27 files changed, 50 insertions(+), 50 deletions(-) diff --git a/src/hypersync/DataSourceBase.ts b/src/hypersync/DataSourceBase.ts index 64023ba..0541a51 100644 --- a/src/hypersync/DataSourceBase.ts +++ b/src/hypersync/DataSourceBase.ts @@ -6,8 +6,8 @@ import { SyncMetadata } from './IDataSource'; -import { DataObject, DataValueMap } from '@hyperproof-int/hypersync-models'; -import { ILocalizable } from '@hyperproof-int/integration-sdk'; +import { DataObject, DataValueMap } from '@hyperproof/hypersync-models'; +import { ILocalizable } from '@hyperproof/integration-sdk'; /** * Abstract base class for a data source object. Provides convenient diff --git a/src/hypersync/HypersyncApp.test.ts b/src/hypersync/HypersyncApp.test.ts index 2c4d92a..088e0aa 100644 --- a/src/hypersync/HypersyncApp.test.ts +++ b/src/hypersync/HypersyncApp.test.ts @@ -14,8 +14,8 @@ import { IntegrationContext, ObjectType, UserContext -} from '@hyperproof-int/integration-sdk/lib'; -import { ILocalizable } from '@hyperproof-int/integration-sdk/src'; +} from '@hyperproof/integration-sdk/lib'; +import { ILocalizable } from '@hyperproof/integration-sdk/src'; describe('HypersyncApp.ts', () => { let app: any; diff --git a/src/hypersync/HypersyncApp.ts b/src/hypersync/HypersyncApp.ts index 2c7a624..ef2e57c 100644 --- a/src/hypersync/HypersyncApp.ts +++ b/src/hypersync/HypersyncApp.ts @@ -35,7 +35,7 @@ import { IHypersyncDefinition, SchemaCategory, ValueLookup -} from '@hyperproof-int/hypersync-models'; +} from '@hyperproof/hypersync-models'; import { AuthorizationType, createApp, @@ -56,7 +56,7 @@ import { OAuthTokenResponse, ObjectType, UserContext -} from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/integration-sdk'; import express from 'express'; import fs from 'fs'; import createHttpError from 'http-errors'; diff --git a/src/hypersync/ICriteriaProvider.ts b/src/hypersync/ICriteriaProvider.ts index bb3c205..ca69ce2 100644 --- a/src/hypersync/ICriteriaProvider.ts +++ b/src/hypersync/ICriteriaProvider.ts @@ -11,8 +11,8 @@ import { ISelectOption, IValidation, SchemaCategory -} from '@hyperproof-int/hypersync-models'; -import { CriteriaPageMessageLevel } from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/hypersync-models'; +import { CriteriaPageMessageLevel } from '@hyperproof/integration-sdk'; /** * Information needed to render a criteria field used in the configuration diff --git a/src/hypersync/IDataSource.ts b/src/hypersync/IDataSource.ts index 789be4e..1c983c9 100644 --- a/src/hypersync/IDataSource.ts +++ b/src/hypersync/IDataSource.ts @@ -2,8 +2,8 @@ import { IErrorInfo } from './models'; import { RestDataSourceBase } from './RestDataSourceBase'; import { TokenContext } from './tokens'; -import { DataObject, DataValueMap } from '@hyperproof-int/hypersync-models'; -import { ILocalizable } from '@hyperproof-int/integration-sdk'; +import { DataObject, DataValueMap } from '@hyperproof/hypersync-models'; +import { ILocalizable } from '@hyperproof/integration-sdk'; export enum DataSetResultStatus { Complete = 'complete', diff --git a/src/hypersync/JsonCriteriaProvider.test.ts b/src/hypersync/JsonCriteriaProvider.test.ts index ac875e2..818b9d8 100644 --- a/src/hypersync/JsonCriteriaProvider.test.ts +++ b/src/hypersync/JsonCriteriaProvider.test.ts @@ -4,8 +4,8 @@ import { JsonCriteriaProvider } from './JsonCriteriaProvider'; import { HypersyncCriteriaFieldType, ValidationTypes -} from '@hyperproof-int/hypersync-models'; -import { Logger } from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/hypersync-models'; +import { Logger } from '@hyperproof/integration-sdk'; import fs from 'fs'; import path from 'path'; diff --git a/src/hypersync/JsonCriteriaProvider.ts b/src/hypersync/JsonCriteriaProvider.ts index bb6e352..d481e59 100644 --- a/src/hypersync/JsonCriteriaProvider.ts +++ b/src/hypersync/JsonCriteriaProvider.ts @@ -16,8 +16,8 @@ import { ICriteriaSearchInput, IProofCriterionRef, ISelectOption -} from '@hyperproof-int/hypersync-models'; -import { compareValues, Logger } from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/hypersync-models'; +import { compareValues, Logger } from '@hyperproof/integration-sdk'; import fs from 'fs'; import path from 'path'; diff --git a/src/hypersync/JsonProofProvider.test.ts b/src/hypersync/JsonProofProvider.test.ts index 333c262..636df96 100644 --- a/src/hypersync/JsonProofProvider.test.ts +++ b/src/hypersync/JsonProofProvider.test.ts @@ -13,17 +13,17 @@ import { HypersyncPageOrientation, HypersyncPeriod, IProofSpec -} from '@hyperproof-int/hypersync-models'; +} from '@hyperproof/hypersync-models'; import { HypersyncProofFormat, HypersyncTemplate, IHypersync -} from '@hyperproof-int/hypersync-sdk/lib'; +} from '@hyperproof/hypersync-sdk/lib'; import { ILocalizable, IntegrationSettingsClass, ObjectType -} from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/integration-sdk'; const CONNECTOR_NAME = 'testConnector'; const PROOF_TYPE = 'testProofType'; diff --git a/src/hypersync/JsonProofProvider.ts b/src/hypersync/JsonProofProvider.ts index 51f9ae1..047e19c 100644 --- a/src/hypersync/JsonProofProvider.ts +++ b/src/hypersync/JsonProofProvider.ts @@ -32,8 +32,8 @@ import { IHypersyncField, IProofSpec, IteratorSource -} from '@hyperproof-int/hypersync-models'; -import { ILocalizable, Logger } from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/hypersync-models'; +import { ILocalizable, Logger } from '@hyperproof/integration-sdk'; import createHttpError from 'http-errors'; import { StatusCodes } from 'http-status-codes'; diff --git a/src/hypersync/Paginator.test.ts b/src/hypersync/Paginator.test.ts index 144916e..b2f4647 100644 --- a/src/hypersync/Paginator.test.ts +++ b/src/hypersync/Paginator.test.ts @@ -14,8 +14,8 @@ import { NextTokenType, PageUntilCondition, PagingType -} from '@hyperproof-int/hypersync-models'; -import { IGraphQLConnectionsScheme } from '@hyperproof-int/hypersync-models/src/dataSource'; +} from '@hyperproof/hypersync-models'; +import { IGraphQLConnectionsScheme } from '@hyperproof/hypersync-models/src/dataSource'; describe('Paginator', () => { describe('createPaginator', () => { diff --git a/src/hypersync/Paginator.ts b/src/hypersync/Paginator.ts index f636078..170a67f 100644 --- a/src/hypersync/Paginator.ts +++ b/src/hypersync/Paginator.ts @@ -13,7 +13,7 @@ import { PageUntilCondition, PagingScheme, PagingType -} from '@hyperproof-int/hypersync-models'; +} from '@hyperproof/hypersync-models'; import jsonata from 'jsonata'; import set from 'lodash/set'; diff --git a/src/hypersync/ProofProviderBase.ts b/src/hypersync/ProofProviderBase.ts index ca9d480..ea8091f 100644 --- a/src/hypersync/ProofProviderBase.ts +++ b/src/hypersync/ProofProviderBase.ts @@ -19,8 +19,8 @@ import { HypersyncFieldType, HypersyncPageOrientation, SchemaCategory -} from '@hyperproof-int/hypersync-models'; -import { ILocalizable } from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/hypersync-models'; +import { ILocalizable } from '@hyperproof/integration-sdk'; import createHttpError from 'http-errors'; import { StatusCodes } from 'http-status-codes'; diff --git a/src/hypersync/ProofProviderFactory.ts b/src/hypersync/ProofProviderFactory.ts index 8fbdd02..98af817 100644 --- a/src/hypersync/ProofProviderFactory.ts +++ b/src/hypersync/ProofProviderFactory.ts @@ -10,8 +10,8 @@ import { IProofType, IProofTypeMap, SchemaCategory -} from '@hyperproof-int/hypersync-models'; -import { compareValues } from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/hypersync-models'; +import { compareValues } from '@hyperproof/integration-sdk'; import fs from 'fs'; import createHttpError from 'http-errors'; import { StatusCodes } from 'http-status-codes'; diff --git a/src/hypersync/RestDataSourceBase.test.ts b/src/hypersync/RestDataSourceBase.test.ts index bdfd138..89d97c1 100644 --- a/src/hypersync/RestDataSourceBase.test.ts +++ b/src/hypersync/RestDataSourceBase.test.ts @@ -3,15 +3,15 @@ import { RestDataSourceBase } from './RestDataSourceBase'; import { IDataSet, IRestDataSourceConfig -} from '@hyperproof-int/hypersync-models'; -import { DataSetResultStatus } from '@hyperproof-int/hypersync-sdk'; -import { ApiClient } from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/hypersync-models'; +import { DataSetResultStatus } from '@hyperproof/hypersync-sdk'; +import { ApiClient } from '@hyperproof/integration-sdk'; import { HeadersInit } from 'node-fetch'; // Mock Logger -jest.mock('@hyperproof-int/integration-sdk', () => { +jest.mock('@hyperproof/integration-sdk', () => { return { - ...jest.requireActual('@hyperproof-int/integration-sdk'), + ...jest.requireActual('@hyperproof/integration-sdk'), Logger: { log: jest.fn(), error: jest.fn(), diff --git a/src/hypersync/RestDataSourceBase.ts b/src/hypersync/RestDataSourceBase.ts index ed92284..190d0dc 100644 --- a/src/hypersync/RestDataSourceBase.ts +++ b/src/hypersync/RestDataSourceBase.ts @@ -27,14 +27,14 @@ import { PagingLevel, Transform, ValueLookup -} from '@hyperproof-int/hypersync-models'; +} from '@hyperproof/hypersync-models'; import { ApiClient, compareValues, IApiClientResponse, ILocalizable, Logger -} from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/integration-sdk'; import createHttpError from 'http-errors'; import { StatusCodes } from 'http-status-codes'; import jsonata from 'jsonata'; diff --git a/src/hypersync/ServiceDataIterator.test.ts b/src/hypersync/ServiceDataIterator.test.ts index b5744c4..458d9bf 100644 --- a/src/hypersync/ServiceDataIterator.test.ts +++ b/src/hypersync/ServiceDataIterator.test.ts @@ -4,7 +4,7 @@ import { IterableObject, ServiceDataIterator } from './ServiceDataIterator'; import { DataSetIteratorDefinition, IteratorSource -} from '@hyperproof-int/hypersync-models'; +} from '@hyperproof/hypersync-models'; describe('ServiceDataIterator', () => { const mockRestDataSource = { diff --git a/src/hypersync/ServiceDataIterator.ts b/src/hypersync/ServiceDataIterator.ts index 1ea96a9..37b3fd5 100644 --- a/src/hypersync/ServiceDataIterator.ts +++ b/src/hypersync/ServiceDataIterator.ts @@ -21,8 +21,8 @@ import { DataValueMap, IProofSpec, IteratorSource -} from '@hyperproof-int/hypersync-models'; -import { ILocalizable } from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/hypersync-models'; +import { ILocalizable } from '@hyperproof/integration-sdk'; export interface IIteratorPlanComplete { status: DataSetResultStatus.Complete; diff --git a/src/hypersync/Sync.ts b/src/hypersync/Sync.ts index c550691..39e6f7c 100644 --- a/src/hypersync/Sync.ts +++ b/src/hypersync/Sync.ts @@ -7,7 +7,7 @@ import { IProofFile } from './ProofProviderBase'; -import { ILocalizable } from '@hyperproof-int/integration-sdk'; +import { ILocalizable } from '@hyperproof/integration-sdk'; /** * Detailed response object that may be returned from getProofData. diff --git a/src/hypersync/common.ts b/src/hypersync/common.ts index 60d8055..4897b4d 100644 --- a/src/hypersync/common.ts +++ b/src/hypersync/common.ts @@ -7,7 +7,7 @@ import { import { HypersyncFieldType, IHypersyncField -} from '@hyperproof-int/hypersync-models'; +} from '@hyperproof/hypersync-models'; import createHttpError from 'http-errors'; import { StatusCodes } from 'http-status-codes'; import queryString from 'query-string'; diff --git a/src/hypersync/hypersyncConnector.ts b/src/hypersync/hypersyncConnector.ts index b6279b9..544a5c8 100644 --- a/src/hypersync/hypersyncConnector.ts +++ b/src/hypersync/hypersyncConnector.ts @@ -13,7 +13,7 @@ import { HypersyncCriteria, ICriteriaSearchInput, SchemaCategory -} from '@hyperproof-int/hypersync-models'; +} from '@hyperproof/hypersync-models'; import { createConnector, CustomAuthCredentials, @@ -33,7 +33,7 @@ import { ObjectType, StorageItem, UserContext -} from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/integration-sdk'; import express from 'express'; import fs from 'fs'; import createHttpError from 'http-errors'; diff --git a/src/hypersync/layout.ts b/src/hypersync/layout.ts index e82ae2e..d872e65 100644 --- a/src/hypersync/layout.ts +++ b/src/hypersync/layout.ts @@ -1,6 +1,6 @@ import { IHypersyncProofField } from './ProofProviderBase'; -import { HypersyncPageOrientation } from '@hyperproof-int/hypersync-models'; +import { HypersyncPageOrientation } from '@hyperproof/hypersync-models'; const DEFAULT_A4_WIDTH_PIXELS = 794; const DEFAULT_A4_LENGTH_PIXELS = 1123; diff --git a/src/hypersync/models.ts b/src/hypersync/models.ts index dacc656..94a44dc 100644 --- a/src/hypersync/models.ts +++ b/src/hypersync/models.ts @@ -5,12 +5,12 @@ import { HypersyncCriteria, HypersyncPeriod, SchemaCategory -} from '@hyperproof-int/hypersync-models'; +} from '@hyperproof/hypersync-models'; import { IIntegration, IIntegrationSettingsBase, IntegrationSettingsClass -} from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/integration-sdk'; /** * Settings that are saved with a Hypersync integration. diff --git a/src/hypersync/time.ts b/src/hypersync/time.ts index 6d13734..d7e7d95 100644 --- a/src/hypersync/time.ts +++ b/src/hypersync/time.ts @@ -1,7 +1,7 @@ import '@js-joda/timezone'; import { formatMessage, MESSAGES } from './messages'; -import { HypersyncPeriod } from '@hyperproof-int/hypersync-models'; +import { HypersyncPeriod } from '@hyperproof/hypersync-models'; import { ChronoUnit, convert, diff --git a/src/hypersync/tokens.ts b/src/hypersync/tokens.ts index 73c9628..ba2199a 100644 --- a/src/hypersync/tokens.ts +++ b/src/hypersync/tokens.ts @@ -3,7 +3,7 @@ import { DataValue, HypersyncCriteria, HypersyncCriteriaValue -} from '@hyperproof-int/hypersync-models'; +} from '@hyperproof/hypersync-models'; /** * Context object that is used when resolving placeholder tokens in a string. diff --git a/src/schema-proof/UarApplicationProofProvider.ts b/src/schema-proof/UarApplicationProofProvider.ts index 2da6f5b..9b66ac5 100644 --- a/src/schema-proof/UarApplicationProofProvider.ts +++ b/src/schema-proof/UarApplicationProofProvider.ts @@ -5,8 +5,8 @@ import { HypersyncDataFormat, HypersyncFieldType, SchemaCategory -} from '@hyperproof-int/hypersync-models'; -import { ILocalizable } from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/hypersync-models'; +import { ILocalizable } from '@hyperproof/integration-sdk'; import { date, InferType, object, string } from 'yup'; import { DataSourceBase } from '../hypersync'; diff --git a/src/schema-proof/UarDirectoryProofProvider.ts b/src/schema-proof/UarDirectoryProofProvider.ts index f9e8cc3..edc7d0b 100644 --- a/src/schema-proof/UarDirectoryProofProvider.ts +++ b/src/schema-proof/UarDirectoryProofProvider.ts @@ -5,8 +5,8 @@ import { HypersyncDataFormat, HypersyncFieldType, SchemaCategory -} from '@hyperproof-int/hypersync-models'; -import { ILocalizable } from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/hypersync-models'; +import { ILocalizable } from '@hyperproof/integration-sdk'; import { date, InferType, object, string } from 'yup'; import { DataSourceBase } from '../hypersync/DataSourceBase'; diff --git a/src/schema-proof/common.ts b/src/schema-proof/common.ts index 73979aa..8d294ed 100644 --- a/src/schema-proof/common.ts +++ b/src/schema-proof/common.ts @@ -1,7 +1,7 @@ import { uarApplicationSchema } from './UarApplicationProofProvider'; import { uarDirectorySchema } from './UarDirectoryProofProvider'; -import { DataObject, SchemaCategory } from '@hyperproof-int/hypersync-models'; +import { DataObject, SchemaCategory } from '@hyperproof/hypersync-models'; import { IHypersync } from '../hypersync'; From ac98010e4044f195c0c17dd390ce368bd5a1885b Mon Sep 17 00:00:00 2001 From: "trent.hashimoto" Date: Mon, 12 Jan 2026 15:23:51 -0800 Subject: [PATCH 10/17] semver engines.node --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b705af7..3d00a65 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "lint": "./node_modules/eslint/bin/eslint.js src/**/*.ts" }, "engines": { - "node": "22" + "node": "^22.0.0" }, "dependencies": { "@hyperproof/hypersync-models": "6.0.0", From 1bde978fdcc9675d025295840c2d5bdd4ed16abd Mon Sep 17 00:00:00 2001 From: "trent.hashimoto" Date: Tue, 20 Jan 2026 23:04:35 -0800 Subject: [PATCH 11/17] Remove 6.0.0 release notes from readme --- README.md | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/README.md b/README.md index f8e0fc1..e879a3e 100644 --- a/README.md +++ b/README.md @@ -8,19 +8,6 @@ To get started with the Hypersync SDK hop on over to the [SDK documentation](doc ## Release Notes -### 6.0.0 - -- Bumped version to 6 to match hypersync-models package. All packages versions will be kept in sync from now on. -- **Breaking:** Updated to Node.js 22 -- **Breaking:** Updated to `@hyperproof/hypersync-models` 6.0.0 and `@hyperproof/integration-sdk` 2.0.0 -- Replaced `hyperproofUser` with `organization` object of type `ILocalizable` that can be used for date localization -- Added `ServiceDataIterator` class for iterative data fetching with support for dataset and criteria-sourced iteration -- Enhanced `JsonProofProvider` with iterator support for generating proofs from iterable data -- Improved `JsonCriteriaProvider` with saved criteria settings and search input handling -- Enhanced `RestDataSourceBase` with improved pagination and error handling -- Added `continueOnError` support in data set lookups -- Plus other bug fixes and performance enhancements - ### 3.2.0 - Integrate latest Integration SDK which contains reliability improvements and bug fixes. From f53f9ac2dcf872bb7fedc8a43c3d1a20eb0ae84f Mon Sep 17 00:00:00 2001 From: "trent.hashimoto" Date: Tue, 20 Jan 2026 23:06:58 -0800 Subject: [PATCH 12/17] Use version 6.0.0-beta --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3d00a65..962dc97 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hyperproof/hypersync-sdk", - "version": "6.0.0", + "version": "6.0.0-beta", "description": "Hypersync SDK", "license": "MIT", "repository": { From 7d71b8c0bbdc25194a98519795b7f321209f713e Mon Sep 17 00:00:00 2001 From: "trent.hashimoto" Date: Tue, 20 Jan 2026 23:07:53 -0800 Subject: [PATCH 13/17] Depend on 6.0.0-beta --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 962dc97..79eac85 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,8 @@ "node": "^22.0.0" }, "dependencies": { - "@hyperproof/hypersync-models": "6.0.0", - "@hyperproof/integration-sdk": "6.0.0", + "@hyperproof/hypersync-models": "6.0.0-beta", + "@hyperproof/integration-sdk": "6.0.0-beta", "@js-joda/core": "3.2.0", "@js-joda/timezone": "2.5.0", "abort-controller": "3.0.0", From e0992b31712fd164b4edbbf9ea8178ef9eb46c2b Mon Sep 17 00:00:00 2001 From: "trent.hashimoto" Date: Tue, 20 Jan 2026 23:29:16 -0800 Subject: [PATCH 14/17] Update docs to say Node 22 --- doc/001-getting-started.md | 8 ++++---- doc/002-dev-workflow.md | 2 +- doc/004-connections.md | 2 +- doc/007-criteria.md | 2 +- doc/031-svg-images.md | 2 +- doc/migration.md | 2 +- src/hypersync/DataSourceBase.ts | 4 ++-- src/hypersync/HypersyncApp.test.ts | 4 ++-- src/hypersync/HypersyncApp.ts | 4 ++-- src/hypersync/ICriteriaProvider.ts | 4 ++-- src/hypersync/IDataSource.ts | 4 ++-- src/hypersync/JsonCriteriaProvider.test.ts | 4 ++-- src/hypersync/JsonCriteriaProvider.ts | 4 ++-- src/hypersync/JsonProofProvider.test.ts | 6 +++--- src/hypersync/JsonProofProvider.ts | 17 +++++++++++++---- src/hypersync/Paginator.test.ts | 4 ++-- src/hypersync/Paginator.ts | 2 +- src/hypersync/ProofProviderBase.ts | 8 +++++--- src/hypersync/ProofProviderFactory.ts | 4 ++-- src/hypersync/RestDataSourceBase.test.ts | 10 +++++----- src/hypersync/RestDataSourceBase.ts | 4 ++-- src/hypersync/ServiceDataIterator.test.ts | 2 +- src/hypersync/ServiceDataIterator.ts | 4 ++-- src/hypersync/Sync.ts | 2 +- src/hypersync/common.ts | 2 +- src/hypersync/hypersyncConnector.ts | 4 ++-- src/hypersync/layout.ts | 2 +- src/hypersync/models.ts | 4 ++-- src/hypersync/time.ts | 2 +- src/hypersync/tokens.ts | 2 +- src/schema-proof/UarApplicationProofProvider.ts | 4 ++-- src/schema-proof/UarDirectoryProofProvider.ts | 4 ++-- src/schema-proof/common.ts | 2 +- 33 files changed, 73 insertions(+), 62 deletions(-) diff --git a/doc/001-getting-started.md b/doc/001-getting-started.md index 8291854..e9e91fe 100644 --- a/doc/001-getting-started.md +++ b/doc/001-getting-started.md @@ -8,11 +8,11 @@ Every Hypersync app is a [Node.js](https://nodejs.org/en/) [module](https://node To use the Hypersync SDK you will need a Windows, Mac or Linux computer with a few software packages installed. -Since each Hypersync is a Node.js module, you will need to have Node.js installed in order to build custom Hypersyncs. The Hypersync platform uses Node.js version 16 which you can install here: +Since each Hypersync is a Node.js module, you will need to have Node.js installed in order to build custom Hypersyncs. The Hypersync platform uses Node.js version 22 which you can install here: -- [Node JS version 18](https://nodejs.org/download/release/v18.17.1/). +- [Node.js](https://nodejs.org/en/download). -You will also need to install the classic yarn package manager (version 1.22.19) using the link below. +You will also need to install the classic yarn package manager using the link below: - [yarn package manager](https://classic.yarnpkg.com) @@ -22,7 +22,7 @@ The Hyperproof CLI is needed to publish and manage your custom Hypersyncs: Finally, we recommend cloning the [Hypersync SDK Samples GitHub repository](https://github.com/Hyperproof/hypersync-sdk-samples). This is a public repository available on GitHub.com. -- The samples repository contains several complete Hypersync apps that can be used to bring data from an external service into Hyperproof. Using the Hyperysnc SDK you can customize these samples to meet your needs. The hypersync-sdk-samples repository contains samples for MySQL, Zoho and other services. Your will find the these Hypersync apps in the `/apps` directory. +- The samples repository contains several complete Hypersync apps that can be used to bring data from an external service into Hyperproof. Using the Hypersync SDK you can customize these samples to meet your needs. The hypersync-sdk-samples repository contains samples for MySQL, Zoho and other services. Your will find the these Hypersync apps in the `/apps` directory. - The samples repository also includes a handful of templates to get you started building your own Hypersyncs from scatch. Templates can be found in the `/templates` directory. See the `README.md` files in each template directory for more information on using the templates. diff --git a/doc/002-dev-workflow.md b/doc/002-dev-workflow.md index 4e985ea..f597ea6 100644 --- a/doc/002-dev-workflow.md +++ b/doc/002-dev-workflow.md @@ -32,7 +32,7 @@ This command will launch a new browser window and allow you to sign in (if you a ### Importing Your Hypersync App -Once you have signed in and authorized the CLI, you are ready to import a custom Hyperysnc app. +Once you have signed in and authorized the CLI, you are ready to import a custom Hypersync app. 1. Find the `apps/open-library/` directory in your clone of the Hypersync SDK Samples repository. 2. Make a copy of that folder somewhere else on your computer. Feel free to rename the folder if that helps. diff --git a/doc/004-connections.md b/doc/004-connections.md index dbed46d..ff5cf45 100644 --- a/doc/004-connections.md +++ b/doc/004-connections.md @@ -93,7 +93,7 @@ Finally, implement the `getUserId` and `getUserAccountName` methods. `getUserId` ## Custom Authentication -All non-OAuth authentication/authorization schemes are classified as "Custom" in the Hyperysnc SDK. If your service does not use OAuth 2.0, you should specify `custom` as your `authType` in `package.json`. See [Custom Hypersync App package.json Reference](./030-package-json-reference.md) for more information. +All non-OAuth authentication/authorization schemes are classified as "Custom" in the Hypersync SDK. If your service does not use OAuth 2.0, you should specify `custom` as your `authType` in `package.json`. See [Custom Hypersync App package.json Reference](./030-package-json-reference.md) for more information. Custom auth covers any type of authentication where the user provides credentials to make a connection. Credentials can include user name/password, access key/secret key, API Token, and many others. Users may also need to designate the endpoint they’re connecting to - for example by providing a URL or a region. diff --git a/doc/007-criteria.md b/doc/007-criteria.md index 68f62a4..d37267d 100644 --- a/doc/007-criteria.md +++ b/doc/007-criteria.md @@ -71,7 +71,7 @@ class MyCriteriaProvider implements ICriteriaProvider{ pages: ICriteriaPage[] ): Promise { // TODO: Generate an ICriteriaMetdata instance containing the metadata for - // the fields the user needs to configure, as well as defaults for the Hyperysnc + // the fields the user needs to configure, as well as defaults for the Hypersync // name, frequency of execution, and versioning behavior. } diff --git a/doc/031-svg-images.md b/doc/031-svg-images.md index c3d327b..8079b98 100644 --- a/doc/031-svg-images.md +++ b/doc/031-svg-images.md @@ -1,6 +1,6 @@ # Custom Hypersync App Icons and Branding -Every custom Hyperysnc app must include four SVG files which the user will see while working with the Hyperysnc app in the Hyperproof UI. These files must be `.SVG` files, and they must follow certain naming and size rules. +Every custom Hypersync app must include four SVG files which the user will see while working with the Hypersync app in the Hyperproof UI. These files must be `.SVG` files, and they must follow certain naming and size rules. | File | Description | Sizing | | ----------------- | ------------------------------------------------------------------------------------------------- | ----------- | diff --git a/doc/migration.md b/doc/migration.md index 76f2f9a..8059917 100644 --- a/doc/migration.md +++ b/doc/migration.md @@ -11,7 +11,7 @@ The Hypersync SDK functionality is distributed via three Node packages: | Package | Version | Description | | ---------------------------- | ------- | --------------------------- | -| @hyperproof/hyperysnc-sdk | 3.0.2 | Core Hypersync SDK | +| @hyperproof/hypersync-sdk | 3.0.2 | Core Hypersync SDK | | @hyperproof/hypersync-models | 5.0.0 | Supporting Hypersync models | | @hyperproof/integration-sdk | 1.0.2 | Common integration elements | diff --git a/src/hypersync/DataSourceBase.ts b/src/hypersync/DataSourceBase.ts index 0541a51..64023ba 100644 --- a/src/hypersync/DataSourceBase.ts +++ b/src/hypersync/DataSourceBase.ts @@ -6,8 +6,8 @@ import { SyncMetadata } from './IDataSource'; -import { DataObject, DataValueMap } from '@hyperproof/hypersync-models'; -import { ILocalizable } from '@hyperproof/integration-sdk'; +import { DataObject, DataValueMap } from '@hyperproof-int/hypersync-models'; +import { ILocalizable } from '@hyperproof-int/integration-sdk'; /** * Abstract base class for a data source object. Provides convenient diff --git a/src/hypersync/HypersyncApp.test.ts b/src/hypersync/HypersyncApp.test.ts index 088e0aa..2c4d92a 100644 --- a/src/hypersync/HypersyncApp.test.ts +++ b/src/hypersync/HypersyncApp.test.ts @@ -14,8 +14,8 @@ import { IntegrationContext, ObjectType, UserContext -} from '@hyperproof/integration-sdk/lib'; -import { ILocalizable } from '@hyperproof/integration-sdk/src'; +} from '@hyperproof-int/integration-sdk/lib'; +import { ILocalizable } from '@hyperproof-int/integration-sdk/src'; describe('HypersyncApp.ts', () => { let app: any; diff --git a/src/hypersync/HypersyncApp.ts b/src/hypersync/HypersyncApp.ts index ef2e57c..2c7a624 100644 --- a/src/hypersync/HypersyncApp.ts +++ b/src/hypersync/HypersyncApp.ts @@ -35,7 +35,7 @@ import { IHypersyncDefinition, SchemaCategory, ValueLookup -} from '@hyperproof/hypersync-models'; +} from '@hyperproof-int/hypersync-models'; import { AuthorizationType, createApp, @@ -56,7 +56,7 @@ import { OAuthTokenResponse, ObjectType, UserContext -} from '@hyperproof/integration-sdk'; +} from '@hyperproof-int/integration-sdk'; import express from 'express'; import fs from 'fs'; import createHttpError from 'http-errors'; diff --git a/src/hypersync/ICriteriaProvider.ts b/src/hypersync/ICriteriaProvider.ts index ca69ce2..bb3c205 100644 --- a/src/hypersync/ICriteriaProvider.ts +++ b/src/hypersync/ICriteriaProvider.ts @@ -11,8 +11,8 @@ import { ISelectOption, IValidation, SchemaCategory -} from '@hyperproof/hypersync-models'; -import { CriteriaPageMessageLevel } from '@hyperproof/integration-sdk'; +} from '@hyperproof-int/hypersync-models'; +import { CriteriaPageMessageLevel } from '@hyperproof-int/integration-sdk'; /** * Information needed to render a criteria field used in the configuration diff --git a/src/hypersync/IDataSource.ts b/src/hypersync/IDataSource.ts index 1c983c9..789be4e 100644 --- a/src/hypersync/IDataSource.ts +++ b/src/hypersync/IDataSource.ts @@ -2,8 +2,8 @@ import { IErrorInfo } from './models'; import { RestDataSourceBase } from './RestDataSourceBase'; import { TokenContext } from './tokens'; -import { DataObject, DataValueMap } from '@hyperproof/hypersync-models'; -import { ILocalizable } from '@hyperproof/integration-sdk'; +import { DataObject, DataValueMap } from '@hyperproof-int/hypersync-models'; +import { ILocalizable } from '@hyperproof-int/integration-sdk'; export enum DataSetResultStatus { Complete = 'complete', diff --git a/src/hypersync/JsonCriteriaProvider.test.ts b/src/hypersync/JsonCriteriaProvider.test.ts index 818b9d8..ac875e2 100644 --- a/src/hypersync/JsonCriteriaProvider.test.ts +++ b/src/hypersync/JsonCriteriaProvider.test.ts @@ -4,8 +4,8 @@ import { JsonCriteriaProvider } from './JsonCriteriaProvider'; import { HypersyncCriteriaFieldType, ValidationTypes -} from '@hyperproof/hypersync-models'; -import { Logger } from '@hyperproof/integration-sdk'; +} from '@hyperproof-int/hypersync-models'; +import { Logger } from '@hyperproof-int/integration-sdk'; import fs from 'fs'; import path from 'path'; diff --git a/src/hypersync/JsonCriteriaProvider.ts b/src/hypersync/JsonCriteriaProvider.ts index d481e59..bb6e352 100644 --- a/src/hypersync/JsonCriteriaProvider.ts +++ b/src/hypersync/JsonCriteriaProvider.ts @@ -16,8 +16,8 @@ import { ICriteriaSearchInput, IProofCriterionRef, ISelectOption -} from '@hyperproof/hypersync-models'; -import { compareValues, Logger } from '@hyperproof/integration-sdk'; +} from '@hyperproof-int/hypersync-models'; +import { compareValues, Logger } from '@hyperproof-int/integration-sdk'; import fs from 'fs'; import path from 'path'; diff --git a/src/hypersync/JsonProofProvider.test.ts b/src/hypersync/JsonProofProvider.test.ts index 636df96..333c262 100644 --- a/src/hypersync/JsonProofProvider.test.ts +++ b/src/hypersync/JsonProofProvider.test.ts @@ -13,17 +13,17 @@ import { HypersyncPageOrientation, HypersyncPeriod, IProofSpec -} from '@hyperproof/hypersync-models'; +} from '@hyperproof-int/hypersync-models'; import { HypersyncProofFormat, HypersyncTemplate, IHypersync -} from '@hyperproof/hypersync-sdk/lib'; +} from '@hyperproof-int/hypersync-sdk/lib'; import { ILocalizable, IntegrationSettingsClass, ObjectType -} from '@hyperproof/integration-sdk'; +} from '@hyperproof-int/integration-sdk'; const CONNECTOR_NAME = 'testConnector'; const PROOF_TYPE = 'testProofType'; diff --git a/src/hypersync/JsonProofProvider.ts b/src/hypersync/JsonProofProvider.ts index 047e19c..209770d 100644 --- a/src/hypersync/JsonProofProvider.ts +++ b/src/hypersync/JsonProofProvider.ts @@ -32,8 +32,8 @@ import { IHypersyncField, IProofSpec, IteratorSource -} from '@hyperproof/hypersync-models'; -import { ILocalizable, Logger } from '@hyperproof/integration-sdk'; +} from '@hyperproof-int/hypersync-models'; +import { ILocalizable, Logger } from '@hyperproof-int/integration-sdk'; import createHttpError from 'http-errors'; import { StatusCodes } from 'http-status-codes'; @@ -140,6 +140,13 @@ export class JsonProofProvider extends ProofProviderBase { const tokenContext = this.initTokenContext(criteriaValues); const proofSpec: IProofSpec = this.buildProofSpec(definition, tokenContext); + if (proofSpec.sort?.some(s => typeof s.property !== 'string')) { + throw createHttpError( + StatusCodes.BAD_REQUEST, + 'Sort clauses must specify a property to sort on.' + ); + } + if (proofSpec.dataSetIterator) { if (!isRestDataSourceBase(this.dataSource)) { throw createHttpError( @@ -184,14 +191,16 @@ export class JsonProofProvider extends ProofProviderBase { return { syncPlan: { iteratorPlan, - combine: true + combine: true, + sort: proofSpec.sort } }; } return { syncPlan: { - combine: true + combine: true, + sort: proofSpec.sort } }; } diff --git a/src/hypersync/Paginator.test.ts b/src/hypersync/Paginator.test.ts index b2f4647..144916e 100644 --- a/src/hypersync/Paginator.test.ts +++ b/src/hypersync/Paginator.test.ts @@ -14,8 +14,8 @@ import { NextTokenType, PageUntilCondition, PagingType -} from '@hyperproof/hypersync-models'; -import { IGraphQLConnectionsScheme } from '@hyperproof/hypersync-models/src/dataSource'; +} from '@hyperproof-int/hypersync-models'; +import { IGraphQLConnectionsScheme } from '@hyperproof-int/hypersync-models/src/dataSource'; describe('Paginator', () => { describe('createPaginator', () => { diff --git a/src/hypersync/Paginator.ts b/src/hypersync/Paginator.ts index 170a67f..f636078 100644 --- a/src/hypersync/Paginator.ts +++ b/src/hypersync/Paginator.ts @@ -13,7 +13,7 @@ import { PageUntilCondition, PagingScheme, PagingType -} from '@hyperproof/hypersync-models'; +} from '@hyperproof-int/hypersync-models'; import jsonata from 'jsonata'; import set from 'lodash/set'; diff --git a/src/hypersync/ProofProviderBase.ts b/src/hypersync/ProofProviderBase.ts index ea8091f..5f2354c 100644 --- a/src/hypersync/ProofProviderBase.ts +++ b/src/hypersync/ProofProviderBase.ts @@ -18,9 +18,10 @@ import { HypersyncFieldFormat, HypersyncFieldType, HypersyncPageOrientation, - SchemaCategory -} from '@hyperproof/hypersync-models'; -import { ILocalizable } from '@hyperproof/integration-sdk'; + SchemaCategory, + SortClause +} from '@hyperproof-int/hypersync-models'; +import { ILocalizable } from '@hyperproof-int/integration-sdk'; import createHttpError from 'http-errors'; import { StatusCodes } from 'http-status-codes'; @@ -66,6 +67,7 @@ export interface IHypersyncSyncPlan { iterableArray: IterableObject[]; subArraySize?: number; }; + sort?: SortClause[]; } /** diff --git a/src/hypersync/ProofProviderFactory.ts b/src/hypersync/ProofProviderFactory.ts index 98af817..8fbdd02 100644 --- a/src/hypersync/ProofProviderFactory.ts +++ b/src/hypersync/ProofProviderFactory.ts @@ -10,8 +10,8 @@ import { IProofType, IProofTypeMap, SchemaCategory -} from '@hyperproof/hypersync-models'; -import { compareValues } from '@hyperproof/integration-sdk'; +} from '@hyperproof-int/hypersync-models'; +import { compareValues } from '@hyperproof-int/integration-sdk'; import fs from 'fs'; import createHttpError from 'http-errors'; import { StatusCodes } from 'http-status-codes'; diff --git a/src/hypersync/RestDataSourceBase.test.ts b/src/hypersync/RestDataSourceBase.test.ts index 89d97c1..bdfd138 100644 --- a/src/hypersync/RestDataSourceBase.test.ts +++ b/src/hypersync/RestDataSourceBase.test.ts @@ -3,15 +3,15 @@ import { RestDataSourceBase } from './RestDataSourceBase'; import { IDataSet, IRestDataSourceConfig -} from '@hyperproof/hypersync-models'; -import { DataSetResultStatus } from '@hyperproof/hypersync-sdk'; -import { ApiClient } from '@hyperproof/integration-sdk'; +} from '@hyperproof-int/hypersync-models'; +import { DataSetResultStatus } from '@hyperproof-int/hypersync-sdk'; +import { ApiClient } from '@hyperproof-int/integration-sdk'; import { HeadersInit } from 'node-fetch'; // Mock Logger -jest.mock('@hyperproof/integration-sdk', () => { +jest.mock('@hyperproof-int/integration-sdk', () => { return { - ...jest.requireActual('@hyperproof/integration-sdk'), + ...jest.requireActual('@hyperproof-int/integration-sdk'), Logger: { log: jest.fn(), error: jest.fn(), diff --git a/src/hypersync/RestDataSourceBase.ts b/src/hypersync/RestDataSourceBase.ts index 190d0dc..ed92284 100644 --- a/src/hypersync/RestDataSourceBase.ts +++ b/src/hypersync/RestDataSourceBase.ts @@ -27,14 +27,14 @@ import { PagingLevel, Transform, ValueLookup -} from '@hyperproof/hypersync-models'; +} from '@hyperproof-int/hypersync-models'; import { ApiClient, compareValues, IApiClientResponse, ILocalizable, Logger -} from '@hyperproof/integration-sdk'; +} from '@hyperproof-int/integration-sdk'; import createHttpError from 'http-errors'; import { StatusCodes } from 'http-status-codes'; import jsonata from 'jsonata'; diff --git a/src/hypersync/ServiceDataIterator.test.ts b/src/hypersync/ServiceDataIterator.test.ts index 458d9bf..b5744c4 100644 --- a/src/hypersync/ServiceDataIterator.test.ts +++ b/src/hypersync/ServiceDataIterator.test.ts @@ -4,7 +4,7 @@ import { IterableObject, ServiceDataIterator } from './ServiceDataIterator'; import { DataSetIteratorDefinition, IteratorSource -} from '@hyperproof/hypersync-models'; +} from '@hyperproof-int/hypersync-models'; describe('ServiceDataIterator', () => { const mockRestDataSource = { diff --git a/src/hypersync/ServiceDataIterator.ts b/src/hypersync/ServiceDataIterator.ts index 37b3fd5..1ea96a9 100644 --- a/src/hypersync/ServiceDataIterator.ts +++ b/src/hypersync/ServiceDataIterator.ts @@ -21,8 +21,8 @@ import { DataValueMap, IProofSpec, IteratorSource -} from '@hyperproof/hypersync-models'; -import { ILocalizable } from '@hyperproof/integration-sdk'; +} from '@hyperproof-int/hypersync-models'; +import { ILocalizable } from '@hyperproof-int/integration-sdk'; export interface IIteratorPlanComplete { status: DataSetResultStatus.Complete; diff --git a/src/hypersync/Sync.ts b/src/hypersync/Sync.ts index 39e6f7c..c550691 100644 --- a/src/hypersync/Sync.ts +++ b/src/hypersync/Sync.ts @@ -7,7 +7,7 @@ import { IProofFile } from './ProofProviderBase'; -import { ILocalizable } from '@hyperproof/integration-sdk'; +import { ILocalizable } from '@hyperproof-int/integration-sdk'; /** * Detailed response object that may be returned from getProofData. diff --git a/src/hypersync/common.ts b/src/hypersync/common.ts index 4897b4d..60d8055 100644 --- a/src/hypersync/common.ts +++ b/src/hypersync/common.ts @@ -7,7 +7,7 @@ import { import { HypersyncFieldType, IHypersyncField -} from '@hyperproof/hypersync-models'; +} from '@hyperproof-int/hypersync-models'; import createHttpError from 'http-errors'; import { StatusCodes } from 'http-status-codes'; import queryString from 'query-string'; diff --git a/src/hypersync/hypersyncConnector.ts b/src/hypersync/hypersyncConnector.ts index 544a5c8..b6279b9 100644 --- a/src/hypersync/hypersyncConnector.ts +++ b/src/hypersync/hypersyncConnector.ts @@ -13,7 +13,7 @@ import { HypersyncCriteria, ICriteriaSearchInput, SchemaCategory -} from '@hyperproof/hypersync-models'; +} from '@hyperproof-int/hypersync-models'; import { createConnector, CustomAuthCredentials, @@ -33,7 +33,7 @@ import { ObjectType, StorageItem, UserContext -} from '@hyperproof/integration-sdk'; +} from '@hyperproof-int/integration-sdk'; import express from 'express'; import fs from 'fs'; import createHttpError from 'http-errors'; diff --git a/src/hypersync/layout.ts b/src/hypersync/layout.ts index d872e65..e82ae2e 100644 --- a/src/hypersync/layout.ts +++ b/src/hypersync/layout.ts @@ -1,6 +1,6 @@ import { IHypersyncProofField } from './ProofProviderBase'; -import { HypersyncPageOrientation } from '@hyperproof/hypersync-models'; +import { HypersyncPageOrientation } from '@hyperproof-int/hypersync-models'; const DEFAULT_A4_WIDTH_PIXELS = 794; const DEFAULT_A4_LENGTH_PIXELS = 1123; diff --git a/src/hypersync/models.ts b/src/hypersync/models.ts index 94a44dc..dacc656 100644 --- a/src/hypersync/models.ts +++ b/src/hypersync/models.ts @@ -5,12 +5,12 @@ import { HypersyncCriteria, HypersyncPeriod, SchemaCategory -} from '@hyperproof/hypersync-models'; +} from '@hyperproof-int/hypersync-models'; import { IIntegration, IIntegrationSettingsBase, IntegrationSettingsClass -} from '@hyperproof/integration-sdk'; +} from '@hyperproof-int/integration-sdk'; /** * Settings that are saved with a Hypersync integration. diff --git a/src/hypersync/time.ts b/src/hypersync/time.ts index d7e7d95..6d13734 100644 --- a/src/hypersync/time.ts +++ b/src/hypersync/time.ts @@ -1,7 +1,7 @@ import '@js-joda/timezone'; import { formatMessage, MESSAGES } from './messages'; -import { HypersyncPeriod } from '@hyperproof/hypersync-models'; +import { HypersyncPeriod } from '@hyperproof-int/hypersync-models'; import { ChronoUnit, convert, diff --git a/src/hypersync/tokens.ts b/src/hypersync/tokens.ts index ba2199a..73c9628 100644 --- a/src/hypersync/tokens.ts +++ b/src/hypersync/tokens.ts @@ -3,7 +3,7 @@ import { DataValue, HypersyncCriteria, HypersyncCriteriaValue -} from '@hyperproof/hypersync-models'; +} from '@hyperproof-int/hypersync-models'; /** * Context object that is used when resolving placeholder tokens in a string. diff --git a/src/schema-proof/UarApplicationProofProvider.ts b/src/schema-proof/UarApplicationProofProvider.ts index 9b66ac5..2da6f5b 100644 --- a/src/schema-proof/UarApplicationProofProvider.ts +++ b/src/schema-proof/UarApplicationProofProvider.ts @@ -5,8 +5,8 @@ import { HypersyncDataFormat, HypersyncFieldType, SchemaCategory -} from '@hyperproof/hypersync-models'; -import { ILocalizable } from '@hyperproof/integration-sdk'; +} from '@hyperproof-int/hypersync-models'; +import { ILocalizable } from '@hyperproof-int/integration-sdk'; import { date, InferType, object, string } from 'yup'; import { DataSourceBase } from '../hypersync'; diff --git a/src/schema-proof/UarDirectoryProofProvider.ts b/src/schema-proof/UarDirectoryProofProvider.ts index edc7d0b..f9e8cc3 100644 --- a/src/schema-proof/UarDirectoryProofProvider.ts +++ b/src/schema-proof/UarDirectoryProofProvider.ts @@ -5,8 +5,8 @@ import { HypersyncDataFormat, HypersyncFieldType, SchemaCategory -} from '@hyperproof/hypersync-models'; -import { ILocalizable } from '@hyperproof/integration-sdk'; +} from '@hyperproof-int/hypersync-models'; +import { ILocalizable } from '@hyperproof-int/integration-sdk'; import { date, InferType, object, string } from 'yup'; import { DataSourceBase } from '../hypersync/DataSourceBase'; diff --git a/src/schema-proof/common.ts b/src/schema-proof/common.ts index 8d294ed..73979aa 100644 --- a/src/schema-proof/common.ts +++ b/src/schema-proof/common.ts @@ -1,7 +1,7 @@ import { uarApplicationSchema } from './UarApplicationProofProvider'; import { uarDirectorySchema } from './UarDirectoryProofProvider'; -import { DataObject, SchemaCategory } from '@hyperproof/hypersync-models'; +import { DataObject, SchemaCategory } from '@hyperproof-int/hypersync-models'; import { IHypersync } from '../hypersync'; From 42b855e8ea4e118af5f89891f3d2ddb793e0bb2e Mon Sep 17 00:00:00 2001 From: "trent.hashimoto" Date: Tue, 20 Jan 2026 23:43:15 -0800 Subject: [PATCH 15/17] Update imports --- src/hypersync/DataSourceBase.ts | 4 ++-- src/hypersync/HypersyncApp.test.ts | 4 ++-- src/hypersync/HypersyncApp.ts | 4 ++-- src/hypersync/ICriteriaProvider.ts | 4 ++-- src/hypersync/IDataSource.ts | 4 ++-- src/hypersync/JsonCriteriaProvider.test.ts | 4 ++-- src/hypersync/JsonCriteriaProvider.ts | 4 ++-- src/hypersync/JsonProofProvider.test.ts | 6 +++--- src/hypersync/JsonProofProvider.ts | 4 ++-- src/hypersync/Paginator.test.ts | 4 ++-- src/hypersync/Paginator.ts | 2 +- src/hypersync/ProofProviderBase.ts | 4 ++-- src/hypersync/ProofProviderFactory.ts | 4 ++-- src/hypersync/RestDataSourceBase.test.ts | 10 +++++----- src/hypersync/RestDataSourceBase.ts | 4 ++-- src/hypersync/ServiceDataIterator.test.ts | 2 +- src/hypersync/ServiceDataIterator.ts | 4 ++-- src/hypersync/Sync.ts | 2 +- src/hypersync/common.ts | 2 +- src/hypersync/hypersyncConnector.ts | 4 ++-- src/hypersync/layout.ts | 2 +- src/hypersync/models.ts | 4 ++-- src/hypersync/time.ts | 2 +- src/hypersync/tokens.ts | 2 +- src/schema-proof/UarApplicationProofProvider.ts | 4 ++-- src/schema-proof/UarDirectoryProofProvider.ts | 4 ++-- src/schema-proof/common.ts | 2 +- 27 files changed, 50 insertions(+), 50 deletions(-) diff --git a/src/hypersync/DataSourceBase.ts b/src/hypersync/DataSourceBase.ts index 64023ba..0541a51 100644 --- a/src/hypersync/DataSourceBase.ts +++ b/src/hypersync/DataSourceBase.ts @@ -6,8 +6,8 @@ import { SyncMetadata } from './IDataSource'; -import { DataObject, DataValueMap } from '@hyperproof-int/hypersync-models'; -import { ILocalizable } from '@hyperproof-int/integration-sdk'; +import { DataObject, DataValueMap } from '@hyperproof/hypersync-models'; +import { ILocalizable } from '@hyperproof/integration-sdk'; /** * Abstract base class for a data source object. Provides convenient diff --git a/src/hypersync/HypersyncApp.test.ts b/src/hypersync/HypersyncApp.test.ts index 2c4d92a..088e0aa 100644 --- a/src/hypersync/HypersyncApp.test.ts +++ b/src/hypersync/HypersyncApp.test.ts @@ -14,8 +14,8 @@ import { IntegrationContext, ObjectType, UserContext -} from '@hyperproof-int/integration-sdk/lib'; -import { ILocalizable } from '@hyperproof-int/integration-sdk/src'; +} from '@hyperproof/integration-sdk/lib'; +import { ILocalizable } from '@hyperproof/integration-sdk/src'; describe('HypersyncApp.ts', () => { let app: any; diff --git a/src/hypersync/HypersyncApp.ts b/src/hypersync/HypersyncApp.ts index 2c7a624..ef2e57c 100644 --- a/src/hypersync/HypersyncApp.ts +++ b/src/hypersync/HypersyncApp.ts @@ -35,7 +35,7 @@ import { IHypersyncDefinition, SchemaCategory, ValueLookup -} from '@hyperproof-int/hypersync-models'; +} from '@hyperproof/hypersync-models'; import { AuthorizationType, createApp, @@ -56,7 +56,7 @@ import { OAuthTokenResponse, ObjectType, UserContext -} from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/integration-sdk'; import express from 'express'; import fs from 'fs'; import createHttpError from 'http-errors'; diff --git a/src/hypersync/ICriteriaProvider.ts b/src/hypersync/ICriteriaProvider.ts index bb3c205..ca69ce2 100644 --- a/src/hypersync/ICriteriaProvider.ts +++ b/src/hypersync/ICriteriaProvider.ts @@ -11,8 +11,8 @@ import { ISelectOption, IValidation, SchemaCategory -} from '@hyperproof-int/hypersync-models'; -import { CriteriaPageMessageLevel } from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/hypersync-models'; +import { CriteriaPageMessageLevel } from '@hyperproof/integration-sdk'; /** * Information needed to render a criteria field used in the configuration diff --git a/src/hypersync/IDataSource.ts b/src/hypersync/IDataSource.ts index 789be4e..1c983c9 100644 --- a/src/hypersync/IDataSource.ts +++ b/src/hypersync/IDataSource.ts @@ -2,8 +2,8 @@ import { IErrorInfo } from './models'; import { RestDataSourceBase } from './RestDataSourceBase'; import { TokenContext } from './tokens'; -import { DataObject, DataValueMap } from '@hyperproof-int/hypersync-models'; -import { ILocalizable } from '@hyperproof-int/integration-sdk'; +import { DataObject, DataValueMap } from '@hyperproof/hypersync-models'; +import { ILocalizable } from '@hyperproof/integration-sdk'; export enum DataSetResultStatus { Complete = 'complete', diff --git a/src/hypersync/JsonCriteriaProvider.test.ts b/src/hypersync/JsonCriteriaProvider.test.ts index ac875e2..818b9d8 100644 --- a/src/hypersync/JsonCriteriaProvider.test.ts +++ b/src/hypersync/JsonCriteriaProvider.test.ts @@ -4,8 +4,8 @@ import { JsonCriteriaProvider } from './JsonCriteriaProvider'; import { HypersyncCriteriaFieldType, ValidationTypes -} from '@hyperproof-int/hypersync-models'; -import { Logger } from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/hypersync-models'; +import { Logger } from '@hyperproof/integration-sdk'; import fs from 'fs'; import path from 'path'; diff --git a/src/hypersync/JsonCriteriaProvider.ts b/src/hypersync/JsonCriteriaProvider.ts index bb6e352..d481e59 100644 --- a/src/hypersync/JsonCriteriaProvider.ts +++ b/src/hypersync/JsonCriteriaProvider.ts @@ -16,8 +16,8 @@ import { ICriteriaSearchInput, IProofCriterionRef, ISelectOption -} from '@hyperproof-int/hypersync-models'; -import { compareValues, Logger } from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/hypersync-models'; +import { compareValues, Logger } from '@hyperproof/integration-sdk'; import fs from 'fs'; import path from 'path'; diff --git a/src/hypersync/JsonProofProvider.test.ts b/src/hypersync/JsonProofProvider.test.ts index 333c262..636df96 100644 --- a/src/hypersync/JsonProofProvider.test.ts +++ b/src/hypersync/JsonProofProvider.test.ts @@ -13,17 +13,17 @@ import { HypersyncPageOrientation, HypersyncPeriod, IProofSpec -} from '@hyperproof-int/hypersync-models'; +} from '@hyperproof/hypersync-models'; import { HypersyncProofFormat, HypersyncTemplate, IHypersync -} from '@hyperproof-int/hypersync-sdk/lib'; +} from '@hyperproof/hypersync-sdk/lib'; import { ILocalizable, IntegrationSettingsClass, ObjectType -} from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/integration-sdk'; const CONNECTOR_NAME = 'testConnector'; const PROOF_TYPE = 'testProofType'; diff --git a/src/hypersync/JsonProofProvider.ts b/src/hypersync/JsonProofProvider.ts index 209770d..efaf544 100644 --- a/src/hypersync/JsonProofProvider.ts +++ b/src/hypersync/JsonProofProvider.ts @@ -32,8 +32,8 @@ import { IHypersyncField, IProofSpec, IteratorSource -} from '@hyperproof-int/hypersync-models'; -import { ILocalizable, Logger } from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/hypersync-models'; +import { ILocalizable, Logger } from '@hyperproof/integration-sdk'; import createHttpError from 'http-errors'; import { StatusCodes } from 'http-status-codes'; diff --git a/src/hypersync/Paginator.test.ts b/src/hypersync/Paginator.test.ts index 144916e..b2f4647 100644 --- a/src/hypersync/Paginator.test.ts +++ b/src/hypersync/Paginator.test.ts @@ -14,8 +14,8 @@ import { NextTokenType, PageUntilCondition, PagingType -} from '@hyperproof-int/hypersync-models'; -import { IGraphQLConnectionsScheme } from '@hyperproof-int/hypersync-models/src/dataSource'; +} from '@hyperproof/hypersync-models'; +import { IGraphQLConnectionsScheme } from '@hyperproof/hypersync-models/src/dataSource'; describe('Paginator', () => { describe('createPaginator', () => { diff --git a/src/hypersync/Paginator.ts b/src/hypersync/Paginator.ts index f636078..170a67f 100644 --- a/src/hypersync/Paginator.ts +++ b/src/hypersync/Paginator.ts @@ -13,7 +13,7 @@ import { PageUntilCondition, PagingScheme, PagingType -} from '@hyperproof-int/hypersync-models'; +} from '@hyperproof/hypersync-models'; import jsonata from 'jsonata'; import set from 'lodash/set'; diff --git a/src/hypersync/ProofProviderBase.ts b/src/hypersync/ProofProviderBase.ts index 5f2354c..d4f997d 100644 --- a/src/hypersync/ProofProviderBase.ts +++ b/src/hypersync/ProofProviderBase.ts @@ -20,8 +20,8 @@ import { HypersyncPageOrientation, SchemaCategory, SortClause -} from '@hyperproof-int/hypersync-models'; -import { ILocalizable } from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/hypersync-models'; +import { ILocalizable } from '@hyperproof/integration-sdk'; import createHttpError from 'http-errors'; import { StatusCodes } from 'http-status-codes'; diff --git a/src/hypersync/ProofProviderFactory.ts b/src/hypersync/ProofProviderFactory.ts index 8fbdd02..98af817 100644 --- a/src/hypersync/ProofProviderFactory.ts +++ b/src/hypersync/ProofProviderFactory.ts @@ -10,8 +10,8 @@ import { IProofType, IProofTypeMap, SchemaCategory -} from '@hyperproof-int/hypersync-models'; -import { compareValues } from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/hypersync-models'; +import { compareValues } from '@hyperproof/integration-sdk'; import fs from 'fs'; import createHttpError from 'http-errors'; import { StatusCodes } from 'http-status-codes'; diff --git a/src/hypersync/RestDataSourceBase.test.ts b/src/hypersync/RestDataSourceBase.test.ts index bdfd138..89d97c1 100644 --- a/src/hypersync/RestDataSourceBase.test.ts +++ b/src/hypersync/RestDataSourceBase.test.ts @@ -3,15 +3,15 @@ import { RestDataSourceBase } from './RestDataSourceBase'; import { IDataSet, IRestDataSourceConfig -} from '@hyperproof-int/hypersync-models'; -import { DataSetResultStatus } from '@hyperproof-int/hypersync-sdk'; -import { ApiClient } from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/hypersync-models'; +import { DataSetResultStatus } from '@hyperproof/hypersync-sdk'; +import { ApiClient } from '@hyperproof/integration-sdk'; import { HeadersInit } from 'node-fetch'; // Mock Logger -jest.mock('@hyperproof-int/integration-sdk', () => { +jest.mock('@hyperproof/integration-sdk', () => { return { - ...jest.requireActual('@hyperproof-int/integration-sdk'), + ...jest.requireActual('@hyperproof/integration-sdk'), Logger: { log: jest.fn(), error: jest.fn(), diff --git a/src/hypersync/RestDataSourceBase.ts b/src/hypersync/RestDataSourceBase.ts index ed92284..190d0dc 100644 --- a/src/hypersync/RestDataSourceBase.ts +++ b/src/hypersync/RestDataSourceBase.ts @@ -27,14 +27,14 @@ import { PagingLevel, Transform, ValueLookup -} from '@hyperproof-int/hypersync-models'; +} from '@hyperproof/hypersync-models'; import { ApiClient, compareValues, IApiClientResponse, ILocalizable, Logger -} from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/integration-sdk'; import createHttpError from 'http-errors'; import { StatusCodes } from 'http-status-codes'; import jsonata from 'jsonata'; diff --git a/src/hypersync/ServiceDataIterator.test.ts b/src/hypersync/ServiceDataIterator.test.ts index b5744c4..458d9bf 100644 --- a/src/hypersync/ServiceDataIterator.test.ts +++ b/src/hypersync/ServiceDataIterator.test.ts @@ -4,7 +4,7 @@ import { IterableObject, ServiceDataIterator } from './ServiceDataIterator'; import { DataSetIteratorDefinition, IteratorSource -} from '@hyperproof-int/hypersync-models'; +} from '@hyperproof/hypersync-models'; describe('ServiceDataIterator', () => { const mockRestDataSource = { diff --git a/src/hypersync/ServiceDataIterator.ts b/src/hypersync/ServiceDataIterator.ts index 1ea96a9..37b3fd5 100644 --- a/src/hypersync/ServiceDataIterator.ts +++ b/src/hypersync/ServiceDataIterator.ts @@ -21,8 +21,8 @@ import { DataValueMap, IProofSpec, IteratorSource -} from '@hyperproof-int/hypersync-models'; -import { ILocalizable } from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/hypersync-models'; +import { ILocalizable } from '@hyperproof/integration-sdk'; export interface IIteratorPlanComplete { status: DataSetResultStatus.Complete; diff --git a/src/hypersync/Sync.ts b/src/hypersync/Sync.ts index c550691..39e6f7c 100644 --- a/src/hypersync/Sync.ts +++ b/src/hypersync/Sync.ts @@ -7,7 +7,7 @@ import { IProofFile } from './ProofProviderBase'; -import { ILocalizable } from '@hyperproof-int/integration-sdk'; +import { ILocalizable } from '@hyperproof/integration-sdk'; /** * Detailed response object that may be returned from getProofData. diff --git a/src/hypersync/common.ts b/src/hypersync/common.ts index 60d8055..4897b4d 100644 --- a/src/hypersync/common.ts +++ b/src/hypersync/common.ts @@ -7,7 +7,7 @@ import { import { HypersyncFieldType, IHypersyncField -} from '@hyperproof-int/hypersync-models'; +} from '@hyperproof/hypersync-models'; import createHttpError from 'http-errors'; import { StatusCodes } from 'http-status-codes'; import queryString from 'query-string'; diff --git a/src/hypersync/hypersyncConnector.ts b/src/hypersync/hypersyncConnector.ts index b6279b9..544a5c8 100644 --- a/src/hypersync/hypersyncConnector.ts +++ b/src/hypersync/hypersyncConnector.ts @@ -13,7 +13,7 @@ import { HypersyncCriteria, ICriteriaSearchInput, SchemaCategory -} from '@hyperproof-int/hypersync-models'; +} from '@hyperproof/hypersync-models'; import { createConnector, CustomAuthCredentials, @@ -33,7 +33,7 @@ import { ObjectType, StorageItem, UserContext -} from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/integration-sdk'; import express from 'express'; import fs from 'fs'; import createHttpError from 'http-errors'; diff --git a/src/hypersync/layout.ts b/src/hypersync/layout.ts index e82ae2e..d872e65 100644 --- a/src/hypersync/layout.ts +++ b/src/hypersync/layout.ts @@ -1,6 +1,6 @@ import { IHypersyncProofField } from './ProofProviderBase'; -import { HypersyncPageOrientation } from '@hyperproof-int/hypersync-models'; +import { HypersyncPageOrientation } from '@hyperproof/hypersync-models'; const DEFAULT_A4_WIDTH_PIXELS = 794; const DEFAULT_A4_LENGTH_PIXELS = 1123; diff --git a/src/hypersync/models.ts b/src/hypersync/models.ts index dacc656..94a44dc 100644 --- a/src/hypersync/models.ts +++ b/src/hypersync/models.ts @@ -5,12 +5,12 @@ import { HypersyncCriteria, HypersyncPeriod, SchemaCategory -} from '@hyperproof-int/hypersync-models'; +} from '@hyperproof/hypersync-models'; import { IIntegration, IIntegrationSettingsBase, IntegrationSettingsClass -} from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/integration-sdk'; /** * Settings that are saved with a Hypersync integration. diff --git a/src/hypersync/time.ts b/src/hypersync/time.ts index 6d13734..d7e7d95 100644 --- a/src/hypersync/time.ts +++ b/src/hypersync/time.ts @@ -1,7 +1,7 @@ import '@js-joda/timezone'; import { formatMessage, MESSAGES } from './messages'; -import { HypersyncPeriod } from '@hyperproof-int/hypersync-models'; +import { HypersyncPeriod } from '@hyperproof/hypersync-models'; import { ChronoUnit, convert, diff --git a/src/hypersync/tokens.ts b/src/hypersync/tokens.ts index 73c9628..ba2199a 100644 --- a/src/hypersync/tokens.ts +++ b/src/hypersync/tokens.ts @@ -3,7 +3,7 @@ import { DataValue, HypersyncCriteria, HypersyncCriteriaValue -} from '@hyperproof-int/hypersync-models'; +} from '@hyperproof/hypersync-models'; /** * Context object that is used when resolving placeholder tokens in a string. diff --git a/src/schema-proof/UarApplicationProofProvider.ts b/src/schema-proof/UarApplicationProofProvider.ts index 2da6f5b..9b66ac5 100644 --- a/src/schema-proof/UarApplicationProofProvider.ts +++ b/src/schema-proof/UarApplicationProofProvider.ts @@ -5,8 +5,8 @@ import { HypersyncDataFormat, HypersyncFieldType, SchemaCategory -} from '@hyperproof-int/hypersync-models'; -import { ILocalizable } from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/hypersync-models'; +import { ILocalizable } from '@hyperproof/integration-sdk'; import { date, InferType, object, string } from 'yup'; import { DataSourceBase } from '../hypersync'; diff --git a/src/schema-proof/UarDirectoryProofProvider.ts b/src/schema-proof/UarDirectoryProofProvider.ts index f9e8cc3..edc7d0b 100644 --- a/src/schema-proof/UarDirectoryProofProvider.ts +++ b/src/schema-proof/UarDirectoryProofProvider.ts @@ -5,8 +5,8 @@ import { HypersyncDataFormat, HypersyncFieldType, SchemaCategory -} from '@hyperproof-int/hypersync-models'; -import { ILocalizable } from '@hyperproof-int/integration-sdk'; +} from '@hyperproof/hypersync-models'; +import { ILocalizable } from '@hyperproof/integration-sdk'; import { date, InferType, object, string } from 'yup'; import { DataSourceBase } from '../hypersync/DataSourceBase'; diff --git a/src/schema-proof/common.ts b/src/schema-proof/common.ts index 73979aa..8d294ed 100644 --- a/src/schema-proof/common.ts +++ b/src/schema-proof/common.ts @@ -1,7 +1,7 @@ import { uarApplicationSchema } from './UarApplicationProofProvider'; import { uarDirectorySchema } from './UarDirectoryProofProvider'; -import { DataObject, SchemaCategory } from '@hyperproof-int/hypersync-models'; +import { DataObject, SchemaCategory } from '@hyperproof/hypersync-models'; import { IHypersync } from '../hypersync'; From 999ad108fe88b8aa34d8bc3c88fdb47c374755ca Mon Sep 17 00:00:00 2001 From: "trent.hashimoto" Date: Wed, 21 Jan 2026 12:55:07 -0800 Subject: [PATCH 16/17] Add sort docs and schema --- doc/055-proof-type-json.md | 7 ++++++ schema/proofType.schema.json | 17 +++++++++++++++ schema/restDataSource.schema.json | 36 ++++++------------------------- 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/doc/055-proof-type-json.md b/doc/055-proof-type-json.md index a4efa03..5d46b70 100644 --- a/doc/055-proof-type-json.md +++ b/doc/055-proof-type-json.md @@ -41,6 +41,7 @@ The `proofSpec` property in a proof type file is a JSON object that specifies ho | fields | Yes | Array of fields to include in the generated proof | | webPageUrl | No | URL shown at the bottom of generated proof | | autoLayout | No | `true` to automatically layout the fields in the specification. Default: `false`. | +| sort | No | Sort applied after all data processing is complete | ## overrides @@ -72,6 +73,12 @@ If specified, the `overrides` property must be formatted as an array of objects. "group": "{{criteria.group}}" }, "noResultsMessage": "{{messages.NO_USERS}}", + "sort": [ + { + "property": "lastName", + "direction": "ascending" + } + ], "fields": [ { "property": "firstName", diff --git a/schema/proofType.schema.json b/schema/proofType.schema.json index 0def759..08cfe42 100644 --- a/schema/proofType.schema.json +++ b/schema/proofType.schema.json @@ -99,6 +99,23 @@ "autoLayout": { "type": "boolean" }, + "sort": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "property": { + "type": "string" + }, + "direction": { + "type": "string", + "enum": ["ascending", "descending"] + } + }, + "required": ["property", "direction"] + } + }, "dataSetIterator": { "type": "array", "items": { diff --git a/schema/restDataSource.schema.json b/schema/restDataSource.schema.json index da515e6..0a7f9e6 100644 --- a/schema/restDataSource.schema.json +++ b/schema/restDataSource.schema.json @@ -81,13 +81,7 @@ "enum": ["connector", "job"] } }, - "required": [ - "type", - "request", - "response", - "pageUntil", - "tokenType" - ], + "required": ["type", "request", "response", "pageUntil", "tokenType"], "additionalProperties": false }, { @@ -112,12 +106,7 @@ "type": "number" } }, - "required": [ - "pageParameter", - "pageStartingValue", - "limitParameter", - "limitValue" - ], + "required": ["pageParameter", "pageStartingValue", "limitParameter", "limitValue"], "additionalProperties": false }, "response": { @@ -164,12 +153,7 @@ "type": "number" } }, - "required": [ - "offsetParameter", - "offsetStartingValue", - "limitParameter", - "limitValue" - ], + "required": ["offsetParameter", "offsetStartingValue", "limitParameter", "limitValue"], "additionalProperties": false }, "response": { @@ -275,11 +259,7 @@ "type": "object", "patternProperties": { "^.*$": { - "anyOf": [ - { "type": "string" }, - { "type": "number" }, - { "type": "boolean" } - ] + "anyOf": [{ "type": "string" }, { "type": "number" }, { "type": "boolean" }] } } } @@ -325,11 +305,7 @@ "type": "string" }, "value": { - "anyOf": [ - { "type": "string" }, - { "type": "number" }, - { "type": "boolean" } - ] + "anyOf": [{ "type": "string" }, { "type": "number" }, { "type": "boolean" }] } }, "required": ["property", "value"] @@ -358,7 +334,7 @@ "additionalProperties": false, "properties": { "property": { - "property": "string" + "type": "string" }, "direction": { "type": "string", From 81fa8ce7ece1db70bb446425987ea688cbdbb4f2 Mon Sep 17 00:00:00 2001 From: "trent.hashimoto" Date: Wed, 21 Jan 2026 13:39:14 -0800 Subject: [PATCH 17/17] Update imports, bump to beta.1 --- package.json | 7 +- yarn.lock | 1224 ++++++++++++++++++++++---------------------------- 2 files changed, 542 insertions(+), 689 deletions(-) diff --git a/package.json b/package.json index 79eac85..f96bfb7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@hyperproof/hypersync-sdk", - "version": "6.0.0-beta", + "version": "6.0.0-beta.1", "description": "Hypersync SDK", "license": "MIT", "repository": { @@ -17,8 +17,8 @@ "node": "^22.0.0" }, "dependencies": { - "@hyperproof/hypersync-models": "6.0.0-beta", - "@hyperproof/integration-sdk": "6.0.0-beta", + "@hyperproof/hypersync-models": "6.0.0-beta.1", + "@hyperproof/integration-sdk": "6.0.0-beta.1", "@js-joda/core": "3.2.0", "@js-joda/timezone": "2.5.0", "abort-controller": "3.0.0", @@ -43,6 +43,7 @@ "@types/jsonwebtoken": "^9.0.0", "@types/lodash": "^4.14.202", "@types/mime": "^3.0.4", + "@types/node": "22.10.10", "@types/node-fetch": "^2.6.13", "@types/superagent": "^8.1.9", "@typescript-eslint/eslint-plugin": "8.7.0", diff --git a/yarn.lock b/yarn.lock index 93aa49e..d0184ab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,16 +10,16 @@ "@jridgewell/trace-mapping" "0.3.9" "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": - version "4.4.0" - resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" - integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + version "4.9.1" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#4e90af67bc51ddee6cdef5284edf572ec376b595" + integrity sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ== dependencies: - eslint-visitor-keys "^3.3.0" + eslint-visitor-keys "^3.4.3" "@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.6.1": - version "4.11.1" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.1.tgz#a547badfc719eb3e5f4b556325e542fbe9d7a18f" - integrity sha512-m4DVN9ZqskZoLU5GlWZadwDnYo3vAEydiUayB9widCl9ffWx2IvPnp6n3on5rJmziJSw9Bv+Z3ChDVdMwXCY8Q== + version "4.12.2" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.2.tgz#bccdf615bcf7b6e8db830ec0b8d21c9a25de597b" + integrity sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew== "@eslint/eslintrc@^2.1.4": version "2.1.4" @@ -60,24 +60,25 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== -"@hyperproof/hypersync-models@5.1.0": - version "5.1.0" - resolved "https://registry.yarnpkg.com/@hyperproof/hypersync-models/-/hypersync-models-5.1.0.tgz#75a37ddaed65b8a8ff00d81f334cff160f0e737c" - integrity sha512-XUt96cqAZLHMa2OZ93nMYowE67bWiN3aXCZvGjejuTlhBvzJpGfsysDGSdCuB7DIS3CgR/BxKLUmM1pfS0A5tQ== +"@hyperproof/hypersync-models@6.0.0-beta.1": + version "6.0.0-beta.1" + resolved "https://registry.yarnpkg.com/@hyperproof/hypersync-models/-/hypersync-models-6.0.0-beta.1.tgz#ddbcd9b40c18db7d01ac2c8f945a05ab250f4114" + integrity sha512-uTGEQaUbVJyXaIteKXv6wVa+/zrtD9Jfc7Pmu8q4MGr0WgpBOMZd8pIwK41l3rNzWbWTMewOCV028iv4pXpfAw== -"@hyperproof/integration-sdk@1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@hyperproof/integration-sdk/-/integration-sdk-1.2.0.tgz#bc827545a90a6e07c595fdb97384e28f747f9169" - integrity sha512-cFgtT3JwYFiKADMJQgtL3dKHfP2qW43W5mI3BTb7tqjGy/rEM8wIjuNsDR0HRGQACasbsQM/w1m51lOcRejzug== +"@hyperproof/integration-sdk@6.0.0-beta.1": + version "6.0.0-beta.1" + resolved "https://registry.yarnpkg.com/@hyperproof/integration-sdk/-/integration-sdk-6.0.0-beta.1.tgz#c6aebdf9353f589852fa73c3b01296e0376c6e74" + integrity sha512-GVRo3C0GFF1LL3drQN8GFKZVhapN0kgKLZaVaxxMdO8O6+gh+VTQhN60iGMWMx1DTC4OErJ9JJNHuImYd7yszQ== dependencies: + "@hyperproof/hypersync-models" "6.0.0-beta.1" "@js-joda/core" "3.2.0" "@pollyjs/adapter-node-http" "6.0.6" "@pollyjs/core" "6.0.6" "@pollyjs/persister-fs" "6.0.6" abort-controller "3.0.0" body-parser "1.20.3" - express "4.21.0" - form-data "3.0.0" + express "4.21.2" + form-data "3.0.4" html-entities "2.5.2" http-errors "2.0.0" http-status-codes "2.3.0" @@ -87,6 +88,7 @@ node-fetch "2.7.0" query-string "7.1.3" superagent "10.1.0" + uuid "10.0.0" xss "1.0.15" "@jridgewell/resolve-uri@^3.0.3": @@ -95,9 +97,9 @@ integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== "@jridgewell/sourcemap-codec@^1.4.10": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" - integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + version "1.5.5" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== "@jridgewell/trace-mapping@0.3.9": version "0.3.9" @@ -117,6 +119,11 @@ resolved "https://registry.yarnpkg.com/@js-joda/timezone/-/timezone-2.5.0.tgz#b422ff400c25ae311384239c62724eecee2e442b" integrity sha512-HHFVhGUKIOtITiT+sbQRdYuO5Q+a8FDj/vQSGUSxe6+5w2in5JsavfRsAN2tU/NCdBeFx/6q8evHMtOrXfdn2g== +"@noble/hashes@^1.1.5": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.8.0.tgz#cee43d801fcef9644b11b8194857695acd5f815a" + integrity sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A== + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -138,6 +145,13 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@paralleldrive/cuid2@^2.2.2": + version "2.3.1" + resolved "https://registry.yarnpkg.com/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz#3d62ea9e7be867d3fa94b9897fab5b0ae187d784" + integrity sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw== + dependencies: + "@noble/hashes" "^1.1.5" + "@pollyjs/adapter-node-http@6.0.6": version "6.0.6" resolved "https://registry.yarnpkg.com/@pollyjs/adapter-node-http/-/adapter-node-http-6.0.6.tgz#7122765604e3dfdd6d68bced8a89c7966d49d710" @@ -219,9 +233,9 @@ integrity sha512-suq9tRQ6bkpMukTG5K5z0sPWB7t0zExMzZCdmYm6xTSSIm/yCKNm7VCL36wVeyTsFr597/UhU1OAYdHGMDiHrw== "@tsconfig/node10@^1.0.7": - version "1.0.11" - resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.11.tgz#6ee46400685f130e278128c7b38b7e031ff5b2f2" - integrity sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw== + version "1.0.12" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.12.tgz#be57ceac1e4692b41be9de6be8c32a106636dba4" + integrity sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ== "@tsconfig/node12@^1.0.7": version "1.0.11" @@ -239,17 +253,17 @@ integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== "@types/body-parser@*": - version "1.19.2" - resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.2.tgz#aea2059e28b7658639081347ac4fab3de166e6f0" - integrity sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g== + version "1.19.6" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.6.tgz#1859bebb8fd7dac9918a45d54c1971ab8b5af474" + integrity sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g== dependencies: "@types/connect" "*" "@types/node" "*" "@types/connect@*": - version "3.4.35" - resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.35.tgz#5fcf6ae445e4021d1fc2219a4873cc73a3bb2ad1" - integrity sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ== + version "3.4.38" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" + integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== dependencies: "@types/node" "*" @@ -259,9 +273,9 @@ integrity sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q== "@types/express-serve-static-core@^4.17.33": - version "4.19.6" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.6.tgz#e01324c2a024ff367d92c66f48553ced0ab50267" - integrity sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A== + version "4.19.8" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.8.tgz#99b960322a4d576b239a640ab52ef191989b036f" + integrity sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA== dependencies: "@types/node" "*" "@types/qs" "*" @@ -269,37 +283,38 @@ "@types/send" "*" "@types/express@^4.17.21": - version "4.17.21" - resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d" - integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ== + version "4.17.25" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.25.tgz#070c8c73a6fee6936d65c195dbbfb7da5026649b" + integrity sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw== dependencies: "@types/body-parser" "*" "@types/express-serve-static-core" "^4.17.33" "@types/qs" "*" - "@types/serve-static" "*" + "@types/serve-static" "^1" -"@types/http-errors@^2.0.0": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f" - integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== +"@types/http-errors@*", "@types/http-errors@^2.0.0": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.5.tgz#5b749ab2b16ba113423feb1a64a95dcd30398472" + integrity sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg== "@types/jsonwebtoken@^9.0.0": - version "9.0.7" - resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.7.tgz#e49b96c2b29356ed462e9708fc73b833014727d2" - integrity sha512-ugo316mmTYBl2g81zDFnZ7cfxlut3o+/EQdaP7J8QN2kY6lJ22hmQYCK5EHcJHbrW+dkCGSCPgbG8JtYj6qSrg== + version "9.0.10" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz#a7932a47177dcd4283b6146f3bd5c26d82647f09" + integrity sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA== dependencies: + "@types/ms" "*" "@types/node" "*" +"@types/lodash@^4.14.202": + version "4.17.23" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.23.tgz#c1bb06db218acc8fc232da0447473fc2fb9d9841" + integrity sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA== + "@types/methods@^1.1.4": version "1.1.4" resolved "https://registry.yarnpkg.com/@types/methods/-/methods-1.1.4.tgz#d3b7ac30ac47c91054ea951ce9eed07b1051e547" integrity sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ== -"@types/mime@*": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.1.tgz#5f8f2bca0a5863cb69bc0b0acd88c96cb1d4ae10" - integrity sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA== - "@types/mime@^1": version "1.3.5" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" @@ -310,56 +325,71 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-3.0.4.tgz#2198ac274de6017b44d941e00261d5bc6a0e0a45" integrity sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw== -"@types/node-fetch@^2.6.11": - version "2.6.11" - resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.11.tgz#9b39b78665dae0e82a08f02f4967d62c66f95d24" - integrity sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g== +"@types/ms@*": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78" + integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== + +"@types/node-fetch@^2.6.13": + version "2.6.13" + resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.6.13.tgz#e0c9b7b5edbdb1b50ce32c127e85e880872d56ee" + integrity sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw== dependencies: "@types/node" "*" - form-data "^4.0.0" + form-data "^4.0.4" "@types/node@*": - version "18.8.4" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.8.4.tgz#54be907698f40de8a45770b48486aa3cbd3adff7" - integrity sha512-WdlVphvfR/GJCLEMbNA8lJ0lhFNBj4SW3O+O5/cEGw9oYrv0al9zTwuQsq+myDUXgNx2jgBynoVgZ2MMJ6pbow== + version "25.0.9" + resolved "https://registry.yarnpkg.com/@types/node/-/node-25.0.9.tgz#81ce3579ddf67cae812a9d49c8a0ab90c82e7782" + integrity sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw== + dependencies: + undici-types "~7.16.0" -"@types/node@^22.7.5": - version "22.7.5" - resolved "https://registry.yarnpkg.com/@types/node/-/node-22.7.5.tgz#cfde981727a7ab3611a481510b473ae54442b92b" - integrity sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ== +"@types/node@22.10.10": + version "22.10.10" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.10.10.tgz#85fe89f8bf459dc57dfef1689bd5b52ad1af07e6" + integrity sha512-X47y/mPNzxviAGY5TcYPtYL8JsY3kAq2n8fMmKoRCxq/c4v4pyGNCzM2R6+M5/umG4ZfHuT+sgqDYqWc9rJ6ww== dependencies: - undici-types "~6.19.2" + undici-types "~6.20.0" "@types/qs@*": - version "6.9.7" - resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb" - integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw== + version "6.14.0" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.14.0.tgz#d8b60cecf62f2db0fb68e5e006077b9178b85de5" + integrity sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ== "@types/range-parser@*": - version "1.2.4" - resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" - integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== + version "1.2.7" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" + integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== "@types/send@*": - version "0.17.4" - resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.4.tgz#6619cd24e7270793702e4e6a4b958a9010cfc57a" - integrity sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA== + version "1.2.1" + resolved "https://registry.yarnpkg.com/@types/send/-/send-1.2.1.tgz#6a784e45543c18c774c049bff6d3dbaf045c9c74" + integrity sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ== + dependencies: + "@types/node" "*" + +"@types/send@<1": + version "0.17.6" + resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.6.tgz#aeb5385be62ff58a52cd5459daa509ae91651d25" + integrity sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og== dependencies: "@types/mime" "^1" "@types/node" "*" -"@types/serve-static@*": - version "1.15.0" - resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.0.tgz#c7930ff61afb334e121a9da780aac0d9b8f34155" - integrity sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg== +"@types/serve-static@^1": + version "1.15.10" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.10.tgz#768169145a778f8f5dfcb6360aead414a3994fee" + integrity sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw== dependencies: - "@types/mime" "*" + "@types/http-errors" "*" "@types/node" "*" + "@types/send" "<1" "@types/set-cookie-parser@^2.4.1": - version "2.4.7" - resolved "https://registry.yarnpkg.com/@types/set-cookie-parser/-/set-cookie-parser-2.4.7.tgz#4a341ed1d3a922573ee54db70b6f0a6d818290e7" - integrity sha512-+ge/loa0oTozxip6zmhRIk8Z/boU51wl9Q6QdLZcokIGMzY5lFXYy/x7Htj2HTC6/KZP1hUbZ1ekx8DYXICvWg== + version "2.4.10" + resolved "https://registry.yarnpkg.com/@types/set-cookie-parser/-/set-cookie-parser-2.4.10.tgz#ad3a807d6d921db9720621ea3374c5d92020bcbc" + integrity sha512-GGmQVGpQWUe5qglJozEjZV/5dyxbOOZ0LHe/lqyWssB88Y4svNfst0uqBVscdDeIKl5Jy5+aPSvy7mI9tYRguw== dependencies: "@types/node" "*" @@ -455,9 +485,9 @@ eslint-visitor-keys "^3.4.3" "@ungap/structured-clone@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" - integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + version "1.3.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.3.0.tgz#d06bbb384ebcf6c505fde1c3d0ed4ddffe0aaff8" + integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g== abort-controller@3.0.0: version "3.0.0" @@ -487,9 +517,9 @@ acorn-walk@^8.1.1: acorn "^8.11.0" acorn@^8.11.0, acorn@^8.4.1, acorn@^8.9.0: - version "8.12.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" - integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== + version "8.15.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.15.0.tgz#a360898bc415edaac46c8241f6383975b930b816" + integrity sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg== ajv@^6.12.4: version "6.12.6" @@ -506,7 +536,7 @@ ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== -ansi-styles@^4.0.0, ansi-styles@^4.1.0: +ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== @@ -555,10 +585,10 @@ blueimp-md5@^2.19.0: resolved "https://registry.yarnpkg.com/blueimp-md5/-/blueimp-md5-2.19.0.tgz#b53feea5498dcb53dc6ec4b823adb84b729c4af0" integrity sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w== -body-parser@1.20.2, body-parser@^1.19.0: - version "1.20.2" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" - integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== +body-parser@1.20.3: + version "1.20.3" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6" + integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g== dependencies: bytes "3.1.2" content-type "~1.0.5" @@ -568,84 +598,81 @@ body-parser@1.20.2, body-parser@^1.19.0: http-errors "2.0.0" iconv-lite "0.4.24" on-finished "2.4.1" - qs "6.11.0" + qs "6.13.0" raw-body "2.5.2" type-is "~1.6.18" unpipe "1.0.0" -body-parser@1.20.3: - version "1.20.3" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6" - integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g== +body-parser@^1.19.0, body-parser@~1.20.3: + version "1.20.4" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.4.tgz#f8e20f4d06ca8a50a71ed329c15dccad1cdc547f" + integrity sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA== dependencies: - bytes "3.1.2" + bytes "~3.1.2" content-type "~1.0.5" debug "2.6.9" depd "2.0.0" - destroy "1.2.0" - http-errors "2.0.0" - iconv-lite "0.4.24" - on-finished "2.4.1" - qs "6.13.0" - raw-body "2.5.2" + destroy "~1.2.0" + http-errors "~2.0.1" + iconv-lite "~0.4.24" + on-finished "~2.4.1" + qs "~6.14.0" + raw-body "~2.5.3" type-is "~1.6.18" - unpipe "1.0.0" + unpipe "~1.0.0" bowser@^2.4.0: - version "2.11.0" - resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.11.0.tgz#5ca3c35757a7aa5771500c70a73a9f91ef420a8f" - integrity sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA== + version "2.13.1" + resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.13.1.tgz#5a4c652de1d002f847dd011819f5fc729f308a7e" + integrity sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw== brace-expansion@^1.1.7: - version "1.1.11" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" - integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + version "1.1.12" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.12.tgz#ab9b454466e5a8cc3a187beaad580412a9c5b843" + integrity sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg== dependencies: balanced-match "^1.0.0" concat-map "0.0.1" brace-expansion@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" - integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + version "2.0.2" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.2.tgz#54fc53237a613d854c7bd37463aad17df87214e7" + integrity sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ== dependencies: balanced-match "^1.0.0" -braces@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== +braces@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== dependencies: - fill-range "^7.0.1" + fill-range "^7.1.1" -buffer-equal-constant-time@1.0.1: +buffer-equal-constant-time@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== -bytes@3.1.2: +bytes@3.1.2, bytes@~3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== -call-bind@^1.0.0: +call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2: version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" - integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== - dependencies: - function-bind "^1.1.1" - get-intrinsic "^1.0.2" - -call-bind@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" - integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== + resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6" + integrity sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ== dependencies: - es-define-property "^1.0.0" es-errors "^1.3.0" function-bind "^1.1.2" - get-intrinsic "^1.2.4" - set-function-length "^1.2.1" + +call-bound@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/call-bound/-/call-bound-1.0.4.tgz#238de935d2a2a692928c538c7ccfa91067fd062a" + integrity sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg== + dependencies: + call-bind-apply-helpers "^1.0.2" + get-intrinsic "^1.3.0" callsites@^3.0.0: version "3.1.0" @@ -660,15 +687,6 @@ chalk@^4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -cliui@^7.0.2: - version "7.0.4" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" - integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^7.0.0" - color-convert@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" @@ -694,28 +712,23 @@ commander@^2.20.3: integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== component-emitter@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" - integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== + version "1.3.1" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.1.tgz#ef1d5796f7d93f135ee6fb684340b26403c97d17" + integrity sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ== concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== -content-disposition@0.5.4: +content-disposition@0.5.4, content-disposition@~0.5.4: version "0.5.4" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== dependencies: safe-buffer "5.2.1" -content-type@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" - integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== - -content-type@~1.0.5: +content-type@~1.0.4, content-type@~1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== @@ -725,34 +738,26 @@ cookie-signature@1.0.6: resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== -cookie@0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" - integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== +cookie-signature@~1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.7.tgz#ab5dd7ab757c54e60f37ef6550f481c426d10454" + integrity sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA== + +cookie@0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.1.tgz#2f73c42142d5d5cf71310a74fc4ae61670e5dbc9" + integrity sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w== + +cookie@~0.7.1: + version "0.7.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" + integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== cookiejar@^2.1.4: version "2.1.4" resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.4.tgz#ee669c1fea2cf42dc31585469d193fef0d65771b" integrity sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw== -copyfiles@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/copyfiles/-/copyfiles-2.4.1.tgz#d2dcff60aaad1015f09d0b66e7f0f1c5cd3c5da5" - integrity sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg== - dependencies: - glob "^7.0.5" - minimatch "^3.0.3" - mkdirp "^1.0.4" - noms "0.0.0" - through2 "^2.0.1" - untildify "^4.0.0" - yargs "^16.1.0" - -core-util-is@~1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" - integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== - cors@^2.8.5: version "2.8.5" resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" @@ -767,9 +772,9 @@ create-require@^1.1.0: integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== cross-spawn@^7.0.2: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== dependencies: path-key "^3.1.0" shebang-command "^2.0.0" @@ -787,17 +792,10 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@^4.1.0, debug@^4.3.2, debug@^4.3.4: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - -debug@^4.3.1: - version "4.3.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" - integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== +debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: + version "4.4.3" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" + integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== dependencies: ms "^2.1.3" @@ -811,15 +809,6 @@ deep-is@^0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== -define-data-property@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" - integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== - dependencies: - es-define-property "^1.0.0" - es-errors "^1.3.0" - gopd "^1.0.1" - delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -830,7 +819,7 @@ depd@2.0.0, depd@~2.0.0: resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== -destroy@1.2.0: +destroy@1.2.0, destroy@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== @@ -844,9 +833,9 @@ dezalgo@^1.0.4: wrappy "1" diff@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" - integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + version "4.0.4" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.4.tgz#7a6dbfda325f25f07517e9b518f897c08332e07d" + integrity sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ== doctrine@^3.0.0: version "3.0.0" @@ -855,6 +844,15 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" +dunder-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/dunder-proto/-/dunder-proto-1.0.1.tgz#d7ae667e1dc83482f8b70fd0f6eefc50da30f58a" + integrity sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A== + dependencies: + call-bind-apply-helpers "^1.0.1" + es-errors "^1.3.0" + gopd "^1.2.0" + ecdsa-sig-formatter@1.0.11: version "1.0.11" resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" @@ -867,11 +865,6 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -882,22 +875,32 @@ encodeurl@~2.0.0: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== -es-define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" - integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== - dependencies: - get-intrinsic "^1.2.4" +es-define-property@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.1.tgz#983eb2f9a6724e9303f61addf011c72e09e0b0fa" + integrity sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g== es-errors@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== -escalade@^3.1.1: - version "3.2.0" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" - integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== +es-object-atoms@^1.0.0, es-object-atoms@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz#1c4f2c4837327597ce69d2ca190a7fdd172338c1" + integrity sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA== + dependencies: + es-errors "^1.3.0" + +es-set-tostringtag@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz#f31dbbe0c183b00a6d26eb6325c810c0fd18bd4d" + integrity sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA== + dependencies: + es-errors "^1.3.0" + get-intrinsic "^1.2.6" + has-tostringtag "^1.0.2" + hasown "^2.0.2" escape-html@~1.0.3: version "1.0.3" @@ -910,9 +913,9 @@ escape-string-regexp@^4.0.0: integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== eslint-config-prettier@^8.5.0: - version "8.5.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz#5a81680ec934beca02c7b1a61cf8ca34b66feab1" - integrity sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q== + version "8.10.2" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.10.2.tgz#0642e53625ebc62c31c24726b0f050df6bd97a2e" + integrity sha512-/IGJ6+Dka158JnP5n5YFMOszjDWrXggGz1LaK/guZq9vZTmniaKlHcsscvkAhn9y4U+BU3JuUdYvtAMcv30y4A== eslint-scope@^7.2.2: version "7.2.2" @@ -922,11 +925,6 @@ eslint-scope@^7.2.2: esrecurse "^4.3.0" estraverse "^5.2.0" -eslint-visitor-keys@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" - integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== - eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: version "3.4.3" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" @@ -986,9 +984,9 @@ espree@^9.6.0, espree@^9.6.1: eslint-visitor-keys "^3.4.1" esquery@^1.4.2: - version "1.6.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" - integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== + version "1.7.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.7.0.tgz#08d048f261f0ddedb5bae95f46809463d9c9496d" + integrity sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g== dependencies: estraverse "^5.1.0" @@ -1019,17 +1017,17 @@ event-target-shim@^5.0.0: resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== -express@4.21.0: - version "4.21.0" - resolved "https://registry.yarnpkg.com/express/-/express-4.21.0.tgz#d57cb706d49623d4ac27833f1cbc466b668eb915" - integrity sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng== +express@4.21.2: + version "4.21.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.21.2.tgz#cf250e48362174ead6cea4a566abef0162c1ec32" + integrity sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA== dependencies: accepts "~1.3.8" array-flatten "1.1.1" body-parser "1.20.3" content-disposition "0.5.4" content-type "~1.0.4" - cookie "0.6.0" + cookie "0.7.1" cookie-signature "1.0.6" debug "2.6.9" depd "2.0.0" @@ -1043,7 +1041,7 @@ express@4.21.0: methods "~1.1.2" on-finished "2.4.1" parseurl "~1.3.3" - path-to-regexp "0.1.10" + path-to-regexp "0.1.12" proxy-addr "~2.0.7" qs "6.13.0" range-parser "~1.2.1" @@ -1057,38 +1055,38 @@ express@4.21.0: vary "~1.1.2" express@^4.17.1: - version "4.19.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465" - integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q== + version "4.22.1" + resolved "https://registry.yarnpkg.com/express/-/express-4.22.1.tgz#1de23a09745a4fffdb39247b344bb5eaff382069" + integrity sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g== dependencies: accepts "~1.3.8" array-flatten "1.1.1" - body-parser "1.20.2" - content-disposition "0.5.4" + body-parser "~1.20.3" + content-disposition "~0.5.4" content-type "~1.0.4" - cookie "0.6.0" - cookie-signature "1.0.6" + cookie "~0.7.1" + cookie-signature "~1.0.6" debug "2.6.9" depd "2.0.0" - encodeurl "~1.0.2" + encodeurl "~2.0.0" escape-html "~1.0.3" etag "~1.8.1" - finalhandler "1.2.0" - fresh "0.5.2" - http-errors "2.0.0" - merge-descriptors "1.0.1" + finalhandler "~1.3.1" + fresh "~0.5.2" + http-errors "~2.0.0" + merge-descriptors "1.0.3" methods "~1.1.2" - on-finished "2.4.1" + on-finished "~2.4.1" parseurl "~1.3.3" - path-to-regexp "0.1.7" + path-to-regexp "~0.1.12" proxy-addr "~2.0.7" - qs "6.11.0" + qs "~6.14.0" range-parser "~1.2.1" safe-buffer "5.2.1" - send "0.18.0" - serve-static "1.15.0" + send "~0.19.0" + serve-static "~1.16.2" setprototypeof "1.2.0" - statuses "2.0.1" + statuses "~2.0.1" type-is "~1.6.18" utils-merge "1.0.1" vary "~1.1.2" @@ -1099,15 +1097,15 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== fast-glob@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" - integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + version "3.3.3" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.3.tgz#d06d585ce8dba90a16b0505c543c3ccfb3aeb818" + integrity sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg== dependencies: "@nodelib/fs.stat" "^2.0.2" "@nodelib/fs.walk" "^1.2.3" glob-parent "^5.1.2" merge2 "^1.3.0" - micromatch "^4.0.4" + micromatch "^4.0.8" fast-json-stable-stringify@^2.0.0, fast-json-stable-stringify@^2.1.0: version "2.1.0" @@ -1125,9 +1123,9 @@ fast-safe-stringify@^2.1.1: integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== fastq@^1.6.0: - version "1.13.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" - integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== + version "1.20.1" + resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.20.1.tgz#ca750a10dc925bc8b18839fd203e3ef4b3ced675" + integrity sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw== dependencies: reusify "^1.0.4" @@ -1138,10 +1136,10 @@ file-entry-cache@^6.0.1: dependencies: flat-cache "^3.0.4" -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== dependencies: to-regex-range "^5.0.1" @@ -1150,30 +1148,30 @@ filter-obj@^1.1.0: resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b" integrity sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ== -finalhandler@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" - integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== +finalhandler@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.1.tgz#0c575f1d1d324ddd1da35ad7ece3df7d19088019" + integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ== dependencies: debug "2.6.9" - encodeurl "~1.0.2" + encodeurl "~2.0.0" escape-html "~1.0.3" on-finished "2.4.1" parseurl "~1.3.3" statuses "2.0.1" unpipe "~1.0.0" -finalhandler@1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.1.tgz#0c575f1d1d324ddd1da35ad7ece3df7d19088019" - integrity sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ== +finalhandler@~1.3.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.3.2.tgz#1ebc2228fc7673aac4a472c310cc05b77d852b88" + integrity sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg== dependencies: debug "2.6.9" encodeurl "~2.0.0" escape-html "~1.0.3" - on-finished "2.4.1" + on-finished "~2.4.1" parseurl "~1.3.3" - statuses "2.0.1" + statuses "~2.0.2" unpipe "~1.0.0" find-up@^5.0.0: @@ -1185,43 +1183,48 @@ find-up@^5.0.0: path-exists "^4.0.0" flat-cache@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" - integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== + version "3.2.0" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.2.0.tgz#2c0c2d5040c99b1632771a9d105725c0115363ee" + integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw== dependencies: - flatted "^3.1.0" + flatted "^3.2.9" + keyv "^4.5.3" rimraf "^3.0.2" -flatted@^3.1.0: - version "3.2.7" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787" - integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ== +flatted@^3.2.9: + version "3.3.3" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.3.tgz#67c8fad95454a7c7abebf74bb78ee74a44023358" + integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg== -form-data@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.0.tgz#31b7e39c85f1355b7139ee0c647cf0de7f83c682" - integrity sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg== +form-data@3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.4.tgz#938273171d3f999286a4557528ce022dc2c98df1" + integrity sha512-f0cRzm6dkyVYV3nPoooP8XlccPQukegwhAnpoLcXy+X+A8KfpGOoXwDr9FLZd3wzgLaBGQBE3lY93Zm/i1JvIQ== dependencies: asynckit "^0.4.0" combined-stream "^1.0.8" - mime-types "^2.1.12" + es-set-tostringtag "^2.1.0" + hasown "^2.0.2" + mime-types "^2.1.35" -form-data@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" - integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== +form-data@^4.0.0, form-data@^4.0.4: + version "4.0.5" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.5.tgz#b49e48858045ff4cbf6b03e1805cebcad3679053" + integrity sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w== dependencies: asynckit "^0.4.0" combined-stream "^1.0.8" + es-set-tostringtag "^2.1.0" + hasown "^2.0.2" mime-types "^2.1.12" formidable@^3.5.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/formidable/-/formidable-3.5.1.tgz#9360a23a656f261207868b1484624c4c8d06ee1a" - integrity sha512-WJWKelbRHN41m5dumb0/k8TeAx7Id/y3a+Z7QfhxP/htI9Js5zYaEDtG8uMgG0vM0lOlqnmjE99/kfpOYi/0Og== + version "3.5.4" + resolved "https://registry.yarnpkg.com/formidable/-/formidable-3.5.4.tgz#ac9a593b951e829b3298f21aa9a2243932f32ed9" + integrity sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug== dependencies: + "@paralleldrive/cuid2" "^2.2.2" dezalgo "^1.0.4" - hexoid "^1.0.0" once "^1.4.0" forwarded@0.2.0: @@ -1229,7 +1232,7 @@ forwarded@0.2.0: resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== -fresh@0.5.2: +fresh@0.5.2, fresh@~0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== @@ -1248,40 +1251,34 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - function-bind@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== -get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -get-intrinsic@^1.0.2: - version "1.1.3" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385" - integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A== - dependencies: - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.3" - -get-intrinsic@^1.1.3, get-intrinsic@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" - integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== +get-intrinsic@^1.2.5, get-intrinsic@^1.2.6, get-intrinsic@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz#743f0e3b6964a93a5491ed1bffaae054d7f98d01" + integrity sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ== dependencies: + call-bind-apply-helpers "^1.0.2" + es-define-property "^1.0.1" es-errors "^1.3.0" + es-object-atoms "^1.1.1" function-bind "^1.1.2" - has-proto "^1.0.1" - has-symbols "^1.0.3" - hasown "^2.0.0" + get-proto "^1.0.1" + gopd "^1.2.0" + has-symbols "^1.1.0" + hasown "^2.0.2" + math-intrinsics "^1.1.0" + +get-proto@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-proto/-/get-proto-1.0.1.tgz#150b3f2743869ef3e851ec0c49d15b1d14d00ee1" + integrity sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g== + dependencies: + dunder-proto "^1.0.1" + es-object-atoms "^1.0.0" glob-parent@^5.1.2: version "5.1.2" @@ -1297,7 +1294,7 @@ glob-parent@^6.0.2: dependencies: is-glob "^4.0.3" -glob@^7.0.5, glob@^7.1.3: +glob@^7.1.3: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -1316,12 +1313,10 @@ globals@^13.19.0: dependencies: type-fest "^0.20.2" -gopd@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" - integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== - dependencies: - get-intrinsic "^1.1.3" +gopd@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.2.0.tgz#89f56b8217bdbc8802bd299df6d7f1081d7e51a1" + integrity sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg== graceful-fs@^4.1.6, graceful-fs@^4.2.0: version "4.2.11" @@ -1338,42 +1333,25 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== -has-property-descriptors@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" - integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== - dependencies: - es-define-property "^1.0.0" - -has-proto@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" - integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== - -has-symbols@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" - integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== +has-symbols@^1.0.3, has-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.1.0.tgz#fc9c6a783a084951d0b971fe1018de813707a338" + integrity sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ== -has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== +has-tostringtag@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.2.tgz#2cdc42d40bef2e5b4eeab7c01a73c54ce7ab5abc" + integrity sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw== dependencies: - function-bind "^1.1.1" + has-symbols "^1.0.3" -hasown@^2.0.0: +hasown@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== dependencies: function-bind "^1.1.2" -hexoid@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/hexoid/-/hexoid-1.0.0.tgz#ad10c6573fb907de23d9ec63a711267d9dc9bc18" - integrity sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g== - html-entities@2.5.2: version "2.5.2" resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.5.2.tgz#201a3cf95d3a15be7099521620d19dfb4f65359f" @@ -1390,10 +1368,21 @@ http-errors@2.0.0: statuses "2.0.1" toidentifier "1.0.1" +http-errors@~2.0.0, http-errors@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.1.tgz#36d2f65bc909c8790018dd36fb4d93da6caae06b" + integrity sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ== + dependencies: + depd "~2.0.0" + inherits "~2.0.4" + setprototypeof "~1.2.0" + statuses "~2.0.2" + toidentifier "~1.0.1" + http-graceful-shutdown@^3.1.5: - version "3.1.13" - resolved "https://registry.yarnpkg.com/http-graceful-shutdown/-/http-graceful-shutdown-3.1.13.tgz#cf3c8f99787d1f5ac2a6bf8a2132ff54c9ce031e" - integrity sha512-Ci5LRufQ8AtrQ1U26AevS8QoMXDOhnAHCJI3eZu1com7mZGHxREmw3dNj85ftpQokQCvak8nI2pnFS8zyM1M+Q== + version "3.1.15" + resolved "https://registry.yarnpkg.com/http-graceful-shutdown/-/http-graceful-shutdown-3.1.15.tgz#c49d6330e30ec8b8629cada7d8cc8a9fc676e49a" + integrity sha512-7BzY5XxGV4g7QaOZ8xf5Hco8vJMMUFDl+AcB8AuOOdS1wTOZ88aV3EIADUnyAItj2QW+QfBPz08/PzxKiDKh6Q== dependencies: debug "^4.3.4" @@ -1402,27 +1391,22 @@ http-status-codes@2.3.0: resolved "https://registry.yarnpkg.com/http-status-codes/-/http-status-codes-2.3.0.tgz#987fefb28c69f92a43aecc77feec2866349a8bfc" integrity sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA== -iconv-lite@0.4.24: +iconv-lite@0.4.24, iconv-lite@~0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== dependencies: safer-buffer ">= 2.1.2 < 3" -ignore@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" - integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== - -ignore@^5.3.1: +ignore@^5.2.0, ignore@^5.3.1: version "5.3.2" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== import-fresh@^3.2.1: - version "3.3.0" - resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" - integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== + version "3.3.1" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.1.tgz#9cecb56503c0ada1f2741dbbd6546e4b13b57ccf" + integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ== dependencies: parent-module "^1.0.0" resolve-from "^4.0.0" @@ -1440,7 +1424,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@~2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -1460,11 +1444,6 @@ is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" @@ -1482,28 +1461,23 @@ is-path-inside@^3.0.3: resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== -isarray@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== - -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ== - isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== js-yaml@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" - integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + version "4.1.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.1.tgz#854c292467705b699476e1a2decc0c8a3458806b" + integrity sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA== dependencies: argparse "^2.0.1" +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" @@ -1525,9 +1499,9 @@ jsonata@1.8.7: integrity sha512-tOW2/hZ+nR2bcQZs+0T62LVe5CHaNa3laFFWb/262r39utN6whJGBF7IR2Wq1QXrDbhftolk5gggW8uUJYlBTQ== jsonfile@^6.0.1: - version "6.1.0" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" - integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ== + version "6.2.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.2.0.tgz#7c265bd1b65de6977478300087c99f1c84383f62" + integrity sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg== dependencies: universalify "^2.0.0" optionalDependencies: @@ -1549,23 +1523,30 @@ jsonwebtoken@9.0.2: ms "^2.1.1" semver "^7.5.4" -jwa@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" - integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== +jwa@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.2.tgz#16011ac6db48de7b102777e57897901520eec7b9" + integrity sha512-eeH5JO+21J78qMvTIDdBXidBd6nG2kZjg5Ohz/1fpa28Z4CcsWUzJ1ZZyFq/3z3N17aZy+ZuBoHljASbL1WfOw== dependencies: - buffer-equal-constant-time "1.0.1" + buffer-equal-constant-time "^1.0.1" ecdsa-sig-formatter "1.0.11" safe-buffer "^5.0.1" jws@^3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" - integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + version "3.2.3" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.3.tgz#5ac0690b460900a27265de24520526853c0b8ca1" + integrity sha512-byiJ0FLRdLdSVSReO/U4E7RoEyOCKnEnEPMjq3HxWtvzLsV08/i5RQKsFVNkCldrCaPr2vDNAOMsfs8T/Hze7g== dependencies: - jwa "^1.4.1" + jwa "^1.4.2" safe-buffer "^5.0.1" +keyv@^4.5.3: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + levn@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" @@ -1582,9 +1563,9 @@ locate-path@^6.0.0: p-locate "^5.0.0" lodash-es@^4.17.21: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.21.tgz#43e626c46e6591b7750beb2b50117390c609e3ee" - integrity sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw== + version "4.17.23" + resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.23.tgz#58c4360fd1b5d33afc6c0bbd3d1149349b1138e0" + integrity sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg== lodash.includes@^4.3.0: version "4.3.0" @@ -1626,33 +1607,31 @@ lodash.once@^4.0.0: resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== -loglevel@^1.8.0: - version "1.9.1" - resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.9.1.tgz#d63976ac9bcd03c7c873116d41c2a85bafff1be7" - integrity sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg== +lodash@4.17.21: + version "4.17.21" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" + integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== - dependencies: - yallist "^4.0.0" +loglevel@^1.8.0: + version "1.9.2" + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.9.2.tgz#c2e028d6c757720107df4e64508530db6621ba08" + integrity sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg== make-error@^1.1.1: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== +math-intrinsics@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9" + integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g== + media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== - merge-descriptors@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5" @@ -1664,21 +1643,21 @@ merge2@^1.3.0: integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== mergee@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/mergee/-/mergee-1.0.0.tgz#027c5addc650f6ecbe4bf56100bd00dae763fda7" - integrity sha512-hbbXD4LOcxVkpS+mp3BMEhkSDf+lTVENFeEeqACgjjL8WrgKuW2EyLT0fOHyTbyDiuRLZJZ1HrHNeiX4iOd79Q== + version "1.0.1" + resolved "https://registry.yarnpkg.com/mergee/-/mergee-1.0.1.tgz#f28797491f5f815f7234bc338f767d3cace82382" + integrity sha512-pItsgflAi3HF3Ly6eWpJ6gc6GJehxFLcHdfCGdRR8BoFz3NcliomGiTU0dm4pzli0rCC1N+00DfPBhjqilYeSg== methods@^1.1.2, methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== -micromatch@^4.0.4: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== +micromatch@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== dependencies: - braces "^3.0.2" + braces "^3.0.3" picomatch "^2.3.1" mime-db@1.52.0: @@ -1686,7 +1665,7 @@ mime-db@1.52.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.12, mime-types@~2.1.24, mime-types@~2.1.34: +mime-types@^2.1.12, mime-types@^2.1.35, mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -1708,7 +1687,7 @@ mime@3.0.0: resolved "https://registry.yarnpkg.com/mime/-/mime-3.0.0.tgz#b374550dca3a0c18443b0c950a6a58f1931cf7a7" integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A== -minimatch@^3.0.3, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: +minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -1722,11 +1701,6 @@ minimatch@^9.0.4: dependencies: brace-expansion "^2.0.1" -mkdirp@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" - integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== - mock-http@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/mock-http/-/mock-http-1.1.0.tgz#b89380a718a103fc5801095804bedd0b20f7638c" @@ -1735,26 +1709,21 @@ mock-http@1.1.0: mergee "^1.0.0" morgan@^1.10.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.10.0.tgz#091778abc1fc47cd3509824653dae1faab6b17d7" - integrity sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ== + version "1.10.1" + resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.10.1.tgz#4e02e6a4465a48e26af540191593955d17f61570" + integrity sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A== dependencies: basic-auth "~2.0.1" debug "2.6.9" depd "~2.0.0" on-finished "~2.3.0" - on-headers "~1.0.2" + on-headers "~1.1.0" ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== -ms@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" - integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== - ms@2.1.3, ms@^2.1.1, ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" @@ -1776,9 +1745,9 @@ nocache@^3.0.1: integrity sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw== nock@^13.2.1: - version "13.5.4" - resolved "https://registry.yarnpkg.com/nock/-/nock-13.5.4.tgz#8918f0addc70a63736170fef7106a9721e0dc479" - integrity sha512-yAyTfdeNJGGBFxWdzSKCBYxs5FxLbCg5X5Q4ets974hcQzG1+qCxvIyOo4j2Ry6MUlhWVMX4OoYDefAIIwupjw== + version "13.5.6" + resolved "https://registry.yarnpkg.com/nock/-/nock-13.5.6.tgz#5e693ec2300bbf603b61dae6df0225673e6c4997" + integrity sha512-o2zOYiCpzRqSzPj0Zt/dQ/DqZeYoaQ7TUonc/xUPjCGl9WeHpNbxgVvOquXYAaJzI0M9BXV3HTzG0p8IUAbBTQ== dependencies: debug "^4.1.0" json-stringify-safe "^5.0.1" @@ -1791,30 +1760,17 @@ node-fetch@2.7.0: dependencies: whatwg-url "^5.0.0" -noms@0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/noms/-/noms-0.0.0.tgz#da8ebd9f3af9d6760919b27d9cdc8092a7332859" - integrity sha512-lNDU9VJaOPxUmXcLb+HQFeUgQQPtMI24Gt6hgfuMHRJgMRHMF/qZ4HJD3GDru4sSw9IQl2jPjAYnQrdIeLbwow== - dependencies: - inherits "^2.0.1" - readable-stream "~1.0.31" - object-assign@^4: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== -object-inspect@^1.13.1: - version "1.13.1" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" - integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== +object-inspect@^1.13.3: + version "1.13.4" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.4.tgz#8375265e21bc20d0fa582c22e1b13485d6e00213" + integrity sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew== -object-inspect@^1.9.0: - version "1.12.2" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" - integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== - -on-finished@2.4.1: +on-finished@2.4.1, on-finished@~2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== @@ -1828,10 +1784,10 @@ on-finished@~2.3.0: dependencies: ee-first "1.1.1" -on-headers@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f" - integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA== +on-headers@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.1.0.tgz#59da4f91c45f5f989c6e4bcedc5a3b0aed70ff65" + integrity sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A== once@^1.3.0, once@^1.4.0: version "1.4.0" @@ -1893,15 +1849,10 @@ path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -path-to-regexp@0.1.10: - version "0.1.10" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b" - integrity sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w== - -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== +path-to-regexp@0.1.12, path-to-regexp@~0.1.12: + version "0.1.12" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.12.tgz#d5e1a12e478a976d432ef3c58d534b9923164bb7" + integrity sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ== picomatch@^2.3.1: version "2.3.1" @@ -1914,14 +1865,9 @@ prelude-ls@^1.2.1: integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== prettier@^2.6.1: - version "2.7.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64" - integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g== - -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== + version "2.8.8" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" + integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== propagate@^2.0.0: version "2.0.1" @@ -1942,30 +1888,23 @@ proxy-addr@~2.0.7: ipaddr.js "1.9.1" punycode@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== - -qs@6.11.0: - version "6.11.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" - integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== - dependencies: - side-channel "^1.0.4" + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== -qs@6.13.0, qs@^6.11.0: +qs@6.13.0: version "6.13.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== dependencies: side-channel "^1.0.6" -qs@^6.10.1: - version "6.12.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.12.1.tgz#39422111ca7cbdb70425541cba20c7d7b216599a" - integrity sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ== +qs@^6.10.1, qs@^6.11.0, qs@~6.14.0: + version "6.14.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.14.1.tgz#a41d85b9d3902f31d27861790506294881871159" + integrity sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ== dependencies: - side-channel "^1.0.6" + side-channel "^1.1.0" query-string@7.1.3: version "7.1.3" @@ -2002,33 +1941,15 @@ raw-body@2.5.2: iconv-lite "0.4.24" unpipe "1.0.0" -readable-stream@~1.0.31: - version "1.0.34" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" - integrity sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - -readable-stream@~2.3.6: - version "2.3.8" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" - integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== +raw-body@~2.5.3: + version "2.5.3" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.3.tgz#11c6650ee770a7de1b494f197927de0c923822e2" + integrity sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA== + dependencies: + bytes "~3.1.2" + http-errors "~2.0.1" + iconv-lite "~0.4.24" + unpipe "~1.0.0" requires-port@^1.0.0: version "1.0.0" @@ -2041,9 +1962,9 @@ resolve-from@^4.0.0: integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== + version "1.1.0" + resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.1.0.tgz#0fe13b9522e1473f51b558ee796e08f11f9b489f" + integrity sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw== rimraf@^3.0.2: version "3.0.2" @@ -2064,7 +1985,7 @@ run-parallel@^1.1.9: dependencies: queue-microtask "^1.2.2" -safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== @@ -2079,22 +2000,15 @@ safe-buffer@5.2.1, safe-buffer@^5.0.1: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -semver@^7.5.4: - version "7.6.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" - integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== - dependencies: - lru-cache "^6.0.0" - -semver@^7.6.0: - version "7.6.3" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" - integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== +semver@^7.5.4, semver@^7.6.0: + version "7.7.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.3.tgz#4b5f4143d007633a8dc671cd0a6ef9147b8bb946" + integrity sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q== -send@0.18.0: - version "0.18.0" - resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" - integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== +send@0.19.0: + version "0.19.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" + integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw== dependencies: debug "2.6.9" depd "2.0.0" @@ -2110,34 +2024,24 @@ send@0.18.0: range-parser "~1.2.1" statuses "2.0.1" -send@0.19.0: - version "0.19.0" - resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" - integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw== +send@~0.19.0, send@~0.19.1: + version "0.19.2" + resolved "https://registry.yarnpkg.com/send/-/send-0.19.2.tgz#59bc0da1b4ea7ad42736fd642b1c4294e114ff29" + integrity sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg== dependencies: debug "2.6.9" depd "2.0.0" destroy "1.2.0" - encodeurl "~1.0.2" + encodeurl "~2.0.0" escape-html "~1.0.3" etag "~1.8.1" - fresh "0.5.2" - http-errors "2.0.0" + fresh "~0.5.2" + http-errors "~2.0.1" mime "1.6.0" ms "2.1.3" - on-finished "2.4.1" + on-finished "~2.4.1" range-parser "~1.2.1" - statuses "2.0.1" - -serve-static@1.15.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" - integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.18.0" + statuses "~2.0.2" serve-static@1.16.2: version "1.16.2" @@ -2149,24 +2053,22 @@ serve-static@1.16.2: parseurl "~1.3.3" send "0.19.0" -set-cookie-parser@^2.4.8: - version "2.6.0" - resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz#131921e50f62ff1a66a461d7d62d7b21d5d15a51" - integrity sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ== - -set-function-length@^1.2.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" - integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== +serve-static@~1.16.2: + version "1.16.3" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.3.tgz#a97b74d955778583f3862a4f0b841eb4d5d78cf9" + integrity sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA== dependencies: - define-data-property "^1.1.4" - es-errors "^1.3.0" - function-bind "^1.1.2" - get-intrinsic "^1.2.4" - gopd "^1.0.1" - has-property-descriptors "^1.0.2" + encodeurl "~2.0.0" + escape-html "~1.0.3" + parseurl "~1.3.3" + send "~0.19.1" -setprototypeof@1.2.0: +set-cookie-parser@^2.4.8: + version "2.7.2" + resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz#ccd08673a9ae5d2e44ea2a2de25089e67c7edf68" + integrity sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw== + +setprototypeof@1.2.0, setprototypeof@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== @@ -2183,24 +2085,45 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -side-channel@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf" - integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw== +side-channel-list@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/side-channel-list/-/side-channel-list-1.0.0.tgz#10cb5984263115d3b7a0e336591e290a830af8ad" + integrity sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA== dependencies: - call-bind "^1.0.0" - get-intrinsic "^1.0.2" - object-inspect "^1.9.0" + es-errors "^1.3.0" + object-inspect "^1.13.3" -side-channel@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" - integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== +side-channel-map@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/side-channel-map/-/side-channel-map-1.0.1.tgz#d6bb6b37902c6fef5174e5f533fab4c732a26f42" + integrity sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA== dependencies: - call-bind "^1.0.7" + call-bound "^1.0.2" es-errors "^1.3.0" - get-intrinsic "^1.2.4" - object-inspect "^1.13.1" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + +side-channel-weakmap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz#11dda19d5368e40ce9ec2bdc1fb0ecbc0790ecea" + integrity sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A== + dependencies: + call-bound "^1.0.2" + es-errors "^1.3.0" + get-intrinsic "^1.2.5" + object-inspect "^1.13.3" + side-channel-map "^1.0.1" + +side-channel@^1.0.6, side-channel@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.1.0.tgz#c3fcff9c4da932784873335ec9765fa94ff66bc9" + integrity sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw== + dependencies: + es-errors "^1.3.0" + object-inspect "^1.13.3" + side-channel-list "^1.0.0" + side-channel-map "^1.0.1" + side-channel-weakmap "^1.0.2" slugify@^1.6.3: version "1.6.6" @@ -2217,33 +2140,17 @@ statuses@2.0.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== +statuses@~2.0.1, statuses@~2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.2.tgz#8f75eecef765b5e1cfcdc080da59409ed424e382" + integrity sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw== + strict-uri-encode@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" integrity sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ== -string-width@^4.1.0, 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_decoder@~0.10.x: - version "0.10.31" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" - integrity sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ== - -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: +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== @@ -2282,14 +2189,6 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw== -through2@^2.0.1: - version "2.0.5" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" - integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== - dependencies: - readable-stream "~2.3.6" - xtend "~4.0.1" - tiny-case@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/tiny-case/-/tiny-case-1.0.3.tgz#d980d66bc72b5d5a9ca86fb7c9ffdb9c898ddd03" @@ -2302,7 +2201,7 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -toidentifier@1.0.1: +toidentifier@1.0.1, toidentifier@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== @@ -2318,9 +2217,9 @@ tr46@~0.0.3: integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== ts-api-utils@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" - integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== + version "1.4.3" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.4.3.tgz#bfc2215fe6528fecab2b0fba570a2e8a4263b064" + integrity sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw== ts-node@10.9.2: version "10.9.2" @@ -2371,10 +2270,15 @@ typescript@5.5.4: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba" integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q== -undici-types@~6.19.2: - version "6.19.8" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" - integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== +undici-types@~6.20.0: + version "6.20.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433" + integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg== + +undici-types@~7.16.0: + version "7.16.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.16.0.tgz#ffccdff36aea4884cbfce9a750a0580224f58a46" + integrity sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw== universalify@^2.0.0: version "2.0.1" @@ -2386,11 +2290,6 @@ unpipe@1.0.0, unpipe@~1.0.0: resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== -untildify@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b" - integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw== - uri-js@^4.2.2: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -2407,14 +2306,9 @@ url-parse@^1.5.3: requires-port "^1.0.0" utf8-byte-length@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz#f45f150c4c66eee968186505ab93fcbb8ad6bf61" - integrity sha512-4+wkEYLBbWxqTahEsWrhxepcoVOJ+1z5PGIjPZxRkytcdSUaNjIjBM7Xn8E+pdSuV7SzvWovBFA54FO0JSoqhA== - -util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== + version "1.0.5" + resolved "https://registry.yarnpkg.com/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz#f9f63910d15536ee2b2d5dd4665389715eac5c1e" + integrity sha512-Xn0w3MtiQ6zoz2vFyUVruaCL53O/DwUvkEeOvj+uulMm0BkUGYWmBYVyElqZaSLhY6ZD0ulfU3aBra2aVT4xfA== utils-merge@1.0.1: version "1.0.1" @@ -2461,15 +2355,6 @@ word-wrap@^1.2.5: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== -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" - wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -2483,39 +2368,6 @@ xss@1.0.15: commander "^2.20.3" cssfilter "0.0.10" -xtend@~4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== - -yargs-parser@^20.2.2: - version "20.2.9" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" - integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== - -yargs@^16.1.0: - version "16.2.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" - integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.0" - y18n "^5.0.5" - yargs-parser "^20.2.2" - yn@3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"