UNPKG

stable-ts-type

Version:

Obtain the most stable type code of 'typescript' through multiple network requests

205 lines (175 loc) 6.4 kB
import axios from 'axios'; import { promiseDelay } from 'prefer-delay'; import EventEmitter from 'eventemitter2'; import merge from 'merge'; import json2Type from './json2type'; import fetch from './fetch'; import { defaultTypeOpts } from './type-opts'; import { easyJSONParse, simpleError, isValidJSON } from './utils'; import type { TypeOpts } from './type-opts'; import { Input, ExampleJSONInput, RequestInput, getOnceMockInputList } from './input'; const TOP_TYPE_NAME = 'Response'; // quicktype@21.0.13的ts转换直接定死了大驼峰,没法改风格 export interface GenerateOpts { topTypeName?: string; quickTypeOpts?: TypeOpts; requestInterval?: number; requireMockSort?: 'random' | 'regular'; on?: (event: `${GenerateEvent}`, codeOrError: string | Error) => void; } const defaultGenerateOpts: GenerateOpts = { requestInterval: 0, topTypeName: TOP_TYPE_NAME, quickTypeOpts: defaultTypeOpts, requireMockSort: 'random', }; enum GenerateEvent { CHUNK_DONE_EVENT = 'CHUNK_DONE', DONE_EVENT = 'DONE', ERROR_EVENT = 'ERROR', } export class Generator { public static CHUNK_DONE_EVENT = GenerateEvent.CHUNK_DONE_EVENT; public static DONE_EVENT = GenerateEvent.DONE_EVENT; public static ERROR_EVENT = GenerateEvent.ERROR_EVENT; private opts: GenerateOpts = {}; private jsonExamples: any[] = []; private requestInputs: RequestInput[] = []; public eventEmitter = new EventEmitter(); private stopped = false; private fetch: typeof fetch; private fetchCancel: () => void; // status private code = ''; private jsonList: any[] = []; constructor(input: Input | Input[], opts: GenerateOpts = {}) { this.opts = merge.recursive(true, defaultGenerateOpts, opts); const easyInputs = Generator.getEasyInputs(Array.isArray(input) ? input : [input]); this.jsonExamples = easyInputs.jsonExamples; this.requestInputs = getOnceMockInputList(easyInputs.requestInputs, opts.requireMockSort); if (this.opts.on) { // @ts-ignore this.eventEmitter.onAny(this.opts.on); } const CancelToken = axios.CancelToken; const { token: fetchCancelToken, cancel: fetchCancel } = CancelToken.source(); this.fetch = promiseDelay( (request, requestConfig, requestMock) => fetch(request, { ...requestConfig, cancelToken: fetchCancelToken }, requestMock), opts.requestInterval! ); this.fetchCancel = () => fetchCancel('the stable-ts-type generator has been interrupted'); } private async onGenerateChunk(jsonList: any[]) { const { opts, eventEmitter, stopped } = this; if (stopped) return; this.jsonList.push(...jsonList); const code = await json2Type( opts.topTypeName!, [JSON.stringify(this.jsonList)], opts.quickTypeOpts, ); this.code = code; eventEmitter.emit(GenerateEvent.CHUNK_DONE_EVENT, code); } async generate() { const { jsonExamples, requestInputs, eventEmitter, fetch } = this; const promises: Promise<void>[] = []; const p = this.onGenerateChunk(jsonExamples) .catch((error) => { this.eventEmitter.emit(GenerateEvent.ERROR_EVENT, error); }); promises.push(p); for (const fetchInput of requestInputs) { // @ts-ignore const p = fetch(fetchInput.value, {}, fetchInput.mock) .then(data => { if (isValidJSON(data)) { return data; } const showFetchInput = { type: fetchInput.type, value: fetchInput.value, }; throw simpleError(`json conversion failed, from fetchInput: ${JSON.stringify(showFetchInput, null, 2)}`); }) .then(json => this.onGenerateChunk([json])) .catch((error) => { this.eventEmitter.emit(GenerateEvent.ERROR_EVENT, error); }); promises.push(p); } await Promise.all(promises); eventEmitter.emit(GenerateEvent.DONE_EVENT, this.code); } stop() { this.stopped = true; this.eventEmitter.removeAllListeners(); this.fetchCancel(); } private static getEasyInputs(inputs: Input[]) { const jsonExamplesInput = (inputs .filter(input => input.type === 'example-json') as ExampleJSONInput[]); const jsonExamples = jsonExamplesInput .map(input => typeof input.value === 'string' ? easyJSONParse(input.value) : input.value ); const requestInputs = inputs .filter(input => input.type !== 'example-json') as RequestInput[]; return { jsonExamples, requestInputs, }; } } export const simpleGenerate = async (input: Input | Input[], opts: Omit<GenerateOpts, 'on'> = {}) => new Promise<string>((resolve, reject) => { const inputs = Array.isArray(input) ? input : [input]; const generator = new Generator(inputs, { ...opts, on: (event, codeOrError) => { switch (event) { case 'DONE': resolve(codeOrError as string); break; case 'ERROR': reject(codeOrError); break; } }, }); generator.generate(); }); /** @deprecated */ export const generate = async (input: Input | Input[], opts: GenerateOpts = {}): Promise<string> => { const inputs = Array.isArray(input) ? input : [input]; const quickTypeOpts = Object.assign({}, defaultTypeOpts, opts.quickTypeOpts); const jsonExamplesInput = (inputs .filter(input => input.type === 'example-json') as ExampleJSONInput[]); const jsonExamples = jsonExamplesInput .map(input => typeof input.value === 'string' ? easyJSONParse(input.value) : JSON.stringify(input.value) ) as string[]; const requiredFetchInputs = inputs .filter(input => input.type !== 'example-json') as RequestInput[]; // @ts-ignore const fetches = requiredFetchInputs.map(input => fetch(input.value)); const respDataList = await Promise.all(fetches); try { const respDataStrList = respDataList.map(data => JSON.stringify(data)); const typeCode = await json2Type(opts.topTypeName ?? TOP_TYPE_NAME, [ ...jsonExamples, ...respDataStrList as any, ], quickTypeOpts); return typeCode; } catch (error) { throw simpleError(`failed to generate typeCode, json-list: ${ JSON.stringify([ ...respDataList, ...jsonExamplesInput.map(item => item.value), ], null, 2) }`); } };