import type { ArrayShape, AttributeValues, Dataset, Entity, NumericType, ProvidedEntity, ScalarShape, Value, } from '@h5web/shared'; import { hasNumericType } from '@h5web/shared'; import { assertArrayOrTypedArray, assertArrayShape, assertDefined, assertMockAttribute, assertMockDataset, findMockEntity, hasArrayShape, isTypedArray, mockFilepath, } from '@h5web/shared'; import type { MockDataset } from '@h5web/shared/src/mock/models'; import axios from 'axios'; import { DataProviderApi } from '../api'; import type { ExportFormat, ExportURL, ValuesStoreParams } from '../models'; import { sliceValue } from '../utils'; const SLOW_TIMEOUT = 3000; export class MockApi extends DataProviderApi { public constructor( fileExt:string[], Filepath:string[], private readonly _getExportURL?: DataProviderApi['getExportURL'] ) { super(Filepath,fileExt); } public async getEntity(path: string): Promise { if (path.includes('slow_metadata')) { await new Promise((resolve) => { setTimeout(() => resolve(), SLOW_TIMEOUT); }); } const entity = findMockEntity(path); assertDefined(entity, `No entity found at ${path}`); return entity; } public async getAttrValues(entity: Entity): Promise { return Object.fromEntries( entity.attributes.map((attr) => { assertMockAttribute(attr); return [attr.name, attr.value]; }) ); } public async getValue(params: ValuesStoreParams): Promise { const { dataset, selection } = params; if (dataset.name === 'error_value') { throw new Error('error'); } if (dataset.name.startsWith('slow')) { await this.cancellableDelay(params); } assertMockDataset(dataset); const { value: rawValue } = dataset; const value = this.processRawValue(dataset, rawValue); if (!selection) { return value; } assertArrayShape(dataset); return sliceValue(value, dataset, selection); } public getExportURL>( dataset: D, selection: string | undefined, value: Value, format: ExportFormat ): ExportURL { if (this._getExportURL) { this._getExportURL(dataset, selection, value, format); } if ( hasNumericType(dataset) && selection === undefined && format === 'csv' ) { return async () => { let csv = ''; (value as Value>).forEach((val) => { csv += `${val.toString()}\n`; }); const finalCsv = csv.slice(0, -2); // Demonstrate both `Blob` and `URL` techniques (cf. `src/providers/api.ts`) return dataset.name === 'oneD' ? new Blob([finalCsv]) : new URL(`data:text/plain,${encodeURIComponent(finalCsv)}`); }; } return undefined; } private async cancellableDelay(storeParams: ValuesStoreParams) { const cancelSource = axios.CancelToken.source(); const request = { storeParams, cancelSource }; this.valueRequests.add(request); try { await new Promise((resolve, reject) => { const timeout = setTimeout(() => resolve(), SLOW_TIMEOUT); void cancelSource.token.promise.then((error) => { clearTimeout(timeout); reject(error); }); }); } finally { this.valueRequests.delete(request); } } private processRawValue( dataset: MockDataset, rawValue: unknown ) { assertDefined(rawValue, 'Expected mock dataset to have value'); if (!hasArrayShape(dataset)) { return rawValue; } assertArrayOrTypedArray(rawValue); return isTypedArray(rawValue) ? rawValue : rawValue.flat(dataset.shape.length - 1); } }